feat: validate declared source format against sniffed magic bytes
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+24
@@ -27,3 +27,27 @@ export function outputMimeType(targetFormat) {
|
|||||||
}
|
}
|
||||||
return mime;
|
return mime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const UNDETECTABLE_TEXT_FORMATS = {
|
||||||
|
txt: 'text/plain',
|
||||||
|
html: 'text/html',
|
||||||
|
};
|
||||||
|
|
||||||
|
function normalizeFormat(format) {
|
||||||
|
return format === 'jpg' ? 'jpeg' : format;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveInputFormat(filePath, declaredFormat) {
|
||||||
|
const detected = await detectInputMime(filePath);
|
||||||
|
|
||||||
|
if (!detected) {
|
||||||
|
const fallbackMime = UNDETECTABLE_TEXT_FORMATS[declaredFormat];
|
||||||
|
if (fallbackMime) {
|
||||||
|
return { mime: fallbackMime, valid: true };
|
||||||
|
}
|
||||||
|
return { mime: null, valid: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const valid = normalizeFormat(detected.ext) === normalizeFormat(declaredFormat);
|
||||||
|
return { mime: detected.mime, valid };
|
||||||
|
}
|
||||||
|
|||||||
+48
-1
@@ -1,6 +1,7 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { detectInputMime, outputMimeType } from '../src/mime.js';
|
import fs from 'node:fs/promises';
|
||||||
|
import { detectInputMime, outputMimeType, resolveInputFormat } from '../src/mime.js';
|
||||||
|
|
||||||
describe('detectInputMime', () => {
|
describe('detectInputMime', () => {
|
||||||
it('detects PNG from magic bytes regardless of file extension', async () => {
|
it('detects PNG from magic bytes regardless of file extension', async () => {
|
||||||
@@ -25,3 +26,49 @@ describe('outputMimeType', () => {
|
|||||||
expect(() => outputMimeType('made-up-format')).toThrowError(/made-up-format/);
|
expect(() => outputMimeType('made-up-format')).toThrowError(/made-up-format/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('resolveInputFormat', () => {
|
||||||
|
it('accepts a PNG file declared as png', async () => {
|
||||||
|
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.png');
|
||||||
|
|
||||||
|
const result = await resolveInputFormat(fixturePath, 'png');
|
||||||
|
|
||||||
|
expect(result).toEqual({ mime: 'image/png', valid: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects a PNG file declared as a different format', async () => {
|
||||||
|
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.png');
|
||||||
|
|
||||||
|
const result = await resolveInputFormat(fixturePath, 'pdf');
|
||||||
|
|
||||||
|
expect(result.valid).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('treats jpg and jpeg as equivalent declared formats', async () => {
|
||||||
|
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.png');
|
||||||
|
const result = await resolveInputFormat(fixturePath, 'png');
|
||||||
|
expect(result.valid).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('trusts the declared format for undetectable txt files', async () => {
|
||||||
|
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample.txt');
|
||||||
|
await fs.writeFile(fixturePath, 'plain text, no magic bytes');
|
||||||
|
|
||||||
|
const result = await resolveInputFormat(fixturePath, 'txt');
|
||||||
|
|
||||||
|
expect(result).toEqual({ mime: 'text/plain', valid: true });
|
||||||
|
|
||||||
|
await fs.unlink(fixturePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects an undetectable file declared as a binary format', async () => {
|
||||||
|
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'sample-fake.png');
|
||||||
|
await fs.writeFile(fixturePath, 'this is not really a PNG');
|
||||||
|
|
||||||
|
const result = await resolveInputFormat(fixturePath, 'png');
|
||||||
|
|
||||||
|
expect(result.valid).toBe(false);
|
||||||
|
|
||||||
|
await fs.unlink(fixturePath);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user