( req: IncomingMessage, res: ServerResponse, allowedRoots: string[], )
| 63 | * dev (mounted) and static (unmounted) servers parse requests identically. |
| 64 | */ |
| 65 | export async function handleFileRequest( |
| 66 | req: IncomingMessage, |
| 67 | res: ServerResponse, |
| 68 | allowedRoots: string[], |
| 69 | ): Promise<boolean> { |
| 70 | const url = new URL(req.url ?? "", "http://localhost"); |
| 71 | if (url.pathname !== "/api/file") return false; |
| 72 | |
| 73 | if (!isTrustedRequest(req)) { |
| 74 | res.statusCode = 403; |
| 75 | res.end("forbidden: non-local request"); |
| 76 | return true; |
| 77 | } |
| 78 | |
| 79 | try { |
| 80 | const filePath = url.searchParams.get("path"); |
| 81 | if (!filePath) { |
| 82 | res.statusCode = 400; |
| 83 | res.end("missing ?path="); |
| 84 | return true; |
| 85 | } |
| 86 | const abs = path.resolve(filePath); |
| 87 | if (!isAllowed(abs, allowedRoots)) { |
| 88 | res.statusCode = 403; |
| 89 | res.end(`path not under an allowed root: ${abs}`); |
| 90 | return true; |
| 91 | } |
| 92 | |
| 93 | if (req.method === "GET") { |
| 94 | try { |
| 95 | const content = await fs.readFile(abs, "utf-8"); |
| 96 | res.setHeader("Content-Type", "application/json"); |
| 97 | res.end(content); |
| 98 | } catch (err: unknown) { |
| 99 | const code = (err as NodeJS.ErrnoException).code; |
| 100 | res.statusCode = code === "ENOENT" ? 404 : 500; |
| 101 | res.end(code ?? "read failed"); |
| 102 | } |
| 103 | return true; |
| 104 | } |
| 105 | |
| 106 | if (req.method === "PUT") { |
| 107 | const chunks: Buffer[] = []; |
| 108 | for await (const chunk of req) chunks.push(chunk as Buffer); |
| 109 | const body = Buffer.concat(chunks).toString("utf-8"); |
| 110 | await fs.writeFile(abs, body, "utf-8"); |
| 111 | res.statusCode = 204; |
| 112 | res.end(); |
| 113 | return true; |
| 114 | } |
| 115 | |
| 116 | res.statusCode = 405; |
| 117 | res.setHeader("Allow", "GET, PUT"); |
| 118 | res.end("method not allowed"); |
| 119 | return true; |
| 120 | } catch (err: unknown) { |
| 121 | res.statusCode = 500; |
| 122 | res.end(err instanceof Error ? err.message : String(err)); |
no test coverage detected