diff --git a/apps/readest-app/src/__tests__/foliate-opf-entity-sanitize.test.ts b/apps/readest-app/src/__tests__/foliate-opf-entity-sanitize.test.ts new file mode 100644 index 00000000..1374d0ed --- /dev/null +++ b/apps/readest-app/src/__tests__/foliate-opf-entity-sanitize.test.ts @@ -0,0 +1,67 @@ +// Regression test for an import failure on EPUBs whose OPF contains a raw, +// unescaped ampersand. +// +// "Shadow Slave - Vol. 6 - All The Devils Are Here.epub" ships an OPF with a +// hand-built manifest id that was never XML-escaped: +// +// +// +// The bare `&` makes the OPF non-well-formed XML. A strict parser (DOMParser in +// Chrome/jsdom, and the same parser foliate-js uses on every platform) reads the +// `&`, then the name `_Rescue_153`, then hits `"` where it expected `;` and +// reports `EntityRef: expecting ';'`. Import then dies with +// "Failed to open the book file: XML parsing error: ...". +// +// foliate-js already mapped a handful of named HTML entities (` ` …) to +// numeric refs, but it never escaped stray ampersands that aren't part of any +// entity reference. This test pins the fix: such an OPF must still parse and +// yield its metadata. +import { describe, expect, it } from 'vitest'; + +import { parseEpubMetadataFromXML } from 'foliate-js/epub.js'; + +const NBSP = String.fromCharCode(160); + +const opf = (title: string, manifest: string) => ` + + + ${title} + Guiltythree + 1 + en + + + ${manifest} + + + + +`; + +const COVER = ``; + +describe('OPF XML entity sanitization', () => { + it('imports an OPF with a raw & in a manifest item id (the reported bug)', () => { + const title = 'Shadow Slave - Vol. 6 - All The Devils Are Here'; + const xml = opf( + title, + `${COVER} + `, + ); + const { metadata } = parseEpubMetadataFromXML(xml); + expect(metadata.title).toBe(title); + }); + + it('escapes a bare & in element text content', () => { + const xml = opf('Search & Rescue & More', COVER); + const { metadata } = parseEpubMetadataFromXML(xml); + expect(metadata.title).toBe('Search & Rescue & More'); + }); + + it('preserves valid entities and numeric/named references (no double-escaping)', () => { + // & -> "&",   -> U+00A0,   -> U+00A0 (mapped from HTML). + const xml = opf('R&D Press Ltd', COVER); + const { metadata } = parseEpubMetadataFromXML(xml); + expect(metadata.title).toBe(`R&D${NBSP}Press${NBSP}Ltd`); + }); +}); diff --git a/packages/foliate-js b/packages/foliate-js index 9c34e835..4aa4efe3 160000 --- a/packages/foliate-js +++ b/packages/foliate-js @@ -1 +1 @@ -Subproject commit 9c34e835c7a58e2c09197e77ca0d678c48202b17 +Subproject commit 4aa4efe3bb372fca472df6e9c0b47d63e0103789