From 5e6d770edd4302b982e83ba8863419b74390976e Mon Sep 17 00:00:00 2001 From: Philipp L Date: Tue, 12 May 2026 08:15:36 +0200 Subject: [PATCH] [IMP] Reliably assign file extensions for pasted clipboard files using MIME mapping --- ui/src/pages/case.notes.js | 3 ++- ui/src/pages/case.summary.js | 3 ++- ui/src/pages/common.js | 29 +++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/ui/src/pages/case.notes.js b/ui/src/pages/case.notes.js index cfd02f1ee..180dac052 100644 --- a/ui/src/pages/case.notes.js +++ b/ui/src/pages/case.notes.js @@ -1078,7 +1078,8 @@ function handle_ed_paste(event) { notify_success('The file is uploading in background. Don\'t leave the page'); if (filename === null) { - filename = random_filename(25); + let ext = get_extension_from_mime(blob.type); + filename = random_filename(25) + '.' + ext; } upload_interactive_data(e.target.result, filename, function(data){ diff --git a/ui/src/pages/case.summary.js b/ui/src/pages/case.summary.js index e636d246d..20bcabc27 100644 --- a/ui/src/pages/case.summary.js +++ b/ui/src/pages/case.summary.js @@ -94,7 +94,8 @@ function handle_ed_paste(event) { notify_success('The file is uploading in background. Don\'t leave the page'); if (filename === null) { - filename = random_filename(25); + let ext = get_extension_from_mime(blob.type); + filename = random_filename(25) + '.' + ext; } upload_interactive_data(e.target.result, filename, function(data){ diff --git a/ui/src/pages/common.js b/ui/src/pages/common.js index 5da80ca58..a3f3abf96 100644 --- a/ui/src/pages/common.js +++ b/ui/src/pages/common.js @@ -1609,6 +1609,35 @@ function random_filename(length) { return filename; } +function get_extension_from_mime(mimeType) { + if (!mimeType || typeof mimeType !== 'string') return 'bin'; + + // Strip parameters (e.g., "image/png; charset=utf-8" -> "image/png") and normalize case + const cleanMime = mimeType.split(';')[0].trim().toLowerCase(); + + const mimeMap = { + 'image/png': 'png', + 'image/jpeg': 'jpg', + 'image/gif': 'gif', + 'image/webp': 'webp', + 'image/avif': 'avif', + 'image/svg+xml': 'svg', + 'image/bmp': 'bmp', + 'image/tiff': 'tiff', + 'image/x-icon': 'ico', + 'image/vnd.microsoft.icon': 'ico' + }; + + let ext = mimeMap[cleanMime]; + if (!ext) { + // Fallback: extract substring after "/" and before any "+", e.g., "application/pdf" -> "pdf" + ext = (cleanMime.split('/')[1] || 'bin').split('+')[0]; + } + + // Ensure the extension only contains safe alphanumeric characters + return ext.replace(/[^a-z0-9]/g, ''); +} + function createPagination(currentPage, totalPages, per_page, callback, paginationContainersNodes) { const maxPagesToShow = 5; const paginationContainers = $(paginationContainersNodes);