fix(upload): store sniffed MIME instead of generic CDN content-type
Publish container image / build-and-push (push) Canceled after 0s
Publish container image / build-and-push (push) Canceled after 0s
This commit is contained in:
+21
-18
@@ -326,15 +326,18 @@ function downloadImage(imageUrl, maxRedirects = 5) {
|
||||
});
|
||||
|
||||
response.on('end', () => {
|
||||
const buffer = Buffer.concat(chunks);
|
||||
let resolvedType = contentType;
|
||||
if (typeIsGeneric) {
|
||||
const sniffed = sniffMime(Buffer.concat(chunks));
|
||||
if (!sniffed) {
|
||||
// Replace the generic CDN type with what the bytes actually are,
|
||||
// so the correct MIME gets stored and served.
|
||||
resolvedType = sniffMimeExact(buffer);
|
||||
if (!resolvedType) {
|
||||
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 });
|
||||
resolve({ buffer, mimeType: resolvedType, filename });
|
||||
});
|
||||
|
||||
response.on('error', reject);
|
||||
@@ -435,22 +438,22 @@ router.use((err, req, res, next) => {
|
||||
|
||||
|
||||
// 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;
|
||||
// as binary/octet-stream). Returns the exact MIME or null.
|
||||
function sniffMimeExact(buf) {
|
||||
if (!buf || buf.length < 12) return null;
|
||||
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;
|
||||
if (hex.startsWith('89504e47')) return 'image/png';
|
||||
if (hex.startsWith('ffd8ff')) return 'image/jpeg';
|
||||
if (ascii.startsWith('GIF87a') || ascii.startsWith('GIF89a')) return 'image/gif';
|
||||
if (ascii.startsWith('RIFF') && buf.slice(8, 12).toString('latin1') === 'WEBP') return 'image/webp';
|
||||
if (ascii.startsWith('%PDF')) return 'application/pdf';
|
||||
if (buf.slice(4, 8).toString('latin1') === 'ftyp') {
|
||||
const brand = buf.slice(8, 12).toString('latin1');
|
||||
return brand.startsWith('qt') ? 'video/quicktime' : 'video/mp4';
|
||||
}
|
||||
if (hex.startsWith('1a45dfa3')) return 'video/webm';
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user