fix(uploads): allow configured raw formats
Assisted-by: Claude Code
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const {
|
||||||
|
EXTENSION_TO_MIME,
|
||||||
|
extensionsToMimeTypes,
|
||||||
|
} = require('../../src/services/uploadSettings');
|
||||||
|
const { validateFileType } = require('../../src/utils/fileSecurityUtils');
|
||||||
|
|
||||||
|
const RAW_AND_HEIF_TYPES = {
|
||||||
|
dng: 'image/x-adobe-dng',
|
||||||
|
heic: 'image/heic',
|
||||||
|
heif: 'image/heif',
|
||||||
|
};
|
||||||
|
|
||||||
|
function getFrontendExtensionMap() {
|
||||||
|
const source = fs.readFileSync(
|
||||||
|
path.join(__dirname, '../../../frontend/src/utils/fileTypes.ts'),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
const match = source.match(/const EXTENSION_TO_MIME[^=]*= \{([\s\S]*?)\n\};/);
|
||||||
|
if (!match) throw new Error('Could not find frontend EXTENSION_TO_MIME');
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
Array.from(match[1].matchAll(/^(\s*)(\w+): '([^']+)',?$/gm), ([, , extension, mime]) => [extension, mime])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('configured upload file types', () => {
|
||||||
|
test('supports configured DNG, HEIC, and HEIF uploads', () => {
|
||||||
|
expect(extensionsToMimeTypes('dng,heic,heif')).toEqual(Object.values(RAW_AND_HEIF_TYPES));
|
||||||
|
|
||||||
|
for (const [extension, mimeType] of Object.entries(RAW_AND_HEIF_TYPES)) {
|
||||||
|
expect(validateFileType(`image.${extension}`, mimeType, [mimeType])).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses the same extension-to-MIME map as the frontend', () => {
|
||||||
|
expect(getFrontendExtensionMap()).toEqual(EXTENSION_TO_MIME);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -24,6 +24,9 @@ const EXTENSION_TO_MIME = {
|
|||||||
'png': 'image/png',
|
'png': 'image/png',
|
||||||
'webp': 'image/webp',
|
'webp': 'image/webp',
|
||||||
'gif': 'image/gif',
|
'gif': 'image/gif',
|
||||||
|
'dng': 'image/x-adobe-dng',
|
||||||
|
'heic': 'image/heic',
|
||||||
|
'heif': 'image/heif',
|
||||||
'mp4': 'video/mp4',
|
'mp4': 'video/mp4',
|
||||||
'm4v': 'video/mp4',
|
'm4v': 'video/mp4',
|
||||||
'webm': 'video/webm',
|
'webm': 'video/webm',
|
||||||
|
|||||||
@@ -75,6 +75,20 @@ const ALLOWED_IMAGE_TYPES = {
|
|||||||
{ offset: 0, bytes: [0x47, 0x49, 0x46, 0x38, 0x39, 0x61] } // GIF89a
|
{ offset: 0, bytes: [0x47, 0x49, 0x46, 0x38, 0x39, 0x61] } // GIF89a
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
// DNG and HEIF-family files use TIFF/ISO Base Media File Format containers,
|
||||||
|
// so their extension and declared MIME type are validated together here.
|
||||||
|
'image/x-adobe-dng': {
|
||||||
|
extensions: ['.dng'],
|
||||||
|
magicNumbers: null
|
||||||
|
},
|
||||||
|
'image/heic': {
|
||||||
|
extensions: ['.heic'],
|
||||||
|
magicNumbers: null
|
||||||
|
},
|
||||||
|
'image/heif': {
|
||||||
|
extensions: ['.heif'],
|
||||||
|
magicNumbers: null
|
||||||
|
},
|
||||||
'image/svg+xml': {
|
'image/svg+xml': {
|
||||||
extensions: ['.svg'],
|
extensions: ['.svg'],
|
||||||
// SVG files are XML-based text files, so we skip magic number validation
|
// SVG files are XML-based text files, so we skip magic number validation
|
||||||
@@ -191,7 +205,7 @@ function getSafeFilename(originalFilename) {
|
|||||||
const ext = path.extname(originalFilename).toLowerCase();
|
const ext = path.extname(originalFilename).toLowerCase();
|
||||||
|
|
||||||
// Validate extension - including both image and video extensions
|
// Validate extension - including both image and video extensions
|
||||||
const validExtensions = ['.jpg', '.jpeg', '.png', '.webp', '.gif', '.svg', '.ico', '.mp4', '.m4v', '.webm', '.mov', '.avi'];
|
const validExtensions = ['.jpg', '.jpeg', '.png', '.webp', '.gif', '.dng', '.heic', '.heif', '.svg', '.ico', '.mp4', '.m4v', '.webm', '.mov', '.avi'];
|
||||||
if (!validExtensions.includes(ext)) {
|
if (!validExtensions.includes(ext)) {
|
||||||
throw new Error('Invalid file extension');
|
throw new Error('Invalid file extension');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ import {
|
|||||||
|
|
||||||
describe('file type settings', () => {
|
describe('file type settings', () => {
|
||||||
it('keeps configured supported extensions for the upload hint', () => {
|
it('keeps configured supported extensions for the upload hint', () => {
|
||||||
expect(getSupportedExtensions('jpg, mov, mp4, .webp')).toEqual([
|
expect(getSupportedExtensions('jpg, dng, heic, heif, mov, mp4, .webp')).toEqual([
|
||||||
'jpg',
|
'jpg',
|
||||||
|
'dng',
|
||||||
|
'heic',
|
||||||
|
'heif',
|
||||||
'mov',
|
'mov',
|
||||||
'mp4',
|
'mp4',
|
||||||
'webp',
|
'webp',
|
||||||
@@ -16,18 +19,21 @@ describe('file type settings', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('uses the same configured types for validation and file selection', () => {
|
it('uses the same configured types for validation and file selection', () => {
|
||||||
expect(extensionsToMimeTypes('jpg,jpeg,mov,mp4')).toEqual([
|
expect(extensionsToMimeTypes('jpg,jpeg,dng,heic,heif,mov,mp4')).toEqual([
|
||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
|
'image/x-adobe-dng',
|
||||||
|
'image/heic',
|
||||||
|
'image/heif',
|
||||||
'video/quicktime',
|
'video/quicktime',
|
||||||
'video/mp4',
|
'video/mp4',
|
||||||
]);
|
]);
|
||||||
expect(extensionsToAcceptString('jpg,jpeg,mov,mp4')).toBe(
|
expect(extensionsToAcceptString('dng,heic,heif')).toBe(
|
||||||
'image/jpeg,video/quicktime,video/mp4'
|
'image/x-adobe-dng,image/heic,image/heif'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('falls back to the default formats when no configured types are supported', () => {
|
it('falls back to the default formats when no configured types are supported', () => {
|
||||||
expect(getSupportedExtensions('dng,unknown')).toEqual([
|
expect(getSupportedExtensions('unknown')).toEqual([
|
||||||
'jpg',
|
'jpg',
|
||||||
'jpeg',
|
'jpeg',
|
||||||
'png',
|
'png',
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ const EXTENSION_TO_MIME: Record<string, string> = {
|
|||||||
png: 'image/png',
|
png: 'image/png',
|
||||||
webp: 'image/webp',
|
webp: 'image/webp',
|
||||||
gif: 'image/gif',
|
gif: 'image/gif',
|
||||||
|
dng: 'image/x-adobe-dng',
|
||||||
|
heic: 'image/heic',
|
||||||
|
heif: 'image/heif',
|
||||||
mp4: 'video/mp4',
|
mp4: 'video/mp4',
|
||||||
m4v: 'video/mp4',
|
m4v: 'video/mp4',
|
||||||
webm: 'video/webm',
|
webm: 'video/webm',
|
||||||
|
|||||||
Reference in New Issue
Block a user