@likecoin/epubcheck-ts 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/epubcheck.js +1 -1
- package/bin/epubcheck.ts +1 -1
- package/dist/index.cjs +923 -241
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +923 -241
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -870,7 +870,7 @@ var MessageDefs = {
|
|
|
870
870
|
NAV_001: {
|
|
871
871
|
id: "NAV-001",
|
|
872
872
|
severity: "error",
|
|
873
|
-
description:
|
|
873
|
+
description: 'Navigation Document must have a nav element with epub:type="toc"'
|
|
874
874
|
},
|
|
875
875
|
NAV_002: { id: "NAV-002", severity: "suppressed", description: "Missing toc nav element" },
|
|
876
876
|
NAV_003: {
|
|
@@ -1634,6 +1634,81 @@ var CSSValidator = class {
|
|
|
1634
1634
|
}
|
|
1635
1635
|
};
|
|
1636
1636
|
|
|
1637
|
+
// src/opf/types.ts
|
|
1638
|
+
var CORE_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
1639
|
+
// Image types
|
|
1640
|
+
"image/gif",
|
|
1641
|
+
"image/jpeg",
|
|
1642
|
+
"image/png",
|
|
1643
|
+
"image/svg+xml",
|
|
1644
|
+
"image/webp",
|
|
1645
|
+
// Audio types
|
|
1646
|
+
"audio/mpeg",
|
|
1647
|
+
"audio/mp4",
|
|
1648
|
+
"audio/ogg",
|
|
1649
|
+
// CSS
|
|
1650
|
+
"text/css",
|
|
1651
|
+
// Fonts
|
|
1652
|
+
"font/otf",
|
|
1653
|
+
"font/ttf",
|
|
1654
|
+
"font/woff",
|
|
1655
|
+
"font/woff2",
|
|
1656
|
+
"application/font-sfnt",
|
|
1657
|
+
// deprecated alias for font/otf, font/ttf
|
|
1658
|
+
"application/font-woff",
|
|
1659
|
+
// deprecated alias for font/woff
|
|
1660
|
+
"application/vnd.ms-opentype",
|
|
1661
|
+
// deprecated alias
|
|
1662
|
+
// Content documents
|
|
1663
|
+
"application/xhtml+xml",
|
|
1664
|
+
"application/x-dtbncx+xml",
|
|
1665
|
+
// NCX
|
|
1666
|
+
// JavaScript (EPUB 3)
|
|
1667
|
+
"text/javascript",
|
|
1668
|
+
"application/javascript",
|
|
1669
|
+
// Media overlays
|
|
1670
|
+
"application/smil+xml",
|
|
1671
|
+
// PLS (Pronunciation Lexicon)
|
|
1672
|
+
"application/pls+xml"
|
|
1673
|
+
]);
|
|
1674
|
+
function isCoreMediaType(mimeType) {
|
|
1675
|
+
if (CORE_MEDIA_TYPES.has(mimeType)) return true;
|
|
1676
|
+
if (mimeType.startsWith("video/")) return true;
|
|
1677
|
+
if (/^audio\/ogg\s*;\s*codecs=opus$/i.test(mimeType)) return true;
|
|
1678
|
+
const semicolonIndex = mimeType.indexOf(";");
|
|
1679
|
+
if (semicolonIndex >= 0) {
|
|
1680
|
+
const baseType = mimeType.substring(0, semicolonIndex).trim();
|
|
1681
|
+
if (CORE_MEDIA_TYPES.has(baseType)) return true;
|
|
1682
|
+
if (baseType.startsWith("video/")) return true;
|
|
1683
|
+
}
|
|
1684
|
+
return false;
|
|
1685
|
+
}
|
|
1686
|
+
var ITEM_PROPERTIES = /* @__PURE__ */ new Set([
|
|
1687
|
+
"cover-image",
|
|
1688
|
+
"mathml",
|
|
1689
|
+
"nav",
|
|
1690
|
+
"remote-resources",
|
|
1691
|
+
"scripted",
|
|
1692
|
+
"svg",
|
|
1693
|
+
"switch"
|
|
1694
|
+
]);
|
|
1695
|
+
var LINK_PROPERTIES = /* @__PURE__ */ new Set(["onix", "marc21xml-record", "mods-record", "xmp-record"]);
|
|
1696
|
+
var SPINE_PROPERTIES = /* @__PURE__ */ new Set([
|
|
1697
|
+
"page-spread-left",
|
|
1698
|
+
"page-spread-right",
|
|
1699
|
+
"rendition:spread-none",
|
|
1700
|
+
"rendition:spread-landscape",
|
|
1701
|
+
"rendition:spread-portrait",
|
|
1702
|
+
"rendition:spread-both",
|
|
1703
|
+
"rendition:spread-auto",
|
|
1704
|
+
"rendition:page-spread-center",
|
|
1705
|
+
"rendition:layout-reflowable",
|
|
1706
|
+
"rendition:layout-pre-paginated",
|
|
1707
|
+
"rendition:orientation-auto",
|
|
1708
|
+
"rendition:orientation-landscape",
|
|
1709
|
+
"rendition:orientation-portrait"
|
|
1710
|
+
]);
|
|
1711
|
+
|
|
1637
1712
|
// src/references/types.ts
|
|
1638
1713
|
function isPublicationResourceReference(type) {
|
|
1639
1714
|
return [
|
|
@@ -1644,7 +1719,10 @@ function isPublicationResourceReference(type) {
|
|
|
1644
1719
|
"audio" /* AUDIO */,
|
|
1645
1720
|
"video" /* VIDEO */,
|
|
1646
1721
|
"track" /* TRACK */,
|
|
1647
|
-
"media-overlay" /* MEDIA_OVERLAY
|
|
1722
|
+
"media-overlay" /* MEDIA_OVERLAY */,
|
|
1723
|
+
"svg-symbol" /* SVG_SYMBOL */,
|
|
1724
|
+
"svg-paint" /* SVG_PAINT */,
|
|
1725
|
+
"svg-clip-path" /* SVG_CLIP_PATH */
|
|
1648
1726
|
].includes(type);
|
|
1649
1727
|
}
|
|
1650
1728
|
|
|
@@ -1702,6 +1780,17 @@ function isHTTP(url) {
|
|
|
1702
1780
|
function isRemoteURL(url) {
|
|
1703
1781
|
return isHTTP(url) || isHTTPS(url);
|
|
1704
1782
|
}
|
|
1783
|
+
function checkUrlLeaking(href) {
|
|
1784
|
+
const TEST_BASE_A = "https://a.example.org/A/";
|
|
1785
|
+
const TEST_BASE_B = "https://b.example.org/B/";
|
|
1786
|
+
try {
|
|
1787
|
+
const urlA = new URL(href, TEST_BASE_A).toString();
|
|
1788
|
+
const urlB = new URL(href, TEST_BASE_B).toString();
|
|
1789
|
+
return !urlA.startsWith(TEST_BASE_A) || !urlB.startsWith(TEST_BASE_B);
|
|
1790
|
+
} catch {
|
|
1791
|
+
return false;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1705
1794
|
function resolveManifestHref(opfDir, href) {
|
|
1706
1795
|
if (isRemoteURL(href)) return href;
|
|
1707
1796
|
try {
|
|
@@ -1837,6 +1926,9 @@ var ContentValidator = class {
|
|
|
1837
1926
|
if (context.version.startsWith("3")) {
|
|
1838
1927
|
this.validateSVGDocument(context, fullPath, item);
|
|
1839
1928
|
}
|
|
1929
|
+
if (refValidator) {
|
|
1930
|
+
this.extractSVGReferences(context, fullPath, opfDir, refValidator);
|
|
1931
|
+
}
|
|
1840
1932
|
}
|
|
1841
1933
|
}
|
|
1842
1934
|
}
|
|
@@ -1884,6 +1976,133 @@ var ContentValidator = class {
|
|
|
1884
1976
|
doc.dispose();
|
|
1885
1977
|
}
|
|
1886
1978
|
}
|
|
1979
|
+
/**
|
|
1980
|
+
* Extract references from SVG documents: font-face-uri, xml-stylesheet PI, @import in style
|
|
1981
|
+
*/
|
|
1982
|
+
extractSVGReferences(context, path, opfDir, refValidator) {
|
|
1983
|
+
const svgData = context.files.get(path);
|
|
1984
|
+
if (!svgData) return;
|
|
1985
|
+
const svgContent = new TextDecoder().decode(svgData);
|
|
1986
|
+
let doc;
|
|
1987
|
+
try {
|
|
1988
|
+
doc = libxml2Wasm.XmlDocument.fromString(svgContent);
|
|
1989
|
+
} catch {
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
const docDir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
1993
|
+
try {
|
|
1994
|
+
const root = doc.root;
|
|
1995
|
+
try {
|
|
1996
|
+
const fontFaceUris = root.find(".//svg:font-face-uri", {
|
|
1997
|
+
svg: "http://www.w3.org/2000/svg"
|
|
1998
|
+
});
|
|
1999
|
+
for (const uri of fontFaceUris) {
|
|
2000
|
+
const href = this.getAttribute(uri, "xlink:href") ?? this.getAttribute(uri, "href");
|
|
2001
|
+
if (!href) continue;
|
|
2002
|
+
if (href.startsWith("http://") || href.startsWith("https://")) {
|
|
2003
|
+
refValidator.addReference({
|
|
2004
|
+
url: href,
|
|
2005
|
+
targetResource: href,
|
|
2006
|
+
type: "font" /* FONT */,
|
|
2007
|
+
location: { path, line: uri.line }
|
|
2008
|
+
});
|
|
2009
|
+
} else {
|
|
2010
|
+
const resolvedPath = this.resolveRelativePath(docDir, href, opfDir);
|
|
2011
|
+
refValidator.addReference({
|
|
2012
|
+
url: href,
|
|
2013
|
+
targetResource: resolvedPath,
|
|
2014
|
+
type: "font" /* FONT */,
|
|
2015
|
+
location: { path, line: uri.line }
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
} catch {
|
|
2020
|
+
}
|
|
2021
|
+
try {
|
|
2022
|
+
const styles = root.find(".//svg:style", { svg: "http://www.w3.org/2000/svg" });
|
|
2023
|
+
for (const style of styles) {
|
|
2024
|
+
const cssContent = style.content;
|
|
2025
|
+
if (cssContent) {
|
|
2026
|
+
this.extractCSSImports(path, cssContent, opfDir, refValidator);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
} catch {
|
|
2030
|
+
}
|
|
2031
|
+
try {
|
|
2032
|
+
const svgUseXlink = root.find(".//svg:use[@xlink:href]", {
|
|
2033
|
+
svg: "http://www.w3.org/2000/svg",
|
|
2034
|
+
xlink: "http://www.w3.org/1999/xlink"
|
|
2035
|
+
});
|
|
2036
|
+
const svgUseHref = root.find(".//svg:use[@href]", {
|
|
2037
|
+
svg: "http://www.w3.org/2000/svg"
|
|
2038
|
+
});
|
|
2039
|
+
for (const useNode of [...svgUseXlink, ...svgUseHref]) {
|
|
2040
|
+
const useElem = useNode;
|
|
2041
|
+
const href = this.getAttribute(useElem, "xlink:href") ?? this.getAttribute(useElem, "href");
|
|
2042
|
+
if (!href) continue;
|
|
2043
|
+
if (href.startsWith("http://") || href.startsWith("https://")) continue;
|
|
2044
|
+
if (!href.includes("#")) {
|
|
2045
|
+
pushMessage(context.messages, {
|
|
2046
|
+
id: MessageId.RSC_015,
|
|
2047
|
+
message: `SVG "use" element requires a fragment identifier, but found "${href}"`,
|
|
2048
|
+
location: { path, line: useNode.line }
|
|
2049
|
+
});
|
|
2050
|
+
continue;
|
|
2051
|
+
}
|
|
2052
|
+
const resolvedPath = this.resolveRelativePath(docDir, href, opfDir);
|
|
2053
|
+
const hashIndex = resolvedPath.indexOf("#");
|
|
2054
|
+
const targetResource = hashIndex >= 0 ? resolvedPath.slice(0, hashIndex) : path;
|
|
2055
|
+
const fragment = hashIndex >= 0 ? resolvedPath.slice(hashIndex + 1) : void 0;
|
|
2056
|
+
const useRef = {
|
|
2057
|
+
url: href,
|
|
2058
|
+
targetResource,
|
|
2059
|
+
type: "svg-symbol" /* SVG_SYMBOL */,
|
|
2060
|
+
location: { path, line: useNode.line }
|
|
2061
|
+
};
|
|
2062
|
+
if (fragment) {
|
|
2063
|
+
useRef.fragment = fragment;
|
|
2064
|
+
}
|
|
2065
|
+
refValidator.addReference(useRef);
|
|
2066
|
+
}
|
|
2067
|
+
} catch {
|
|
2068
|
+
}
|
|
2069
|
+
} finally {
|
|
2070
|
+
doc.dispose();
|
|
2071
|
+
}
|
|
2072
|
+
this.extractXmlStylesheetPIs(svgContent, path, docDir, opfDir, refValidator);
|
|
2073
|
+
}
|
|
2074
|
+
/**
|
|
2075
|
+
* Extract href from <?xml-stylesheet?> processing instructions
|
|
2076
|
+
*/
|
|
2077
|
+
extractXmlStylesheetPIs(content, path, docDir, opfDir, refValidator) {
|
|
2078
|
+
const piRegex = /<\?xml-stylesheet\s+([^?]*)\?>/g;
|
|
2079
|
+
let match;
|
|
2080
|
+
while ((match = piRegex.exec(content)) !== null) {
|
|
2081
|
+
const attrs = match[1];
|
|
2082
|
+
if (!attrs) continue;
|
|
2083
|
+
const hrefMatch = /href\s*=\s*["']([^"']*)["']/.exec(attrs);
|
|
2084
|
+
if (!hrefMatch?.[1]) continue;
|
|
2085
|
+
const href = hrefMatch[1];
|
|
2086
|
+
const beforeMatch = content.substring(0, match.index);
|
|
2087
|
+
const line = beforeMatch.split("\n").length;
|
|
2088
|
+
if (href.startsWith("http://") || href.startsWith("https://")) {
|
|
2089
|
+
refValidator.addReference({
|
|
2090
|
+
url: href,
|
|
2091
|
+
targetResource: href,
|
|
2092
|
+
type: "stylesheet" /* STYLESHEET */,
|
|
2093
|
+
location: { path, line }
|
|
2094
|
+
});
|
|
2095
|
+
} else {
|
|
2096
|
+
const resolvedPath = this.resolveRelativePath(docDir, href, opfDir);
|
|
2097
|
+
refValidator.addReference({
|
|
2098
|
+
url: href,
|
|
2099
|
+
targetResource: resolvedPath,
|
|
2100
|
+
type: "stylesheet" /* STYLESHEET */,
|
|
2101
|
+
location: { path, line }
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
1887
2106
|
detectSVGRemoteResources(root) {
|
|
1888
2107
|
try {
|
|
1889
2108
|
const fontFaceUris = root.find(".//svg:font-face-uri", {
|
|
@@ -1949,9 +2168,11 @@ var ContentValidator = class {
|
|
|
1949
2168
|
for (const ref of result.references) {
|
|
1950
2169
|
if (ref.type === "font") {
|
|
1951
2170
|
if (ref.url.startsWith("http://") || ref.url.startsWith("https://")) {
|
|
2171
|
+
const hashIndex = ref.url.indexOf("#");
|
|
2172
|
+
const targetResource = hashIndex >= 0 ? ref.url.slice(0, hashIndex) : ref.url;
|
|
1952
2173
|
refValidator.addReference({
|
|
1953
2174
|
url: ref.url,
|
|
1954
|
-
targetResource
|
|
2175
|
+
targetResource,
|
|
1955
2176
|
type: "font" /* FONT */,
|
|
1956
2177
|
location: { path }
|
|
1957
2178
|
});
|
|
@@ -1968,9 +2189,11 @@ var ContentValidator = class {
|
|
|
1968
2189
|
}
|
|
1969
2190
|
} else if (ref.type === "image") {
|
|
1970
2191
|
if (ref.url.startsWith("http://") || ref.url.startsWith("https://")) {
|
|
2192
|
+
const hashIndex = ref.url.indexOf("#");
|
|
2193
|
+
const targetResource = hashIndex >= 0 ? ref.url.slice(0, hashIndex) : ref.url;
|
|
1971
2194
|
refValidator.addReference({
|
|
1972
2195
|
url: ref.url,
|
|
1973
|
-
targetResource
|
|
2196
|
+
targetResource,
|
|
1974
2197
|
type: "image" /* IMAGE */,
|
|
1975
2198
|
location: { path }
|
|
1976
2199
|
});
|
|
@@ -1985,9 +2208,27 @@ var ContentValidator = class {
|
|
|
1985
2208
|
location: { path }
|
|
1986
2209
|
});
|
|
1987
2210
|
}
|
|
2211
|
+
} else if (ref.type === "import") {
|
|
2212
|
+
const location = { path };
|
|
2213
|
+
if (ref.line !== void 0) location.line = ref.line;
|
|
2214
|
+
if (ref.url.startsWith("http://") || ref.url.startsWith("https://")) {
|
|
2215
|
+
refValidator.addReference({
|
|
2216
|
+
url: ref.url,
|
|
2217
|
+
targetResource: ref.url,
|
|
2218
|
+
type: "stylesheet" /* STYLESHEET */,
|
|
2219
|
+
location
|
|
2220
|
+
});
|
|
2221
|
+
} else {
|
|
2222
|
+
const resolvedPath = this.resolveRelativePath(cssDir, ref.url, opfDir);
|
|
2223
|
+
refValidator.addReference({
|
|
2224
|
+
url: ref.url,
|
|
2225
|
+
targetResource: resolvedPath,
|
|
2226
|
+
type: "stylesheet" /* STYLESHEET */,
|
|
2227
|
+
location
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
1988
2230
|
}
|
|
1989
2231
|
}
|
|
1990
|
-
this.extractCSSImports(path, cssContent, opfDir, refValidator);
|
|
1991
2232
|
}
|
|
1992
2233
|
validateXHTMLDocument(context, path, itemId, opfDir, registry, refValidator) {
|
|
1993
2234
|
const data = context.files.get(path);
|
|
@@ -2094,6 +2335,13 @@ var ContentValidator = class {
|
|
|
2094
2335
|
location: { path }
|
|
2095
2336
|
});
|
|
2096
2337
|
}
|
|
2338
|
+
if (!hasMathML && manifestItem?.properties?.includes("mathml")) {
|
|
2339
|
+
pushMessage(context.messages, {
|
|
2340
|
+
id: MessageId.OPF_015,
|
|
2341
|
+
message: 'The property "mathml" should not be declared in the OPF file',
|
|
2342
|
+
location: { path }
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2097
2345
|
const hasSVG = this.detectSVG(context, path, root);
|
|
2098
2346
|
if (hasSVG && !manifestItem?.properties?.includes("svg")) {
|
|
2099
2347
|
pushMessage(context.messages, {
|
|
@@ -2117,6 +2365,13 @@ var ContentValidator = class {
|
|
|
2117
2365
|
location: { path }
|
|
2118
2366
|
});
|
|
2119
2367
|
}
|
|
2368
|
+
if (!hasSwitch && manifestItem?.properties?.includes("switch")) {
|
|
2369
|
+
pushMessage(context.messages, {
|
|
2370
|
+
id: MessageId.OPF_015,
|
|
2371
|
+
message: 'The property "switch" should not be declared in the OPF file',
|
|
2372
|
+
location: { path }
|
|
2373
|
+
});
|
|
2374
|
+
}
|
|
2120
2375
|
const hasRemoteResources = this.detectRemoteResources(context, path, root);
|
|
2121
2376
|
if (hasRemoteResources && !manifestItem?.properties?.includes("remote-resources")) {
|
|
2122
2377
|
pushMessage(context.messages, {
|
|
@@ -2145,13 +2400,21 @@ var ContentValidator = class {
|
|
|
2145
2400
|
this.extractAndRegisterIDs(path, root, registry);
|
|
2146
2401
|
}
|
|
2147
2402
|
if (refValidator && opfDir !== void 0) {
|
|
2148
|
-
this.extractAndRegisterHyperlinks(context, path, root, opfDir, refValidator);
|
|
2403
|
+
this.extractAndRegisterHyperlinks(context, path, root, opfDir, refValidator, !!isNavItem);
|
|
2149
2404
|
this.extractAndRegisterStylesheets(path, root, opfDir, refValidator);
|
|
2150
|
-
this.extractAndRegisterImages(path, root, opfDir, refValidator);
|
|
2405
|
+
this.extractAndRegisterImages(context, path, root, opfDir, refValidator, registry);
|
|
2151
2406
|
this.extractAndRegisterMathMLAltimg(path, root, opfDir, refValidator);
|
|
2152
2407
|
this.extractAndRegisterScripts(path, root, opfDir, refValidator);
|
|
2153
2408
|
this.extractAndRegisterCiteAttributes(path, root, opfDir, refValidator);
|
|
2154
|
-
this.extractAndRegisterMediaElements(path, root, opfDir, refValidator);
|
|
2409
|
+
this.extractAndRegisterMediaElements(context, path, root, opfDir, refValidator, registry);
|
|
2410
|
+
this.extractAndRegisterEmbeddedElements(
|
|
2411
|
+
context,
|
|
2412
|
+
path,
|
|
2413
|
+
root,
|
|
2414
|
+
opfDir,
|
|
2415
|
+
refValidator,
|
|
2416
|
+
registry
|
|
2417
|
+
);
|
|
2155
2418
|
}
|
|
2156
2419
|
} finally {
|
|
2157
2420
|
doc.dispose();
|
|
@@ -2207,14 +2470,12 @@ var ContentValidator = class {
|
|
|
2207
2470
|
return epubTypeAttr ? epubTypeAttr.value.trim().split(/\s+/) : [];
|
|
2208
2471
|
};
|
|
2209
2472
|
let tocNav;
|
|
2210
|
-
let tocEpubTypeValue = "";
|
|
2211
2473
|
let pageListCount = 0;
|
|
2212
2474
|
let landmarksCount = 0;
|
|
2213
2475
|
for (const nav of navElements) {
|
|
2214
2476
|
const types = getNavTypes(nav);
|
|
2215
2477
|
if (types.includes("toc") && !tocNav) {
|
|
2216
2478
|
tocNav = nav;
|
|
2217
|
-
tocEpubTypeValue = types.join(" ");
|
|
2218
2479
|
}
|
|
2219
2480
|
if (types.includes("page-list")) pageListCount++;
|
|
2220
2481
|
if (types.includes("landmarks")) landmarksCount++;
|
|
@@ -2265,7 +2526,7 @@ var ContentValidator = class {
|
|
|
2265
2526
|
}
|
|
2266
2527
|
this.checkNavHeadingContent(context, path, root);
|
|
2267
2528
|
this.checkNavHiddenAttribute(context, path, root);
|
|
2268
|
-
this.checkNavRemoteLinks(context, path, root
|
|
2529
|
+
this.checkNavRemoteLinks(context, path, root);
|
|
2269
2530
|
this.collectTocLinks(context, path, tocNav);
|
|
2270
2531
|
}
|
|
2271
2532
|
checkNavFirstChildHeading(context, path, navElem) {
|
|
@@ -2448,24 +2709,30 @@ var ContentValidator = class {
|
|
|
2448
2709
|
}
|
|
2449
2710
|
}
|
|
2450
2711
|
}
|
|
2451
|
-
checkNavRemoteLinks(context, path, root
|
|
2452
|
-
const
|
|
2453
|
-
const
|
|
2454
|
-
const
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
const
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2712
|
+
checkNavRemoteLinks(context, path, root) {
|
|
2713
|
+
const HTML_NS = { html: "http://www.w3.org/1999/xhtml" };
|
|
2714
|
+
const navElements = root.find(".//html:nav", HTML_NS);
|
|
2715
|
+
for (const nav of navElements) {
|
|
2716
|
+
const navElem = nav;
|
|
2717
|
+
const epubTypeAttr = "attrs" in navElem ? navElem.attrs.find(
|
|
2718
|
+
(attr) => attr.name === "type" && attr.prefix === "epub" && attr.namespaceUri === "http://www.idpf.org/2007/ops"
|
|
2719
|
+
) : void 0;
|
|
2720
|
+
const types = epubTypeAttr ? epubTypeAttr.value.trim().split(/\s+/) : [];
|
|
2721
|
+
const isToc = types.includes("toc");
|
|
2722
|
+
const isLandmarks = types.includes("landmarks");
|
|
2723
|
+
const isPageList = types.includes("page-list");
|
|
2724
|
+
if (!isToc && !isLandmarks && !isPageList) continue;
|
|
2725
|
+
const navType = isToc ? "toc" : isLandmarks ? "landmarks" : "page-list";
|
|
2726
|
+
const links = navElem.find(".//html:a[@href]", HTML_NS);
|
|
2727
|
+
for (const link of links) {
|
|
2728
|
+
const href = this.getAttribute(link, "href");
|
|
2729
|
+
if (href && (href.startsWith("http://") || href.startsWith("https://"))) {
|
|
2730
|
+
pushMessage(context.messages, {
|
|
2731
|
+
id: MessageId.NAV_010,
|
|
2732
|
+
message: `"${navType}" nav must not link to remote resources; found link to "${href}"`,
|
|
2733
|
+
location: { path }
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2469
2736
|
}
|
|
2470
2737
|
}
|
|
2471
2738
|
}
|
|
@@ -2603,6 +2870,20 @@ var ContentValidator = class {
|
|
|
2603
2870
|
return true;
|
|
2604
2871
|
}
|
|
2605
2872
|
}
|
|
2873
|
+
const objects = root.find(".//html:object[@data]", { html: "http://www.w3.org/1999/xhtml" });
|
|
2874
|
+
for (const obj of objects) {
|
|
2875
|
+
const data = this.getAttribute(obj, "data");
|
|
2876
|
+
if (data && (data.startsWith("http://") || data.startsWith("https://"))) {
|
|
2877
|
+
return true;
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
const embeds = root.find(".//html:embed[@src]", { html: "http://www.w3.org/1999/xhtml" });
|
|
2881
|
+
for (const embed of embeds) {
|
|
2882
|
+
const src = this.getAttribute(embed, "src");
|
|
2883
|
+
if (src && (src.startsWith("http://") || src.startsWith("https://"))) {
|
|
2884
|
+
return true;
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2606
2887
|
const linkElements = root.find(".//html:link[@rel and @href]", {
|
|
2607
2888
|
html: "http://www.w3.org/1999/xhtml"
|
|
2608
2889
|
});
|
|
@@ -2881,14 +3162,38 @@ var ContentValidator = class {
|
|
|
2881
3162
|
extractAndRegisterIDs(path, root, registry) {
|
|
2882
3163
|
const elementsWithId = root.find(".//*[@id]");
|
|
2883
3164
|
for (const elem of elementsWithId) {
|
|
2884
|
-
const
|
|
3165
|
+
const xmlElem = elem;
|
|
3166
|
+
const id = this.getAttribute(xmlElem, "id");
|
|
2885
3167
|
if (id) {
|
|
2886
3168
|
registry.registerID(path, id);
|
|
3169
|
+
const localName = xmlElem.name.includes(":") ? xmlElem.name.split(":").pop() : xmlElem.name;
|
|
3170
|
+
if (localName === "symbol") {
|
|
3171
|
+
registry.registerSVGSymbolID(path, id);
|
|
3172
|
+
}
|
|
2887
3173
|
}
|
|
2888
3174
|
}
|
|
2889
3175
|
}
|
|
2890
|
-
extractAndRegisterHyperlinks(context, path, root, opfDir, refValidator) {
|
|
3176
|
+
extractAndRegisterHyperlinks(context, path, root, opfDir, refValidator, isNavDocument = false) {
|
|
2891
3177
|
const docDir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
3178
|
+
const navAnchorTypes = /* @__PURE__ */ new Map();
|
|
3179
|
+
if (isNavDocument) {
|
|
3180
|
+
const HTML_NS = { html: "http://www.w3.org/1999/xhtml" };
|
|
3181
|
+
const navElements = root.find(".//html:nav", HTML_NS);
|
|
3182
|
+
for (const nav of navElements) {
|
|
3183
|
+
const navElem = nav;
|
|
3184
|
+
const epubTypeAttr = "attrs" in navElem ? navElem.attrs.find(
|
|
3185
|
+
(attr) => attr.name === "type" && attr.prefix === "epub" && attr.namespaceUri === "http://www.idpf.org/2007/ops"
|
|
3186
|
+
) : void 0;
|
|
3187
|
+
const types = epubTypeAttr ? epubTypeAttr.value.trim().split(/\s+/) : [];
|
|
3188
|
+
let refType = "hyperlink" /* HYPERLINK */;
|
|
3189
|
+
if (types.includes("toc")) refType = "nav-toc-link" /* NAV_TOC_LINK */;
|
|
3190
|
+
else if (types.includes("page-list")) refType = "nav-pagelist-link" /* NAV_PAGELIST_LINK */;
|
|
3191
|
+
const navAnchors = navElem.find(".//html:a[@href]", HTML_NS);
|
|
3192
|
+
for (const a of navAnchors) {
|
|
3193
|
+
navAnchorTypes.set(a.line, refType);
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
2892
3197
|
const links = root.find(".//html:a[@href]", { html: "http://www.w3.org/1999/xhtml" });
|
|
2893
3198
|
for (const link of links) {
|
|
2894
3199
|
const href = this.getAttribute(link, "href")?.trim() ?? null;
|
|
@@ -2902,6 +3207,7 @@ var ContentValidator = class {
|
|
|
2902
3207
|
continue;
|
|
2903
3208
|
}
|
|
2904
3209
|
const line = link.line;
|
|
3210
|
+
const refType = isNavDocument ? navAnchorTypes.get(line) ?? "hyperlink" /* HYPERLINK */ : "hyperlink" /* HYPERLINK */;
|
|
2905
3211
|
if (href.startsWith("http://") || href.startsWith("https://")) {
|
|
2906
3212
|
continue;
|
|
2907
3213
|
}
|
|
@@ -2918,7 +3224,7 @@ var ContentValidator = class {
|
|
|
2918
3224
|
url: href,
|
|
2919
3225
|
targetResource: targetResource2,
|
|
2920
3226
|
fragment,
|
|
2921
|
-
type:
|
|
3227
|
+
type: refType,
|
|
2922
3228
|
location: { path, line }
|
|
2923
3229
|
});
|
|
2924
3230
|
continue;
|
|
@@ -2930,7 +3236,7 @@ var ContentValidator = class {
|
|
|
2930
3236
|
const ref = {
|
|
2931
3237
|
url: href,
|
|
2932
3238
|
targetResource,
|
|
2933
|
-
type:
|
|
3239
|
+
type: refType,
|
|
2934
3240
|
location: { path, line }
|
|
2935
3241
|
};
|
|
2936
3242
|
if (fragmentPart) {
|
|
@@ -2938,6 +3244,39 @@ var ContentValidator = class {
|
|
|
2938
3244
|
}
|
|
2939
3245
|
refValidator.addReference(ref);
|
|
2940
3246
|
}
|
|
3247
|
+
const areaLinks = root.find(".//html:area[@href]", { html: "http://www.w3.org/1999/xhtml" });
|
|
3248
|
+
for (const area of areaLinks) {
|
|
3249
|
+
const href = this.getAttribute(area, "href")?.trim();
|
|
3250
|
+
if (!href) continue;
|
|
3251
|
+
const line = area.line;
|
|
3252
|
+
if (href.startsWith("http://") || href.startsWith("https://")) continue;
|
|
3253
|
+
if (href.startsWith("mailto:") || href.startsWith("tel:")) continue;
|
|
3254
|
+
if (href.includes("#epubcfi(")) continue;
|
|
3255
|
+
if (href.startsWith("#")) {
|
|
3256
|
+
refValidator.addReference({
|
|
3257
|
+
url: href,
|
|
3258
|
+
targetResource: path,
|
|
3259
|
+
fragment: href.slice(1),
|
|
3260
|
+
type: "hyperlink" /* HYPERLINK */,
|
|
3261
|
+
location: { path, line }
|
|
3262
|
+
});
|
|
3263
|
+
continue;
|
|
3264
|
+
}
|
|
3265
|
+
const resolvedAreaPath = this.resolveRelativePath(docDir, href, opfDir);
|
|
3266
|
+
const areaHashIndex = resolvedAreaPath.indexOf("#");
|
|
3267
|
+
const areaTarget = areaHashIndex >= 0 ? resolvedAreaPath.slice(0, areaHashIndex) : resolvedAreaPath;
|
|
3268
|
+
const areaFragment = areaHashIndex >= 0 ? resolvedAreaPath.slice(areaHashIndex + 1) : void 0;
|
|
3269
|
+
const areaRef = {
|
|
3270
|
+
url: href,
|
|
3271
|
+
targetResource: areaTarget,
|
|
3272
|
+
type: "hyperlink" /* HYPERLINK */,
|
|
3273
|
+
location: { path, line }
|
|
3274
|
+
};
|
|
3275
|
+
if (areaFragment) {
|
|
3276
|
+
areaRef.fragment = areaFragment;
|
|
3277
|
+
}
|
|
3278
|
+
refValidator.addReference(areaRef);
|
|
3279
|
+
}
|
|
2941
3280
|
const svgLinks = root.find(".//svg:a", {
|
|
2942
3281
|
svg: "http://www.w3.org/2000/svg",
|
|
2943
3282
|
xlink: "http://www.w3.org/1999/xlink"
|
|
@@ -3014,10 +3353,10 @@ var ContentValidator = class {
|
|
|
3014
3353
|
extractCSSImports(cssPath, cssContent, opfDir, refValidator) {
|
|
3015
3354
|
const cssDir = cssPath.includes("/") ? cssPath.substring(0, cssPath.lastIndexOf("/")) : "";
|
|
3016
3355
|
const cleanedCSS = cssContent.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
3017
|
-
const importRegex = /@import\s+(?:url\s*\(\s*
|
|
3356
|
+
const importRegex = /@import\s+(?:url\s*\(\s*["']?([^"')]+?)["']?\s*\)|["']([^"']+)["'])[^;]*;/gi;
|
|
3018
3357
|
let match;
|
|
3019
3358
|
while ((match = importRegex.exec(cleanedCSS)) !== null) {
|
|
3020
|
-
const importUrl = match[1];
|
|
3359
|
+
const importUrl = match[1] ?? match[2];
|
|
3021
3360
|
if (!importUrl) continue;
|
|
3022
3361
|
const beforeMatch = cleanedCSS.substring(0, match.index);
|
|
3023
3362
|
const line = beforeMatch.split("\n").length;
|
|
@@ -3039,21 +3378,56 @@ var ContentValidator = class {
|
|
|
3039
3378
|
});
|
|
3040
3379
|
}
|
|
3041
3380
|
}
|
|
3042
|
-
extractAndRegisterImages(path, root, opfDir, refValidator) {
|
|
3381
|
+
extractAndRegisterImages(context, path, root, opfDir, refValidator, registry) {
|
|
3043
3382
|
const docDir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
3044
|
-
const
|
|
3383
|
+
const ns = { html: "http://www.w3.org/1999/xhtml" };
|
|
3384
|
+
const pictureHasCMTSource = /* @__PURE__ */ new Set();
|
|
3385
|
+
if (registry) {
|
|
3386
|
+
const pictures = root.find(".//html:picture", ns);
|
|
3387
|
+
for (const pic of pictures) {
|
|
3388
|
+
const picElem = pic;
|
|
3389
|
+
const sources = picElem.find("html:source[@src]", ns);
|
|
3390
|
+
const sourcesWithSrcset = picElem.find("html:source[@srcset]", ns);
|
|
3391
|
+
for (const source of [...sources, ...sourcesWithSrcset]) {
|
|
3392
|
+
const srcAttr = this.getAttribute(source, "src");
|
|
3393
|
+
const srcsetAttr = this.getAttribute(source, "srcset");
|
|
3394
|
+
const sourceUrl = srcAttr ?? srcsetAttr?.split(",")[0]?.trim().split(/\s+/)[0];
|
|
3395
|
+
if (!sourceUrl || sourceUrl.startsWith("http://") || sourceUrl.startsWith("https://"))
|
|
3396
|
+
continue;
|
|
3397
|
+
const resolvedSource = this.resolveRelativePath(docDir, sourceUrl, opfDir);
|
|
3398
|
+
const resource = registry.getResource(resolvedSource);
|
|
3399
|
+
if (resource && isCoreMediaType(resource.mimeType)) {
|
|
3400
|
+
pictureHasCMTSource.add(pic.line);
|
|
3401
|
+
break;
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
const images = root.find(".//html:img[@src]", ns);
|
|
3045
3407
|
for (const img of images) {
|
|
3046
3408
|
const imgElem = img;
|
|
3047
3409
|
const src = this.getAttribute(imgElem, "src");
|
|
3048
3410
|
if (!src) continue;
|
|
3049
3411
|
const line = img.line;
|
|
3412
|
+
let hasIntrinsicFallback;
|
|
3413
|
+
if (pictureHasCMTSource.size > 0) {
|
|
3414
|
+
try {
|
|
3415
|
+
const pictureParent = imgElem.get("ancestor::html:picture", ns);
|
|
3416
|
+
if (pictureParent && pictureHasCMTSource.has(pictureParent.line)) {
|
|
3417
|
+
hasIntrinsicFallback = true;
|
|
3418
|
+
}
|
|
3419
|
+
} catch {
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3050
3422
|
if (src.startsWith("http://") || src.startsWith("https://")) {
|
|
3051
|
-
|
|
3423
|
+
const ref = {
|
|
3052
3424
|
url: src,
|
|
3053
3425
|
targetResource: src,
|
|
3054
3426
|
type: "image" /* IMAGE */,
|
|
3055
3427
|
location: { path, line }
|
|
3056
|
-
}
|
|
3428
|
+
};
|
|
3429
|
+
if (hasIntrinsicFallback) ref.hasIntrinsicFallback = true;
|
|
3430
|
+
refValidator.addReference(ref);
|
|
3057
3431
|
} else {
|
|
3058
3432
|
const resolvedPath = this.resolveRelativePath(docDir, src, opfDir);
|
|
3059
3433
|
const hashIndex = resolvedPath.indexOf("#");
|
|
@@ -3065,6 +3439,7 @@ var ContentValidator = class {
|
|
|
3065
3439
|
type: "image" /* IMAGE */,
|
|
3066
3440
|
location: { path, line }
|
|
3067
3441
|
};
|
|
3442
|
+
if (hasIntrinsicFallback) ref.hasIntrinsicFallback = true;
|
|
3068
3443
|
if (fragment) {
|
|
3069
3444
|
ref.fragment = fragment;
|
|
3070
3445
|
}
|
|
@@ -3117,6 +3492,45 @@ var ContentValidator = class {
|
|
|
3117
3492
|
}
|
|
3118
3493
|
refValidator.addReference(svgImgRef);
|
|
3119
3494
|
}
|
|
3495
|
+
try {
|
|
3496
|
+
const svgUseXlink = root.find(".//svg:use[@xlink:href]", {
|
|
3497
|
+
svg: "http://www.w3.org/2000/svg",
|
|
3498
|
+
xlink: "http://www.w3.org/1999/xlink"
|
|
3499
|
+
});
|
|
3500
|
+
const svgUseHref = root.find(".//svg:use[@href]", {
|
|
3501
|
+
svg: "http://www.w3.org/2000/svg"
|
|
3502
|
+
});
|
|
3503
|
+
for (const useNode of [...svgUseXlink, ...svgUseHref]) {
|
|
3504
|
+
const useElem = useNode;
|
|
3505
|
+
const href = this.getAttribute(useElem, "xlink:href") ?? this.getAttribute(useElem, "href");
|
|
3506
|
+
if (href === null) continue;
|
|
3507
|
+
const line = useNode.line;
|
|
3508
|
+
if (href.startsWith("http://") || href.startsWith("https://")) continue;
|
|
3509
|
+
if (href === "" || !href.includes("#")) {
|
|
3510
|
+
pushMessage(context.messages, {
|
|
3511
|
+
id: MessageId.RSC_015,
|
|
3512
|
+
message: `SVG "use" element requires a fragment identifier, but found "${href}"`,
|
|
3513
|
+
location: { path, line }
|
|
3514
|
+
});
|
|
3515
|
+
continue;
|
|
3516
|
+
}
|
|
3517
|
+
const resolvedPath = this.resolveRelativePath(docDir, href, opfDir);
|
|
3518
|
+
const hashIndex = resolvedPath.indexOf("#");
|
|
3519
|
+
const targetResource = hashIndex >= 0 ? resolvedPath.slice(0, hashIndex) : path;
|
|
3520
|
+
const fragment = hashIndex >= 0 ? resolvedPath.slice(hashIndex + 1) : void 0;
|
|
3521
|
+
const useRef = {
|
|
3522
|
+
url: href,
|
|
3523
|
+
targetResource,
|
|
3524
|
+
type: "svg-symbol" /* SVG_SYMBOL */,
|
|
3525
|
+
location: { path, line }
|
|
3526
|
+
};
|
|
3527
|
+
if (fragment) {
|
|
3528
|
+
useRef.fragment = fragment;
|
|
3529
|
+
}
|
|
3530
|
+
refValidator.addReference(useRef);
|
|
3531
|
+
}
|
|
3532
|
+
} catch {
|
|
3533
|
+
}
|
|
3120
3534
|
const videos = root.find(".//html:video[@poster]", { html: "http://www.w3.org/1999/xhtml" });
|
|
3121
3535
|
for (const video of videos) {
|
|
3122
3536
|
const poster = this.getAttribute(video, "poster");
|
|
@@ -3218,7 +3632,7 @@ var ContentValidator = class {
|
|
|
3218
3632
|
url: cite,
|
|
3219
3633
|
targetResource: targetResource2,
|
|
3220
3634
|
fragment: fragment2,
|
|
3221
|
-
type: "
|
|
3635
|
+
type: "cite" /* CITE */,
|
|
3222
3636
|
location: { path, line }
|
|
3223
3637
|
});
|
|
3224
3638
|
continue;
|
|
@@ -3230,93 +3644,71 @@ var ContentValidator = class {
|
|
|
3230
3644
|
const ref = {
|
|
3231
3645
|
url: cite,
|
|
3232
3646
|
targetResource,
|
|
3233
|
-
type: "
|
|
3647
|
+
type: "cite" /* CITE */,
|
|
3234
3648
|
location: { path, line }
|
|
3235
3649
|
};
|
|
3236
3650
|
if (fragment) {
|
|
3237
3651
|
ref.fragment = fragment;
|
|
3238
3652
|
}
|
|
3239
3653
|
refValidator.addReference(ref);
|
|
3240
|
-
}
|
|
3241
|
-
}
|
|
3242
|
-
extractAndRegisterMediaElements(path, root, opfDir, refValidator) {
|
|
3243
|
-
const docDir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
3244
|
-
const
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
const
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
for (const source of sourceElements) {
|
|
3296
|
-
const src = this.getAttribute(source, "src");
|
|
3297
|
-
if (!src) continue;
|
|
3298
|
-
const parent = source.parent;
|
|
3299
|
-
const parentName = parent?.name ?? "";
|
|
3300
|
-
const isAudioChild = parentName === "audio";
|
|
3301
|
-
const type = isAudioChild ? "audio" /* AUDIO */ : "video" /* VIDEO */;
|
|
3302
|
-
const line = source.line;
|
|
3303
|
-
if (src.startsWith("http://") || src.startsWith("https://")) {
|
|
3304
|
-
refValidator.addReference({
|
|
3305
|
-
url: src,
|
|
3306
|
-
targetResource: src,
|
|
3307
|
-
type,
|
|
3308
|
-
location: line !== void 0 ? { path, line } : { path }
|
|
3309
|
-
});
|
|
3310
|
-
} else {
|
|
3311
|
-
const resolvedPath = this.resolveRelativePath(docDir, src, opfDir);
|
|
3312
|
-
refValidator.addReference({
|
|
3313
|
-
url: src,
|
|
3314
|
-
targetResource: resolvedPath,
|
|
3315
|
-
type,
|
|
3316
|
-
location: line !== void 0 ? { path, line } : { path }
|
|
3317
|
-
});
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
extractAndRegisterMediaElements(context, path, root, opfDir, refValidator, registry) {
|
|
3657
|
+
const docDir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
3658
|
+
const ns = { html: "http://www.w3.org/1999/xhtml" };
|
|
3659
|
+
for (const tagName of ["audio", "video"]) {
|
|
3660
|
+
const isAudio = tagName === "audio";
|
|
3661
|
+
const refType = isAudio ? "audio" /* AUDIO */ : "video" /* VIDEO */;
|
|
3662
|
+
const elements = root.find(`.//html:${tagName}`, ns);
|
|
3663
|
+
for (const elem of elements) {
|
|
3664
|
+
const mediaElem = elem;
|
|
3665
|
+
const pendingRefs = [];
|
|
3666
|
+
const src = this.getAttribute(mediaElem, "src");
|
|
3667
|
+
if (src) {
|
|
3668
|
+
const line = elem.line;
|
|
3669
|
+
if (src.startsWith("http://") || src.startsWith("https://")) {
|
|
3670
|
+
pendingRefs.push({ url: src, targetResource: src, type: refType, line });
|
|
3671
|
+
} else {
|
|
3672
|
+
const resolvedPath = this.resolveRelativePath(docDir, src, opfDir);
|
|
3673
|
+
pendingRefs.push({ url: src, targetResource: resolvedPath, type: refType, line });
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
const sources = mediaElem.find("html:source[@src]", ns);
|
|
3677
|
+
for (const source of sources) {
|
|
3678
|
+
const sourceElem = source;
|
|
3679
|
+
const sourceSrc = this.getAttribute(sourceElem, "src");
|
|
3680
|
+
if (!sourceSrc) continue;
|
|
3681
|
+
const line = source.line;
|
|
3682
|
+
if (sourceSrc.startsWith("http://") || sourceSrc.startsWith("https://")) {
|
|
3683
|
+
pendingRefs.push({ url: sourceSrc, targetResource: sourceSrc, type: refType, line });
|
|
3684
|
+
} else {
|
|
3685
|
+
const resolvedPath = this.resolveRelativePath(docDir, sourceSrc, opfDir);
|
|
3686
|
+
pendingRefs.push({ url: sourceSrc, targetResource: resolvedPath, type: refType, line });
|
|
3687
|
+
}
|
|
3688
|
+
if (registry) {
|
|
3689
|
+
this.checkMimeTypeMatch(context, path, docDir, opfDir, sourceElem, "src", registry);
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
let hasIntrinsicFallback = false;
|
|
3693
|
+
if (registry && pendingRefs.length > 1) {
|
|
3694
|
+
hasIntrinsicFallback = pendingRefs.some((ref) => {
|
|
3695
|
+
const resource = registry.getResource(ref.targetResource);
|
|
3696
|
+
return resource && isCoreMediaType(resource.mimeType);
|
|
3697
|
+
});
|
|
3698
|
+
}
|
|
3699
|
+
for (const ref of pendingRefs) {
|
|
3700
|
+
const reference = {
|
|
3701
|
+
url: ref.url,
|
|
3702
|
+
targetResource: ref.targetResource,
|
|
3703
|
+
type: ref.type,
|
|
3704
|
+
location: ref.line !== void 0 ? { path, line: ref.line } : { path }
|
|
3705
|
+
};
|
|
3706
|
+
if (hasIntrinsicFallback) reference.hasIntrinsicFallback = true;
|
|
3707
|
+
refValidator.addReference(reference);
|
|
3708
|
+
}
|
|
3318
3709
|
}
|
|
3319
3710
|
}
|
|
3711
|
+
this.extractAndRegisterPictureElements(context, path, root, opfDir, refValidator, registry);
|
|
3320
3712
|
const iframeElements = root.find(".//html:iframe[@src]", {
|
|
3321
3713
|
html: "http://www.w3.org/1999/xhtml"
|
|
3322
3714
|
});
|
|
@@ -3366,6 +3758,188 @@ var ContentValidator = class {
|
|
|
3366
3758
|
}
|
|
3367
3759
|
}
|
|
3368
3760
|
}
|
|
3761
|
+
extractAndRegisterEmbeddedElements(context, path, root, opfDir, refValidator, registry) {
|
|
3762
|
+
const docDir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
3763
|
+
const ns = { html: "http://www.w3.org/1999/xhtml" };
|
|
3764
|
+
const addRef = (src, type, line, hasIntrinsicFallback) => {
|
|
3765
|
+
const location = line !== void 0 ? { path, line } : { path };
|
|
3766
|
+
if (src.startsWith("http://") || src.startsWith("https://")) {
|
|
3767
|
+
const ref = {
|
|
3768
|
+
url: src,
|
|
3769
|
+
targetResource: src,
|
|
3770
|
+
type,
|
|
3771
|
+
location
|
|
3772
|
+
};
|
|
3773
|
+
if (hasIntrinsicFallback) ref.hasIntrinsicFallback = true;
|
|
3774
|
+
refValidator.addReference(ref);
|
|
3775
|
+
} else {
|
|
3776
|
+
const resolvedPath = this.resolveRelativePath(docDir, src, opfDir);
|
|
3777
|
+
const hashIndex = resolvedPath.indexOf("#");
|
|
3778
|
+
const targetResource = hashIndex >= 0 ? resolvedPath.slice(0, hashIndex) : resolvedPath;
|
|
3779
|
+
const ref = {
|
|
3780
|
+
url: src,
|
|
3781
|
+
targetResource,
|
|
3782
|
+
type,
|
|
3783
|
+
location
|
|
3784
|
+
};
|
|
3785
|
+
if (hashIndex >= 0) ref.fragment = resolvedPath.slice(hashIndex + 1);
|
|
3786
|
+
if (hasIntrinsicFallback) ref.hasIntrinsicFallback = true;
|
|
3787
|
+
refValidator.addReference(ref);
|
|
3788
|
+
}
|
|
3789
|
+
};
|
|
3790
|
+
for (const elem of root.find(".//html:embed[@src]", ns)) {
|
|
3791
|
+
const embedElem = elem;
|
|
3792
|
+
const src = this.getAttribute(embedElem, "src");
|
|
3793
|
+
if (src) addRef(src, "generic" /* GENERIC */, elem.line);
|
|
3794
|
+
if (registry) {
|
|
3795
|
+
this.checkMimeTypeMatch(context, path, docDir, opfDir, embedElem, "src", registry);
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
for (const elem of root.find(".//html:input[@src]", ns)) {
|
|
3799
|
+
const type = this.getAttribute(elem, "type");
|
|
3800
|
+
if (type?.toLowerCase() === "image") {
|
|
3801
|
+
const src = this.getAttribute(elem, "src");
|
|
3802
|
+
if (src) addRef(src, "image" /* IMAGE */, elem.line);
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
for (const elem of root.find(".//html:object[@data]", ns)) {
|
|
3806
|
+
const objElem = elem;
|
|
3807
|
+
const data = this.getAttribute(objElem, "data");
|
|
3808
|
+
if (!data) continue;
|
|
3809
|
+
const allChildren = objElem.find("html:*", ns);
|
|
3810
|
+
const hasFallbackContent = allChildren.some((child) => {
|
|
3811
|
+
const c = child;
|
|
3812
|
+
return c.name !== "param" && this.getAttribute(c, "hidden") === null;
|
|
3813
|
+
});
|
|
3814
|
+
addRef(data, "generic" /* GENERIC */, elem.line, hasFallbackContent || void 0);
|
|
3815
|
+
if (registry) {
|
|
3816
|
+
this.checkMimeTypeMatch(context, path, docDir, opfDir, objElem, "data", registry);
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
/**
|
|
3821
|
+
* Check if an element's type attribute matches the manifest MIME type (OPF-013)
|
|
3822
|
+
*/
|
|
3823
|
+
checkMimeTypeMatch(context, path, docDir, opfDir, element, srcAttr, registry) {
|
|
3824
|
+
const typeAttr = this.getAttribute(element, "type");
|
|
3825
|
+
if (!typeAttr) return;
|
|
3826
|
+
const src = this.getAttribute(element, srcAttr);
|
|
3827
|
+
if (!src || src.startsWith("http://") || src.startsWith("https://")) return;
|
|
3828
|
+
const resolvedPath = this.resolveRelativePath(docDir, src, opfDir);
|
|
3829
|
+
const hashIndex = resolvedPath.indexOf("#");
|
|
3830
|
+
const targetResource = hashIndex >= 0 ? resolvedPath.slice(0, hashIndex) : resolvedPath;
|
|
3831
|
+
const resource = registry.getResource(targetResource);
|
|
3832
|
+
if (!resource) return;
|
|
3833
|
+
const stripParams = (t) => {
|
|
3834
|
+
const idx = t.indexOf(";");
|
|
3835
|
+
return (idx >= 0 ? t.substring(0, idx) : t).trim();
|
|
3836
|
+
};
|
|
3837
|
+
const declaredType = stripParams(typeAttr);
|
|
3838
|
+
const manifestType = stripParams(resource.mimeType);
|
|
3839
|
+
if (declaredType && declaredType !== manifestType) {
|
|
3840
|
+
pushMessage(context.messages, {
|
|
3841
|
+
id: MessageId.OPF_013,
|
|
3842
|
+
message: `Resource "${targetResource}" is declared with MIME type "${declaredType}" in content, but has MIME type "${manifestType}" in the package document`,
|
|
3843
|
+
location: { path, line: element.line }
|
|
3844
|
+
});
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
/**
|
|
3848
|
+
* Extract and validate picture elements (MED-003, MED-007, OPF-013)
|
|
3849
|
+
*/
|
|
3850
|
+
extractAndRegisterPictureElements(context, path, root, opfDir, refValidator, registry) {
|
|
3851
|
+
const docDir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : "";
|
|
3852
|
+
const ns = { html: "http://www.w3.org/1999/xhtml" };
|
|
3853
|
+
const BLESSED_IMAGE_TYPES = /* @__PURE__ */ new Set([
|
|
3854
|
+
"image/gif",
|
|
3855
|
+
"image/jpeg",
|
|
3856
|
+
"image/png",
|
|
3857
|
+
"image/svg+xml",
|
|
3858
|
+
"image/webp"
|
|
3859
|
+
]);
|
|
3860
|
+
const pictures = root.find(".//html:picture", ns);
|
|
3861
|
+
for (const pic of pictures) {
|
|
3862
|
+
const picElem = pic;
|
|
3863
|
+
const imgs = picElem.find("html:img[@src]", ns);
|
|
3864
|
+
for (const img of imgs) {
|
|
3865
|
+
const imgElem = img;
|
|
3866
|
+
const src = this.getAttribute(imgElem, "src");
|
|
3867
|
+
if (!src || src.startsWith("http://") || src.startsWith("https://")) continue;
|
|
3868
|
+
if (registry) {
|
|
3869
|
+
const resolvedPath = this.resolveRelativePath(docDir, src, opfDir);
|
|
3870
|
+
const resource = registry.getResource(resolvedPath);
|
|
3871
|
+
if (resource && !BLESSED_IMAGE_TYPES.has(resource.mimeType)) {
|
|
3872
|
+
pushMessage(context.messages, {
|
|
3873
|
+
id: MessageId.MED_003,
|
|
3874
|
+
message: `Image in "picture" element must be a core image type, but found "${resource.mimeType}"`,
|
|
3875
|
+
location: { path, line: img.line }
|
|
3876
|
+
});
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
const srcset = this.getAttribute(imgElem, "srcset");
|
|
3880
|
+
if (srcset && registry) {
|
|
3881
|
+
const entries = srcset.split(",");
|
|
3882
|
+
for (const entry of entries) {
|
|
3883
|
+
const url = entry.trim().split(/\s+/)[0];
|
|
3884
|
+
if (!url || url.startsWith("http://") || url.startsWith("https://")) continue;
|
|
3885
|
+
const resolvedPath = this.resolveRelativePath(docDir, url, opfDir);
|
|
3886
|
+
const resource = registry.getResource(resolvedPath);
|
|
3887
|
+
if (resource && !BLESSED_IMAGE_TYPES.has(resource.mimeType)) {
|
|
3888
|
+
pushMessage(context.messages, {
|
|
3889
|
+
id: MessageId.MED_003,
|
|
3890
|
+
message: `Image in "picture" element must be a core image type, but found "${resource.mimeType}"`,
|
|
3891
|
+
location: { path, line: img.line }
|
|
3892
|
+
});
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
const sourcesWithSrc = picElem.find("html:source[@src]", ns);
|
|
3898
|
+
const sourcesWithSrcset = picElem.find("html:source[@srcset]", ns);
|
|
3899
|
+
const allSources = /* @__PURE__ */ new Set([...sourcesWithSrc, ...sourcesWithSrcset]);
|
|
3900
|
+
for (const source of allSources) {
|
|
3901
|
+
const sourceElem = source;
|
|
3902
|
+
const typeAttr = this.getAttribute(sourceElem, "type");
|
|
3903
|
+
const src = this.getAttribute(sourceElem, "src");
|
|
3904
|
+
const srcset = this.getAttribute(sourceElem, "srcset");
|
|
3905
|
+
const sourceUrl = src ?? srcset?.split(",")[0]?.trim().split(/\s+/)[0];
|
|
3906
|
+
if (!sourceUrl || sourceUrl.startsWith("http://") || sourceUrl.startsWith("https://"))
|
|
3907
|
+
continue;
|
|
3908
|
+
if (registry) {
|
|
3909
|
+
if (src) {
|
|
3910
|
+
this.checkMimeTypeMatch(context, path, docDir, opfDir, sourceElem, "src", registry);
|
|
3911
|
+
} else if (srcset && typeAttr) {
|
|
3912
|
+
const resolvedPath2 = this.resolveRelativePath(docDir, sourceUrl, opfDir);
|
|
3913
|
+
const resource2 = registry.getResource(resolvedPath2);
|
|
3914
|
+
if (resource2) {
|
|
3915
|
+
const stripParams = (t) => {
|
|
3916
|
+
const idx = t.indexOf(";");
|
|
3917
|
+
return (idx >= 0 ? t.substring(0, idx) : t).trim();
|
|
3918
|
+
};
|
|
3919
|
+
const declaredType = stripParams(typeAttr);
|
|
3920
|
+
const manifestType = stripParams(resource2.mimeType);
|
|
3921
|
+
if (declaredType && declaredType !== manifestType) {
|
|
3922
|
+
pushMessage(context.messages, {
|
|
3923
|
+
id: MessageId.OPF_013,
|
|
3924
|
+
message: `Resource "${resolvedPath2}" is declared with MIME type "${declaredType}" in content, but has MIME type "${manifestType}" in the package document`,
|
|
3925
|
+
location: { path, line: source.line }
|
|
3926
|
+
});
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
const resolvedPath = this.resolveRelativePath(docDir, sourceUrl, opfDir);
|
|
3931
|
+
const resource = registry.getResource(resolvedPath);
|
|
3932
|
+
if (resource && !BLESSED_IMAGE_TYPES.has(resource.mimeType) && !typeAttr) {
|
|
3933
|
+
pushMessage(context.messages, {
|
|
3934
|
+
id: MessageId.MED_007,
|
|
3935
|
+
message: `Source element in "picture" with foreign resource type "${resource.mimeType}" must declare a "type" attribute`,
|
|
3936
|
+
location: { path, line: source.line }
|
|
3937
|
+
});
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3369
3943
|
parseSrcset(srcset, docDir, opfDir, path, line, refValidator) {
|
|
3370
3944
|
const entries = srcset.split(",");
|
|
3371
3945
|
for (const entry of entries) {
|
|
@@ -4574,69 +5148,6 @@ function parseCollections(xml) {
|
|
|
4574
5148
|
return collections;
|
|
4575
5149
|
}
|
|
4576
5150
|
|
|
4577
|
-
// src/opf/types.ts
|
|
4578
|
-
var CORE_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
4579
|
-
// Image types
|
|
4580
|
-
"image/gif",
|
|
4581
|
-
"image/jpeg",
|
|
4582
|
-
"image/png",
|
|
4583
|
-
"image/svg+xml",
|
|
4584
|
-
"image/webp",
|
|
4585
|
-
// Audio types
|
|
4586
|
-
"audio/mpeg",
|
|
4587
|
-
"audio/mp4",
|
|
4588
|
-
"audio/ogg",
|
|
4589
|
-
// CSS
|
|
4590
|
-
"text/css",
|
|
4591
|
-
// Fonts
|
|
4592
|
-
"font/otf",
|
|
4593
|
-
"font/ttf",
|
|
4594
|
-
"font/woff",
|
|
4595
|
-
"font/woff2",
|
|
4596
|
-
"application/font-sfnt",
|
|
4597
|
-
// deprecated alias for font/otf, font/ttf
|
|
4598
|
-
"application/font-woff",
|
|
4599
|
-
// deprecated alias for font/woff
|
|
4600
|
-
"application/vnd.ms-opentype",
|
|
4601
|
-
// deprecated alias
|
|
4602
|
-
// Content documents
|
|
4603
|
-
"application/xhtml+xml",
|
|
4604
|
-
"application/x-dtbncx+xml",
|
|
4605
|
-
// NCX
|
|
4606
|
-
// JavaScript (EPUB 3)
|
|
4607
|
-
"text/javascript",
|
|
4608
|
-
"application/javascript",
|
|
4609
|
-
// Media overlays
|
|
4610
|
-
"application/smil+xml",
|
|
4611
|
-
// PLS (Pronunciation Lexicon)
|
|
4612
|
-
"application/pls+xml"
|
|
4613
|
-
]);
|
|
4614
|
-
var ITEM_PROPERTIES = /* @__PURE__ */ new Set([
|
|
4615
|
-
"cover-image",
|
|
4616
|
-
"mathml",
|
|
4617
|
-
"nav",
|
|
4618
|
-
"remote-resources",
|
|
4619
|
-
"scripted",
|
|
4620
|
-
"svg",
|
|
4621
|
-
"switch"
|
|
4622
|
-
]);
|
|
4623
|
-
var LINK_PROPERTIES = /* @__PURE__ */ new Set(["onix", "marc21xml-record", "mods-record", "xmp-record"]);
|
|
4624
|
-
var SPINE_PROPERTIES = /* @__PURE__ */ new Set([
|
|
4625
|
-
"page-spread-left",
|
|
4626
|
-
"page-spread-right",
|
|
4627
|
-
"rendition:spread-none",
|
|
4628
|
-
"rendition:spread-landscape",
|
|
4629
|
-
"rendition:spread-portrait",
|
|
4630
|
-
"rendition:spread-both",
|
|
4631
|
-
"rendition:spread-auto",
|
|
4632
|
-
"rendition:page-spread-center",
|
|
4633
|
-
"rendition:layout-reflowable",
|
|
4634
|
-
"rendition:layout-pre-paginated",
|
|
4635
|
-
"rendition:orientation-auto",
|
|
4636
|
-
"rendition:orientation-landscape",
|
|
4637
|
-
"rendition:orientation-portrait"
|
|
4638
|
-
]);
|
|
4639
|
-
|
|
4640
5151
|
// src/opf/validator.ts
|
|
4641
5152
|
var OPFValidator = class {
|
|
4642
5153
|
packageDoc = null;
|
|
@@ -5050,9 +5561,17 @@ var OPFValidator = class {
|
|
|
5050
5561
|
this.detectRefinesCycles(context, opfPath);
|
|
5051
5562
|
}
|
|
5052
5563
|
if (this.packageDoc.version !== "2.0") {
|
|
5053
|
-
const
|
|
5564
|
+
const modifiedMetas = this.packageDoc.metaElements.filter(
|
|
5054
5565
|
(meta) => meta.property === "dcterms:modified"
|
|
5055
5566
|
);
|
|
5567
|
+
const modifiedMeta = modifiedMetas[0];
|
|
5568
|
+
if (modifiedMetas.length > 1) {
|
|
5569
|
+
pushMessage(context.messages, {
|
|
5570
|
+
id: MessageId.RSC_005,
|
|
5571
|
+
message: "package dcterms:modified meta element must occur exactly once",
|
|
5572
|
+
location: { path: opfPath }
|
|
5573
|
+
});
|
|
5574
|
+
}
|
|
5056
5575
|
if (!modifiedMeta) {
|
|
5057
5576
|
pushMessage(context.messages, {
|
|
5058
5577
|
id: MessageId.RSC_005,
|
|
@@ -5186,7 +5705,7 @@ var OPFValidator = class {
|
|
|
5186
5705
|
});
|
|
5187
5706
|
}
|
|
5188
5707
|
if (!item.href.startsWith("http") && !item.href.startsWith("mailto:")) {
|
|
5189
|
-
const leaked =
|
|
5708
|
+
const leaked = checkUrlLeaking2(item.href);
|
|
5190
5709
|
if (leaked) {
|
|
5191
5710
|
pushMessage(context.messages, {
|
|
5192
5711
|
id: MessageId.RSC_026,
|
|
@@ -5280,20 +5799,23 @@ var OPFValidator = class {
|
|
|
5280
5799
|
});
|
|
5281
5800
|
}
|
|
5282
5801
|
if (this.packageDoc.version !== "2.0" && (item.href.startsWith("http://") || item.href.startsWith("https://"))) {
|
|
5283
|
-
|
|
5284
|
-
pushMessage(context.messages, {
|
|
5285
|
-
id: MessageId.RSC_006,
|
|
5286
|
-
message: `Remote resource reference is not allowed in this context; resource "${item.href}" must be located in the EPUB container`,
|
|
5287
|
-
location: { path: opfPath }
|
|
5288
|
-
});
|
|
5289
|
-
}
|
|
5802
|
+
const isAllowedRemoteType = item.mediaType.startsWith("audio/") || item.mediaType.startsWith("video/") || item.mediaType.startsWith("font/") || item.mediaType === "application/font-sfnt" || item.mediaType === "application/font-woff" || item.mediaType === "application/vnd.ms-opentype";
|
|
5290
5803
|
const inSpine = this.packageDoc.spine.some((s) => s.idref === item.id);
|
|
5291
|
-
if (inSpine
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5804
|
+
if (inSpine) {
|
|
5805
|
+
if (!isAllowedRemoteType) {
|
|
5806
|
+
pushMessage(context.messages, {
|
|
5807
|
+
id: MessageId.RSC_006,
|
|
5808
|
+
message: `Remote resource reference is not allowed in this context; resource "${item.href}" must be located in the EPUB container`,
|
|
5809
|
+
location: { path: opfPath }
|
|
5810
|
+
});
|
|
5811
|
+
}
|
|
5812
|
+
if (!item.properties?.includes("remote-resources")) {
|
|
5813
|
+
pushMessage(context.messages, {
|
|
5814
|
+
id: MessageId.RSC_006,
|
|
5815
|
+
message: `Manifest item "${item.id}" references remote resource but is missing "remote-resources" property`,
|
|
5816
|
+
location: { path: opfPath }
|
|
5817
|
+
});
|
|
5818
|
+
}
|
|
5297
5819
|
}
|
|
5298
5820
|
}
|
|
5299
5821
|
}
|
|
@@ -5398,12 +5920,20 @@ var OPFValidator = class {
|
|
|
5398
5920
|
});
|
|
5399
5921
|
}
|
|
5400
5922
|
seenIdrefs.add(itemref.idref);
|
|
5401
|
-
if (!isSpineMediaType(item.mediaType)
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5923
|
+
if (!isSpineMediaType(item.mediaType)) {
|
|
5924
|
+
if (!item.fallback) {
|
|
5925
|
+
pushMessage(context.messages, {
|
|
5926
|
+
id: MessageId.OPF_043,
|
|
5927
|
+
message: `Spine item "${item.id}" has non-standard media type "${item.mediaType}" without fallback`,
|
|
5928
|
+
location: { path: opfPath }
|
|
5929
|
+
});
|
|
5930
|
+
} else if (!this.fallbackChainResolvesToContentDocument(item.id)) {
|
|
5931
|
+
pushMessage(context.messages, {
|
|
5932
|
+
id: MessageId.OPF_044,
|
|
5933
|
+
message: `Spine item "${item.id}" has non-standard media type "${item.mediaType}" and its fallback chain does not resolve to a content document`,
|
|
5934
|
+
location: { path: opfPath }
|
|
5935
|
+
});
|
|
5936
|
+
}
|
|
5407
5937
|
}
|
|
5408
5938
|
if (this.packageDoc.version !== "2.0" && itemref.properties) {
|
|
5409
5939
|
for (const prop of itemref.properties) {
|
|
@@ -5601,14 +6131,57 @@ var OPFValidator = class {
|
|
|
5601
6131
|
}
|
|
5602
6132
|
}
|
|
5603
6133
|
}
|
|
6134
|
+
fallbackChainResolvesToContentDocument(itemId) {
|
|
6135
|
+
const visited = /* @__PURE__ */ new Set();
|
|
6136
|
+
let currentId = itemId;
|
|
6137
|
+
while (currentId) {
|
|
6138
|
+
if (visited.has(currentId)) return false;
|
|
6139
|
+
visited.add(currentId);
|
|
6140
|
+
const item = this.manifestById.get(currentId);
|
|
6141
|
+
if (!item) return false;
|
|
6142
|
+
if (isSpineMediaType(item.mediaType)) return true;
|
|
6143
|
+
currentId = item.fallback;
|
|
6144
|
+
}
|
|
6145
|
+
return false;
|
|
6146
|
+
}
|
|
5604
6147
|
};
|
|
5605
6148
|
function isSpineMediaType(mediaType) {
|
|
5606
6149
|
return mediaType === "application/xhtml+xml" || mediaType === "image/svg+xml" || // EPUB 2 also allows these in spine
|
|
5607
6150
|
mediaType === "application/x-dtbook+xml";
|
|
5608
6151
|
}
|
|
5609
6152
|
function isValidLanguageTag(tag) {
|
|
5610
|
-
const pattern = /^[a-zA-Z]{2,3}(-[a-zA-Z]{4})?(-[a-zA-Z]{2}|-\d{3})?(-([a-zA-Z\d]{5,8}|\d[a-zA-Z\d]{3}))
|
|
5611
|
-
|
|
6153
|
+
const pattern = /^[a-zA-Z]{2,3}(-[a-zA-Z]{4})?(-[a-zA-Z]{2}|-\d{3})?(-([a-zA-Z\d]{5,8}|\d[a-zA-Z\d]{3}))*(-[a-wyzA-WYZ](-[a-zA-Z\d]{2,8})+)*(-x(-[a-zA-Z\d]{1,8})+)?$/;
|
|
6154
|
+
if (pattern.test(tag)) return true;
|
|
6155
|
+
if (/^x(-[a-zA-Z\d]{1,8})+$/.test(tag)) return true;
|
|
6156
|
+
const grandfathered = /* @__PURE__ */ new Set([
|
|
6157
|
+
"en-GB-oed",
|
|
6158
|
+
"i-ami",
|
|
6159
|
+
"i-bnn",
|
|
6160
|
+
"i-default",
|
|
6161
|
+
"i-enochian",
|
|
6162
|
+
"i-hak",
|
|
6163
|
+
"i-klingon",
|
|
6164
|
+
"i-lux",
|
|
6165
|
+
"i-mingo",
|
|
6166
|
+
"i-navajo",
|
|
6167
|
+
"i-pwn",
|
|
6168
|
+
"i-tao",
|
|
6169
|
+
"i-tay",
|
|
6170
|
+
"i-tsu",
|
|
6171
|
+
"sgn-BE-FR",
|
|
6172
|
+
"sgn-BE-NL",
|
|
6173
|
+
"sgn-CH-DE",
|
|
6174
|
+
"art-lojban",
|
|
6175
|
+
"cel-gaulish",
|
|
6176
|
+
"no-bok",
|
|
6177
|
+
"no-nyn",
|
|
6178
|
+
"zh-guoyu",
|
|
6179
|
+
"zh-hakka",
|
|
6180
|
+
"zh-min",
|
|
6181
|
+
"zh-min-nan",
|
|
6182
|
+
"zh-xiang"
|
|
6183
|
+
]);
|
|
6184
|
+
return grandfathered.has(tag);
|
|
5612
6185
|
}
|
|
5613
6186
|
function resolvePath(basePath, relativePath) {
|
|
5614
6187
|
if (relativePath.startsWith("/")) {
|
|
@@ -5636,7 +6209,7 @@ function tryDecodeUriComponent(encoded) {
|
|
|
5636
6209
|
return encoded;
|
|
5637
6210
|
}
|
|
5638
6211
|
}
|
|
5639
|
-
function
|
|
6212
|
+
function checkUrlLeaking2(href) {
|
|
5640
6213
|
const TEST_BASE_A = "https://a.example.org/A/";
|
|
5641
6214
|
const TEST_BASE_B = "https://b.example.org/B/";
|
|
5642
6215
|
try {
|
|
@@ -5714,9 +6287,11 @@ function isValidW3CDateFormat(dateStr) {
|
|
|
5714
6287
|
var ResourceRegistry = class {
|
|
5715
6288
|
resources;
|
|
5716
6289
|
ids;
|
|
6290
|
+
svgSymbolIds;
|
|
5717
6291
|
constructor() {
|
|
5718
6292
|
this.resources = /* @__PURE__ */ new Map();
|
|
5719
6293
|
this.ids = /* @__PURE__ */ new Map();
|
|
6294
|
+
this.svgSymbolIds = /* @__PURE__ */ new Map();
|
|
5720
6295
|
}
|
|
5721
6296
|
/**
|
|
5722
6297
|
* Register a resource from manifest
|
|
@@ -5778,6 +6353,21 @@ var ResourceRegistry = class {
|
|
|
5778
6353
|
}
|
|
5779
6354
|
return -1;
|
|
5780
6355
|
}
|
|
6356
|
+
/**
|
|
6357
|
+
* Register an ID as belonging to an SVG symbol element
|
|
6358
|
+
*/
|
|
6359
|
+
registerSVGSymbolID(resourceURL, id) {
|
|
6360
|
+
if (!this.svgSymbolIds.has(resourceURL)) {
|
|
6361
|
+
this.svgSymbolIds.set(resourceURL, /* @__PURE__ */ new Set());
|
|
6362
|
+
}
|
|
6363
|
+
this.svgSymbolIds.get(resourceURL)?.add(id);
|
|
6364
|
+
}
|
|
6365
|
+
/**
|
|
6366
|
+
* Check if an ID in a resource belongs to an SVG symbol element
|
|
6367
|
+
*/
|
|
6368
|
+
isSVGSymbolID(resourceURL, id) {
|
|
6369
|
+
return this.svgSymbolIds.get(resourceURL)?.has(id) ?? false;
|
|
6370
|
+
}
|
|
5781
6371
|
/**
|
|
5782
6372
|
* Get all resources
|
|
5783
6373
|
*/
|
|
@@ -5827,6 +6417,7 @@ var ReferenceValidator = class {
|
|
|
5827
6417
|
for (const reference of this.references) {
|
|
5828
6418
|
this.validateReference(context, reference);
|
|
5829
6419
|
}
|
|
6420
|
+
this.checkRemoteResources(context);
|
|
5830
6421
|
this.checkUndeclaredResources(context);
|
|
5831
6422
|
this.checkReadingOrder(context);
|
|
5832
6423
|
this.checkNonLinearReachability(context);
|
|
@@ -5836,14 +6427,6 @@ var ReferenceValidator = class {
|
|
|
5836
6427
|
*/
|
|
5837
6428
|
validateReference(context, reference) {
|
|
5838
6429
|
const url = reference.url.trim();
|
|
5839
|
-
if (isMalformedURL(url)) {
|
|
5840
|
-
pushMessage(context.messages, {
|
|
5841
|
-
id: MessageId.RSC_020,
|
|
5842
|
-
message: `Malformed URL: ${url}`,
|
|
5843
|
-
location: reference.location
|
|
5844
|
-
});
|
|
5845
|
-
return;
|
|
5846
|
-
}
|
|
5847
6430
|
if (isDataURL(url)) {
|
|
5848
6431
|
if (this.version.startsWith("3.")) {
|
|
5849
6432
|
const forbiddenDataUrlTypes = [
|
|
@@ -5858,10 +6441,35 @@ var ReferenceValidator = class {
|
|
|
5858
6441
|
message: "Data URLs are not allowed in this context",
|
|
5859
6442
|
location: reference.location
|
|
5860
6443
|
});
|
|
6444
|
+
} else {
|
|
6445
|
+
const fallbackCheckedTypes = [
|
|
6446
|
+
"image" /* IMAGE */,
|
|
6447
|
+
"audio" /* AUDIO */,
|
|
6448
|
+
"video" /* VIDEO */,
|
|
6449
|
+
"generic" /* GENERIC */
|
|
6450
|
+
];
|
|
6451
|
+
if (fallbackCheckedTypes.includes(reference.type) && !reference.hasIntrinsicFallback) {
|
|
6452
|
+
const dataUrlMimeType = this.extractDataURLMimeType(url);
|
|
6453
|
+
if (dataUrlMimeType && !isCoreMediaType(dataUrlMimeType)) {
|
|
6454
|
+
pushMessage(context.messages, {
|
|
6455
|
+
id: MessageId.RSC_032,
|
|
6456
|
+
message: `Fallback must be provided for foreign resources, but found none for data URL of type "${dataUrlMimeType}"`,
|
|
6457
|
+
location: reference.location
|
|
6458
|
+
});
|
|
6459
|
+
}
|
|
6460
|
+
}
|
|
5861
6461
|
}
|
|
5862
6462
|
}
|
|
5863
6463
|
return;
|
|
5864
6464
|
}
|
|
6465
|
+
if (isMalformedURL(url)) {
|
|
6466
|
+
pushMessage(context.messages, {
|
|
6467
|
+
id: MessageId.RSC_020,
|
|
6468
|
+
message: `Malformed URL: ${url}`,
|
|
6469
|
+
location: reference.location
|
|
6470
|
+
});
|
|
6471
|
+
return;
|
|
6472
|
+
}
|
|
5865
6473
|
if (isFileURL(url)) {
|
|
5866
6474
|
pushMessage(context.messages, {
|
|
5867
6475
|
id: MessageId.RSC_030,
|
|
@@ -5873,6 +6481,13 @@ var ReferenceValidator = class {
|
|
|
5873
6481
|
const resourcePath = reference.targetResource || parseURL(url).resource;
|
|
5874
6482
|
const fragment = reference.fragment ?? parseURL(url).fragment;
|
|
5875
6483
|
const hasFragment = fragment !== void 0 && fragment !== "";
|
|
6484
|
+
if (!isRemoteURL(url) && url.includes("?")) {
|
|
6485
|
+
pushMessage(context.messages, {
|
|
6486
|
+
id: MessageId.RSC_033,
|
|
6487
|
+
message: `Relative URL strings must not have a query component: "${url}"`,
|
|
6488
|
+
location: reference.location
|
|
6489
|
+
});
|
|
6490
|
+
}
|
|
5876
6491
|
if (!isRemoteURL(url)) {
|
|
5877
6492
|
this.validateLocalReference(context, reference, resourcePath);
|
|
5878
6493
|
} else {
|
|
@@ -5888,7 +6503,7 @@ var ReferenceValidator = class {
|
|
|
5888
6503
|
validateLocalReference(context, reference, resourcePath) {
|
|
5889
6504
|
if (hasAbsolutePath(resourcePath)) {
|
|
5890
6505
|
pushMessage(context.messages, {
|
|
5891
|
-
id: MessageId.
|
|
6506
|
+
id: MessageId.RSC_026,
|
|
5892
6507
|
message: "Absolute paths are not allowed in EPUB",
|
|
5893
6508
|
location: reference.location
|
|
5894
6509
|
});
|
|
@@ -5900,10 +6515,16 @@ var ReferenceValidator = class {
|
|
|
5900
6515
|
];
|
|
5901
6516
|
if (hasParentDirectoryReference(reference.url) && forbiddenParentDirTypes.includes(reference.type)) {
|
|
5902
6517
|
pushMessage(context.messages, {
|
|
5903
|
-
id: MessageId.
|
|
6518
|
+
id: MessageId.RSC_026,
|
|
5904
6519
|
message: "Parent directory references (..) are not allowed",
|
|
5905
6520
|
location: reference.location
|
|
5906
6521
|
});
|
|
6522
|
+
} else if (!hasAbsolutePath(resourcePath) && !hasParentDirectoryReference(reference.url) && checkUrlLeaking(reference.url)) {
|
|
6523
|
+
pushMessage(context.messages, {
|
|
6524
|
+
id: MessageId.RSC_026,
|
|
6525
|
+
message: `URL "${reference.url}" leaks outside the container`,
|
|
6526
|
+
location: reference.location
|
|
6527
|
+
});
|
|
5907
6528
|
}
|
|
5908
6529
|
if (!this.registry.hasResource(resourcePath)) {
|
|
5909
6530
|
const fileExistsInContainer = context.files.has(resourcePath);
|
|
@@ -5928,14 +6549,15 @@ var ReferenceValidator = class {
|
|
|
5928
6549
|
return;
|
|
5929
6550
|
}
|
|
5930
6551
|
const resource = this.registry.getResource(resourcePath);
|
|
5931
|
-
|
|
6552
|
+
const isHyperlinkLike = reference.type === "hyperlink" /* HYPERLINK */ || reference.type === "nav-toc-link" /* NAV_TOC_LINK */ || reference.type === "nav-pagelist-link" /* NAV_PAGELIST_LINK */;
|
|
6553
|
+
if (this.version.startsWith("3") && isHyperlinkLike && !resource?.inSpine) {
|
|
5932
6554
|
pushMessage(context.messages, {
|
|
5933
6555
|
id: MessageId.RSC_011,
|
|
5934
6556
|
message: "Hyperlinks must reference spine items",
|
|
5935
6557
|
location: reference.location
|
|
5936
6558
|
});
|
|
5937
6559
|
}
|
|
5938
|
-
if (
|
|
6560
|
+
if (isHyperlinkLike || reference.type === "overlay-text-link" /* OVERLAY_TEXT_LINK */) {
|
|
5939
6561
|
const targetMimeType = resource?.mimeType;
|
|
5940
6562
|
if (targetMimeType && !this.isBlessedItemType(targetMimeType, context.version) && !this.isDeprecatedBlessedItemType(targetMimeType) && !resource.hasCoreMediaTypeFallback) {
|
|
5941
6563
|
pushMessage(context.messages, {
|
|
@@ -5945,7 +6567,13 @@ var ReferenceValidator = class {
|
|
|
5945
6567
|
});
|
|
5946
6568
|
}
|
|
5947
6569
|
}
|
|
5948
|
-
|
|
6570
|
+
const fallbackCheckedTypes = [
|
|
6571
|
+
"image" /* IMAGE */,
|
|
6572
|
+
"audio" /* AUDIO */,
|
|
6573
|
+
"video" /* VIDEO */,
|
|
6574
|
+
"generic" /* GENERIC */
|
|
6575
|
+
];
|
|
6576
|
+
if (resource && fallbackCheckedTypes.includes(reference.type) && !isCoreMediaType(resource.mimeType) && !resource.hasCoreMediaTypeFallback && !reference.hasIntrinsicFallback) {
|
|
5949
6577
|
pushMessage(context.messages, {
|
|
5950
6578
|
id: MessageId.RSC_032,
|
|
5951
6579
|
message: `Fallback must be provided for foreign resources, but found none for resource "${resourcePath}" of type "${resource.mimeType}"`,
|
|
@@ -5958,20 +6586,24 @@ var ReferenceValidator = class {
|
|
|
5958
6586
|
*/
|
|
5959
6587
|
validateRemoteReference(context, reference) {
|
|
5960
6588
|
const url = reference.url;
|
|
5961
|
-
if (isHTTP(url) && !isHTTPS(url)) {
|
|
5962
|
-
pushMessage(context.messages, {
|
|
5963
|
-
id: MessageId.RSC_031,
|
|
5964
|
-
message: "Remote resources must use HTTPS",
|
|
5965
|
-
location: reference.location
|
|
5966
|
-
});
|
|
5967
|
-
}
|
|
5968
6589
|
if (isPublicationResourceReference(reference.type)) {
|
|
5969
|
-
|
|
6590
|
+
if (isHTTP(url) && !isHTTPS(url)) {
|
|
6591
|
+
pushMessage(context.messages, {
|
|
6592
|
+
id: MessageId.RSC_031,
|
|
6593
|
+
message: "Remote resources must use HTTPS",
|
|
6594
|
+
location: reference.location
|
|
6595
|
+
});
|
|
6596
|
+
}
|
|
6597
|
+
const allowedRemoteRefTypes = /* @__PURE__ */ new Set([
|
|
5970
6598
|
"audio" /* AUDIO */,
|
|
5971
6599
|
"video" /* VIDEO */,
|
|
5972
6600
|
"font" /* FONT */
|
|
5973
6601
|
]);
|
|
5974
|
-
|
|
6602
|
+
const targetResource = reference.targetResource || url;
|
|
6603
|
+
const resource = this.registry.getResource(targetResource);
|
|
6604
|
+
const isAllowedByRefType = allowedRemoteRefTypes.has(reference.type);
|
|
6605
|
+
const isAllowedByMimeType = resource && this.isRemoteResourceType(resource.mimeType);
|
|
6606
|
+
if (!isAllowedByRefType && !isAllowedByMimeType) {
|
|
5975
6607
|
pushMessage(context.messages, {
|
|
5976
6608
|
id: MessageId.RSC_006,
|
|
5977
6609
|
message: "Remote resources are only allowed for audio, video, and fonts",
|
|
@@ -5979,8 +6611,7 @@ var ReferenceValidator = class {
|
|
|
5979
6611
|
});
|
|
5980
6612
|
return;
|
|
5981
6613
|
}
|
|
5982
|
-
|
|
5983
|
-
if (!this.registry.hasResource(targetResource)) {
|
|
6614
|
+
if (!resource) {
|
|
5984
6615
|
pushMessage(context.messages, {
|
|
5985
6616
|
id: MessageId.RSC_008,
|
|
5986
6617
|
message: `Referenced resource "${targetResource}" is not declared in the OPF manifest`,
|
|
@@ -6013,9 +6644,9 @@ var ReferenceValidator = class {
|
|
|
6013
6644
|
});
|
|
6014
6645
|
return;
|
|
6015
6646
|
}
|
|
6016
|
-
if (resource?.mimeType === "image/svg+xml") {
|
|
6647
|
+
if (resource?.mimeType === "image/svg+xml" && reference.type === "hyperlink" /* HYPERLINK */) {
|
|
6017
6648
|
const hasSVGView = fragment.includes("svgView(") || fragment.includes("viewBox(");
|
|
6018
|
-
if (hasSVGView
|
|
6649
|
+
if (hasSVGView) {
|
|
6019
6650
|
pushMessage(context.messages, {
|
|
6020
6651
|
id: MessageId.RSC_014,
|
|
6021
6652
|
message: "SVG view fragments can only be referenced from SVG documents",
|
|
@@ -6023,11 +6654,51 @@ var ReferenceValidator = class {
|
|
|
6023
6654
|
});
|
|
6024
6655
|
}
|
|
6025
6656
|
}
|
|
6026
|
-
if (
|
|
6657
|
+
if (reference.type === "hyperlink" /* HYPERLINK */) {
|
|
6658
|
+
if (this.registry.isSVGSymbolID(resourcePath, fragment)) {
|
|
6659
|
+
pushMessage(context.messages, {
|
|
6660
|
+
id: MessageId.RSC_014,
|
|
6661
|
+
message: `Fragment identifier "${fragment}" defines an incompatible resource type (SVG symbol)`,
|
|
6662
|
+
location: reference.location
|
|
6663
|
+
});
|
|
6664
|
+
}
|
|
6665
|
+
}
|
|
6666
|
+
const parsedMimeTypes = ["application/xhtml+xml", "image/svg+xml"];
|
|
6667
|
+
if (resource && parsedMimeTypes.includes(resource.mimeType) && !isRemoteURL(resourcePath)) {
|
|
6668
|
+
if (!this.registry.hasID(resourcePath, fragment)) {
|
|
6669
|
+
pushMessage(context.messages, {
|
|
6670
|
+
id: MessageId.RSC_012,
|
|
6671
|
+
message: `Fragment identifier not found: #${fragment}`,
|
|
6672
|
+
location: reference.location
|
|
6673
|
+
});
|
|
6674
|
+
}
|
|
6675
|
+
}
|
|
6676
|
+
}
|
|
6677
|
+
/**
|
|
6678
|
+
* Check non-spine remote resources that have non-standard types.
|
|
6679
|
+
* Fires RSC-006 for remote items that aren't audio/video/font types
|
|
6680
|
+
* and aren't referenced as audio/video/font by content documents.
|
|
6681
|
+
* This mirrors Java's checkItemAfterResourceValidation behavior.
|
|
6682
|
+
*/
|
|
6683
|
+
checkRemoteResources(context) {
|
|
6684
|
+
if (!this.version.startsWith("3")) return;
|
|
6685
|
+
const referencedAsAllowed = /* @__PURE__ */ new Set();
|
|
6686
|
+
for (const ref of this.references) {
|
|
6687
|
+
if (isRemoteURL(ref.url) || isRemoteURL(ref.targetResource)) {
|
|
6688
|
+
if (ref.type === "font" /* FONT */ || ref.type === "audio" /* AUDIO */ || ref.type === "video" /* VIDEO */) {
|
|
6689
|
+
referencedAsAllowed.add(ref.targetResource);
|
|
6690
|
+
}
|
|
6691
|
+
}
|
|
6692
|
+
}
|
|
6693
|
+
for (const resource of this.registry.getAllResources()) {
|
|
6694
|
+
if (!isRemoteURL(resource.url)) continue;
|
|
6695
|
+
if (resource.inSpine) continue;
|
|
6696
|
+
if (this.isRemoteResourceType(resource.mimeType)) continue;
|
|
6697
|
+
if (referencedAsAllowed.has(resource.url)) continue;
|
|
6027
6698
|
pushMessage(context.messages, {
|
|
6028
|
-
id: MessageId.
|
|
6029
|
-
message: `
|
|
6030
|
-
location:
|
|
6699
|
+
id: MessageId.RSC_006,
|
|
6700
|
+
message: `Remote resource reference is not allowed; resource "${resource.url}" must be located in the EPUB container`,
|
|
6701
|
+
location: { path: resource.url }
|
|
6031
6702
|
});
|
|
6032
6703
|
}
|
|
6033
6704
|
}
|
|
@@ -6051,9 +6722,9 @@ var ReferenceValidator = class {
|
|
|
6051
6722
|
for (const resource of this.registry.getAllResources()) {
|
|
6052
6723
|
if (resource.inSpine) continue;
|
|
6053
6724
|
if (referencedResources.has(resource.url)) continue;
|
|
6054
|
-
if (resource.
|
|
6055
|
-
if (resource.
|
|
6056
|
-
if (resource.
|
|
6725
|
+
if (resource.isNav) continue;
|
|
6726
|
+
if (resource.isNcx) continue;
|
|
6727
|
+
if (resource.isCoverImage) continue;
|
|
6057
6728
|
pushMessage(context.messages, {
|
|
6058
6729
|
id: MessageId.OPF_097,
|
|
6059
6730
|
message: `Resource declared in manifest but not referenced: ${resource.url}`,
|
|
@@ -6114,7 +6785,7 @@ var ReferenceValidator = class {
|
|
|
6114
6785
|
const opfDir = opfPath.includes("/") ? opfPath.substring(0, opfPath.lastIndexOf("/")) : "";
|
|
6115
6786
|
const hyperlinkTargets = /* @__PURE__ */ new Set();
|
|
6116
6787
|
for (const ref of this.references) {
|
|
6117
|
-
if (ref.type === "hyperlink" /* HYPERLINK */) {
|
|
6788
|
+
if (ref.type === "hyperlink" /* HYPERLINK */ || ref.type === "nav-toc-link" /* NAV_TOC_LINK */ || ref.type === "nav-pagelist-link" /* NAV_PAGELIST_LINK */) {
|
|
6118
6789
|
hyperlinkTargets.add(ref.targetResource);
|
|
6119
6790
|
}
|
|
6120
6791
|
}
|
|
@@ -6151,6 +6822,13 @@ var ReferenceValidator = class {
|
|
|
6151
6822
|
isDeprecatedBlessedItemType(mimeType) {
|
|
6152
6823
|
return mimeType === "text/x-oeb1-document" || mimeType === "text/html";
|
|
6153
6824
|
}
|
|
6825
|
+
extractDataURLMimeType(url) {
|
|
6826
|
+
const match = /^data:([^;,]+)/.exec(url);
|
|
6827
|
+
return match?.[1]?.trim().toLowerCase() ?? "text/plain";
|
|
6828
|
+
}
|
|
6829
|
+
isRemoteResourceType(mimeType) {
|
|
6830
|
+
return mimeType.startsWith("audio/") || mimeType.startsWith("video/") || mimeType.startsWith("font/") || mimeType === "application/font-sfnt" || mimeType === "application/font-woff" || mimeType === "application/font-woff2" || mimeType === "application/vnd.ms-opentype";
|
|
6831
|
+
}
|
|
6154
6832
|
};
|
|
6155
6833
|
var COMPRESSED_SCHEMAS = {
|
|
6156
6834
|
"applications.rng": "H4sIAJC0cWkCA81aX2/bNhB/76fgXKBPtb1u6zC4ToqgSdMC7TqsGbZXWqIlohQpUFQS99PvSOq/RVmSZTdBHhLy/vF49+PxqPXbx4iheyITKvjF7NXi5xki3BM+5cHF7J+79/M/Zm8vn60DiaMISwTUPFnhi1moVLxaLiVh+JEHCyGDJU+WnohirOiGMqp2S8y5UPCvgBkj2XC7eBMlU0+lkhjay2cIrX+az9HzKX7QfG4E+mRLOUEcR+RiBsZGgi+wUjJZUK6IxJ6aIRjeANHFzAwxgu+JsQbY8coXXhoRbld1if6++XT1H/rzFn31QhJhtBUSfbj7/Am9XqF/yQZdxTGjniFG7wnWq0u0pPLn+XrZlGp1Tb32FvOvfJ+a3UFKoHfGG+gKvEE3qSKJy7DSLXYAhkSs5zHLB2BIkm2bmz0B3PALivGGkdmykLFsChkg1Zc4CCaUF1LfJ3wiYbGIBSSYW9p6WXfpemnD9EDEChWC1K5wdRhUhLqxqKe25sY5Quomm4dwMvQrsK/G6IoqrcXEXaG8TR8QeqGgXhF6MHCPWUouATxAtv27ORczrNf8qOaCs52LaotZ4hJR2buqflhMbvYAV5bR6nDidU6AbhjRwytU8PT1X1PJOM+1+WQKF2QJ5lj/BzNbLt5S9115TbZ72bnQ9oWnXFE234qU++cIiRwjHA75y06XHsno+7qkLt7tE5wq4fJIhHmKmWs2hAQ7h6M0HkBpMuiUvmVigxkq1CUI22NZZxgcecR6NUEPVIUoR5wcuE5yDFIepyoS/lQHVhITxqAE8b5NJFBHgodjcAWj36eyUuO5/EZ2OlieznGYh9SQ07DcPyeEcYXIY0w8OMKQjxVGaheTFSo4++ZuU5U7e7ngTiDXp597zpnWqXROQblLnZMcliWp55r2iUcjN5gkBEsvPMGxU0kTx759LSggJBDmPspvPvngCpVS+u7hnt7pTuOToGwj+13XhyqVGerrj1b5bp+I7dYZaB0xr+1xBZiGdk/fcdoJHoT0nZNeiHX5DHfmE4RoDSMdjr8DJEFii240LQIFCk4x+L2nUsEZjYB9IzCsYAV3+VJc391pMcG9N4bYmecduxM4Kw3egVWxJPdUpM69aYWO6r43y7mRexZLEcBdHvYLigdn2QZEQIM+cl83AYRcoRdMvcl5XwTqjWtPspqkoa3cjf3qo3bAFeZRzqtXz3YiE3nVA7kpfb3M7OnpFCvQXSi1n8QVCzprirqahdnO8RVKQ1qEH4fKamZGR1UlKV7QSBefVC1yzRtc26ED7FIw4mKtB/MxpVWrjx14UZ1sM15XP4stE1gtAK7nnAQQ6VCWZYYPz7lypxwWlVMH7IlFAsXfMbbYBHNVmGZ2EYcSJ+0lpu7yvTN9UoBq6B56BOlSLeue6H5ihBWCypZ0tTB1dpZayhrYRsO+dXX0OlD4+hQzEXQC3bUhQRvx+BIKXKh2PAC7lwiaoQ+U++LB4p4V1B/1LH1vzMvs7EK8jGQqvKuJOxXaVZUsRDy8LTkWnjL3D0QmzIhU+6zTIdO+PxwgUJnrKC2B6EQVdS0euwECwOih5/3TpLqhP5zm1cw9IB2CPe1M8TshGJw2NpM1df881tS9s9gY0pXDhmCqDK4ImzZ/xyYdo0mlNdIv5XwqDeDuhjIGUqTxUKYxFmoeOByGspnAGcEzoqCS2KdwJo7wh24WjnCJsvk0mE2SSq3bt1gk+sJrEvQ0mFxJWncafSdSfJGfhXQkPqMVpLL691kOSamiZOJJGqt5ksaxkGWLr1304K7jECAu0fUQDOdO6EZhRBWJLBQz2h+IGe0Pw2BIJwrD/GQgXMj6oRhcIJV27hjYOYbPNAdHQqTmN/g1lNn6bATgjUGtMd45A3IVcT5BhYb5PbZ3KkvvSGK47wGd+Vbkeges1EO3Esch9aBhp3PaCuqf15a+d25ndjYX5qKbKs2r7inuqpOvp37XPtOaTnsDrCpZhIQGYcfr2SBhD9RX4VQvhpC1kzzqtazW9dJfnXV2erTiACLjqN5Ti9scRlUnz2JTJY+7MUxJDL0ZLCGqh+BZI69Gt7qOLaL24HWAigG9sVaYOtQiIwoeQ7sfA/KP7eB95gufX8PzKTwuftQ9P+jwwXjWI7OSBjTJLEP/LllmamebLKOZrE9Wk3eyRllVy1k7ZW33Rt3Y1VRz+AgAdho+AojSRKEN9HfhhRfRLdIWmo9SUFbmvLFvzGizQzhJoI1mPs3Mv94cfamfsPW27+Gn2nurRflejiepftLf1e6b5+7NVSHjgPiauc6i0mzqMqM11WWmI//AySJMRtAfYTKG3giTW9uFMDnNVAhTl/c0Wnn2XX7olWefa7r8re/MAC+5SutuQA5f7TV0htWX4S/HCvj1WAG/HSvg9bECfj9WgDkQDghxx9B6mX0Adfnsf1i38T4sMgAA",
|
|
@@ -6665,11 +7343,15 @@ var EpubCheck = class _EpubCheck {
|
|
|
6665
7343
|
const manifestById = new Map(packageDoc.manifest.map((item) => [item.id, item]));
|
|
6666
7344
|
for (const item of packageDoc.manifest) {
|
|
6667
7345
|
const fullPath = resolveManifestHref(opfDir, item.href);
|
|
7346
|
+
const properties = item.properties ?? [];
|
|
6668
7347
|
registry.registerResource({
|
|
6669
7348
|
url: fullPath,
|
|
6670
7349
|
mimeType: item.mediaType,
|
|
6671
7350
|
inSpine: spineIdrefs.has(item.id),
|
|
6672
7351
|
hasCoreMediaTypeFallback: this.hasCMTFallback(item.id, manifestById),
|
|
7352
|
+
isNav: properties.includes("nav"),
|
|
7353
|
+
isCoverImage: properties.includes("cover-image"),
|
|
7354
|
+
isNcx: item.mediaType === "application/x-dtbncx+xml",
|
|
6673
7355
|
ids: /* @__PURE__ */ new Set()
|
|
6674
7356
|
});
|
|
6675
7357
|
}
|
|
@@ -6685,7 +7367,7 @@ var EpubCheck = class _EpubCheck {
|
|
|
6685
7367
|
visited.add(currentId);
|
|
6686
7368
|
const item = manifestById.get(currentId);
|
|
6687
7369
|
if (!item) return false;
|
|
6688
|
-
if (
|
|
7370
|
+
if (isCoreMediaType(item.mediaType)) return true;
|
|
6689
7371
|
currentId = item.fallback;
|
|
6690
7372
|
}
|
|
6691
7373
|
return false;
|