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