authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-27 21:07:06-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-27 21:30:46-08:00
log3e99019af85e67cdbc5be0ef95b71662ebe683e7
tree1a97e439e8df9fa4c03b4ee52c8322089d96968f
parent7d4b02689ebcb9dbe56e283f0ec3a17f45c8816b

Serve attachments using URL-encoded relative paths

- Read attachment route paths without URI decoding before normalization - Align test fixture paths with encoded thread/message/file names

2 files changed, 4 insertions(+), 11 deletions(-)

apps/server/src/wsServer.test.ts+3-3
...@@ -492,9 +492,9 @@ describe("WebSocket Server", () => {...@@ -492,9 +492,9 @@ describe("WebSocket Server", () => {
492 const attachmentPath = path.join(492 const attachmentPath = path.join(
493 stateDir,493 stateDir,
494 "attachments",494 "attachments",
495 "thread folder",495 "thread%20folder",
496 "message folder",496 "message%20folder",
497 "file name.png",497 "file%20name.png",
498 );498 );
499 fs.mkdirSync(path.dirname(attachmentPath), { recursive: true });499 fs.mkdirSync(path.dirname(attachmentPath), { recursive: true });
500 fs.writeFileSync(attachmentPath, Buffer.from("hello-encoded-attachment"));500 fs.writeFileSync(attachmentPath, Buffer.from("hello-encoded-attachment"));
apps/server/src/wsServer.ts+1-8
...@@ -251,14 +251,7 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<...@@ -251,14 +251,7 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return<
251251
252 if (url.pathname.startsWith(ATTACHMENTS_ROUTE_PREFIX)) {252 if (url.pathname.startsWith(ATTACHMENTS_ROUTE_PREFIX)) {
253 const attachmentsRoot = path.resolve(path.join(serverConfig.stateDir, "attachments"));253 const attachmentsRoot = path.resolve(path.join(serverConfig.stateDir, "attachments"));
254 const encodedRelativePath = url.pathname.slice(ATTACHMENTS_ROUTE_PREFIX.length);254 const rawRelativePath = url.pathname.slice(ATTACHMENTS_ROUTE_PREFIX.length);
255 let rawRelativePath: string;
256 try {
257 rawRelativePath = decodeURIComponent(encodedRelativePath);
258 } catch {
259 respond(400, { "Content-Type": "text/plain" }, "Invalid attachment path");
260 return;
261 }
262 const normalizedRelativePath = path.normalize(rawRelativePath).replace(/^[/\\]+/, "");255 const normalizedRelativePath = path.normalize(rawRelativePath).replace(/^[/\\]+/, "");
263256
264 if (257 if (