diff --git a/lib/__tests__/file-preview.test.ts b/lib/__tests__/file-preview.test.ts index 45fb796bf..a6c17b39d 100644 --- a/lib/__tests__/file-preview.test.ts +++ b/lib/__tests__/file-preview.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; -import { getFilePreviewKind, imageBlobUrl, inertBlobType, isFilePreviewable, previewBlobType, toInertBlob } from '../file-preview'; +import { getFilePreviewKind, imageBlobUrl, inertBlobType, isFilePreviewable, isMimeTypeSafeForInlinePreview, previewBlobType, toInertBlob } from '../file-preview'; describe('file preview detection', () => { it('detects browser-renderable image attachments', () => { @@ -47,6 +47,17 @@ describe('inert blob typing for cid: parts (GHSA-xvjh-v9c6-qcvc)', () => { expect(inertBlobType('')).toBe('application/octet-stream'); }); + it('refuses a type the Blob constructor would drop to an empty, sniffed type', () => { + // A TNEF/winmail.dat MIME tag is free text. new Blob() empties any type + // with a character outside U+0020-U+007E, and Firefox sniffs an untyped + // blob: document, so such a type must not count as inert. + for (const type of ['image/png\u0000', 'image/pngé', 'video/mp4\t', 'application/pdf\u0085']) { + expect(new Blob([], { type }).type).toBe(''); + expect(isMimeTypeSafeForInlinePreview(type)).toBe(false); + expect(inertBlobType(type)).toBe('application/octet-stream'); + } + }); + it('re-types a fetched Blob without touching its bytes', async () => { const html = new Blob([''], { type: 'text/html' }); const inert = toInertBlob(html); diff --git a/lib/file-preview.ts b/lib/file-preview.ts index 760e92c22..df4a052b1 100644 --- a/lib/file-preview.ts +++ b/lib/file-preview.ts @@ -157,7 +157,11 @@ const INLINE_PREVIEW_UNSAFE_MIME_TYPES = new Set([ // XML variants would execute in our origin. Only an explicit allowlist of inert // types is permitted; everything else must be downloaded. export function isMimeTypeSafeForInlinePreview(type?: string): boolean { - const mimeType = type?.split(';')[0]?.trim().toLowerCase() || ''; + // new Blob() drops a type with any character outside U+0020-U+007E to "", + // and Firefox sniffs an untyped blob: document. A winmail.dat MIME tag is + // free text, so judge only types the Blob keeps as written. + if (!type || !/^[\x20-\x7e]+$/.test(type)) return false; + const mimeType = type.split(';')[0]?.trim().toLowerCase() || ''; if (!mimeType) return false; if (INLINE_PREVIEW_UNSAFE_MIME_TYPES.has(mimeType)) return false; if (INLINE_PREVIEW_SAFE_MIME_TYPES.has(mimeType)) return true;