Spaces:
Running
Running
File size: 5,842 Bytes
8991f39 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | import { readFile, realpath, stat } from 'node:fs/promises';
import { extname, resolve, sep } from 'node:path';
import { isConfinedSvgPath } from './icon-candidate.mjs';
import { createApprovedIconProxy } from './icon-proxy.mjs';
const CONTENT_TYPES = {
'.bin': 'application/octet-stream',
'.gz': 'application/gzip',
'.html': 'text/html; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.mjs': 'text/javascript; charset=utf-8',
'.onnx': 'application/octet-stream',
'.svg': 'image/svg+xml',
'.wasm': 'application/wasm',
};
// SVG export fetches the same approved icon bytes that img-src already renders, so the one
// pinned CDN host is allowed to connect as well.
const CONTENT_SECURITY_POLICY = "default-src 'self'; img-src 'self' https://cdn.jsdelivr.net data:; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https://cdn.jsdelivr.net; object-src 'none'; base-uri 'none'; frame-ancestors 'none'";
const DOCUMENT_ROUTES = new Map([
['/', 'index.html'],
['/index.html', 'index.html'],
]);
// The icon index is hundreds of generated shards, so routes match generated names
// instead of exposing the directory to arbitrary traversal.
const ASSET_ROUTES = [
{ prefix: '/src/', name: /^[a-z0-9-]+\.mjs$/ },
{ prefix: '/vendor/', name: /^ort[a-z0-9.-]*\.(?:mjs|wasm)$/ },
{ prefix: '/models/', name: /^(?:quickdraw-mvp|sketch-embedder)\/[a-z0-9-]+\.(?:json|onnx)$/ },
{ prefix: '/data/icon-embeddings/', name: /^(?:index\.json|shard-\d{1,4}\.(?:bin|meta\.json\.gz))$/ },
{ prefix: '/data/', name: /^quickdraw-candidates\.json$/ },
];
const SVGDEPOT_PREFIX = '/svgdepot/';
const SVGDEPOT_CACHE = ['.cache', 'svgdepot'];
function pathError(message, statusCode) {
const error = new Error(message);
error.statusCode = statusCode;
return error;
}
function responseHeaders(contentType) {
return {
'Cache-Control': 'no-store',
'Content-Security-Policy': CONTENT_SECURITY_POLICY,
'Content-Type': contentType,
'X-Content-Type-Options': 'nosniff',
};
}
function isInside(rootPath, filePath) {
return filePath === rootPath || filePath.startsWith(`${rootPath}${sep}`);
}
function decodedPathname(requestPathname) {
try {
return decodeURIComponent(requestPathname);
} catch {
throw pathError('Not found', 404);
}
}
export function svgDepotPath(pathname) {
if (!pathname.startsWith(SVGDEPOT_PREFIX)) return null;
const relativePath = pathname.slice(SVGDEPOT_PREFIX.length);
if (!isConfinedSvgPath(relativePath)) throw pathError('Not found', 404);
return relativePath;
}
function assetPath(pathname) {
for (const route of ASSET_ROUTES) {
if (!pathname.startsWith(route.prefix)) continue;
const name = pathname.slice(route.prefix.length);
if (!route.name.test(name)) throw pathError('Not found', 404);
return `${route.prefix.slice(1)}${name}`;
}
return null;
}
export async function resolveServedFile(projectRoot, requestPathname) {
const pathname = decodedPathname(requestPathname);
const localSvgPath = svgDepotPath(pathname);
const relativePath = localSvgPath
?? DOCUMENT_ROUTES.get(pathname)
?? assetPath(pathname);
if (!relativePath) throw pathError('Not found', 404);
const rootRealPath = await realpath(projectRoot);
const allowedRootPath = localSvgPath
? await realpath(resolve(projectRoot, ...SVGDEPOT_CACHE))
: rootRealPath;
if (!isInside(rootRealPath, allowedRootPath)) throw pathError('Forbidden', 403);
const fileRealPath = await realpath(resolve(allowedRootPath, relativePath));
if (!isInside(allowedRootPath, fileRealPath)) throw pathError('Forbidden', 403);
return fileRealPath;
}
export function createAppHandler(projectRoot, { fetchImpl = globalThis.fetch } = {}) {
let proxyPromise;
// Icons live in a commit-pinned upstream repository; the manifest supplies that pin so the
// proxy can only ever reach approved jsDelivr URLs.
async function approvedIconFallback(requestPathname) {
const localSvgPath = svgDepotPath(decodedPathname(requestPathname));
if (!localSvgPath) return null;
proxyPromise ??= readFile(
resolve(projectRoot, 'data', 'quickdraw-candidates.json'),
'utf8',
).then(JSON.parse).then(manifest => createApprovedIconProxy({ manifest, fetchImpl }));
return (await proxyPromise).icon(localSvgPath);
}
return async function handleRequest(request, response) {
if (request.method !== 'GET' && request.method !== 'HEAD') {
response.writeHead(405, {
...responseHeaders('text/plain; charset=utf-8'),
Allow: 'GET, HEAD',
});
response.end('Method not allowed');
return;
}
const { pathname } = new URL(request.url, 'http://localhost');
try {
const filePath = await resolveServedFile(projectRoot, pathname);
const fileStats = await stat(filePath);
if (!fileStats.isFile()) throw pathError('Not found', 404);
const body = await readFile(filePath);
const contentType = CONTENT_TYPES[extname(filePath).toLowerCase()] ?? 'application/octet-stream';
response.writeHead(200, responseHeaders(contentType));
response.end(request.method === 'HEAD' ? undefined : body);
} catch (error) {
if (!error.statusCode) {
try {
const fallback = await approvedIconFallback(pathname);
if (fallback) {
response.writeHead(200, responseHeaders('image/svg+xml'));
response.end(request.method === 'HEAD' ? undefined : fallback.body);
return;
}
} catch {}
}
const statusCode = error.statusCode ?? 404;
response.writeHead(statusCode, responseHeaders('text/plain; charset=utf-8'));
response.end(request.method === 'HEAD' ? undefined : statusCode === 403 ? 'Forbidden' : 'Not found');
}
};
}
|