pretext-pdf 1.1.0 → 1.6.0
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/CHANGELOG.md +687 -0
- package/README.md +83 -8
- package/dist/allowed-props.d.ts +76 -0
- package/dist/allowed-props.d.ts.map +1 -1
- package/dist/allowed-props.js.map +1 -1
- package/dist/assets/generators/barcode.d.ts +9 -0
- package/dist/assets/generators/barcode.d.ts.map +1 -0
- package/dist/assets/generators/barcode.js +24 -0
- package/dist/assets/generators/barcode.js.map +1 -0
- package/dist/assets/generators/chart.d.ts +13 -0
- package/dist/assets/generators/chart.d.ts.map +1 -0
- package/dist/assets/generators/chart.js +32 -0
- package/dist/assets/generators/chart.js.map +1 -0
- package/dist/assets/generators/qr.d.ts +9 -0
- package/dist/assets/generators/qr.d.ts.map +1 -0
- package/dist/assets/generators/qr.js +25 -0
- package/dist/assets/generators/qr.js.map +1 -0
- package/dist/assets/index.d.ts +19 -0
- package/dist/assets/index.d.ts.map +1 -0
- package/dist/assets/index.js +19 -0
- package/dist/assets/index.js.map +1 -0
- package/dist/assets/loaders/images.d.ts +20 -0
- package/dist/assets/loaders/images.d.ts.map +1 -0
- package/dist/assets/loaders/images.js +69 -0
- package/dist/assets/loaders/images.js.map +1 -0
- package/dist/assets/loaders/orchestrator.d.ts +24 -0
- package/dist/assets/loaders/orchestrator.d.ts.map +1 -0
- package/dist/assets/loaders/orchestrator.js +109 -0
- package/dist/assets/loaders/orchestrator.js.map +1 -0
- package/dist/assets/loaders/vectors.d.ts +25 -0
- package/dist/assets/loaders/vectors.d.ts.map +1 -0
- package/dist/assets/loaders/vectors.js +118 -0
- package/dist/assets/loaders/vectors.js.map +1 -0
- package/dist/assets/loaders/watermark.d.ts +12 -0
- package/dist/assets/loaders/watermark.d.ts.map +1 -0
- package/dist/assets/loaders/watermark.js +40 -0
- package/dist/assets/loaders/watermark.js.map +1 -0
- package/dist/assets/security/fetch.d.ts +14 -0
- package/dist/assets/security/fetch.d.ts.map +1 -0
- package/dist/assets/security/fetch.js +112 -0
- package/dist/assets/security/fetch.js.map +1 -0
- package/dist/assets/security/ipv4-normalize.d.ts +28 -0
- package/dist/assets/security/ipv4-normalize.d.ts.map +1 -0
- package/dist/assets/security/ipv4-normalize.js +116 -0
- package/dist/assets/security/ipv4-normalize.js.map +1 -0
- package/dist/assets/security/path-allowlist.d.ts +12 -0
- package/dist/assets/security/path-allowlist.d.ts.map +1 -0
- package/dist/assets/security/path-allowlist.js +26 -0
- package/dist/assets/security/path-allowlist.js.map +1 -0
- package/dist/assets/security/url-validation.d.ts +22 -0
- package/dist/assets/security/url-validation.d.ts.map +1 -0
- package/dist/assets/security/url-validation.js +164 -0
- package/dist/assets/security/url-validation.js.map +1 -0
- package/dist/assets/svg/dimensions.d.ts +19 -0
- package/dist/assets/svg/dimensions.d.ts.map +1 -0
- package/dist/assets/svg/dimensions.js +43 -0
- package/dist/assets/svg/dimensions.js.map +1 -0
- package/dist/assets/svg/rasterize.d.ts +6 -0
- package/dist/assets/svg/rasterize.d.ts.map +1 -0
- package/dist/assets/svg/rasterize.js +38 -0
- package/dist/assets/svg/rasterize.js.map +1 -0
- package/dist/assets/svg/resolve-content.d.ts +16 -0
- package/dist/assets/svg/resolve-content.d.ts.map +1 -0
- package/dist/assets/svg/resolve-content.js +38 -0
- package/dist/assets/svg/resolve-content.js.map +1 -0
- package/dist/assets/svg/sanitize.d.ts +22 -0
- package/dist/assets/svg/sanitize.d.ts.map +1 -0
- package/dist/assets/svg/sanitize.js +46 -0
- package/dist/assets/svg/sanitize.js.map +1 -0
- package/dist/assets/util/redact-path.d.ts +14 -0
- package/dist/assets/util/redact-path.d.ts.map +1 -0
- package/dist/assets/util/redact-path.js +16 -0
- package/dist/assets/util/redact-path.js.map +1 -0
- package/dist/assets.d.ts +10 -27
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +10 -549
- package/dist/assets.js.map +1 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +2 -1
- package/dist/builder.js.map +1 -1
- package/dist/cli.js +11 -1
- package/dist/cli.js.map +1 -1
- package/dist/compat.d.ts +63 -1
- package/dist/compat.d.ts.map +1 -1
- package/dist/compat.js +42 -5
- package/dist/compat.js.map +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -2
- package/dist/errors.js.map +1 -1
- package/dist/fonts.d.ts.map +1 -1
- package/dist/fonts.js +8 -10
- package/dist/fonts.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/layout-state.d.ts +1 -1
- package/dist/layout-state.d.ts.map +1 -1
- package/dist/layout-state.js +5 -0
- package/dist/layout-state.js.map +1 -1
- package/dist/measure-blocks/float-group.d.ts +9 -0
- package/dist/measure-blocks/float-group.d.ts.map +1 -0
- package/dist/measure-blocks/float-group.js +103 -0
- package/dist/measure-blocks/float-group.js.map +1 -0
- package/dist/measure-blocks/helpers.d.ts +44 -0
- package/dist/measure-blocks/helpers.d.ts.map +1 -0
- package/dist/measure-blocks/helpers.js +43 -0
- package/dist/measure-blocks/helpers.js.map +1 -0
- package/dist/measure-blocks/highlight.d.ts +26 -0
- package/dist/measure-blocks/highlight.d.ts.map +1 -0
- package/dist/measure-blocks/highlight.js +169 -0
- package/dist/measure-blocks/highlight.js.map +1 -0
- package/dist/measure-blocks/image.d.ts +9 -0
- package/dist/measure-blocks/image.d.ts.map +1 -0
- package/dist/measure-blocks/image.js +136 -0
- package/dist/measure-blocks/image.js.map +1 -0
- package/dist/measure-blocks/index.d.ts +24 -0
- package/dist/measure-blocks/index.d.ts.map +1 -0
- package/dist/measure-blocks/index.js +179 -0
- package/dist/measure-blocks/index.js.map +1 -0
- package/dist/measure-blocks/list.d.ts +8 -0
- package/dist/measure-blocks/list.d.ts.map +1 -0
- package/dist/measure-blocks/list.js +108 -0
- package/dist/measure-blocks/list.js.map +1 -0
- package/dist/measure-blocks/simple-blocks.d.ts +18 -0
- package/dist/measure-blocks/simple-blocks.d.ts.map +1 -0
- package/dist/measure-blocks/simple-blocks.js +121 -0
- package/dist/measure-blocks/simple-blocks.js.map +1 -0
- package/dist/measure-blocks/table/columns.d.ts +17 -0
- package/dist/measure-blocks/table/columns.d.ts.map +1 -0
- package/dist/measure-blocks/table/columns.js +83 -0
- package/dist/measure-blocks/table/columns.js.map +1 -0
- package/dist/measure-blocks/table/measure.d.ts +8 -0
- package/dist/measure-blocks/table/measure.d.ts.map +1 -0
- package/dist/measure-blocks/table/measure.js +231 -0
- package/dist/measure-blocks/table/measure.js.map +1 -0
- package/dist/measure-blocks/table/spans.d.ts +25 -0
- package/dist/measure-blocks/table/spans.d.ts.map +1 -0
- package/dist/measure-blocks/table/spans.js +55 -0
- package/dist/measure-blocks/table/spans.js.map +1 -0
- package/dist/measure-blocks/text-blocks.d.ts +17 -0
- package/dist/measure-blocks/text-blocks.d.ts.map +1 -0
- package/dist/measure-blocks/text-blocks.js +242 -0
- package/dist/measure-blocks/text-blocks.js.map +1 -0
- package/dist/measure-text.d.ts +21 -3
- package/dist/measure-text.d.ts.map +1 -1
- package/dist/measure-text.js +87 -36
- package/dist/measure-text.js.map +1 -1
- package/dist/measure.d.ts +1 -1
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +8 -6
- package/dist/measure.js.map +1 -1
- package/dist/node-polyfill.d.ts.map +1 -1
- package/dist/node-polyfill.js +9 -0
- package/dist/node-polyfill.js.map +1 -1
- package/dist/pipeline-footnotes.d.ts +1 -1
- package/dist/pipeline-footnotes.d.ts.map +1 -1
- package/dist/pipeline-toc.d.ts +1 -1
- package/dist/pipeline-toc.d.ts.map +1 -1
- package/dist/pipeline.d.ts +3 -3
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +4 -5
- package/dist/pipeline.js.map +1 -1
- package/dist/plugin-types.d.ts +1 -1
- package/dist/plugin-types.d.ts.map +1 -1
- package/dist/post-process.d.ts +2 -2
- package/dist/post-process.d.ts.map +1 -1
- package/dist/post-process.js +32 -9
- package/dist/post-process.js.map +1 -1
- package/dist/render-blocks/blockquote.d.ts +7 -0
- package/dist/render-blocks/blockquote.d.ts.map +1 -0
- package/dist/render-blocks/blockquote.js +87 -0
- package/dist/render-blocks/blockquote.js.map +1 -0
- package/dist/render-blocks/callout.d.ts +7 -0
- package/dist/render-blocks/callout.d.ts.map +1 -0
- package/dist/render-blocks/callout.js +84 -0
- package/dist/render-blocks/callout.js.map +1 -0
- package/dist/render-blocks/code.d.ts +7 -0
- package/dist/render-blocks/code.d.ts.map +1 -0
- package/dist/render-blocks/code.js +84 -0
- package/dist/render-blocks/code.js.map +1 -0
- package/dist/render-blocks/footnote.d.ts +11 -0
- package/dist/render-blocks/footnote.d.ts.map +1 -0
- package/dist/render-blocks/footnote.js +45 -0
- package/dist/render-blocks/footnote.js.map +1 -0
- package/dist/render-blocks/header-footer.d.ts +11 -0
- package/dist/render-blocks/header-footer.d.ts.map +1 -0
- package/dist/render-blocks/header-footer.js +56 -0
- package/dist/render-blocks/header-footer.js.map +1 -0
- package/dist/render-blocks/hr.d.ts +7 -0
- package/dist/render-blocks/hr.d.ts.map +1 -0
- package/dist/render-blocks/hr.js +24 -0
- package/dist/render-blocks/hr.js.map +1 -0
- package/dist/render-blocks/image.d.ts +9 -0
- package/dist/render-blocks/image.d.ts.map +1 -0
- package/dist/render-blocks/image.js +135 -0
- package/dist/render-blocks/image.js.map +1 -0
- package/dist/render-blocks/index.d.ts +17 -0
- package/dist/render-blocks/index.d.ts.map +1 -0
- package/dist/render-blocks/index.js +17 -0
- package/dist/render-blocks/index.js.map +1 -0
- package/dist/render-blocks/list-item.d.ts +7 -0
- package/dist/render-blocks/list-item.d.ts.map +1 -0
- package/dist/render-blocks/list-item.js +80 -0
- package/dist/render-blocks/list-item.js.map +1 -0
- package/dist/render-blocks/rich.d.ts +7 -0
- package/dist/render-blocks/rich.d.ts.map +1 -0
- package/dist/render-blocks/rich.js +160 -0
- package/dist/render-blocks/rich.js.map +1 -0
- package/dist/render-blocks/table.d.ts +7 -0
- package/dist/render-blocks/table.d.ts.map +1 -0
- package/dist/render-blocks/table.js +139 -0
- package/dist/render-blocks/table.js.map +1 -0
- package/dist/render-blocks/text.d.ts +7 -0
- package/dist/render-blocks/text.d.ts.map +1 -0
- package/dist/render-blocks/text.js +183 -0
- package/dist/render-blocks/text.js.map +1 -0
- package/dist/render-blocks/watermark.d.ts +8 -0
- package/dist/render-blocks/watermark.d.ts.map +1 -0
- package/dist/render-blocks/watermark.js +52 -0
- package/dist/render-blocks/watermark.js.map +1 -0
- package/dist/render-extras.d.ts.map +1 -1
- package/dist/render-extras.js +1 -2
- package/dist/render-extras.js.map +1 -1
- package/dist/render-utils.d.ts.map +1 -1
- package/dist/render-utils.js +10 -6
- package/dist/render-utils.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +9 -3
- package/dist/render.js.map +1 -1
- package/dist/rich-text.d.ts +2 -1
- package/dist/rich-text.d.ts.map +1 -1
- package/dist/rich-text.js +0 -1
- package/dist/rich-text.js.map +1 -1
- package/dist/types-internal.d.ts +19 -3
- package/dist/types-internal.d.ts.map +1 -1
- package/dist/types-public/document.d.ts +261 -0
- package/dist/types-public/document.d.ts.map +1 -0
- package/dist/types-public/document.js +2 -0
- package/dist/types-public/document.js.map +1 -0
- package/dist/types-public/elements-block.d.ts +246 -0
- package/dist/types-public/elements-block.d.ts.map +1 -0
- package/dist/types-public/elements-block.js +8 -0
- package/dist/types-public/elements-block.js.map +1 -0
- package/dist/types-public/elements-media.d.ts +199 -0
- package/dist/types-public/elements-media.d.ts.map +1 -0
- package/dist/types-public/elements-media.js +2 -0
- package/dist/types-public/elements-media.js.map +1 -0
- package/dist/types-public/elements-text.d.ts +327 -0
- package/dist/types-public/elements-text.d.ts.map +1 -0
- package/dist/types-public/elements-text.js +2 -0
- package/dist/types-public/elements-text.js.map +1 -0
- package/dist/types-public/index.d.ts +14 -0
- package/dist/types-public/index.d.ts.map +1 -0
- package/dist/types-public/index.js +2 -0
- package/dist/types-public/index.js.map +1 -0
- package/dist/types-public/render-options.d.ts +38 -0
- package/dist/types-public/render-options.d.ts.map +1 -0
- package/dist/types-public/render-options.js +2 -0
- package/dist/types-public/render-options.js.map +1 -0
- package/dist/types-public/union.d.ts +13 -0
- package/dist/types-public/union.d.ts.map +1 -0
- package/dist/types-public/union.js +2 -0
- package/dist/types-public/union.js.map +1 -0
- package/dist/types-public/validation.d.ts +64 -0
- package/dist/types-public/validation.d.ts.map +1 -0
- package/dist/types-public/validation.js +2 -0
- package/dist/types-public/validation.js.map +1 -0
- package/dist/types-public.d.ts +5 -1081
- package/dist/types-public.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/validate/document.d.ts +28 -0
- package/dist/validate/document.d.ts.map +1 -0
- package/dist/validate/document.js +295 -0
- package/dist/validate/document.js.map +1 -0
- package/dist/validate/elements/forms-floats.d.ts +19 -0
- package/dist/validate/elements/forms-floats.d.ts.map +1 -0
- package/dist/validate/elements/forms-floats.js +96 -0
- package/dist/validate/elements/forms-floats.js.map +1 -0
- package/dist/validate/elements/list.d.ts +10 -0
- package/dist/validate/elements/list.d.ts.map +1 -0
- package/dist/validate/elements/list.js +66 -0
- package/dist/validate/elements/list.js.map +1 -0
- package/dist/validate/elements/media.d.ts +23 -0
- package/dist/validate/elements/media.d.ts.map +1 -0
- package/dist/validate/elements/media.js +179 -0
- package/dist/validate/elements/media.js.map +1 -0
- package/dist/validate/elements/structural-simple.d.ts +21 -0
- package/dist/validate/elements/structural-simple.d.ts.map +1 -0
- package/dist/validate/elements/structural-simple.js +63 -0
- package/dist/validate/elements/structural-simple.js.map +1 -0
- package/dist/validate/elements/structural.d.ts +12 -0
- package/dist/validate/elements/structural.d.ts.map +1 -0
- package/dist/validate/elements/structural.js +12 -0
- package/dist/validate/elements/structural.js.map +1 -0
- package/dist/validate/elements/table.d.ts +10 -0
- package/dist/validate/elements/table.d.ts.map +1 -0
- package/dist/validate/elements/table.js +165 -0
- package/dist/validate/elements/table.js.map +1 -0
- package/dist/validate/elements/text.d.ts +26 -0
- package/dist/validate/elements/text.d.ts.map +1 -0
- package/dist/validate/elements/text.js +331 -0
- package/dist/validate/elements/text.js.map +1 -0
- package/dist/validate/errors.d.ts +9 -0
- package/dist/validate/errors.d.ts.map +1 -0
- package/dist/validate/errors.js +43 -0
- package/dist/validate/errors.js.map +1 -0
- package/dist/validate/fonts.d.ts +11 -0
- package/dist/validate/fonts.d.ts.map +1 -0
- package/dist/validate/fonts.js +118 -0
- package/dist/validate/fonts.js.map +1 -0
- package/dist/validate/helpers.d.ts +76 -0
- package/dist/validate/helpers.d.ts.map +1 -0
- package/dist/validate/helpers.js +169 -0
- package/dist/validate/helpers.js.map +1 -0
- package/dist/validate/index.d.ts +37 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/dist/validate/index.js +279 -0
- package/dist/validate/index.js.map +1 -0
- package/dist/validate.d.ts +6 -18
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +6 -1582
- package/dist/validate.js.map +1 -1
- package/dist/vendor/pretext/VERSION.d.ts +3 -0
- package/dist/vendor/pretext/VERSION.d.ts.map +1 -0
- package/dist/vendor/pretext/VERSION.js +12 -0
- package/dist/vendor/pretext/VERSION.js.map +1 -0
- package/dist/version-check.d.ts +47 -0
- package/dist/version-check.d.ts.map +1 -0
- package/dist/version-check.js +75 -0
- package/dist/version-check.js.map +1 -0
- package/package.json +27 -8
- package/dist/measure-blocks.d.ts +0 -26
- package/dist/measure-blocks.d.ts.map +0 -1
- package/dist/measure-blocks.js +0 -1317
- package/dist/measure-blocks.js.map +0 -1
- package/dist/render-blocks.d.ts +0 -28
- package/dist/render-blocks.d.ts.map +0 -1
- package/dist/render-blocks.js +0 -1059
- package/dist/render-blocks.js.map +0 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enforce allowedFileDirs: resolved absolute path must start with an allowed dir.
|
|
3
|
+
* Deny-by-default: when allowedFileDirs is undefined or empty, file:// access is
|
|
4
|
+
* rejected unless the caller explicitly configures the allowed directories.
|
|
5
|
+
*
|
|
6
|
+
* Extracted from `src/assets.ts` in v1.6.0 commit 5/16 as part of the
|
|
7
|
+
* post-v1.5.2 assets.ts file-size sprint. The original module re-exports
|
|
8
|
+
* this symbol for back-compat with production consumers (fonts.ts,
|
|
9
|
+
* post-process.ts) and the public API surface.
|
|
10
|
+
*/
|
|
11
|
+
export declare function assertPathAllowed(resolvedPath: string, allowedDirs: string[] | undefined, label: string): void;
|
|
12
|
+
//# sourceMappingURL=path-allowlist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-allowlist.d.ts","sourceRoot":"","sources":["../../../src/assets/security/path-allowlist.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EAAE,GAAG,SAAS,EACjC,KAAK,EAAE,MAAM,GACZ,IAAI,CAmBN"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PretextPdfError } from '../../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Enforce allowedFileDirs: resolved absolute path must start with an allowed dir.
|
|
4
|
+
* Deny-by-default: when allowedFileDirs is undefined or empty, file:// access is
|
|
5
|
+
* rejected unless the caller explicitly configures the allowed directories.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from `src/assets.ts` in v1.6.0 commit 5/16 as part of the
|
|
8
|
+
* post-v1.5.2 assets.ts file-size sprint. The original module re-exports
|
|
9
|
+
* this symbol for back-compat with production consumers (fonts.ts,
|
|
10
|
+
* post-process.ts) and the public API surface.
|
|
11
|
+
*/
|
|
12
|
+
export function assertPathAllowed(resolvedPath, allowedDirs, label) {
|
|
13
|
+
if (!allowedDirs || allowedDirs.length === 0) {
|
|
14
|
+
throw new PretextPdfError('PATH_TRAVERSAL', `${label} src uses a local file path but doc.allowedFileDirs is not set. ` +
|
|
15
|
+
`Configure allowedFileDirs to explicitly list the directories from which files may be read.`);
|
|
16
|
+
}
|
|
17
|
+
const norm = resolvedPath.replace(/\\/g, '/');
|
|
18
|
+
const allowed = allowedDirs.some((dir) => {
|
|
19
|
+
const d = dir.replace(/\\/g, '/').replace(/\/$/, '');
|
|
20
|
+
return norm === d || norm.startsWith(d + '/');
|
|
21
|
+
});
|
|
22
|
+
if (!allowed) {
|
|
23
|
+
throw new PretextPdfError('PATH_TRAVERSAL', `${label} src is outside allowedFileDirs. Configure doc.allowedFileDirs to include the file's directory.`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=path-allowlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-allowlist.js","sourceRoot":"","sources":["../../../src/assets/security/path-allowlist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjD;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,YAAoB,EACpB,WAAiC,EACjC,KAAa;IAEb,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,eAAe,CACvB,gBAAgB,EAChB,GAAG,KAAK,kEAAkE;YACxE,4FAA4F,CAC/F,CAAA;IACH,CAAC;IACD,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IAC7C,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QACvC,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;QACpD,OAAO,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,GAAG,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;IACF,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,eAAe,CACvB,gBAAgB,EAChB,GAAG,KAAK,iGAAiG,CAC1G,CAAA;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of resolving a URL: the parsed URL plus the pre-validated IP that
|
|
3
|
+
* downstream fetches should pin to (closing the TOCTOU rebinding window).
|
|
4
|
+
* `ip` is null only for IP-literal hostnames (no DNS lookup performed).
|
|
5
|
+
*/
|
|
6
|
+
export interface ResolvedSafeUrl {
|
|
7
|
+
url: URL;
|
|
8
|
+
ip: string | null;
|
|
9
|
+
family: 4 | 6 | null;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Validate that a URL is safe to fetch. Returns the parsed URL and the
|
|
13
|
+
* pre-resolved IP so callers can pin the connection. Throws PretextPdfError
|
|
14
|
+
* if the URL targets a private/internal address.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveAndValidateUrl(url: string, errorCode: 'IMAGE_LOAD_FAILED' | 'SVG_LOAD_FAILED', label: string): Promise<ResolvedSafeUrl>;
|
|
17
|
+
/**
|
|
18
|
+
* Back-compat wrapper that drops the resolution result. Kept so existing
|
|
19
|
+
* tests and call sites that only need the validation side-effect still work.
|
|
20
|
+
*/
|
|
21
|
+
export declare function assertSafeUrl(url: string, errorCode: 'IMAGE_LOAD_FAILED' | 'SVG_LOAD_FAILED', label: string): Promise<void>;
|
|
22
|
+
//# sourceMappingURL=url-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-validation.d.ts","sourceRoot":"","sources":["../../../src/assets/security/url-validation.ts"],"names":[],"mappings":"AAsEA;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,GAAG,CAAA;IACR,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IACjB,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA;CACrB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,mBAAmB,GAAG,iBAAiB,EAClD,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,eAAe,CAAC,CAyF1B;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,mBAAmB,GAAG,iBAAiB,EAClD,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAEf"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { promises as dnsPromises } from 'node:dns';
|
|
2
|
+
import { PretextPdfError } from '../../errors.js';
|
|
3
|
+
import { normalizeIpv4Hostname } from './ipv4-normalize.js';
|
|
4
|
+
/**
|
|
5
|
+
* SSRF defence-in-depth helpers for remote image / SVG fetches.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from `src/assets.ts` in v1.6.0 commit 7/16 as part of the
|
|
8
|
+
* post-v1.5.2 assets.ts file-size sprint. Pure module — no top-level side
|
|
9
|
+
* effects. `src/assets.ts` re-exports `resolveAndValidateUrl`, `assertSafeUrl`,
|
|
10
|
+
* and `ResolvedSafeUrl` for back-compat with consumers that import from
|
|
11
|
+
* `'./assets.js'` / `'../dist/assets.js'` (notably `test/security-ssrf.test.ts`).
|
|
12
|
+
*
|
|
13
|
+
* `isPrivateAddress` is intentionally NOT re-exported — it is a private
|
|
14
|
+
* implementation detail.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Validate a remote URL before fetching:
|
|
18
|
+
* - Rejects http:// (plaintext only)
|
|
19
|
+
* - Rejects private/internal IP ranges (SSRF prevention), including IPv4-mapped IPv6
|
|
20
|
+
* forms like [::ffff:127.0.0.1] which would otherwise bypass dotted-decimal regexes.
|
|
21
|
+
* - Rejects alternative IPv4 notations (decimal `2130706433`, octal `0177.0.0.1`,
|
|
22
|
+
* hex `0x7f000001`, short form `127.1`) by normalizing through
|
|
23
|
+
* `normalizeIpv4Hostname` before regex matching.
|
|
24
|
+
* Throws IMAGE_LOAD_FAILED or SVG_LOAD_FAILED on violations.
|
|
25
|
+
*/
|
|
26
|
+
function isPrivateAddress(h, raw) {
|
|
27
|
+
// Defense-in-depth: if `h` is an alternative IPv4 notation, fold to its
|
|
28
|
+
// canonical dotted form before regex matching. Callers should normally
|
|
29
|
+
// pre-normalize, but private-address checks are also invoked on freshly
|
|
30
|
+
// resolved DNS results — normalize there too in case a resolver ever
|
|
31
|
+
// returns a non-dotted form.
|
|
32
|
+
const normalized = normalizeIpv4Hostname(h);
|
|
33
|
+
if (normalized && normalized !== h) {
|
|
34
|
+
h = normalized;
|
|
35
|
+
}
|
|
36
|
+
return _isPrivateAddressInner(h, raw);
|
|
37
|
+
}
|
|
38
|
+
function _isPrivateAddressInner(h, raw) {
|
|
39
|
+
// IPv6 prefix checks must only fire on actual IPv6 hostnames (which contain
|
|
40
|
+
// a colon) — otherwise legitimate hostnames like `ffmpeg.com` or `fcc.gov`
|
|
41
|
+
// would be blocked.
|
|
42
|
+
const isV6 = raw.includes(':');
|
|
43
|
+
return (h === 'localhost' ||
|
|
44
|
+
h === '0.0.0.0' ||
|
|
45
|
+
h === '::' || // IPv6 unspecified address (== 0.0.0.0)
|
|
46
|
+
h === '::1' ||
|
|
47
|
+
raw === '::1' ||
|
|
48
|
+
raw === '::' || // also catch un-normalized IPv6 forms
|
|
49
|
+
/^0\./.test(h) || // 0.0.0.0/8 "this network"
|
|
50
|
+
/^127\./.test(h) ||
|
|
51
|
+
/^10\./.test(h) ||
|
|
52
|
+
/^192\.168\./.test(h) ||
|
|
53
|
+
/^192\.0\.0\./.test(h) || // 192.0.0/24 IETF protocol assignments
|
|
54
|
+
/^172\.(1[6-9]|2\d|3[01])\./.test(h) ||
|
|
55
|
+
/^169\.254\./.test(h) || // link-local / AWS IMDS
|
|
56
|
+
/^198\.1[89]\./.test(h) || // 198.18/15 benchmark testing
|
|
57
|
+
/^22[4-9]\./.test(h) ||
|
|
58
|
+
/^23\d\./.test(h) || // 224/4 multicast
|
|
59
|
+
/^2[4-5]\d\./.test(h) || // 240/4 reserved (240–255)
|
|
60
|
+
/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(h) || // CGNAT RFC 6598
|
|
61
|
+
(isV6 && (raw.startsWith('fc') || raw.startsWith('fd'))) || // IPv6 ULA fc00::/7
|
|
62
|
+
(isV6 && /^fe[89ab]/i.test(raw)) || // IPv6 link-local fe80::/10
|
|
63
|
+
(isV6 && /^ff/i.test(raw)) // IPv6 multicast ff00::/8
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Validate that a URL is safe to fetch. Returns the parsed URL and the
|
|
68
|
+
* pre-resolved IP so callers can pin the connection. Throws PretextPdfError
|
|
69
|
+
* if the URL targets a private/internal address.
|
|
70
|
+
*/
|
|
71
|
+
export async function resolveAndValidateUrl(url, errorCode, label) {
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = new URL(url);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
throw new PretextPdfError(errorCode, `${label}: invalid URL`);
|
|
78
|
+
}
|
|
79
|
+
if (parsed.protocol === 'http:') {
|
|
80
|
+
throw new PretextPdfError(errorCode, `${label}: HTTP URLs are not allowed — use HTTPS`);
|
|
81
|
+
}
|
|
82
|
+
if (parsed.protocol === 'data:' || parsed.protocol === 'file:' || parsed.protocol === 'javascript:') {
|
|
83
|
+
throw new PretextPdfError(errorCode, `${label}: ${parsed.protocol} URLs are not allowed — use HTTPS only`);
|
|
84
|
+
}
|
|
85
|
+
if (parsed.protocol !== 'https:') {
|
|
86
|
+
throw new PretextPdfError(errorCode, `${label}: refused scheme: ${parsed.protocol}`);
|
|
87
|
+
}
|
|
88
|
+
const raw = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, ''); // strip IPv6 brackets
|
|
89
|
+
// Normalize IPv4-mapped IPv6 to its dotted-decimal form so the IPv4
|
|
90
|
+
// private-range regexes catch it. WHATWG URL normalizes `[::ffff:127.0.0.1]`
|
|
91
|
+
// to `[::ffff:7f00:1]` (hex form), so we must handle BOTH the dotted
|
|
92
|
+
// (`::ffff:127.0.0.1`) and hex-compressed (`::ffff:7f00:1`) forms.
|
|
93
|
+
// Without this an attacker can bypass the localhost/private-IP check via
|
|
94
|
+
// `https://[::ffff:127.0.0.1]/admin` → resolves to 127.0.0.1.
|
|
95
|
+
let h = raw;
|
|
96
|
+
const v4Dotted = raw.match(/^::ffff:(?:0:)?(\d{1,3}(?:\.\d{1,3}){3})$/i);
|
|
97
|
+
const v4Hex = raw.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
|
|
98
|
+
if (v4Dotted) {
|
|
99
|
+
h = v4Dotted[1];
|
|
100
|
+
}
|
|
101
|
+
else if (v4Hex) {
|
|
102
|
+
const hi = parseInt(v4Hex[1], 16);
|
|
103
|
+
const lo = parseInt(v4Hex[2], 16);
|
|
104
|
+
h = `${(hi >> 8) & 0xff}.${hi & 0xff}.${(lo >> 8) & 0xff}.${lo & 0xff}`;
|
|
105
|
+
}
|
|
106
|
+
// Normalize alternative IPv4 notations (decimal `2130706433`, octal
|
|
107
|
+
// `0177.0.0.1`, hex `0x7f000001`, short form `127.1`) to dotted-decimal
|
|
108
|
+
// BEFORE the private-IP check. Without this an attacker can bypass the
|
|
109
|
+
// localhost / RFC1918 regexes by encoding 127.0.0.1 in any non-dotted form;
|
|
110
|
+
// WHATWG URL does not normalize these and DNS will happily resolve them
|
|
111
|
+
// on Linux via getaddrinfo.
|
|
112
|
+
const normalizedIpv4 = normalizeIpv4Hostname(h);
|
|
113
|
+
if (normalizedIpv4) {
|
|
114
|
+
h = normalizedIpv4;
|
|
115
|
+
}
|
|
116
|
+
if (isPrivateAddress(h, raw)) {
|
|
117
|
+
throw new PretextPdfError(errorCode, `${label}: connections to private or internal addresses are not allowed`);
|
|
118
|
+
}
|
|
119
|
+
// DNS pre-resolution: re-verify the resolved IP AND remember it so callers
|
|
120
|
+
// can pin the actual TCP connection to this exact IP (closes the TOCTOU
|
|
121
|
+
// window where an attacker with TTL=0 DNS could rebind between check and
|
|
122
|
+
// connect).
|
|
123
|
+
const isIpv4Literal = /^\d{1,3}(\.\d{1,3}){3}$/.test(parsed.hostname);
|
|
124
|
+
const isIpv6Literal = parsed.hostname.startsWith('[');
|
|
125
|
+
if (isIpv4Literal) {
|
|
126
|
+
return { url: parsed, ip: parsed.hostname, family: 4 };
|
|
127
|
+
}
|
|
128
|
+
// Alternative IPv4 notation that we just normalized to dotted form: treat
|
|
129
|
+
// as a literal (no DNS lookup needed, and we already verified it's public).
|
|
130
|
+
// Pin to the normalized dotted form so undici skips its own getaddrinfo.
|
|
131
|
+
if (normalizedIpv4) {
|
|
132
|
+
return { url: parsed, ip: normalizedIpv4, family: 4 };
|
|
133
|
+
}
|
|
134
|
+
if (isIpv6Literal) {
|
|
135
|
+
return { url: parsed, ip: raw, family: 6 };
|
|
136
|
+
}
|
|
137
|
+
if (!parsed.hostname) {
|
|
138
|
+
return { url: parsed, ip: null, family: null };
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const { address, family } = await dnsPromises.lookup(parsed.hostname);
|
|
142
|
+
const resolvedH = address.toLowerCase();
|
|
143
|
+
if (isPrivateAddress(resolvedH, resolvedH)) {
|
|
144
|
+
throw new PretextPdfError(errorCode, `${label}: hostname resolves to a private address`);
|
|
145
|
+
}
|
|
146
|
+
return { url: parsed, ip: address, family: family === 6 ? 6 : 4 };
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
if (err instanceof PretextPdfError)
|
|
150
|
+
throw err;
|
|
151
|
+
// DNS unavailable: fetch() will also fail, so rebinding is not possible.
|
|
152
|
+
// Without a resolved IP we cannot pin the connection; let undici resolve
|
|
153
|
+
// itself, which will then fail with the same DNS error.
|
|
154
|
+
return { url: parsed, ip: null, family: null };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Back-compat wrapper that drops the resolution result. Kept so existing
|
|
159
|
+
* tests and call sites that only need the validation side-effect still work.
|
|
160
|
+
*/
|
|
161
|
+
export async function assertSafeUrl(url, errorCode, label) {
|
|
162
|
+
await resolveAndValidateUrl(url, errorCode, label);
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=url-validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-validation.js","sourceRoot":"","sources":["../../../src/assets/security/url-validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG dimension parsing — extracted from src/assets.ts in v1.6.0 commit 10/16.
|
|
3
|
+
*
|
|
4
|
+
* Pure parsers — no I/O, no module-level state. Safe to import eagerly.
|
|
5
|
+
*/
|
|
6
|
+
import type { SvgElement } from '../../types.js';
|
|
7
|
+
export declare function parseSvgViewBox(svg: string): {
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
} | null;
|
|
11
|
+
export declare function parseSvgAttributes(svg: string): {
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
} | null;
|
|
15
|
+
export declare function resolveSvgDimensions(el: SvgElement, contentWidth: number): {
|
|
16
|
+
widthPt: number;
|
|
17
|
+
heightPt: number;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=dimensions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dimensions.d.ts","sourceRoot":"","sources":["../../../src/assets/svg/dimensions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAGhD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQrF;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQxF;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAiBhH"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { SVG_MAX_BYTES } from './sanitize.js';
|
|
2
|
+
export function parseSvgViewBox(svg) {
|
|
3
|
+
if (svg.length > SVG_MAX_BYTES)
|
|
4
|
+
return null;
|
|
5
|
+
const match = svg.match(/viewBox=["']([^"']+)["']/);
|
|
6
|
+
if (!match)
|
|
7
|
+
return null;
|
|
8
|
+
const parts = match[1].split(/[\s,]+/).map(Number);
|
|
9
|
+
const w = parts[2], h = parts[3];
|
|
10
|
+
if (!w || !h || !isFinite(w) || !isFinite(h) || w <= 0 || h <= 0)
|
|
11
|
+
return null;
|
|
12
|
+
return { width: w, height: h };
|
|
13
|
+
}
|
|
14
|
+
export function parseSvgAttributes(svg) {
|
|
15
|
+
if (svg.length > SVG_MAX_BYTES)
|
|
16
|
+
return null;
|
|
17
|
+
const wMatch = svg.match(/<svg[^>]*\swidth=["'](\d+(?:\.\d+)?)["']/);
|
|
18
|
+
const hMatch = svg.match(/<svg[^>]*\sheight=["'](\d+(?:\.\d+)?)["']/);
|
|
19
|
+
if (!wMatch || !hMatch)
|
|
20
|
+
return null;
|
|
21
|
+
const w = Number(wMatch[1]), h = Number(hMatch[1]);
|
|
22
|
+
if (!w || !h || w <= 0 || h <= 0)
|
|
23
|
+
return null;
|
|
24
|
+
return { width: w, height: h };
|
|
25
|
+
}
|
|
26
|
+
export function resolveSvgDimensions(el, contentWidth) {
|
|
27
|
+
const svgStr = el.svg ?? '';
|
|
28
|
+
const viewbox = parseSvgViewBox(svgStr) ?? parseSvgAttributes(svgStr);
|
|
29
|
+
const aspectRatio = viewbox ? viewbox.height / viewbox.width : null;
|
|
30
|
+
if (el.width !== undefined && el.height !== undefined) {
|
|
31
|
+
return { widthPt: el.width, heightPt: el.height };
|
|
32
|
+
}
|
|
33
|
+
if (el.width !== undefined) {
|
|
34
|
+
return { widthPt: el.width, heightPt: aspectRatio !== null ? el.width * aspectRatio : el.width };
|
|
35
|
+
}
|
|
36
|
+
if (el.height !== undefined) {
|
|
37
|
+
return { widthPt: aspectRatio !== null ? el.height / aspectRatio : el.height, heightPt: el.height };
|
|
38
|
+
}
|
|
39
|
+
const widthPt = contentWidth;
|
|
40
|
+
const heightPt = aspectRatio !== null ? widthPt * aspectRatio : 200;
|
|
41
|
+
return { widthPt, heightPt };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=dimensions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dimensions.js","sourceRoot":"","sources":["../../../src/assets/svg/dimensions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rasterize an SVG string to a PNG buffer at 2x scale.
|
|
3
|
+
* Pure compute — no pdf-lib interaction — so it is safe to run in parallel.
|
|
4
|
+
*/
|
|
5
|
+
export declare function rasterizeSvgToPng(svg: string, widthPt: number, heightPt: number): Promise<Buffer>;
|
|
6
|
+
//# sourceMappingURL=rasterize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rasterize.d.ts","sourceRoot":"","sources":["../../../src/assets/svg/rasterize.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BvG"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG rasterizer — extracted from src/assets.ts in v1.6.0 commit 11/16.
|
|
3
|
+
*
|
|
4
|
+
* Pure compute — no pdf-lib interaction — so it is safe to run in parallel.
|
|
5
|
+
*
|
|
6
|
+
* @napi-rs/canvas is loaded via dynamic import (optional peer dep). The
|
|
7
|
+
* lazy-load pattern is preserved so cold-start cost stays equivalent and
|
|
8
|
+
* users without canvas installed still see a precise error code.
|
|
9
|
+
*/
|
|
10
|
+
import { PretextPdfError } from '../../errors.js';
|
|
11
|
+
/**
|
|
12
|
+
* Rasterize an SVG string to a PNG buffer at 2x scale.
|
|
13
|
+
* Pure compute — no pdf-lib interaction — so it is safe to run in parallel.
|
|
14
|
+
*/
|
|
15
|
+
export async function rasterizeSvgToPng(svg, widthPt, heightPt) {
|
|
16
|
+
let canvasLib;
|
|
17
|
+
try {
|
|
18
|
+
canvasLib = await import('@napi-rs/canvas');
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new PretextPdfError('SVG_RENDER_FAILED', 'SVG rendering requires the optional dependency @napi-rs/canvas. Install it with: pnpm add @napi-rs/canvas');
|
|
22
|
+
}
|
|
23
|
+
const scale = 2;
|
|
24
|
+
const widthPx = Math.round(widthPt * scale);
|
|
25
|
+
const heightPx = Math.round(heightPt * scale);
|
|
26
|
+
try {
|
|
27
|
+
const canvas = canvasLib.createCanvas(widthPx, heightPx);
|
|
28
|
+
const ctx = canvas.getContext('2d');
|
|
29
|
+
const img = new canvasLib.Image();
|
|
30
|
+
img.src = Buffer.from(svg);
|
|
31
|
+
ctx.drawImage(img, 0, 0, widthPx, heightPx);
|
|
32
|
+
return canvas.toBuffer('image/png');
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
throw new PretextPdfError('SVG_RENDER_FAILED', `Failed to rasterize SVG: ${err instanceof Error ? err.message : String(err)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=rasterize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rasterize.js","sourceRoot":"","sources":["../../../src/assets/svg/rasterize.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,OAAe,EAAE,QAAgB;IACpF,IAAI,SAAc,CAAA;IAClB,IAAI,CAAC;QACH,SAAS,GAAG,MAAM,MAAM,CAAC,iBAA2B,CAAC,CAAA;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,eAAe,CACvB,mBAAmB,EACnB,2GAA2G,CAC5G,CAAA;IACH,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,CAAA;IACf,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,CAAA;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,CAAA;IAE7C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;QACxD,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QACnC,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,KAAK,EAAE,CAAA;QACjC,GAAG,CAAC,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAC1B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAA;QAC3C,OAAO,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;IACrC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,eAAe,CACvB,mBAAmB,EACnB,4BAA4B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC/E,CAAA;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG content resolver — extracted from src/assets.ts in v1.6.0 commit 10/16.
|
|
3
|
+
*
|
|
4
|
+
* Resolves an SvgElement's content string from either the inline `svg` field
|
|
5
|
+
* or a `src` file/URL. All loaded content is sanitized before return.
|
|
6
|
+
*
|
|
7
|
+
* Dynamic imports of `fs`/`path` are preserved so cold-start cost stays the
|
|
8
|
+
* same as the pre-split assets.ts.
|
|
9
|
+
*/
|
|
10
|
+
import type { SvgElement } from '../../types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Resolve SVG content string from either an inline `svg` field or a `src` file/URL.
|
|
13
|
+
* Throws PretextPdfError if neither is provided or the source cannot be loaded.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveSvgContent(el: SvgElement, allowedFileDirs?: string[]): Promise<string>;
|
|
16
|
+
//# sourceMappingURL=resolve-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-content.d.ts","sourceRoot":"","sources":["../../../src/assets/svg/resolve-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAOhD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,UAAU,EAAE,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BnG"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { PretextPdfError } from '../../errors.js';
|
|
2
|
+
import { redactPath } from '../util/redact-path.js';
|
|
3
|
+
import { assertPathAllowed } from '../security/path-allowlist.js';
|
|
4
|
+
import { fetchWithTimeout } from '../security/fetch.js';
|
|
5
|
+
import { sanitizeSvg } from './sanitize.js';
|
|
6
|
+
/**
|
|
7
|
+
* Resolve SVG content string from either an inline `svg` field or a `src` file/URL.
|
|
8
|
+
* Throws PretextPdfError if neither is provided or the source cannot be loaded.
|
|
9
|
+
*/
|
|
10
|
+
export async function resolveSvgContent(el, allowedFileDirs) {
|
|
11
|
+
if (el.svg)
|
|
12
|
+
return sanitizeSvg(el.svg);
|
|
13
|
+
if (!el.src) {
|
|
14
|
+
throw new PretextPdfError('SVG_LOAD_FAILED', "SvgElement requires either 'svg' (inline string) or 'src' (file path or https:// URL)");
|
|
15
|
+
}
|
|
16
|
+
if (el.src.startsWith('https://') || el.src.startsWith('http://')) {
|
|
17
|
+
// SSRF validation happens inside fetchWithTimeout — no need to pre-validate
|
|
18
|
+
let resp;
|
|
19
|
+
try {
|
|
20
|
+
resp = await fetchWithTimeout(el.src, 'SVG_LOAD_FAILED', 'SVG');
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
throw new PretextPdfError('SVG_LOAD_FAILED', `SVG URL fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
24
|
+
}
|
|
25
|
+
if (!resp.ok) {
|
|
26
|
+
throw new PretextPdfError('SVG_LOAD_FAILED', `SVG URL returned HTTP ${resp.status}`);
|
|
27
|
+
}
|
|
28
|
+
return sanitizeSvg(await resp.text());
|
|
29
|
+
}
|
|
30
|
+
const [fs, pathMod] = await Promise.all([import('fs'), import('path')]);
|
|
31
|
+
const filePath = pathMod.resolve(el.src);
|
|
32
|
+
assertPathAllowed(filePath, allowedFileDirs, 'SVG');
|
|
33
|
+
if (!fs.existsSync(filePath)) {
|
|
34
|
+
throw new PretextPdfError('SVG_LOAD_FAILED', `SVG file not found: "${redactPath(el.src)}"`);
|
|
35
|
+
}
|
|
36
|
+
return sanitizeSvg(fs.readFileSync(filePath, 'utf-8'));
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=resolve-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-content.js","sourceRoot":"","sources":["../../../src/assets/svg/resolve-content.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAA;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,EAAc,EAAE,eAA0B;IAChF,IAAI,EAAE,CAAC,GAAG;QAAE,OAAO,WAAW,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IAEtC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC;QACZ,MAAM,IAAI,eAAe,CAAC,iBAAiB,EAAE,uFAAuF,CAAC,CAAA;IACvI,CAAC;IAED,IAAI,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAClE,4EAA4E;QAC5E,IAAI,IAAc,CAAA;QAClB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,EAAE,KAAK,CAAC,CAAA;QACjE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,eAAe,CAAC,iBAAiB,EAAE,yBAAyB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAC3H,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,IAAI,eAAe,CAAC,iBAAiB,EAAE,yBAAyB,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;QACtF,CAAC;QACD,OAAO,WAAW,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;IACvC,CAAC;IAED,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;IACvE,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IACxC,iBAAiB,CAAC,QAAQ,EAAE,eAAe,EAAE,KAAK,CAAC,CAAA;IACnD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,eAAe,CAAC,iBAAiB,EAAE,wBAAwB,UAAU,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC7F,CAAC;IACD,OAAO,WAAW,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAA;AACxD,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG sanitizer — extracted from src/assets.ts in v1.6.0 commit 9/16.
|
|
3
|
+
*
|
|
4
|
+
* Strips dangerous content from SVG before rasterization:
|
|
5
|
+
* - <script> blocks
|
|
6
|
+
* - on* event handler attributes
|
|
7
|
+
* - <image>/<use> with file://, data:, or javascript: hrefs (local-file and injection vectors)
|
|
8
|
+
* - <foreignObject> (HTML escape hatch into the SVG content model)
|
|
9
|
+
* - <a> elements whose xlink:href / href use javascript:, vbscript:, or data:
|
|
10
|
+
* - CSS expression(...) inside <style> blocks (legacy IE XSS vector)
|
|
11
|
+
*
|
|
12
|
+
* v1.6.0 Phase 0a (commit 3/16) added the last three to harden against
|
|
13
|
+
* payloads that survived the previous regex chain.
|
|
14
|
+
*
|
|
15
|
+
* IMPORTANT: this symbol is also re-exported from `src/assets.ts` so the
|
|
16
|
+
* `dist/assets.js` consumers (test/svg-sanitizer.test.ts, the snapshot
|
|
17
|
+
* tripwire) keep working unchanged.
|
|
18
|
+
*/
|
|
19
|
+
/** Maximum SVG string length (5 MB) — prevents ReDoS on oversized inputs. */
|
|
20
|
+
export declare const SVG_MAX_BYTES: number;
|
|
21
|
+
export declare function sanitizeSvg(svg: string): string;
|
|
22
|
+
//# sourceMappingURL=sanitize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../../src/assets/svg/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,6EAA6E;AAC7E,eAAO,MAAM,aAAa,QAAkB,CAAA;AAE5C,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA6B/C"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG sanitizer — extracted from src/assets.ts in v1.6.0 commit 9/16.
|
|
3
|
+
*
|
|
4
|
+
* Strips dangerous content from SVG before rasterization:
|
|
5
|
+
* - <script> blocks
|
|
6
|
+
* - on* event handler attributes
|
|
7
|
+
* - <image>/<use> with file://, data:, or javascript: hrefs (local-file and injection vectors)
|
|
8
|
+
* - <foreignObject> (HTML escape hatch into the SVG content model)
|
|
9
|
+
* - <a> elements whose xlink:href / href use javascript:, vbscript:, or data:
|
|
10
|
+
* - CSS expression(...) inside <style> blocks (legacy IE XSS vector)
|
|
11
|
+
*
|
|
12
|
+
* v1.6.0 Phase 0a (commit 3/16) added the last three to harden against
|
|
13
|
+
* payloads that survived the previous regex chain.
|
|
14
|
+
*
|
|
15
|
+
* IMPORTANT: this symbol is also re-exported from `src/assets.ts` so the
|
|
16
|
+
* `dist/assets.js` consumers (test/svg-sanitizer.test.ts, the snapshot
|
|
17
|
+
* tripwire) keep working unchanged.
|
|
18
|
+
*/
|
|
19
|
+
/** Maximum SVG string length (5 MB) — prevents ReDoS on oversized inputs. */
|
|
20
|
+
export const SVG_MAX_BYTES = 5 * 1024 * 1024;
|
|
21
|
+
export function sanitizeSvg(svg) {
|
|
22
|
+
// Skip regex passes on oversized strings — canvas will reject them anyway
|
|
23
|
+
if (svg.length > SVG_MAX_BYTES)
|
|
24
|
+
return svg;
|
|
25
|
+
// Remove self-closing <script/> then paired <script>...</script> blocks
|
|
26
|
+
let s = svg.replace(/<script\b[^>]*\/>/gi, '');
|
|
27
|
+
s = s.replace(/<script[\s\S]*?<\/script>/gi, '');
|
|
28
|
+
// Remove event handler attributes (onload, onclick, onerror, etc.)
|
|
29
|
+
s = s.replace(/\bon\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]*)/gi, '');
|
|
30
|
+
// Remove <image> and <use> hrefs pointing to unsafe schemes
|
|
31
|
+
s = s.replace(/(<(?:image|use)\b[^>]*?)\s+(?:xlink:)?href\s*=\s*["'](?:file|data|javascript):[^"']*["']/gi, '$1');
|
|
32
|
+
// v1.6.0: strip <foreignObject> entirely — it's an HTML escape hatch and
|
|
33
|
+
// the only XML-in-SVG construct that can host arbitrary tags.
|
|
34
|
+
s = s.replace(/<foreignObject\b[^>]*\/>/gi, '');
|
|
35
|
+
s = s.replace(/<foreignObject[\s\S]*?<\/foreignObject>/gi, '');
|
|
36
|
+
// v1.6.0: strip dangerous hrefs from <a> (xlink:href or plain href).
|
|
37
|
+
// Drop only the attribute, not the whole <a>, so the surrounding text content
|
|
38
|
+
// (children of <a>) still renders.
|
|
39
|
+
s = s.replace(/\s+(?:xlink:)?href\s*=\s*["'](?:javascript|vbscript|data):[^"']*["']/gi, '');
|
|
40
|
+
// v1.6.0: strip CSS expression(...) inside <style> blocks. Replace just the
|
|
41
|
+
// expression call with an empty string so the surrounding stylesheet stays
|
|
42
|
+
// parseable.
|
|
43
|
+
s = s.replace(/expression\s*\([^)]*\)/gi, '');
|
|
44
|
+
return s;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=sanitize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../../src/assets/svg/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,6EAA6E;AAC7E,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAA;AAE5C,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,0EAA0E;IAC1E,IAAI,GAAG,CAAC,MAAM,GAAG,aAAa;QAAE,OAAO,GAAG,CAAA;IAC1C,wEAAwE;IACxE,IAAI,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAA;IAC9C,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,6BAA6B,EAAE,EAAE,CAAC,CAAA;IAChD,mEAAmE;IACnE,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,6CAA6C,EAAE,EAAE,CAAC,CAAA;IAChE,4DAA4D;IAC5D,CAAC,GAAG,CAAC,CAAC,OAAO,CACX,4FAA4F,EAC5F,IAAI,CACL,CAAA;IACD,yEAAyE;IACzE,8DAA8D;IAC9D,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,4BAA4B,EAAE,EAAE,CAAC,CAAA;IAC/C,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,2CAA2C,EAAE,EAAE,CAAC,CAAA;IAC9D,qEAAqE;IACrE,8EAA8E;IAC9E,mCAAmC;IACnC,CAAC,GAAG,CAAC,CAAC,OAAO,CACX,wEAAwE,EACxE,EAAE,CACH,CAAA;IACD,4EAA4E;IAC5E,2EAA2E;IAC3E,aAAa;IACb,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,0BAA0B,EAAE,EAAE,CAAC,CAAA;IAC7C,OAAO,CAAC,CAAA;AACV,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return just the filename from a path. Used in error messages to avoid
|
|
3
|
+
* leaking absolute directory structure to untrusted output sinks (logs,
|
|
4
|
+
* PretextPdfError.message strings).
|
|
5
|
+
*
|
|
6
|
+
* Behavior: take the last `/` or `\` separated segment. Returns the literal
|
|
7
|
+
* string `(file)` when the input has no useful trailing segment.
|
|
8
|
+
*
|
|
9
|
+
* Extracted from `src/assets.ts` in v1.6.0 commit 4/16 as part of the
|
|
10
|
+
* post-v1.5.2 assets.ts file-size sprint. Pure function — no module-level
|
|
11
|
+
* side effects.
|
|
12
|
+
*/
|
|
13
|
+
export declare function redactPath(src: string): string;
|
|
14
|
+
//# sourceMappingURL=redact-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact-path.d.ts","sourceRoot":"","sources":["../../../src/assets/util/redact-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return just the filename from a path. Used in error messages to avoid
|
|
3
|
+
* leaking absolute directory structure to untrusted output sinks (logs,
|
|
4
|
+
* PretextPdfError.message strings).
|
|
5
|
+
*
|
|
6
|
+
* Behavior: take the last `/` or `\` separated segment. Returns the literal
|
|
7
|
+
* string `(file)` when the input has no useful trailing segment.
|
|
8
|
+
*
|
|
9
|
+
* Extracted from `src/assets.ts` in v1.6.0 commit 4/16 as part of the
|
|
10
|
+
* post-v1.5.2 assets.ts file-size sprint. Pure function — no module-level
|
|
11
|
+
* side effects.
|
|
12
|
+
*/
|
|
13
|
+
export function redactPath(src) {
|
|
14
|
+
return src.replace(/\\/g, '/').split('/').pop() ?? '(file)';
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=redact-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact-path.js","sourceRoot":"","sources":["../../../src/assets/util/redact-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,QAAQ,CAAA;AAC7D,CAAC"}
|
package/dist/assets.d.ts
CHANGED
|
@@ -1,32 +1,15 @@
|
|
|
1
|
-
import { PDFDocument } from '@cantoo/pdf-lib';
|
|
2
|
-
import type { PdfDocument, Logger } from './types.js';
|
|
3
|
-
import type { ImageMap } from './types-internal.js';
|
|
4
|
-
import type { PluginDefinition } from './plugin-types.js';
|
|
5
1
|
/**
|
|
6
|
-
*
|
|
7
|
-
* No-op when allowedFileDirs is unset (backwards-compatible default).
|
|
8
|
-
*/
|
|
9
|
-
export declare function assertPathAllowed(resolvedPath: string, allowedDirs: string[] | undefined, label: string): void;
|
|
10
|
-
/**
|
|
11
|
-
* Validate a remote URL before fetching:
|
|
12
|
-
* - Rejects http:// (plaintext only)
|
|
13
|
-
* - Rejects private/internal IP ranges (SSRF prevention), including IPv4-mapped IPv6
|
|
14
|
-
* forms like [::ffff:127.0.0.1] which would otherwise bypass dotted-decimal regexes.
|
|
15
|
-
* Throws IMAGE_LOAD_FAILED or SVG_LOAD_FAILED on violations.
|
|
16
|
-
*/
|
|
17
|
-
export declare function assertSafeUrl(url: string, errorCode: 'IMAGE_LOAD_FAILED' | 'SVG_LOAD_FAILED', label: string): void;
|
|
18
|
-
/**
|
|
19
|
-
* Stage 2b: Load and embed all images into pdfDoc.
|
|
20
|
-
* Runs after loadFonts(), receives the same pdfDoc.
|
|
21
|
-
*
|
|
22
|
-
* Image keys are 'img-N' where N is the element's position in doc.content.
|
|
23
|
-
* This makes keys stable and avoids collisions from duplicate src paths.
|
|
2
|
+
* Back-compat shim — v1.6.0 commit 15/16.
|
|
24
3
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
4
|
+
* The assets module was split into src/assets/ during the v1.6.0 sprint
|
|
5
|
+
* (commits 4–15). Everything still flows through dist/assets.js so that
|
|
6
|
+
* internal consumers (fonts.ts, pipeline.ts, post-process.ts) and direct
|
|
7
|
+
* test imports (security-ssrf, security-ipv4-bypass, assets-dns-dedup,
|
|
8
|
+
* svg-sanitizer, public-api-surface) keep working unchanged.
|
|
27
9
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
10
|
+
* Public API contract is enforced by api-extractor (etc/pretext-pdf.api.md);
|
|
11
|
+
* the snapshot tripwire (test/assets-split-tripwire.test.ts) plus the
|
|
12
|
+
* six other G-gates pin the runtime contract.
|
|
30
13
|
*/
|
|
31
|
-
export
|
|
14
|
+
export * from './assets/index.js';
|
|
32
15
|
//# sourceMappingURL=assets.d.ts.map
|
package/dist/assets.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,cAAc,mBAAmB,CAAA"}
|