From b6e3a95741d7084862a910942bdb3f3c7700a1c6 Mon Sep 17 00:00:00 2001 From: Kamil Jiwa Date: Sun, 21 Jun 2026 08:56:47 -0700 Subject: [PATCH] Recognize `blob:` and `data:image` URLs as images (#307) --- src/util/gedcom_util.spec.ts | 11 +++++++++++ src/util/gedcom_util.ts | 22 ++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/util/gedcom_util.spec.ts b/src/util/gedcom_util.spec.ts index de0f311..daa3108 100644 --- a/src/util/gedcom_util.spec.ts +++ b/src/util/gedcom_util.spec.ts @@ -215,6 +215,17 @@ describe('Media Resolution and Utilities', () => { expect(isImageFile('test.webp?a=1&b=2#h')).toBe(true); expect(isImageFile('test.pdf?img=test.jpg')).toBe(false); }); + + it('treats blob: and data:image URLs as images', () => { + expect(isImageFile('blob:https://example.org/0-1-2-3')).toBe(true); + expect(isImageFile('data:image/png;base64,iVBORw0KGgo=')).toBe(true); + expect(isImageFile('data:image/avif;base64,AAAA')).toBe(true); + }); + + it('does not treat non-image extensionless URLs as images', () => { + expect(isImageFile('data:application/pdf;base64,AAAA')).toBe(false); + expect(isImageFile('https://example.org/photo')).toBe(false); + }); }); describe('isBrowserLoadable()', () => { diff --git a/src/util/gedcom_util.ts b/src/util/gedcom_util.ts index 5abbe24..159a698 100644 --- a/src/util/gedcom_util.ts +++ b/src/util/gedcom_util.ts @@ -207,11 +207,25 @@ export function normalizeGedcom(gedcom: JsonGedcomData): JsonGedcomData { const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp']; -/** Returns true if the given file name has a known image extension. */ +/** + * Returns true if the given file reference points to an image: a name/URL with + * a known image extension, or an extensionless but inherently browser-renderable + * `blob:` object URL or `data:image/...` URI. The latter two carry no path + * extension but are already accepted by `isBrowserLoadable`, so without this + * `filterImage` would drop them. A `blob:` URL's type is not derivable from the + * string, so it is accepted unconditionally; `data:` is required to be an image. + */ export function isImageFile(fileName: string): boolean { - const cleanName = fileName.split(/[?#]/)[0]; - const lowerName = cleanName.toLowerCase(); - return IMAGE_EXTENSIONS.some((ext) => lowerName.endsWith(ext)); + // Anchored tests read only the scheme, so an inlined data: payload (which can + // be multi-megabyte) is never scanned or copied. + if (/^blob:/i.test(fileName)) { + return true; + } + if (/^data:/i.test(fileName)) { + return /^data:image\//i.test(fileName); + } + const cleanName = fileName.split(/[?#]/)[0].toLowerCase(); + return IMAGE_EXTENSIONS.some((ext) => cleanName.endsWith(ext)); } /**