From f2ce43f18f0444f8f63f7c33758d1837dd5ba91e Mon Sep 17 00:00:00 2001 From: Ryan Date: Sat, 29 Nov 2025 23:11:50 -0500 Subject: [PATCH] fix(preview): harden SVG handling and normalize mime type --- CHANGELOG.md | 11 +++++++++ public/js/domUtils.js | 49 +++++++++++++++++++++++++-------------- public/js/fileListView.js | 2 +- public/js/filePreview.js | 15 +++++++++++- public/js/i18n.js | 3 ++- src/models/FileModel.php | 20 ++++++++++------ 6 files changed, 73 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a792d31..cfa5795 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Changes 11/29/2025 (v2.2.3) + +fix(preview): harden SVG handling and normalize mime type + +- Stop treating SVGs as inline-previewable images in file list and preview modal +- Show a clear “SVG preview disabled for security reasons” message instead +- Keep SVGs downloadable via /api/file/download.php with proper image/svg+xml MIME +- Add i18n key for svg_preview_disabled + +--- + ## Changes 11/29/2025 (v2.2.2) release(v2.2.2): feat(folders): show inline folder stats & dates diff --git a/public/js/domUtils.js b/public/js/domUtils.js index d8749a6..2873b9a 100644 --- a/public/js/domUtils.js +++ b/public/js/domUtils.js @@ -179,9 +179,22 @@ export function buildFileTableRow(file, folderPath) { const safeUploader = escapeHTML(file.uploader || "Unknown"); let previewButton = ""; - if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tif|tiff|eps|heic|pdf|mp4|webm|mov|mp3|wav|m4a|ogg|flac|aac|wma|opus|mkv|ogv)$/i.test(file.name)) { + + const isSvg = /\.svg$/i.test(file.name); + + // IMPORTANT: do NOT treat SVG as previewable + if ( + !isSvg && + /\.(jpg|jpeg|png|gif|bmp|webp|ico|tif|tiff|eps|heic|pdf|mp4|webm|mov|mp3|wav|m4a|ogg|flac|aac|wma|opus|mkv|ogv)$/i + .test(file.name) + ) { let previewIcon = ""; - if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tif|tiff|eps|heic)$/i.test(file.name)) { + + // images (SVG explicitly excluded) + if ( + /\.(jpg|jpeg|png|gif|bmp|webp|ico|tif|tiff|eps|heic)$/i + .test(file.name) + ) { previewIcon = `image`; } else if (/\.(mp4|mkv|webm|mov|ogv)$/i.test(file.name)) { previewIcon = `videocam`; @@ -190,14 +203,16 @@ export function buildFileTableRow(file, folderPath) { } else if (/\.(mp3|wav|m4a|ogg|flac|aac|wma|opus)$/i.test(file.name)) { previewIcon = `audiotrack`; } - previewButton = ``; + + previewButton = ` + `; } return ` @@ -242,13 +257,13 @@ export function buildFileTableRow(file, folderPath) { drive_file_rename_outline - + diff --git a/public/js/fileListView.js b/public/js/fileListView.js index 6589d3d..e04b3b4 100644 --- a/public/js/fileListView.js +++ b/public/js/fileListView.js @@ -1954,7 +1954,7 @@ export function renderGalleryView(folder, container) { // thumbnail let thumbnail; - if (/\.(jpe?g|png|gif|bmp|webp|svg|ico)$/i.test(file.name)) { + if (/\.(jpe?g|png|gif|bmp|webp|ico)$/i.test(file.name)) { const cacheKey = previewURL; // include folder & file if (window.imageCache && window.imageCache[cacheKey]) { thumbnail = ` tags: +const IMG_RE = /\.(jpg|jpeg|png|gif|bmp|webp|ico)$/i; + +// SVG handled separately so we *don’t* inline it +const SVG_RE = /\.svg$/i; + const VID_RE = /\.(mp4|mkv|webm|mov|ogv)$/i; const AUD_RE = /\.(mp3|wav|m4a|ogg|flac|aac|wma|opus)$/i; const ARCH_RE = /\.(zip|rar|7z|gz|bz2|xz|tar)$/i; @@ -422,11 +427,19 @@ export function previewFile(fileUrl, fileName) { const folder = window.currentFolder || 'root'; const name = fileName; const lower = (name || '').toLowerCase(); + const isSvg = SVG_RE.test(lower); const isImage = IMG_RE.test(lower); const isVideo = VID_RE.test(lower); const isAudio = AUD_RE.test(lower); setTitle(overlay, name); + if (isSvg) { + container.textContent = + t("svg_preview_disabled") || + "SVG preview is disabled for security. Use Download to view this file."; + overlay.style.display = "flex"; + return; + } /* -------------------- IMAGES -------------------- */ if (isImage) { diff --git a/public/js/i18n.js b/public/js/i18n.js index 22b1e19..316716d 100644 --- a/public/js/i18n.js +++ b/public/js/i18n.js @@ -342,7 +342,8 @@ const translations = { "owner": "Owner", "hide_header_zoom_controls": "Hide header zoom controls", "preview_not_available": "Preview is not available for this file type.", - "storage_pro_bundle_outdated": "Please upgrade to the latest FileRise Pro bundle to use the Storage explorer." + "storage_pro_bundle_outdated": "Please upgrade to the latest FileRise Pro bundle to use the Storage explorer.", + "svg_preview_disabled": "SVG preview is disabled for now for security reasons." }, es: { "please_log_in_to_continue": "Por favor, inicie sesión para continuar.", diff --git a/src/models/FileModel.php b/src/models/FileModel.php index 5418db8..9fb7d45 100644 --- a/src/models/FileModel.php +++ b/src/models/FileModel.php @@ -503,13 +503,13 @@ class FileModel { if (!preg_match(REGEX_FILE_NAME, $file)) { return ["error" => "Invalid file name."]; } - + // Determine the real upload directory. $uploadDirReal = realpath(UPLOAD_DIR); if ($uploadDirReal === false) { return ["error" => "Server misconfiguration."]; } - + // Determine directory based on folder. if (strtolower($folder) === 'root' || trim($folder) === '') { $directory = $uploadDirReal; @@ -524,11 +524,11 @@ class FileModel { return ["error" => "Invalid folder path."]; } } - + // Build the file path. - $filePath = $directory . DIRECTORY_SEPARATOR . $file; + $filePath = $directory . DIRECTORY_SEPARATOR . $file; $realFilePath = realpath($filePath); - + // Ensure the file exists and is within the allowed directory. if ($realFilePath === false || strpos($realFilePath, $uploadDirReal) !== 0) { return ["error" => "Access forbidden."]; @@ -536,13 +536,19 @@ class FileModel { if (!file_exists($realFilePath)) { return ["error" => "File not found."]; } - + // Get the MIME type with safe fallback. $mimeType = function_exists('mime_content_type') ? mime_content_type($realFilePath) : null; if (!$mimeType) { $mimeType = 'application/octet-stream'; } - + + // OPTIONAL: normalize SVG MIME + $ext = strtolower(pathinfo($realFilePath, PATHINFO_EXTENSION)); + if ($ext === 'svg') { + $mimeType = 'image/svg+xml'; + } + return [ "filePath" => $realFilePath, "mimeType" => $mimeType