Files
wendeIT-scripting/Intune/Intune-App-Manager-Web/src/Router.ps1
T
2026-06-17 07:40:52 +02:00

100 lines
4.4 KiB
PowerShell

# HTTP-Router: leitet Requests an statische Dateien oder API-Endpoints weiter.
function Invoke-Router {
param([Parameter(Mandatory=$true)]$Context)
$request = $Context.Request
$path = $request.Url.AbsolutePath.TrimEnd('/')
if ([string]::IsNullOrEmpty($path)) { $path = "/" }
$method = $request.HttpMethod
$isApi = $path.StartsWith("/api/")
if ($isApi) {
Write-Host (">>> [{0}] {1} {2}" -f (Get-Date -Format "HH:mm:ss.fff"), $method, $path) -ForegroundColor DarkCyan
}
$reqStart = Get-Date
# API-Endpoints
if ($isApi) {
$body = $null
if ($method -in @("POST","PUT","PATCH")) {
$body = Read-RequestBody -Context $Context
}
$query = @{}
foreach ($key in $request.QueryString.AllKeys) {
if ($null -ne $key) { $query[$key] = $request.QueryString[$key] }
}
try {
$result = Invoke-ApiHandler -Path $path -Method $method -Body $body -Query $query
$handlerMs = [int]((Get-Date) - $reqStart).TotalMilliseconds
if ($null -eq $result) {
Send-JsonResponse -Context $Context -StatusCode 404 -Body @{ error = "Unknown endpoint: $method $path" }
Write-Host ("<<< [{0}] {1} {2} -> 404 ({3}ms)" -f (Get-Date -Format "HH:mm:ss.fff"), $method, $path, $handlerMs) -ForegroundColor DarkYellow
} else {
$statusCode = 200
if ($result -is [hashtable] -and $result.ContainsKey('__status')) {
$statusCode = $result['__status']
$result.Remove('__status')
}
Send-JsonResponse -Context $Context -StatusCode $statusCode -Body $result
$totalMs = [int]((Get-Date) - $reqStart).TotalMilliseconds
Write-Host ("<<< [{0}] {1} {2} -> {3} (handler={4}ms total={5}ms)" -f (Get-Date -Format "HH:mm:ss.fff"), $method, $path, $statusCode, $handlerMs, $totalMs) -ForegroundColor DarkCyan
}
} catch {
$msg = $_.Exception.Message
# Client-Disconnects (AbortController) sind harmlos und werden nicht geloggt
if ($msg -match "Netzwerkname.*nicht.*verfügbar|connection was forcibly closed|aborted by the software|response has been submitted") {
Write-Host " [client disconnect] $path" -ForegroundColor DarkGray
} else {
Write-Host "<<< [API ERROR] $path -> $msg" -ForegroundColor Red
Write-Host " Stack: $($_.ScriptStackTrace)" -ForegroundColor DarkRed
try {
Send-JsonResponse -Context $Context -StatusCode 500 -Body @{
error = $msg
stack = $_.ScriptStackTrace
}
} catch {}
}
}
return
}
# Assets aus dem Projekt-Root (Logos etc.)
if ($path.StartsWith("/assets/")) {
$name = $path.Substring("/assets/".Length)
$name = $name -replace "[^a-zA-Z0-9._-]", ""
$rootDir = Split-Path -Parent $script:Config.WebRoot
$file = Join-Path $rootDir $name
# Branding-Logos NIE cachen — sonst sieht der User nach dem Upload
# weiter die alte Version, auch wenn die URL gleich bleibt.
$extraHeaders = $null
if ($name -like 'branding-logo.*') {
$extraHeaders = @{ 'Cache-Control' = 'no-store, max-age=0' }
}
Send-FileResponse -Context $Context -FilePath $file -ExtraHeaders $extraHeaders
return
}
# Reports (HTML-Reports werden in Temp gespeichert)
if ($path.StartsWith("/reports/")) {
$name = $path.Substring("/reports/".Length)
$name = $name -replace "[^a-zA-Z0-9._-]", ""
$file = Join-Path $script:Config.ReportDir $name
Send-FileResponse -Context $Context -FilePath $file
return
}
# Statische Dateien aus www/
$relativePath = if ($path -eq "/") { "index.html" } else { $path.TrimStart('/') }
$relativePath = $relativePath -replace '\.\.', '' # path traversal blocken
$file = Join-Path $script:Config.WebRoot $relativePath
if (Test-Path $file -PathType Leaf) {
Send-FileResponse -Context $Context -FilePath $file
} else {
# SPA fallback - alle nicht erkannten Pfade auf index.html
Send-FileResponse -Context $Context -FilePath (Join-Path $script:Config.WebRoot "index.html")
}
}