From 0cec4753d8afaa5673eba526c121d8b1e1062a5d Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Tue, 17 Aug 2021 23:29:46 +0100 Subject: [PATCH] Server: Fixes #5328: Filenames with non-ascii characters could not be downloaded from published note --- packages/lib/path-utils.ts | 8 ++++++++ packages/server/src/routes/index/shares.ts | 7 ++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/lib/path-utils.ts b/packages/lib/path-utils.ts index 49f877451..2a9091191 100644 --- a/packages/lib/path-utils.ts +++ b/packages/lib/path-utils.ts @@ -65,6 +65,14 @@ for (let i = 0; i < 32; i++) { const friendlySafeFilename_blackListNames = ['.', '..', 'CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']; +// The goal of this function is to provide a safe filename, that should work in +// any filesystem, but that's still user friendly, in particular because it +// supports any charset - Chinese, Russian, etc. +// +// "Safe" however doesn't mean it can be safely inserted in any content (HTML, +// Markdown, etc.) - it still needs to be encoded by the calling code according +// to the context. + export function friendlySafeFilename(e: string, maxLength: number = null, preserveExtension: boolean = false) { // Although Windows supports paths up to 255 characters, but that includes the filename and its // parent directory path. Also there's generally no good reason for dir or file names diff --git a/packages/server/src/routes/index/shares.ts b/packages/server/src/routes/index/shares.ts index 5741c85e4..19e4852fa 100644 --- a/packages/server/src/routes/index/shares.ts +++ b/packages/server/src/routes/index/shares.ts @@ -21,6 +21,11 @@ async function renderItem(context: AppContext, item: Item, share: Share): Promis }; } +function createContentDispositionHeader(filename: string) { + const encoded = encodeURIComponent(friendlySafeFilename(filename)); + return `attachment; filename*=UTF-8''${encoded}; filename="${encoded}"`; +} + const router: Router = new Router(RouteType.Web); router.public = true; @@ -46,7 +51,7 @@ router.get('shares/:id', async (path: SubPath, ctx: AppContext) => { ctx.response.body = result.body; ctx.response.set('Content-Type', result.mime); ctx.response.set('Content-Length', result.size.toString()); - if (result.filename) ctx.response.set('Content-disposition', `attachment; filename="${friendlySafeFilename(result.filename)}"`); + if (result.filename) ctx.response.set('Content-disposition', createContentDispositionHeader(result.filename)); return new Response(ResponseType.KoaResponse, ctx.response); }, RouteType.UserContent);