From 3e3013045c6141599c3761df7d7625b4b0ee95f0 Mon Sep 17 00:00:00 2001 From: Niklas Date: Sat, 5 Sep 2026 12:57:20 +0200 Subject: [PATCH] fix(upload): sniff magic bytes when CDN serves generic binary/octet-stream --- backend/routes/upload.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/backend/routes/upload.js b/backend/routes/upload.js index b0ce87e..08e497c 100644 --- a/backend/routes/upload.js +++ b/backend/routes/upload.js @@ -307,9 +307,11 @@ function downloadImage(imageUrl, maxRedirects = 5) { } const contentType = (response.headers['content-type'] || '').split(';')[0].trim(); - if (!ALLOWED_MIME_TYPES.includes(contentType)) { + const GENERIC_TYPES = ['binary/octet-stream', 'application/octet-stream']; + if (!ALLOWED_MIME_TYPES.includes(contentType) && !GENERIC_TYPES.includes(contentType)) { return reject(new Error(`Unsupported content type: ${contentType}`)); } + const typeIsGeneric = GENERIC_TYPES.includes(contentType); const chunks = []; let totalSize = 0; @@ -324,6 +326,12 @@ function downloadImage(imageUrl, maxRedirects = 5) { }); response.on('end', () => { + if (typeIsGeneric) { + const sniffed = sniffMime(Buffer.concat(chunks)); + if (!sniffed) { + return reject(new Error('Unsupported content type: binary/octet-stream (unknown magic bytes)')); + } + } const buffer = Buffer.concat(chunks); const filename = parsed.pathname.split('/').pop() || 'image'; resolve({ buffer, mimeType: contentType, filename }); @@ -425,4 +433,24 @@ router.use((err, req, res, next) => { next(err); }); + +// Sniff the real MIME type from magic bytes (for CDNs that serve everything +// as binary/octet-stream). Returns true when the buffer looks like an +// allowed image/video/pdf type. +function sniffMime(buf) { + if (!buf || buf.length < 12) return false; + const hex = buf.slice(0, 12).toString('hex'); + const ascii = buf.slice(0, 12).toString('latin1'); + if (hex.startsWith('89504e47')) return true; // PNG + if (hex.startsWith('ffd8ff')) return true; // JPEG + if (ascii.startsWith('GIF87a') || ascii.startsWith('GIF89a')) return true; // GIF + if (ascii.startsWith('RIFF') && buf.slice(8, 12).toString('latin1') === 'WEBP') return true; // WebP + if (ascii.startsWith('%PDF')) return true; // PDF + if (ascii.startsWith('ftyp')) return true; // MP4/QuickTime (ftyp at 0 for some, 4 for others) + if (buf.slice(4, 8).toString('latin1') === 'ftyp') return true; + if (hex.startsWith('1a45dfa3')) return true; // WebM/Matroska + if (ascii.startsWith('OggS')) return false; // ogg not allowed + return false; +} + module.exports = router;