From 0813163e4de16b7b9f0946597de3c1041eb65c7b Mon Sep 17 00:00:00 2001 From: sneak Date: Thu, 1 Oct 2026 19:59:36 +0000 Subject: [PATCH] Drop exifreader's faulty-value text; test unnamed EXIF tags A text tag whose value lies outside the file, which exifreader reads as "", no longer gives a make, model, lens model or offset field. Tests cover such a JPEG and the backup-metadata dump keying a tag exifreader has no name for as undefined-. Model: opus-5-5 --- src/exif.ts | 6 +++++- test/cli/metadata-exif.test.ts | 39 ++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/exif.ts b/src/exif.ts index 224631c..b0f13f5 100644 --- a/src/exif.ts +++ b/src/exif.ts @@ -58,8 +58,12 @@ export interface PhotoExif { gpsAltitude?: number; } +// exifreader gives "" for a tag whose value lies outside the +// file; that tag is left out like one the file lacks. const asString = (v: unknown): string | undefined => - typeof v === "string" && v.length > 0 ? v : undefined; + typeof v === "string" && v.length > 0 && v !== "" + ? v + : undefined; const asNumber = (v: unknown): number | undefined => typeof v === "number" && Number.isFinite(v) ? v : undefined; diff --git a/test/cli/metadata-exif.test.ts b/test/cli/metadata-exif.test.ts index 2bdd533..273181c 100644 --- a/test/cli/metadata-exif.test.ts +++ b/test/cli/metadata-exif.test.ts @@ -63,6 +63,30 @@ const TIFF_ALTITUDE_WITHOUT_REF = [ ...[0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x02], // 25/2, at 44 ]; +// A big-endian TIFF block holding Orientation 6 and a Make whose value lies +// past the end of the file. +const TIFF_MAKE_PAST_END = [ + ...[0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08], // the first IFD at 8 + // The first IFD, at 8: two entries, then no next IFD. + ...[0x00, 0x02], + // Make (0x010f), 6 ASCII bytes at 4096. + ...[0x01, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x10, 0x00], + // Orientation (0x0112), SHORT, 6. + ...[0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00], + ...[0x00, 0x00, 0x00, 0x00], +]; + +// A big-endian TIFF block with one IFD entry that exifreader has no name for: +// tag 0xc000 (49152), SHORT, 7. +const TIFF_UNNAMED_TAG = [ + ...[0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08], // the first IFD at 8 + // The first IFD, at 8: one entry, then no next IFD. + ...[0x00, 0x01], + // Tag 0xc000, SHORT, 7. + ...[0xc0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x00], + ...[0x00, 0x00, 0x00, 0x00], +]; + // An APP1 segment whose length field matches its data. const app1 = (data: number[]): number[] => { const len = data.length + 2; @@ -94,6 +118,13 @@ describe("readPhotoExif", () => { }); }); + it("gives no make for a Make whose value lies past the end of the file", () => { + const data = [...EXIF_HEADER, ...TIFF_MAKE_PAST_END]; + expect(readPhotoExif(bytes(SOI, app1(data), SOS))).toStrictEqual({ + orientation: 6, + }); + }); + it.each([ [ "a file that is not an image", @@ -170,6 +201,14 @@ describe("extractImageMetadata", () => { }); }); + it("keys a tag exifreader has no name for by its number", () => { + const meta = extractImageMetadata( + bytes(SOI, app1([...EXIF_HEADER, ...TIFF_UNNAMED_TAG]), SOS), + ); + expect(meta?.exifError).toBeUndefined(); + expect(meta?.exif).toMatchObject({ "undefined-49152": { value: 7 } }); + }); + it("returns nothing for a file that is not an image", () => { const text = new TextEncoder().encode("just some text, not an image"); expect(extractImageMetadata(text)).toBeUndefined();