From f29e06254de558c3ca4bf96276cab3884714b9a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Thu, 14 May 2020 11:24:38 +0200 Subject: [PATCH] Added tests and security hardening --- server.test.ts | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++ server.ts | 63 ++++++++++++++++++++++++++++++------------- 2 files changed, 117 insertions(+), 19 deletions(-) create mode 100644 server.test.ts diff --git a/server.test.ts b/server.test.ts new file mode 100644 index 0000000..0367370 --- /dev/null +++ b/server.test.ts @@ -0,0 +1,73 @@ +import { + expect, + mockfn, + test, +} from "https://code.thunderk.net/typescript/devtools/raw/1.0.1/testing.ts"; +import { processRequest } from "./server.ts"; + +test("serveBundles standard", async () => { + const mock_run = mockfn(() => { + return { + output: () => Promise.resolve(new TextEncoder().encode("abc")), + status: () => Promise.resolve({ code: 0, success: true }), + } as any; + }); + + const response = await processRequest( + { url: "/greatlib/1.0.0/reader/" } as any, + mock_run, + ); + expect(response).toEqual({ body: new TextEncoder().encode("abc") }); + expect(mock_run).toHaveBeenCalledTimes(1); + expect(mock_run).toHaveBeenCalledWith({ + cmd: [ + "deno", + "bundle", + "https://code.thunderk.net/typescript/greatlib/raw/1.0.0/reader.ts", + ], + stdout: "piped", + }); +}); + +test("serveBundles bad path", async () => { + const mock_run = mockfn(() => { + return { + output: () => Promise.resolve(new TextEncoder().encode("abc")), + status: () => Promise.resolve({ code: 0, success: true }), + } as any; + }); + + const response = await processRequest( + { url: "/greatlib/1.0.0/reader{}/" } as any, + mock_run, + ); + expect(response).toEqual( + { + status: 400, + body: + 'console.error("bundler error - Invalid path https://code.thunderk.net/typescript/greatlib/raw/1.0.0/reader{}.ts");', + }, + ); + expect(mock_run).toHaveBeenCalledTimes(0); +}); + +test("serveBundles bundle fail", async () => { + const mock_run = mockfn(() => { + return { + output: () => Promise.resolve(undefined), + status: () => Promise.resolve({ code: 1, success: false }), + } as any; + }); + + const response = await processRequest( + { url: "/great_lib/1.0.0-dev1/reader/" } as any, + mock_run, + ); + expect(response).toEqual( + { + status: 500, + body: + 'console.error("bundler error - Failed to bundle https://code.thunderk.net/typescript/great_lib/raw/1.0.0-dev1/reader.ts");', + }, + ); +}); diff --git a/server.ts b/server.ts index 12dabc8..ab310e3 100755 --- a/server.ts +++ b/server.ts @@ -1,31 +1,56 @@ #!/usr/bin/env -S deno run --allow-run --allow-net // Automated bundle server -import { serve } from "https://deno.land/std/http/server.ts"; import { bool } from "https://code.thunderk.net/typescript/functional/raw/1.0.0/all.ts"; +import { + Response, + serve, + ServerRequest, +} from "https://deno.land/std/http/server.ts"; -async function serveBundles() { +export async function processRequest( + req: ServerRequest, + runner = Deno.run, +): Promise { + const params = req.url.split("/").filter(bool); + const lib = params[0] || "all"; + const version = params[1] || "master"; + const file = params.length > 2 ? params.slice(2).join("/") : "all"; + const path = + `https://code.thunderk.net/typescript/${lib}/raw/${version}/${file}.ts`; + if (!path.match(/^[a-z0-9\/\.\:-_]+$/)) { + return { + status: 400, + body: `console.error("bundler error - Invalid path ${path}");`, + }; + } + + const process = runner({ + cmd: ["deno", "bundle", path], + stdout: "piped", + }); + const output = await process.output(); + const status = await process.status(); + if (status.success) { + return { body: output }; + } else { + return { + status: 500, + body: `console.error("bundler error - Failed to bundle ${path}");`, + }; + } +} + +export async function serveBundles() { const listen = { hostname: "0.0.0.0", port: 8000 }; const server = serve(listen); console.log(`Serving bundles on ${listen.hostname}:${listen.port} ...`); for await (const req of server) { - const params = req.url.split("/").filter(bool); - const lib = params[0] || "all"; - const version = params[1] || "master"; - const file = params.length > 2 ? params.slice(2).join("/") : "all"; - const path = - `https://code.thunderk.net/typescript/${lib}/raw/${version}/${file}.ts`; - - try { - const process = Deno.run({ - cmd: ["deno", "bundle", path], - stdout: "piped", - }); - req.respond({ body: await process.output() }); - } catch { - req.respond({ body: `console.error("Failed to bundle ${path}");` }); - } + const response = await processRequest(req); + await req.respond(response); } } -await serveBundles(); +if (import.meta.main) { + await serveBundles(); +}