Files
orca/src/main/runtime/rpc/static-web-client-handler.ts
T
NeilandOrca 46646d7ff1 chore(lint): upgrade oxlint to 1.71 + enable 7 new rules (autofixed backlog) (#6841)
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules

Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day
minimum-release-age supply-chain guard; nothing here needs it). The
bump is a no-op on the existing config.

Enable 3 error rules (backlog autofixed to zero in this commit) and
4 warn rules (surface signal without gating CI):

error (autofixed, behavior-preserving):
- unicorn/prefer-node-protocol        (~1531 sites: bare builtin -> node:)
- typescript/no-import-type-side-effects (~36: all-inline-type -> import type)
- unicorn/no-array-reverse            (19: copy-then-reverse -> toReversed)

warn (real signal, current fires are test-only/correct):
- unicorn/no-array-fill-with-reference-type  (aliasing footgun guard)
- typescript/no-unsafe-function-type         (bans bare Function type)
- unicorn/prefer-array-flat-map              (map().flat() -> flatMap())
- unicorn/prefer-regexp-test                 (.match() in bool ctx -> .test())

mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix
ran from root and covered mobile/ too.

Verification (all green): oxlint 0 errors (root+mobile+aux configs),
oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed,
builds (electron-vite + web + cli) succeed. node: rewrites confirmed to
skip embedded SSH/CLI string payloads (AST-only); all toReversed sites
verified to operate on fresh copies or write-once locals.

* chore(lint): bump mobile oxlint to 1.71 so inherited rules parse

mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which
lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since
mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile &&
oxlint' failed to parse the new rule. Bump mobile to match root (1.71).

Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit
pass, vitest 978 passed / 0 failed.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-06-29 22:38:29 -07:00

140 lines
3.9 KiB
TypeScript

import { createReadStream } from 'node:fs'
import { stat } from 'node:fs/promises'
import type { IncomingMessage, RequestListener, ServerResponse } from 'node:http'
import { extname, isAbsolute, posix, relative, resolve } from 'node:path'
const STATIC_WEB_ALLOWED_PATHS = new Set(['/web-index.html'])
const STATIC_WEB_ALLOWED_PREFIXES = ['/assets/']
const STATIC_WEB_CONTENT_TYPES = new Map([
['.css', 'text/css; charset=utf-8'],
['.html', 'text/html; charset=utf-8'],
['.js', 'text/javascript; charset=utf-8'],
['.json', 'application/json; charset=utf-8'],
['.png', 'image/png'],
['.svg', 'image/svg+xml; charset=utf-8'],
['.wasm', 'application/wasm'],
['.webp', 'image/webp'],
['.woff2', 'font/woff2']
])
export function createStaticWebClientHandler(staticRoot: string): RequestListener {
const resolvedRoot = resolve(staticRoot)
return (request, response) => {
void handleStaticRequest(resolvedRoot, request, response)
}
}
async function handleStaticRequest(
staticRoot: string,
request: IncomingMessage,
response: ServerResponse
): Promise<void> {
if (request.method !== 'GET' && request.method !== 'HEAD') {
response.setHeader('Allow', 'GET, HEAD')
writeHttpStatus(response, 405)
return
}
const pathname = parseStaticPathname(request.url)
if (!pathname) {
writeHttpStatus(response, 400)
return
}
if (!isAllowedStaticWebPath(pathname)) {
writeHttpStatus(response, 404)
return
}
const absolutePath = resolve(staticRoot, pathname.slice(1))
const relativePath = relative(staticRoot, absolutePath)
if (relativePath === '' || relativePath.startsWith('..') || isAbsolute(relativePath)) {
writeHttpStatus(response, 404)
return
}
let fileStat
try {
fileStat = await stat(absolutePath)
} catch {
writeHttpStatus(response, 404)
return
}
if (!fileStat.isFile()) {
writeHttpStatus(response, 404)
return
}
response.statusCode = 200
response.setHeader(
'Content-Type',
STATIC_WEB_CONTENT_TYPES.get(extname(absolutePath)) ?? 'application/octet-stream'
)
response.setHeader('Content-Length', fileStat.size)
response.setHeader(
'Cache-Control',
pathname.startsWith('/assets/') ? 'public, max-age=31536000, immutable' : 'no-cache'
)
if (request.method === 'HEAD') {
response.end()
return
}
const stream = createReadStream(absolutePath)
stream.on('error', () => {
if (!response.headersSent) {
writeHttpStatus(response, 500)
return
}
response.destroy()
})
stream.pipe(response)
}
function parseStaticPathname(rawUrl: string | undefined): string | null {
if (!rawUrl) {
return '/web-index.html'
}
let pathname: string
try {
pathname = decodeURIComponent(new URL(rawUrl, 'http://127.0.0.1').pathname)
} catch {
return null
}
if (pathname === '/' || pathname === '/index.html') {
return '/web-index.html'
}
if (pathname.includes('\0') || pathname.includes('\\') || pathname.split('/').includes('..')) {
return null
}
if (posix.normalize(pathname) !== pathname) {
return null
}
return mapProxyPrefixedStaticPathname(pathname)
}
function mapProxyPrefixedStaticPathname(pathname: string): string {
if (pathname === '/web-index.html' || pathname.endsWith('/web-index.html')) {
return '/web-index.html'
}
const assetMarker = '/assets/'
const assetIndex = pathname.indexOf(assetMarker)
if (assetIndex !== -1) {
// Why: reverse proxies may forward the external path prefix through to
// Orca. Only the bundled /assets subtree is served after the prefix.
return pathname.slice(assetIndex)
}
return pathname
}
function isAllowedStaticWebPath(pathname: string): boolean {
return (
STATIC_WEB_ALLOWED_PATHS.has(pathname) ||
STATIC_WEB_ALLOWED_PREFIXES.some((prefix) => pathname.startsWith(prefix))
)
}
function writeHttpStatus(response: ServerResponse, statusCode: number): void {
response.statusCode = statusCode
response.end()
}