Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions uploads/mime-bytes/__tests__/file-type-detector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ describe('FileTypeDetector', () => {
});

it('should detect MP4 files with offset', async () => {
// ftyp + "isom" brand = generic MP4
const mp4Buffer = Buffer.from([
0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70,
0x69, 0x73, 0x6F, 0x6D, 0x00, 0x00, 0x02, 0x00
Expand All @@ -77,6 +78,34 @@ describe('FileTypeDetector', () => {
expect(result?.extensions).toContain('mp4');
});

it('should detect HEIC files (not misidentify as MP4)', async () => {
// ftyp + "heic" brand = HEIC image
const heicBuffer = Buffer.from([
0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70,
0x68, 0x65, 0x69, 0x63, 0x00, 0x00, 0x00, 0x00
]);

const result = await detector.detectFromBuffer(heicBuffer);
expect(result).toBeDefined();
expect(result?.name).toBe('heic');
expect(result?.mimeType).toBe('image/heic');
expect(result?.extensions).toContain('heic');
});

it('should detect HEIF files (not misidentify as MP4)', async () => {
// ftyp + "mif1" brand = HEIF image
const heifBuffer = Buffer.from([
0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70,
0x6D, 0x69, 0x66, 0x31, 0x00, 0x00, 0x00, 0x00
]);

const result = await detector.detectFromBuffer(heifBuffer);
expect(result).toBeDefined();
expect(result?.name).toBe('heif');
expect(result?.mimeType).toBe('image/heif');
expect(result?.extensions).toContain('heif');
});

it('should detect UTF-8 BOM', async () => {
const utf8Buffer = Buffer.from([
0xEF, 0xBB, 0xBF, 0x48, 0x65, 0x6C, 0x6C, 0x6F
Expand Down
10 changes: 8 additions & 2 deletions uploads/mime-bytes/src/file-type-detector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,14 @@ export class FileTypeDetector {
private extensionCache: Map<string, FileTypeDefinition[]>;

constructor(options: FileTypeDetectorOptions = {}) {
// Create a copy of FILE_TYPES to avoid modifying the global registry
this.fileTypes = [...FILE_TYPES];
// Create a copy of FILE_TYPES sorted so that longer magic byte sequences
// are checked before shorter ones. This ensures that specific formats
// (e.g. HEIC with 8-byte signature "ftypheic") are matched before generic
// container formats (e.g. MP4 with 4-byte signature "ftyp") that share
// the same prefix.
this.fileTypes = [...FILE_TYPES].sort(
(a, b) => b.magicBytes.length - a.magicBytes.length
);
this.options = {
peekBytes: options.peekBytes || 32,
checkMultipleOffsets: options.checkMultipleOffsets !== false,
Expand Down
Loading