From a9551911f2ba682ac8da50b6919f515c9fd1d63b Mon Sep 17 00:00:00 2001 From: Towsty Date: Sat, 12 Sep 2026 10:17:02 -0500 Subject: [PATCH] Encode Unicode video titles in playback response headers --- server/api/library/clips/[id]/video.get.ts | 3 ++- shared/content-disposition.mjs | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 shared/content-disposition.mjs diff --git a/server/api/library/clips/[id]/video.get.ts b/server/api/library/clips/[id]/video.get.ts index acb37ec..7f423d2 100644 --- a/server/api/library/clips/[id]/video.get.ts +++ b/server/api/library/clips/[id]/video.get.ts @@ -1,5 +1,6 @@ import { existsSync } from 'node:fs' import { sendPathWithRange } from '~/server/utils/httpRange' +import { inlineFilename } from '~/shared/content-disposition.mjs' export default defineEventHandler((event) => { const { owner } = assertLibraryOwner(event) @@ -11,7 +12,7 @@ export default defineEventHandler((event) => { throw createError({ statusCode: 404, statusMessage: 'Video file is missing' }) } return sendPathWithRange(event, path, 'video/mp4', { - 'Content-Disposition': `inline; filename="${safeDownloadName(clipTitle(clip))}.mp4"`, + 'Content-Disposition': inlineFilename(`${safeDownloadName(clipTitle(clip))}.mp4`), 'Cache-Control': 'private, max-age=0, must-revalidate' }) }) diff --git a/shared/content-disposition.mjs b/shared/content-disposition.mjs new file mode 100644 index 0000000..3f9a429 --- /dev/null +++ b/shared/content-disposition.mjs @@ -0,0 +1,7 @@ +/** ASCII header fallback plus UTF-8 filename; titles may contain emoji or smart punctuation. */ +export function inlineFilename(name) { + const clean=String(name).replace(/[\r\n\x00-\x1f\x7f]/g,'').toWellFormed() + const ascii=clean.replace(/[^\x20-\x7e]/g,'_').replace(/["\\]/g,'_') || 'video.mp4' + const encoded=encodeURIComponent(clean || 'video.mp4').replace(/['()*]/g,c=>'%'+c.charCodeAt(0).toString(16).toUpperCase()) + return `inline; filename="${ascii}"; filename*=UTF-8''${encoded}` +}