/* This software is licensed by the MIT License, see LICENSE file */ /* Copyright © 2024-2025 Gregory Lirent */ using Microsoft.AspNetCore.Mvc; using System.Text.Json; using System.Text.Json.Serialization; using WebmrAPI.Models; using WebmrAPI.Services; using WebmrAPI.Services.Scanners; namespace WebmrAPI.Controllers { [ApiController] [Route("api/v1/[controller]")] [System.Runtime.Versioning.SupportedOSPlatform("windows")] public class ProcessController : ControllerBase { private readonly ProcessMonitor _monitor; private readonly ILogger _logger; public ProcessController(ProcessMonitor monitor, ILogger logger) { _monitor = monitor; _logger = logger; } internal static string GetFormattedJson(T data, bool pretty) { var options = new JsonSerializerOptions { WriteIndented = pretty, PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, Converters = { new JsonStringEnumConverter() } }; return JsonSerializer.Serialize(data, options); } [HttpGet] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public IActionResult GetProcesses( [FromQuery] bool pretty = false, [FromQuery] string sortBy = "", [FromQuery] bool desc = false, [FromQuery] int limit = 0, [FromQuery] int offset = 0, [FromQuery] string search = "" ) { try { var data = _monitor.GetBufferedProcesses(); if (data != null && !String.IsNullOrEmpty(search)) { data = data.Where(p => p.Name != null && p.Name.ToLowerInvariant().Contains(search.ToLowerInvariant())); } if (data != null && !String.IsNullOrEmpty(sortBy)) { sortBy = sortBy.ToLowerInvariant(); switch (sortBy) { case "pid": data = Sort(data, p => p.PID, desc); break; case "memaddress": data = Sort(data, p => p.MemoryAddress, desc); break; case "memsize": data = Sort(data, p => p.MemorySize, desc); break; case "parentpid": data = Sort(data, p => p.ParentPID, desc); break; case "name": data = Sort(data, p => p.Name, desc); break; case "filename": data = Sort(data, p => p.FileName, desc); break; case "commandline": data = Sort(data, p => p.CommandLine, desc); break; case "threadcount": data = Sort(data, p => p.ThreadCount, desc); break; case "status": data = Sort(data, p => p.Status, desc); break; case "starttime": data = Sort(data, p => p.StartTime, desc); break; case "cpuusage": data = Sort(data, p => p.CpuUsage, desc); break; default: return StatusCode(StatusCodes.Status400BadRequest, $"Unexpected search filter {sortBy}."); } } return Content(GetFormattedJson(Paginate(data, limit, offset), pretty), "application/json"); } catch (Exception ex) { _logger.LogError(ex, "An error occurred when getting the list of processes"); return StatusCode(StatusCodes.Status500InternalServerError, "An internal server error occurred when receiving processes."); } } [HttpGet("{pid}")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ProcessInfo))] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] async public Task GetProcessById(int pid, [FromQuery] bool pretty = false ) { try { var data = await _monitor.GetProcessDetails(pid, ScanTarget.ProcessDetails); if (data == null) { return NotFound($"The process with the PID {pid} was not found or its details could not be obtained."); } return Content(GetFormattedJson(data, pretty), "application/json"); } catch (Exception ex) { _logger.LogError(ex, $"An error occurred while receiving process details for PID {pid}."); return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving process details for PID {pid}."); } } [HttpGet("{pid}/base_info")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ProcessBaseInfo))] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] async public Task GetProcessBaseInfoById(int pid, [FromQuery] bool pretty = false ) { try { var data = await _monitor.GetProcessDetails(pid, 0); if (data == null) { return NotFound($"The process with the PID {pid} was not found or its details could not be obtained."); } return Content(GetFormattedJson((ProcessBaseInfo)data, pretty), "application/json"); } catch (Exception ex) { _logger.LogError(ex, $"An error occurred while receiving memory regions for PID {pid}."); return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving memory regions for PID {pid}."); } } [HttpGet("{pid}/memory_regions")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] async public Task GetProcessMemoryPagesById(int pid, [FromQuery] bool pretty = false, [FromQuery] string sortBy = "", [FromQuery] bool desc = false, [FromQuery] int limit = 0, [FromQuery] int offset = 0 ) { try { var data = (await _monitor.GetProcessDetails(pid, ScanTarget.MemoryRegions))?.MemoryRegions; if (data == null) { return NotFound($"The process with the PID {pid} was not found or its details could not be obtained."); } if (!String.IsNullOrEmpty(sortBy)) { sortBy = sortBy.ToLowerInvariant(); switch (sortBy) { case "state": data = Sort(data, p => p.MemoryState, desc); break; case "protection": data = Sort(data, p => p.MemoryPageProtection, desc); break; case "type": data = Sort(data, p => p.MemoryType, desc); break; case "memaddress": data = Sort(data, p => p.MemoryAddress, desc); break; case "memsize": data = Sort(data, p => p.MemorySize, desc); break; default: return StatusCode(StatusCodes.Status400BadRequest, $"Unexpected search filter {sortBy}."); } } return Content(GetFormattedJson(Paginate(data, limit, offset), pretty), "application/json"); } catch (Exception ex) { _logger.LogError(ex, $"An error occurred while receiving memory regions for PID {pid}."); return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving memory regions for PID {pid}."); } } [HttpGet("{pid}/modules")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] async public Task GetProcessModulesById(int pid, [FromQuery] bool pretty = false, [FromQuery] string sortBy = "", [FromQuery] bool desc = false, [FromQuery] int limit = 0, [FromQuery] int offset = 0 ) { try { var data = (await _monitor.GetProcessDetails(pid, ScanTarget.Modules))?.Modules; if (data == null) { return NotFound($"The process with the PID {pid} was not found or its details could not be obtained."); } if (!String.IsNullOrEmpty(sortBy)) { sortBy = sortBy.ToLowerInvariant(); switch (sortBy) { case "name": data = Sort(data, p => p.ModuleName, desc); break; case "filename": data = Sort(data, p => p.FileName, desc); break; case "memaddress": data = Sort(data, p => p.MemoryAddress, desc); break; case "entryaddress": data = Sort(data, p => p.EntrypointRawAddress, desc); break; case "memsize": data = Sort(data, p => p.MemorySize, desc); break; default: return StatusCode(StatusCodes.Status400BadRequest, $"Unexpected search filter {sortBy}."); } } return Content(GetFormattedJson(Paginate(data, limit, offset), pretty), "application/json"); } catch (Exception ex) { _logger.LogError(ex, $"An error occurred while receiving modules for PID {pid}."); return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving modules for PID {pid}."); } } [HttpGet("{pid}/threads")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] async public Task GetProcessThreadsById(int pid, [FromQuery] bool pretty = false, [FromQuery] string sortBy = "", [FromQuery] bool desc = false, [FromQuery] int limit = 0, [FromQuery] int offset = 0 ) { try { var data = (await _monitor.GetProcessDetails(pid, ScanTarget.Threads))?.Threads; if (data == null) { return NotFound($"The process with the PID {pid} was not found or its details could not be obtained."); } if (!String.IsNullOrEmpty(sortBy)) { sortBy = sortBy.ToLowerInvariant(); switch (sortBy) { case "id": data = Sort(data, p => p.ID, desc); break; case "priority": data = Sort(data, p => p.CurrentPriority, desc); break; case "basepriority": data = Sort(data, p => p.BasePriority, desc); break; case "cpuusage": data = Sort(data, p => p.CpuUsage, desc); break; default: return StatusCode(StatusCodes.Status400BadRequest, $"Unexpected search filter {sortBy}."); } } return Content(GetFormattedJson(Paginate(data, limit, offset), pretty), "application/json"); } catch (Exception ex) { _logger.LogError(ex, $"An error occurred while receiving threads for PID {pid}."); return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving threads for PID {pid}."); } } [HttpGet("{pid}/windows")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] async public Task GetProcessWindowsById(int pid, [FromQuery] bool pretty = false, [FromQuery] string sortBy = "", [FromQuery] bool desc = false, [FromQuery] int limit = 0, [FromQuery] int offset = 0 ) { try { var data = await _monitor.GetProcessWindows(pid); if (data == null) { return NotFound($"The process with the PID {pid} was not found or its windows could not be obtained."); } if (!String.IsNullOrEmpty(sortBy)) { sortBy = sortBy.ToLowerInvariant(); switch (sortBy) { case "title": data = ProcessController.Sort(data, p => p.Title, desc); break; case "id": data = ProcessController.Sort(data, p => p.Id, desc); break; default: return StatusCode(StatusCodes.Status400BadRequest, $"Unexpected search filter {sortBy}."); } } return Content(GetFormattedJson(Paginate(data, limit, offset), pretty), "application/json"); } catch (Exception ex) { _logger.LogError(ex, $"An error occurred while receiving threads for PID {pid}."); return StatusCode(StatusCodes.Status500InternalServerError, $"An internal server error occurred while receiving threads for PID {pid}."); } } internal static IEnumerable Sort(IEnumerable data, Func selector, bool desc) { return desc ? data.OrderByDescending(selector) : data.OrderBy(selector); } internal static IEnumerable Paginate(IEnumerable data, int limit, int offset) { if (offset > 0) data = data.Skip(offset); if (limit > 0) data = data.Take(limit); return data; } } }