From 1fb679ecd6e32ccbb456b5b73a2555fb123e3c26 Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Wed, 13 May 2026 06:30:57 +0200 Subject: [PATCH] better filename extraction --- src/inc/multipart.mjs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/inc/multipart.mjs b/src/inc/multipart.mjs index acad522..fa9ddec 100644 --- a/src/inc/multipart.mjs +++ b/src/inc/multipart.mjs @@ -31,14 +31,32 @@ export const parseMultipart = (buffer, boundary) => { const body = segment.slice(headerEnd + 4); const nameMatch = headers.match(/name="([^"]+)"/); - const filenameMatch = headers.match(/filename="([^"]+)"/); + + // Robust filename extraction: + // 1. RFC 5987: filename*=UTF-8''encoded-name (used by modern browsers for non-ASCII) + // 2. Standard: filename="name" (ASCII) + // 3. Fallback: filename=name (unquoted) + let extractedFilename = null; + const filenameStarMatch = headers.match(/filename\*\s*=\s*[Uu][Tt][Ff]-8''([^\r\n;]+)/i); + if (filenameStarMatch) { + try { extractedFilename = decodeURIComponent(filenameStarMatch[1].trim()); } catch (e) { extractedFilename = filenameStarMatch[1].trim(); } + } else { + const filenameQuotedMatch = headers.match(/filename="((?:[^"\\]|\\.)*)"/); + if (filenameQuotedMatch) { + extractedFilename = filenameQuotedMatch[1].replace(/\\(.)/g, '$1'); + } else { + const filenameUnquotedMatch = headers.match(/filename=([^\r\n;]+)/); + if (filenameUnquotedMatch) extractedFilename = filenameUnquotedMatch[1].trim(); + } + } + const contentTypeMatch = headers.match(/Content-Type:\s*([^\r\n]+)/i); if (nameMatch) { const name = nameMatch[1]; - if (filenameMatch) { + if (extractedFilename !== null) { parts[name] = { - filename: filenameMatch[1], + filename: extractedFilename, contentType: contentTypeMatch ? contentTypeMatch[1] : 'application/octet-stream', data: body };