From e08106898b44f49b1ff2a91c96ac7877241116c6 Mon Sep 17 00:00:00 2001 From: phucnguyen1707 Date: Thu, 11 Jun 2026 15:58:25 +0700 Subject: [PATCH] Handle bad feed path encoding --- apps/commandboard-api/src/contract.test.ts | 8 ++++++++ apps/commandboard-api/src/index.ts | 24 ++++++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/apps/commandboard-api/src/contract.test.ts b/apps/commandboard-api/src/contract.test.ts index 2b9629d..706b194 100644 --- a/apps/commandboard-api/src/contract.test.ts +++ b/apps/commandboard-api/src/contract.test.ts @@ -100,6 +100,14 @@ describe("CommandBoard API contracts", () => { expect(opmlBody).toContain(" { + const response = await fetch(`${baseUrl}/rss/discover/%E0%A4%A.xml`); + const body = await response.json() as { error: string }; + + expect(response.status).toBe(400); + expect(body).toEqual({ error: "Invalid path encoding" }); + }); + it("exposes sh1pt project and action contracts", async () => { const projectsResponse = await fetch(`${baseUrl}/api/plugins/sh1pt/projects`); const projectsBody = await projectsResponse.json() as { projects: Array<{ id: string; board: string; status: string; actions: number }> }; diff --git a/apps/commandboard-api/src/index.ts b/apps/commandboard-api/src/index.ts index 58b6166..1825c68 100644 --- a/apps/commandboard-api/src/index.ts +++ b/apps/commandboard-api/src/index.ts @@ -172,6 +172,11 @@ async function route(request: IncomingMessage, response: ServerResponse) { const formattedDiscover = matchFormattedDiscoverPath(url.pathname); if (request.method === "GET" && formattedDiscover) { + if (formattedDiscover.format === "invalid-encoding") { + json(response, 400, { error: "Invalid path encoding" }); + return; + } + const result = await discoverFeeds({ q: formattedDiscover.keyword, type: "all", @@ -312,23 +317,34 @@ function listParam(value: string | null) { function matchFormattedDiscoverPath(pathname: string) { const rss = /^\/rss\/discover\/(.+)\.xml$/.exec(pathname); if (rss) { - return { format: "rss" as const, keyword: decodeURIComponent(rss[1]) }; + return formattedDiscoverMatch("rss", rss[1]); } const opml = /^\/opml\/discover\/(.+)\.xml$/.exec(pathname) ?? /^\/rss\/discover\/(.+)\.opml$/.exec(pathname); if (opml) { - return { format: "opml" as const, keyword: decodeURIComponent(opml[1]) }; + return formattedDiscoverMatch("opml", opml[1]); } const atom = /^\/atom\/discover\/(.+)\.xml$/.exec(pathname); if (atom) { - return { format: "atom" as const, keyword: decodeURIComponent(atom[1]) }; + return formattedDiscoverMatch("atom", atom[1]); } const jsonFeed = /^\/json-feed\/discover\/(.+)\.json$/.exec(pathname); if (jsonFeed) { - return { format: "json-feed" as const, keyword: decodeURIComponent(jsonFeed[1]) }; + return formattedDiscoverMatch("json-feed", jsonFeed[1]); } return undefined; } +function formattedDiscoverMatch(format: "rss" | "opml" | "atom" | "json-feed", encodedKeyword: string) { + try { + return { format, keyword: decodeURIComponent(encodedKeyword) }; + } catch (error) { + if (error instanceof URIError) { + return { format: "invalid-encoding" as const }; + } + throw error; + } +} + export function startCommandBoardServer(port = Number(process.env.PORT ?? 4010)) { const server = createCommandBoardServer(); server.listen(port, () => {