function Send-Text { param($Context, $Text) if (-not $Context -or -not $Context.Response) { return } $bytes = [Text.Encoding]::UTF8.GetBytes([string]$Text) $Context.Response.ContentType = 'text/plain; charset=utf-8' $Context.Response.ContentLength64 = $bytes.Length $Context.Response.OutputStream.Write($bytes,0,$bytes.Length) $Context.Response.OutputStream.Close() } function Send-HTML { [CmdletBinding()] param( [Parameter(Mandatory = $true)][object] $Context, [Parameter(Mandatory = $true)][string] $Html ) if (-not $Context -or -not $Context.Response) { return } try { $Context.Response.Headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0" $Context.Response.Headers["Pragma"] = "no-cache" $Context.Response.Headers["Expires"] = "0" } catch { } $bytes = [Text.Encoding]::UTF8.GetBytes($Html) $Context.Response.ContentType = "text/html; charset=utf-8" $Context.Response.ContentLength64 = $bytes.Length $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length) $Context.Response.OutputStream.Close() } function Send-RemoteAsset { [CmdletBinding()] param( [Parameter(Mandatory)][object] $Context, [Parameter(Mandatory)][string] $Url, [Parameter(Mandatory)][string] $ContentType ) if (-not $Context -or -not $Context.Response) { return } if ([string]::IsNullOrWhiteSpace($Url)) { try { $Context.Response.StatusCode = 500 } catch { } Send-Text $Context "Asset URL is empty (ContentType=$ContentType)" return } try { $resp = Invoke-WebRequest -UseBasicParsing -Uri $Url -ErrorAction Stop try { $Context.Response.Headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0" $Context.Response.Headers["Pragma"] = "no-cache" $Context.Response.Headers["Expires"] = "0" } catch { } $Context.Response.ContentType = $ContentType # Prefer binary stream for images/icons. For js/css/html, treat as UTF-8 text. $bytes = $null if ($ContentType -like 'image/*' -or $ContentType -like '*x-icon*' -or $ContentType -like '*octet-stream*') { try { $ms = New-Object System.IO.MemoryStream $resp.RawContentStream.CopyTo($ms) $bytes = $ms.ToArray() } catch { # Fallback: treat as text if RawContentStream isn't usable $bytes = [Text.Encoding]::UTF8.GetBytes([string]$resp.Content) } } else { $bytes = [Text.Encoding]::UTF8.GetBytes([string]$resp.Content) } $Context.Response.ContentLength64 = $bytes.Length $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length) } catch { Write-LogHybrid "Send-RemoteAsset failed for $Url : $($_.Exception.Message)" Error Server -LogToEvent try { $Context.Response.StatusCode = 502 $msg = "Failed to fetch asset: $Url" $bytes = [Text.Encoding]::UTF8.GetBytes($msg) $Context.Response.ContentType = "text/plain; charset=utf-8" $Context.Response.ContentLength64 = $bytes.Length $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length) } catch { } } finally { try { $Context.Response.OutputStream.Close() } catch { } } } function Send-JSON { [CmdletBinding()] param( $Context, $Object ) if (-not $Context -or -not $Context.Response) { return } $json = $null try { if ($null -eq $Object) { Write-LogHybrid "Send-JSON called with `$null object; returning empty JSON array." Warning Printers -LogToEvent $json = '[]' } else { try { $json = $Object | ConvertTo-Json -Depth 8 -ErrorAction Stop } catch { Write-LogHybrid "Send-JSON serialization failed: $($_.Exception.Message); returning empty JSON array." Error Printers -LogToEvent $json = '[]' } } $json = [string]$json try { $Context.Response.Headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0" $Context.Response.Headers["Pragma"] = "no-cache" $Context.Response.Headers["Expires"] = "0" } catch { } $bytes = [Text.Encoding]::UTF8.GetBytes($json) $Context.Response.ContentType = "application/json; charset=utf-8" $Context.Response.ContentLength64 = $bytes.Length $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length) } catch { Write-LogHybrid "Send-JSON fatal error: $($_.Exception.Message)" Error Printers -LogToEvent try { $fallback = '[]' $bytes = [Text.Encoding]::UTF8.GetBytes($fallback) $Context.Response.ContentType = "application/json; charset=utf-8" $Context.Response.ContentLength64 = $bytes.Length $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length) } catch { } } finally { try { $Context.Response.OutputStream.Close() } catch { } } }