modern-pdf-lib 0.33.0 → 0.35.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/README.md +3 -3
- package/dist/browser.cjs +10 -3
- package/dist/browser.d.cts +5 -5
- package/dist/browser.d.mts +5 -5
- package/dist/browser.mjs +4 -4
- package/dist/{compressionAnalysis-0m5jJG1_.d.mts → compressionAnalysis-4EW8MJBa.d.mts} +2 -2
- package/dist/{compressionAnalysis-0m5jJG1_.d.mts.map → compressionAnalysis-4EW8MJBa.d.mts.map} +1 -1
- package/dist/{compressionAnalysis-CSBcmi0_.d.cts → compressionAnalysis-Cz3yWa-O.d.cts} +2 -2
- package/dist/{compressionAnalysis-CSBcmi0_.d.cts.map → compressionAnalysis-Cz3yWa-O.d.cts.map} +1 -1
- package/dist/create.cjs +2 -2
- package/dist/create.d.cts +2 -2
- package/dist/create.d.mts +2 -2
- package/dist/create.mjs +2 -2
- package/dist/{fontEmbed-Bzv89wgp.d.mts → fontEmbed-CTTeV1mZ.d.mts} +2 -2
- package/dist/{fontEmbed-Bzv89wgp.d.mts.map → fontEmbed-CTTeV1mZ.d.mts.map} +1 -1
- package/dist/{fontEmbed-Dwl1XeEA.d.cts → fontEmbed-DxvWoqOt.d.cts} +2 -2
- package/dist/{fontEmbed-Dwl1XeEA.d.cts.map → fontEmbed-DxvWoqOt.d.cts.map} +1 -1
- package/dist/{index-BqYQ9lsY.d.cts → index-CIdDdXlK.d.cts} +318 -45
- package/dist/index-CIdDdXlK.d.cts.map +1 -0
- package/dist/{index-BJtSRrMf.d.mts → index-CwgepM_G.d.mts} +318 -45
- package/dist/index-CwgepM_G.d.mts.map +1 -0
- package/dist/index.cjs +10 -3
- package/dist/index.d.cts +5 -5
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +4 -4
- package/dist/{layout-xOcesNCh.mjs → layout-BZ9InKTl.mjs} +2 -2
- package/dist/{layout-BYXpcYtC.cjs → layout-DvciSXsr.cjs} +2 -2
- package/dist/parse.cjs +1 -1
- package/dist/parse.d.cts +2 -2
- package/dist/parse.d.mts +2 -2
- package/dist/parse.mjs +1 -1
- package/dist/{pdfDocument-B8tQcZqj.d.cts → pdfDocument-BX5o5hI7.d.cts} +13 -1
- package/dist/{pdfDocument-B-8iPl_j.d.mts.map → pdfDocument-BX5o5hI7.d.cts.map} +1 -1
- package/dist/{pdfDocument-B-8iPl_j.d.mts → pdfDocument-C7TItmPQ.d.mts} +13 -1
- package/dist/{pdfDocument-B8tQcZqj.d.cts.map → pdfDocument-C7TItmPQ.d.mts.map} +1 -1
- package/dist/{pdfDocument-Bh4ebDqE.cjs → pdfDocument-D5qz7duP.cjs} +376 -21
- package/dist/{pdfDocument-Cgkkk8F8.mjs → pdfDocument-Q0wrwKEZ.mjs} +347 -22
- package/dist/{src-zeRl33xQ.mjs → src-BlUlmYsU.mjs} +1300 -25
- package/dist/{src-BdVnpRoy.cjs → src-C-mBizNU.cjs} +1327 -22
- package/package.json +1 -1
- package/dist/index-BJtSRrMf.d.mts.map +0 -1
- package/dist/index-BqYQ9lsY.d.cts.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_pdfDocument = require("./pdfDocument-
|
|
1
|
+
const require_pdfDocument = require("./pdfDocument-D5qz7duP.cjs");
|
|
2
2
|
const require_pdfObjects = require("./pdfObjects-BcPlSI0a.cjs");
|
|
3
3
|
const require_streamDecode = require("./streamDecode-kQ-REV7v.cjs");
|
|
4
4
|
const require_pdfForm = require("./pdfForm-BVS_do95.cjs");
|
|
@@ -277,7 +277,7 @@ function compressStream(stream, level) {
|
|
|
277
277
|
* @returns The incremental save result.
|
|
278
278
|
*/
|
|
279
279
|
async function saveDocumentIncremental(originalBytes, doc, options) {
|
|
280
|
-
const { buildDocumentStructure } = await Promise.resolve().then(() => require("./pdfDocument-
|
|
280
|
+
const { buildDocumentStructure } = await Promise.resolve().then(() => require("./pdfDocument-D5qz7duP.cjs")).then((n) => n.pdfCatalog_exports);
|
|
281
281
|
const registry = doc.getRegistry();
|
|
282
282
|
const structure = buildDocumentStructure(doc.getInternalPages().map((p) => p.finalize()), {
|
|
283
283
|
producer: doc.getProducer(),
|
|
@@ -4280,7 +4280,7 @@ function findChangedObjects(original, modified) {
|
|
|
4280
4280
|
continue;
|
|
4281
4281
|
}
|
|
4282
4282
|
if (computeObjectHash(origObj.content) !== computeObjectHash(modObj.content)) {
|
|
4283
|
-
if (!bytesEqual(origObj.content, modObj.content)) changedObjNums.push(objNum);
|
|
4283
|
+
if (!bytesEqual$1(origObj.content, modObj.content)) changedObjNums.push(objNum);
|
|
4284
4284
|
}
|
|
4285
4285
|
}
|
|
4286
4286
|
return changedObjNums.sort((a, b) => a - b);
|
|
@@ -4288,7 +4288,7 @@ function findChangedObjects(original, modified) {
|
|
|
4288
4288
|
/**
|
|
4289
4289
|
* Compare two Uint8Arrays for equality.
|
|
4290
4290
|
*/
|
|
4291
|
-
function bytesEqual(a, b) {
|
|
4291
|
+
function bytesEqual$1(a, b) {
|
|
4292
4292
|
if (a.length !== b.length) return false;
|
|
4293
4293
|
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
4294
4294
|
return true;
|
|
@@ -4326,7 +4326,7 @@ function optimizeIncrementalSave(originalPdf, changes) {
|
|
|
4326
4326
|
const changeHash = computeObjectHash(change.newContent);
|
|
4327
4327
|
const origObj = originalObjs.get(change.objectNumber);
|
|
4328
4328
|
if (origObj) {
|
|
4329
|
-
if (computeObjectHash(origObj.content) === changeHash && bytesEqual(origObj.content, change.newContent)) continue;
|
|
4329
|
+
if (computeObjectHash(origObj.content) === changeHash && bytesEqual$1(origObj.content, change.newContent)) continue;
|
|
4330
4330
|
}
|
|
4331
4331
|
const dedupeKey = `${change.objectNumber}:${change.generationNumber}:${changeHash}`;
|
|
4332
4332
|
if (seenHashes.has(dedupeKey)) continue;
|
|
@@ -24457,7 +24457,7 @@ function extractCrlUrls(cert) {
|
|
|
24457
24457
|
/**
|
|
24458
24458
|
* Extract the DER-encoded subject Name from a certificate.
|
|
24459
24459
|
*/
|
|
24460
|
-
function extractSubjectDer$
|
|
24460
|
+
function extractSubjectDer$2(certDer) {
|
|
24461
24461
|
const tbsCert = require_pdfDocument.parseDerTlv(certDer, 0).children[0];
|
|
24462
24462
|
let idx = 0;
|
|
24463
24463
|
if (tbsCert.children[0].tag === 160) idx = 1;
|
|
@@ -24468,7 +24468,7 @@ function extractSubjectDer$1(certDer) {
|
|
|
24468
24468
|
/**
|
|
24469
24469
|
* Extract the DER-encoded issuer Name from a certificate.
|
|
24470
24470
|
*/
|
|
24471
|
-
function extractIssuerDer$
|
|
24471
|
+
function extractIssuerDer$2(certDer) {
|
|
24472
24472
|
const tbsCert = require_pdfDocument.parseDerTlv(certDer, 0).children[0];
|
|
24473
24473
|
let idx = 0;
|
|
24474
24474
|
if (tbsCert.children[0].tag === 160) idx = 1;
|
|
@@ -24590,7 +24590,7 @@ var TrustStore = class {
|
|
|
24590
24590
|
*/
|
|
24591
24591
|
async isTrusted(cert) {
|
|
24592
24592
|
await this._ready;
|
|
24593
|
-
const key = await computeSubjectKey(extractSubjectDer$
|
|
24593
|
+
const key = await computeSubjectKey(extractSubjectDer$2(cert));
|
|
24594
24594
|
const entries = this._certs.get(key);
|
|
24595
24595
|
if (!entries) return false;
|
|
24596
24596
|
return entries.some((e) => compareBytes$1(e.der, cert));
|
|
@@ -24607,7 +24607,7 @@ var TrustStore = class {
|
|
|
24607
24607
|
*/
|
|
24608
24608
|
async findIssuer(cert) {
|
|
24609
24609
|
await this._ready;
|
|
24610
|
-
const key = await computeSubjectKey(extractIssuerDer$
|
|
24610
|
+
const key = await computeSubjectKey(extractIssuerDer$2(cert));
|
|
24611
24611
|
const entries = this._certs.get(key);
|
|
24612
24612
|
if (!entries || entries.length === 0) return null;
|
|
24613
24613
|
return entries[0].der;
|
|
@@ -24641,7 +24641,7 @@ var TrustStore = class {
|
|
|
24641
24641
|
* Add a single certificate to the store.
|
|
24642
24642
|
*/
|
|
24643
24643
|
async _addOne(cert) {
|
|
24644
|
-
const key = await computeSubjectKey(extractSubjectDer$
|
|
24644
|
+
const key = await computeSubjectKey(extractSubjectDer$2(cert));
|
|
24645
24645
|
const entry = {
|
|
24646
24646
|
der: cert,
|
|
24647
24647
|
serialBytes: extractSerialBytes(cert),
|
|
@@ -26729,7 +26729,7 @@ function parseAsn1Time(node) {
|
|
|
26729
26729
|
/**
|
|
26730
26730
|
* Extract the subject raw DER bytes from a certificate.
|
|
26731
26731
|
*/
|
|
26732
|
-
function extractSubjectDer(certDer) {
|
|
26732
|
+
function extractSubjectDer$1(certDer) {
|
|
26733
26733
|
const tbsCert = require_pdfDocument.parseDerTlv(certDer, 0).children[0];
|
|
26734
26734
|
let idx = 0;
|
|
26735
26735
|
if (tbsCert.children[0].tag === 160) idx = 1;
|
|
@@ -26740,7 +26740,7 @@ function extractSubjectDer(certDer) {
|
|
|
26740
26740
|
/**
|
|
26741
26741
|
* Extract the issuer raw DER bytes from a certificate.
|
|
26742
26742
|
*/
|
|
26743
|
-
function extractIssuerDer(certDer) {
|
|
26743
|
+
function extractIssuerDer$1(certDer) {
|
|
26744
26744
|
const tbsCert = require_pdfDocument.parseDerTlv(certDer, 0).children[0];
|
|
26745
26745
|
let idx = 0;
|
|
26746
26746
|
if (tbsCert.children[0].tag === 160) idx = 1;
|
|
@@ -26794,8 +26794,8 @@ function isCA(certDer) {
|
|
|
26794
26794
|
* Check if a certificate is self-signed (subject === issuer).
|
|
26795
26795
|
*/
|
|
26796
26796
|
function isSelfSigned(certDer) {
|
|
26797
|
-
const subjectDer = extractSubjectDer(certDer);
|
|
26798
|
-
const issuerDer = extractIssuerDer(certDer);
|
|
26797
|
+
const subjectDer = extractSubjectDer$1(certDer);
|
|
26798
|
+
const issuerDer = extractIssuerDer$1(certDer);
|
|
26799
26799
|
if (subjectDer.length !== issuerDer.length) return false;
|
|
26800
26800
|
for (let i = 0; i < subjectDer.length; i++) if (subjectDer[i] !== issuerDer[i]) return false;
|
|
26801
26801
|
return true;
|
|
@@ -26993,9 +26993,9 @@ function buildCertificateChain(leaf, intermediates) {
|
|
|
26993
26993
|
chain,
|
|
26994
26994
|
complete: true
|
|
26995
26995
|
};
|
|
26996
|
-
const currentIssuerDer = extractIssuerDer(current);
|
|
26996
|
+
const currentIssuerDer = extractIssuerDer$1(current);
|
|
26997
26997
|
let foundIdx = -1;
|
|
26998
|
-
for (let i = 0; i < available.length; i++) if (namesEqual(currentIssuerDer, extractSubjectDer(available[i]))) {
|
|
26998
|
+
for (let i = 0; i < available.length; i++) if (namesEqual(currentIssuerDer, extractSubjectDer$1(available[i]))) {
|
|
26999
26999
|
foundIdx = i;
|
|
27000
27000
|
break;
|
|
27001
27001
|
}
|
|
@@ -27083,8 +27083,8 @@ async function validateCertificateChain(chain, options) {
|
|
|
27083
27083
|
if (!signatureValid) certErrors.push("Root certificate self-signature verification failed");
|
|
27084
27084
|
} else {
|
|
27085
27085
|
let foundTrusted = false;
|
|
27086
|
-
const certIssuerDer = extractIssuerDer(certDer);
|
|
27087
|
-
for (const trusted of trustedCerts) if (namesEqual(certIssuerDer, extractSubjectDer(trusted))) {
|
|
27086
|
+
const certIssuerDer = extractIssuerDer$1(certDer);
|
|
27087
|
+
for (const trusted of trustedCerts) if (namesEqual(certIssuerDer, extractSubjectDer$1(trusted))) {
|
|
27088
27088
|
signatureValid = await verifyCertSignature(certDer, trusted);
|
|
27089
27089
|
foundTrusted = true;
|
|
27090
27090
|
break;
|
|
@@ -27841,6 +27841,1281 @@ function extractNodeBytesFromParent(fullBuffer, parent, child) {
|
|
|
27841
27841
|
return fullBuffer.subarray(start, start + child.totalLength);
|
|
27842
27842
|
}
|
|
27843
27843
|
//#endregion
|
|
27844
|
+
//#region src/signature/certPathBuilder.ts
|
|
27845
|
+
/**
|
|
27846
|
+
* @module signature/certPathBuilder
|
|
27847
|
+
*
|
|
27848
|
+
* Certification-path (trust-chain) construction per RFC 5280 §6.1.
|
|
27849
|
+
*
|
|
27850
|
+
* Given a leaf certificate, a pool of intermediate CA certificates, and a
|
|
27851
|
+
* set of trust anchors, {@link buildCertPath} assembles the ordered chain
|
|
27852
|
+
*
|
|
27853
|
+
* ```
|
|
27854
|
+
* leaf -> intermediate -> ... -> (issuer that is a trust anchor)
|
|
27855
|
+
* ```
|
|
27856
|
+
*
|
|
27857
|
+
* by matching, at each step, the *current* certificate's **issuer** Name to
|
|
27858
|
+
* a candidate certificate's **subject** Name (RFC 5280 §4.1.2.4 / §4.1.2.6).
|
|
27859
|
+
* When both certificates additionally carry the relevant key-identifier
|
|
27860
|
+
* extensions, the candidate's **Subject Key Identifier** (SKI, OID
|
|
27861
|
+
* `2.5.29.14`, RFC 5280 §4.2.1.2) must equal the current certificate's
|
|
27862
|
+
* **Authority Key Identifier** (AKI `keyIdentifier`, OID `2.5.29.35`,
|
|
27863
|
+
* RFC 5280 §4.2.1.1) for the link to be accepted. If only one side carries
|
|
27864
|
+
* the identifier, name matching alone is used (RFC 5280 §6.1.4 treats key
|
|
27865
|
+
* identifiers as a hint, not a hard requirement).
|
|
27866
|
+
*
|
|
27867
|
+
* Building stops when:
|
|
27868
|
+
* - the current certificate (or a matched issuer) is a member of `anchors`
|
|
27869
|
+
* → `complete: true`, `anchor` set, and `path` excludes the anchor; or
|
|
27870
|
+
* - no issuer can be found among `intermediates`/`anchors`
|
|
27871
|
+
* → `complete: false` (partial path returned).
|
|
27872
|
+
*
|
|
27873
|
+
* Loop protection: a certificate already present in the constructed path is
|
|
27874
|
+
* never revisited (RFC 5280 §6.1 prohibits a certificate appearing more than
|
|
27875
|
+
* once in a certification path).
|
|
27876
|
+
*
|
|
27877
|
+
* Name matching uses byte-for-byte equality of the DER-encoded `Name`. This
|
|
27878
|
+
* is the exact-match comparison permitted by RFC 5280 §7.1 when both names
|
|
27879
|
+
* are produced by conforming encoders (as is the case for fixtures generated
|
|
27880
|
+
* here and for the overwhelming majority of real-world CAs).
|
|
27881
|
+
*
|
|
27882
|
+
* References:
|
|
27883
|
+
* - RFC 5280 §4.1.2.4 (issuer), §4.1.2.6 (subject)
|
|
27884
|
+
* - RFC 5280 §4.2.1.1 (Authority Key Identifier, OID 2.5.29.35)
|
|
27885
|
+
* - RFC 5280 §4.2.1.2 (Subject Key Identifier, OID 2.5.29.14)
|
|
27886
|
+
* - RFC 5280 §6.1 (Certification Path Validation — path construction)
|
|
27887
|
+
*
|
|
27888
|
+
* @packageDocumentation
|
|
27889
|
+
*/
|
|
27890
|
+
/** Subject Key Identifier — RFC 5280 §4.2.1.2. */
|
|
27891
|
+
const OID_SUBJECT_KEY_IDENTIFIER = "2.5.29.14";
|
|
27892
|
+
/** Authority Key Identifier — RFC 5280 §4.2.1.1. */
|
|
27893
|
+
const OID_AUTHORITY_KEY_IDENTIFIER = "2.5.29.35";
|
|
27894
|
+
/**
|
|
27895
|
+
* Reconstruct the absolute byte range of a child node within the original
|
|
27896
|
+
* certificate buffer, returning the full TLV (header + value) as a subarray
|
|
27897
|
+
* that shares the cert's underlying ArrayBuffer.
|
|
27898
|
+
*
|
|
27899
|
+
* `parentNode.data` is a subarray of `certDer`; `child.offset` is the child's
|
|
27900
|
+
* offset within that parent value; `child.totalLength` covers header + value.
|
|
27901
|
+
*/
|
|
27902
|
+
function sliceChild(certDer, parentNode, child) {
|
|
27903
|
+
const start = child.offset + (parentNode.data.byteOffset - certDer.byteOffset);
|
|
27904
|
+
return certDer.subarray(start, start + child.totalLength);
|
|
27905
|
+
}
|
|
27906
|
+
/**
|
|
27907
|
+
* Resolve the TBSCertificate node and the index offset that accounts for the
|
|
27908
|
+
* optional `version [0] EXPLICIT` field.
|
|
27909
|
+
*
|
|
27910
|
+
* TBSCertificate ::= SEQUENCE {
|
|
27911
|
+
* version [0] EXPLICIT INTEGER DEFAULT v1, -- tag 0xA0 when present
|
|
27912
|
+
* serialNumber INTEGER,
|
|
27913
|
+
* signature AlgorithmIdentifier,
|
|
27914
|
+
* issuer Name,
|
|
27915
|
+
* validity Validity,
|
|
27916
|
+
* subject Name,
|
|
27917
|
+
* subjectPublicKeyInfo SubjectPublicKeyInfo,
|
|
27918
|
+
* ... extensions [3] EXPLICIT ...
|
|
27919
|
+
* }
|
|
27920
|
+
*/
|
|
27921
|
+
function tbsContext(certDer) {
|
|
27922
|
+
const tbs = require_pdfDocument.parseDerTlv(certDer, 0).children[0];
|
|
27923
|
+
if (!tbs) throw new Error("Malformed certificate: missing TBSCertificate");
|
|
27924
|
+
const first = tbs.children[0];
|
|
27925
|
+
return {
|
|
27926
|
+
tbs,
|
|
27927
|
+
idx: first && first.tag === 160 ? 1 : 0
|
|
27928
|
+
};
|
|
27929
|
+
}
|
|
27930
|
+
/** Extract the raw DER bytes of the issuer Name (RFC 5280 §4.1.2.4). */
|
|
27931
|
+
function extractIssuerDer(certDer) {
|
|
27932
|
+
const { tbs, idx } = tbsContext(certDer);
|
|
27933
|
+
const issuerNode = tbs.children[idx + 2];
|
|
27934
|
+
if (!issuerNode) throw new Error("Malformed certificate: missing issuer");
|
|
27935
|
+
return sliceChild(certDer, tbs, issuerNode);
|
|
27936
|
+
}
|
|
27937
|
+
/** Extract the raw DER bytes of the subject Name (RFC 5280 §4.1.2.6). */
|
|
27938
|
+
function extractSubjectDer(certDer) {
|
|
27939
|
+
const { tbs, idx } = tbsContext(certDer);
|
|
27940
|
+
const subjectNode = tbs.children[idx + 4];
|
|
27941
|
+
if (!subjectNode) throw new Error("Malformed certificate: missing subject");
|
|
27942
|
+
return sliceChild(certDer, tbs, subjectNode);
|
|
27943
|
+
}
|
|
27944
|
+
/**
|
|
27945
|
+
* Extract the list of Extension nodes (each a SEQUENCE) from a certificate,
|
|
27946
|
+
* or an empty array when no extensions are present.
|
|
27947
|
+
*
|
|
27948
|
+
* extensions [3] EXPLICIT SEQUENCE OF Extension
|
|
27949
|
+
* Extension ::= SEQUENCE { extnID OID, critical BOOLEAN DEFAULT FALSE, extnValue OCTET STRING }
|
|
27950
|
+
*/
|
|
27951
|
+
function extractExtensionNodes(certDer) {
|
|
27952
|
+
try {
|
|
27953
|
+
const { tbs } = tbsContext(certDer);
|
|
27954
|
+
for (const child of tbs.children) if (child.tag === 163 && child.children.length > 0) return child.children[0].children;
|
|
27955
|
+
} catch {}
|
|
27956
|
+
return [];
|
|
27957
|
+
}
|
|
27958
|
+
/**
|
|
27959
|
+
* Locate the `extnValue` OCTET STRING contents for a given extension OID.
|
|
27960
|
+
* Returns the raw bytes inside the OCTET STRING (the DER of the extension's
|
|
27961
|
+
* value), or `undefined` if the extension is absent.
|
|
27962
|
+
*/
|
|
27963
|
+
function findExtensionValue(certDer, oid) {
|
|
27964
|
+
for (const ext of extractExtensionNodes(certDer)) {
|
|
27965
|
+
const oidNode = ext.children[0];
|
|
27966
|
+
if (!oidNode) continue;
|
|
27967
|
+
if (require_pdfDocument.decodeOidBytes(oidNode.data) !== oid) continue;
|
|
27968
|
+
const valueNode = ext.children[ext.children.length - 1];
|
|
27969
|
+
if (!valueNode) return void 0;
|
|
27970
|
+
return valueNode.data;
|
|
27971
|
+
}
|
|
27972
|
+
}
|
|
27973
|
+
/**
|
|
27974
|
+
* Extract the Subject Key Identifier (key bytes) — RFC 5280 §4.2.1.2.
|
|
27975
|
+
*
|
|
27976
|
+
* SubjectKeyIdentifier ::= KeyIdentifier
|
|
27977
|
+
* KeyIdentifier ::= OCTET STRING
|
|
27978
|
+
*
|
|
27979
|
+
* extnValue (an OCTET STRING) wraps the KeyIdentifier OCTET STRING, so we
|
|
27980
|
+
* parse one inner OCTET STRING to obtain the raw identifier bytes.
|
|
27981
|
+
*/
|
|
27982
|
+
function extractSki(certDer) {
|
|
27983
|
+
const extnValue = findExtensionValue(certDer, OID_SUBJECT_KEY_IDENTIFIER);
|
|
27984
|
+
if (!extnValue) return void 0;
|
|
27985
|
+
try {
|
|
27986
|
+
const inner = require_pdfDocument.parseDerTlv(extnValue, 0);
|
|
27987
|
+
if (inner.tag !== 4) return void 0;
|
|
27988
|
+
return inner.data;
|
|
27989
|
+
} catch {
|
|
27990
|
+
return;
|
|
27991
|
+
}
|
|
27992
|
+
}
|
|
27993
|
+
/**
|
|
27994
|
+
* Extract the Authority Key Identifier `keyIdentifier` — RFC 5280 §4.2.1.1.
|
|
27995
|
+
*
|
|
27996
|
+
* AuthorityKeyIdentifier ::= SEQUENCE {
|
|
27997
|
+
* keyIdentifier [0] KeyIdentifier OPTIONAL, -- IMPLICIT OCTET STRING, tag 0x80
|
|
27998
|
+
* authorityCertIssuer [1] GeneralNames OPTIONAL,
|
|
27999
|
+
* authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
|
|
28000
|
+
*
|
|
28001
|
+
* extnValue (an OCTET STRING) wraps the SEQUENCE. We return the bytes of the
|
|
28002
|
+
* `[0]` IMPLICIT field (context tag 0x80), or `undefined` if absent.
|
|
28003
|
+
*/
|
|
28004
|
+
function extractAkiKeyId(certDer) {
|
|
28005
|
+
const extnValue = findExtensionValue(certDer, OID_AUTHORITY_KEY_IDENTIFIER);
|
|
28006
|
+
if (!extnValue) return void 0;
|
|
28007
|
+
try {
|
|
28008
|
+
const seq = require_pdfDocument.parseDerTlv(extnValue, 0);
|
|
28009
|
+
if (seq.tag !== 48) return void 0;
|
|
28010
|
+
for (const field of seq.children) if (field.tag === 128) return field.data;
|
|
28011
|
+
} catch {
|
|
28012
|
+
return;
|
|
28013
|
+
}
|
|
28014
|
+
}
|
|
28015
|
+
/** Byte-for-byte equality of two arrays. */
|
|
28016
|
+
function bytesEqual(a, b) {
|
|
28017
|
+
if (a.length !== b.length) return false;
|
|
28018
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
28019
|
+
return true;
|
|
28020
|
+
}
|
|
28021
|
+
/**
|
|
28022
|
+
* Determine whether `candidate` could be the issuer of `cert`:
|
|
28023
|
+
* the candidate's subject DN must equal the cert's issuer DN, and — only when
|
|
28024
|
+
* BOTH key identifiers are present — the candidate's SKI must equal the cert's
|
|
28025
|
+
* AKI keyIdentifier (RFC 5280 §6.1.4 / §4.2.1.1).
|
|
28026
|
+
*/
|
|
28027
|
+
function isIssuerOf(cert, candidate) {
|
|
28028
|
+
if (!bytesEqual(extractIssuerDer(cert), extractSubjectDer(candidate))) return false;
|
|
28029
|
+
const aki = extractAkiKeyId(cert);
|
|
28030
|
+
const ski = extractSki(candidate);
|
|
28031
|
+
if (aki && ski) return bytesEqual(aki, ski);
|
|
28032
|
+
return true;
|
|
28033
|
+
}
|
|
28034
|
+
/**
|
|
28035
|
+
* Build an ordered certification path from a leaf certificate to a trust
|
|
28036
|
+
* anchor per RFC 5280 §6.1.
|
|
28037
|
+
*
|
|
28038
|
+
* @param leafCertDer DER-encoded end-entity (leaf) certificate.
|
|
28039
|
+
* @param intermediates DER-encoded intermediate CA certificates (any order).
|
|
28040
|
+
* @param anchors DER-encoded trust anchors (root or directly-trusted CAs).
|
|
28041
|
+
* @returns {@link CertPathResult} — `path` is leaf-first and
|
|
28042
|
+
* excludes the anchor; `anchor` is reported separately
|
|
28043
|
+
* when the path is `complete`.
|
|
28044
|
+
*
|
|
28045
|
+
* @example
|
|
28046
|
+
* ```ts
|
|
28047
|
+
* const { path, complete, anchor } = buildCertPath(leaf, [intermediate], [root]);
|
|
28048
|
+
* // path = [leaf, intermediate], complete = true, anchor = root
|
|
28049
|
+
* ```
|
|
28050
|
+
*/
|
|
28051
|
+
function buildCertPath(leafCertDer, intermediates, anchors) {
|
|
28052
|
+
const leafAnchor = anchors.find((a) => bytesEqual(a, leafCertDer));
|
|
28053
|
+
if (leafAnchor) return {
|
|
28054
|
+
path: [leafCertDer],
|
|
28055
|
+
complete: true,
|
|
28056
|
+
anchor: leafAnchor
|
|
28057
|
+
};
|
|
28058
|
+
const path = [leafCertDer];
|
|
28059
|
+
let current = leafCertDer;
|
|
28060
|
+
const maxDepth = intermediates.length + anchors.length + 2;
|
|
28061
|
+
for (let depth = 0; depth < maxDepth; depth++) {
|
|
28062
|
+
const anchor = anchors.find((a) => isIssuerOf(current, a));
|
|
28063
|
+
if (anchor) return {
|
|
28064
|
+
path,
|
|
28065
|
+
complete: true,
|
|
28066
|
+
anchor
|
|
28067
|
+
};
|
|
28068
|
+
const next = intermediates.find((c) => isIssuerOf(current, c) && !path.some((p) => bytesEqual(p, c)));
|
|
28069
|
+
if (!next) return {
|
|
28070
|
+
path,
|
|
28071
|
+
complete: false,
|
|
28072
|
+
anchor: void 0
|
|
28073
|
+
};
|
|
28074
|
+
path.push(next);
|
|
28075
|
+
current = next;
|
|
28076
|
+
}
|
|
28077
|
+
return {
|
|
28078
|
+
path,
|
|
28079
|
+
complete: false,
|
|
28080
|
+
anchor: void 0
|
|
28081
|
+
};
|
|
28082
|
+
}
|
|
28083
|
+
//#endregion
|
|
28084
|
+
//#region src/security/threatScanner.ts
|
|
28085
|
+
/** Numeric rank for severities so we can take a maximum. */
|
|
28086
|
+
const SEVERITY_RANK = {
|
|
28087
|
+
low: 1,
|
|
28088
|
+
medium: 2,
|
|
28089
|
+
high: 3
|
|
28090
|
+
};
|
|
28091
|
+
/** Reverse lookup from rank back to severity. */
|
|
28092
|
+
const RANK_SEVERITY = [
|
|
28093
|
+
"low",
|
|
28094
|
+
"medium",
|
|
28095
|
+
"high"
|
|
28096
|
+
];
|
|
28097
|
+
/**
|
|
28098
|
+
* File-name extensions that denote directly executable / script payloads.
|
|
28099
|
+
* An attachment ending in one of these is treated as high severity because a
|
|
28100
|
+
* viewer's "open attachment" (or a `/Launch` action targeting it) can run it.
|
|
28101
|
+
*/
|
|
28102
|
+
const EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
28103
|
+
"exe",
|
|
28104
|
+
"com",
|
|
28105
|
+
"scr",
|
|
28106
|
+
"pif",
|
|
28107
|
+
"cpl",
|
|
28108
|
+
"msi",
|
|
28109
|
+
"msp",
|
|
28110
|
+
"mst",
|
|
28111
|
+
"dll",
|
|
28112
|
+
"sys",
|
|
28113
|
+
"drv",
|
|
28114
|
+
"ocx",
|
|
28115
|
+
"bat",
|
|
28116
|
+
"cmd",
|
|
28117
|
+
"ps1",
|
|
28118
|
+
"psm1",
|
|
28119
|
+
"vbs",
|
|
28120
|
+
"vbe",
|
|
28121
|
+
"vb",
|
|
28122
|
+
"wsf",
|
|
28123
|
+
"wsh",
|
|
28124
|
+
"js",
|
|
28125
|
+
"jse",
|
|
28126
|
+
"jar",
|
|
28127
|
+
"class",
|
|
28128
|
+
"sh",
|
|
28129
|
+
"bash",
|
|
28130
|
+
"zsh",
|
|
28131
|
+
"ksh",
|
|
28132
|
+
"csh",
|
|
28133
|
+
"command",
|
|
28134
|
+
"app",
|
|
28135
|
+
"dmg",
|
|
28136
|
+
"pkg",
|
|
28137
|
+
"lnk",
|
|
28138
|
+
"url",
|
|
28139
|
+
"reg",
|
|
28140
|
+
"hta",
|
|
28141
|
+
"gadget",
|
|
28142
|
+
"inf",
|
|
28143
|
+
"scf",
|
|
28144
|
+
"apk",
|
|
28145
|
+
"deb",
|
|
28146
|
+
"rpm",
|
|
28147
|
+
"run",
|
|
28148
|
+
"bin",
|
|
28149
|
+
"elf",
|
|
28150
|
+
"so",
|
|
28151
|
+
"dylib",
|
|
28152
|
+
"py",
|
|
28153
|
+
"pyc",
|
|
28154
|
+
"pyw",
|
|
28155
|
+
"rb",
|
|
28156
|
+
"pl",
|
|
28157
|
+
"php"
|
|
28158
|
+
]);
|
|
28159
|
+
/** Read a `PdfName` value (without the leading `/`) from a dict entry. */
|
|
28160
|
+
function nameValue$1(obj) {
|
|
28161
|
+
if (obj !== void 0 && obj.kind === "name") {
|
|
28162
|
+
const v = obj.value;
|
|
28163
|
+
return v.startsWith("/") ? v.slice(1) : v;
|
|
28164
|
+
}
|
|
28165
|
+
}
|
|
28166
|
+
/** Read a `PdfString` value from a dict entry. */
|
|
28167
|
+
function stringValue(obj) {
|
|
28168
|
+
if (obj !== void 0 && obj.kind === "string") return obj.value;
|
|
28169
|
+
}
|
|
28170
|
+
/** Format a `PdfRef` as the canonical `"N G R"` string, if available. */
|
|
28171
|
+
function refString(ref) {
|
|
28172
|
+
return ref ? `${ref.objectNumber} ${ref.generationNumber} R` : void 0;
|
|
28173
|
+
}
|
|
28174
|
+
/** Lower-case file extension of a name, or `''` when there is none. */
|
|
28175
|
+
function extensionOf(name) {
|
|
28176
|
+
const base = name.replace(/[\\/]+/g, "/").split("/").pop() ?? name;
|
|
28177
|
+
const dot = base.lastIndexOf(".");
|
|
28178
|
+
if (dot < 0 || dot === base.length - 1) return "";
|
|
28179
|
+
return base.slice(dot + 1).toLowerCase();
|
|
28180
|
+
}
|
|
28181
|
+
/**
|
|
28182
|
+
* Scan a PDF for hostile constructs and return a {@link ThreatReport}.
|
|
28183
|
+
*
|
|
28184
|
+
* The scan never executes any document content. It parses the PDF and walks
|
|
28185
|
+
* the parsed object graph (see the module header for the precise method and
|
|
28186
|
+
* ISO 32000 references). When parsing fails, a guarded raw-byte fallback runs.
|
|
28187
|
+
*
|
|
28188
|
+
* @param pdf Raw PDF bytes.
|
|
28189
|
+
* @returns A report of findings plus the aggregate `riskLevel`.
|
|
28190
|
+
*/
|
|
28191
|
+
async function scanPdfThreats(pdf) {
|
|
28192
|
+
const findings = [];
|
|
28193
|
+
let scannedGraph = false;
|
|
28194
|
+
try {
|
|
28195
|
+
scanObjectGraph((await require_pdfDocument.loadPdf(pdf, { ignoreEncryption: true })).getRegistry(), findings);
|
|
28196
|
+
scannedGraph = true;
|
|
28197
|
+
} catch {
|
|
28198
|
+
scannedGraph = false;
|
|
28199
|
+
}
|
|
28200
|
+
if (!scannedGraph) scanRawBytesFallback(pdf, findings);
|
|
28201
|
+
return {
|
|
28202
|
+
findings,
|
|
28203
|
+
riskLevel: aggregateRisk(findings)
|
|
28204
|
+
};
|
|
28205
|
+
}
|
|
28206
|
+
/**
|
|
28207
|
+
* Walk every registered object and emit findings.
|
|
28208
|
+
*
|
|
28209
|
+
* We iterate the registry (every resolved indirect object). For each
|
|
28210
|
+
* dictionary (including stream dictionaries) we inspect that dict's *own
|
|
28211
|
+
* keys* and, where relevant, the resolved action-subtype `/S` *name*. Content
|
|
28212
|
+
* streams' raw bytes are never substring-scanned here, which is what makes the
|
|
28213
|
+
* scan precise (drawn text that merely says "JavaScript" is not a finding).
|
|
28214
|
+
*/
|
|
28215
|
+
function scanObjectGraph(registry, findings) {
|
|
28216
|
+
for (const entry of registry) {
|
|
28217
|
+
const obj = entry.object;
|
|
28218
|
+
const refStr = refString(entry.ref);
|
|
28219
|
+
let dict;
|
|
28220
|
+
if (obj.kind === "dict") dict = obj;
|
|
28221
|
+
else if (obj.kind === "stream") dict = obj.dict;
|
|
28222
|
+
if (dict === void 0) continue;
|
|
28223
|
+
inspectDict(dict, refStr, findings);
|
|
28224
|
+
}
|
|
28225
|
+
}
|
|
28226
|
+
/**
|
|
28227
|
+
* Inspect a single dictionary's keys/values for dangerous constructs.
|
|
28228
|
+
*
|
|
28229
|
+
* @param dict The dictionary (or stream dictionary).
|
|
28230
|
+
* @param refStr The owning object's `"N G R"` string, if known.
|
|
28231
|
+
* @param findings Accumulator.
|
|
28232
|
+
*/
|
|
28233
|
+
function inspectDict(dict, refStr, findings) {
|
|
28234
|
+
if (dict.has("/OpenAction")) findings.push({
|
|
28235
|
+
category: "OpenAction",
|
|
28236
|
+
severity: "medium",
|
|
28237
|
+
detail: "Document defines an /OpenAction that runs automatically when the PDF is opened (ISO 32000 §12.6.3).",
|
|
28238
|
+
objectRef: refStr
|
|
28239
|
+
});
|
|
28240
|
+
if (dict.has("/AA")) findings.push({
|
|
28241
|
+
category: "AA",
|
|
28242
|
+
severity: "medium",
|
|
28243
|
+
detail: "Document or object defines /AA additional actions that fire on viewer events such as open/close/print/save (ISO 32000 §12.6.2).",
|
|
28244
|
+
objectRef: refStr
|
|
28245
|
+
});
|
|
28246
|
+
if (dict.has("/JavaScript")) findings.push({
|
|
28247
|
+
category: "JavaScript",
|
|
28248
|
+
severity: "high",
|
|
28249
|
+
detail: "A /JavaScript name tree (catalog /Names → /JavaScript) is present; these scripts execute automatically on open (ISO 32000 §12.6.4.16, §7.7.4).",
|
|
28250
|
+
objectRef: refStr
|
|
28251
|
+
});
|
|
28252
|
+
const subtype = nameValue$1(dict.get("/S"));
|
|
28253
|
+
if (subtype !== void 0) inspectActionSubtype(subtype, dict, refStr, findings);
|
|
28254
|
+
if (dict.has("/XFA")) findings.push({
|
|
28255
|
+
category: "XFA",
|
|
28256
|
+
severity: "low",
|
|
28257
|
+
detail: "Document contains an XFA form (/AcroForm → /XFA, ISO 32000 §12.7.8); XFA carries its own scripting and data-binding model.",
|
|
28258
|
+
objectRef: refStr
|
|
28259
|
+
});
|
|
28260
|
+
const annotSubtype = nameValue$1(dict.get("/Subtype"));
|
|
28261
|
+
if (annotSubtype === "RichMedia") findings.push({
|
|
28262
|
+
category: "RichMedia",
|
|
28263
|
+
severity: "low",
|
|
28264
|
+
detail: "Rich-media annotation (/Subtype /RichMedia, ISO 32000-2 §13.6) embeds interactive multimedia (e.g. Flash/video).",
|
|
28265
|
+
objectRef: refStr
|
|
28266
|
+
});
|
|
28267
|
+
else if (annotSubtype === "Movie") findings.push({
|
|
28268
|
+
category: "Movie",
|
|
28269
|
+
severity: "low",
|
|
28270
|
+
detail: "Movie annotation (/Subtype /Movie, ISO 32000 §13.4) embeds video/animation content.",
|
|
28271
|
+
objectRef: refStr
|
|
28272
|
+
});
|
|
28273
|
+
else if (annotSubtype === "Sound") findings.push({
|
|
28274
|
+
category: "Sound",
|
|
28275
|
+
severity: "low",
|
|
28276
|
+
detail: "Sound annotation (/Subtype /Sound, ISO 32000 §13.3) embeds audio content.",
|
|
28277
|
+
objectRef: refStr
|
|
28278
|
+
});
|
|
28279
|
+
if (dict.has("/RichMedia") && annotSubtype !== "RichMedia") findings.push({
|
|
28280
|
+
category: "RichMedia",
|
|
28281
|
+
severity: "low",
|
|
28282
|
+
detail: "Object references rich-media content via a /RichMedia entry (ISO 32000-2 §13.6).",
|
|
28283
|
+
objectRef: refStr
|
|
28284
|
+
});
|
|
28285
|
+
if (dict.has("/Movie") && annotSubtype !== "Movie") findings.push({
|
|
28286
|
+
category: "Movie",
|
|
28287
|
+
severity: "low",
|
|
28288
|
+
detail: "Object references movie content via a /Movie entry (ISO 32000 §13.4).",
|
|
28289
|
+
objectRef: refStr
|
|
28290
|
+
});
|
|
28291
|
+
if (dict.has("/Sound") && annotSubtype !== "Sound" && subtype !== "Sound") findings.push({
|
|
28292
|
+
category: "Sound",
|
|
28293
|
+
severity: "low",
|
|
28294
|
+
detail: "Object references sound content via a /Sound entry (ISO 32000 §13.3).",
|
|
28295
|
+
objectRef: refStr
|
|
28296
|
+
});
|
|
28297
|
+
if (nameValue$1(dict.get("/Type")) === "Filespec" || dict.has("/EF")) {
|
|
28298
|
+
const fileName = stringValue(dict.get("/UF")) ?? stringValue(dict.get("/F"));
|
|
28299
|
+
if (fileName !== void 0) {
|
|
28300
|
+
const ext = extensionOf(fileName);
|
|
28301
|
+
if (ext !== "" && EXECUTABLE_EXTENSIONS.has(ext)) findings.push({
|
|
28302
|
+
category: "EmbeddedFile",
|
|
28303
|
+
severity: "high",
|
|
28304
|
+
detail: `Embedded file "${fileName}" has an executable/script extension ".${ext}" (ISO 32000 §7.11.3/§7.11.4); opening it can run code.`,
|
|
28305
|
+
objectRef: refStr
|
|
28306
|
+
});
|
|
28307
|
+
}
|
|
28308
|
+
}
|
|
28309
|
+
}
|
|
28310
|
+
/**
|
|
28311
|
+
* Flag a dangerous action `/S` subtype. Subtypes are ISO 32000 §12.6.4 /
|
|
28312
|
+
* §12.7.5 action types.
|
|
28313
|
+
*/
|
|
28314
|
+
function inspectActionSubtype(subtype, dict, refStr, findings) {
|
|
28315
|
+
switch (subtype) {
|
|
28316
|
+
case "JavaScript":
|
|
28317
|
+
findings.push({
|
|
28318
|
+
category: "JavaScript",
|
|
28319
|
+
severity: "high",
|
|
28320
|
+
detail: "JavaScript action (/S /JavaScript, ISO 32000 §12.6.4.16) executes script in the viewer.",
|
|
28321
|
+
objectRef: refStr
|
|
28322
|
+
});
|
|
28323
|
+
break;
|
|
28324
|
+
case "Launch":
|
|
28325
|
+
findings.push({
|
|
28326
|
+
category: "Launch",
|
|
28327
|
+
severity: "high",
|
|
28328
|
+
detail: "Launch action (/S /Launch, ISO 32000 §12.6.4.5) can run an external application or open a file.",
|
|
28329
|
+
objectRef: refStr
|
|
28330
|
+
});
|
|
28331
|
+
break;
|
|
28332
|
+
case "URI":
|
|
28333
|
+
findings.push({
|
|
28334
|
+
category: "URI",
|
|
28335
|
+
severity: "medium",
|
|
28336
|
+
detail: `URI action (/S /URI, ISO 32000 §12.6.4.7) opens a remote URL ${describeUri(dict)}.`,
|
|
28337
|
+
objectRef: refStr
|
|
28338
|
+
});
|
|
28339
|
+
break;
|
|
28340
|
+
case "SubmitForm":
|
|
28341
|
+
findings.push({
|
|
28342
|
+
category: "SubmitForm",
|
|
28343
|
+
severity: "medium",
|
|
28344
|
+
detail: "SubmitForm action (/S /SubmitForm, ISO 32000 §12.7.5.2) sends form data to a URL (potential data exfiltration).",
|
|
28345
|
+
objectRef: refStr
|
|
28346
|
+
});
|
|
28347
|
+
break;
|
|
28348
|
+
case "ImportData":
|
|
28349
|
+
findings.push({
|
|
28350
|
+
category: "ImportData",
|
|
28351
|
+
severity: "low",
|
|
28352
|
+
detail: "ImportData action (/S /ImportData, ISO 32000 §12.7.5.4) imports external form data.",
|
|
28353
|
+
objectRef: refStr
|
|
28354
|
+
});
|
|
28355
|
+
break;
|
|
28356
|
+
case "GoToR":
|
|
28357
|
+
findings.push({
|
|
28358
|
+
category: "GoToR",
|
|
28359
|
+
severity: "low",
|
|
28360
|
+
detail: "Remote Go-To action (/S /GoToR, ISO 32000 §12.6.4.3) targets an external document.",
|
|
28361
|
+
objectRef: refStr
|
|
28362
|
+
});
|
|
28363
|
+
break;
|
|
28364
|
+
case "Movie":
|
|
28365
|
+
findings.push({
|
|
28366
|
+
category: "Movie",
|
|
28367
|
+
severity: "low",
|
|
28368
|
+
detail: "Movie action (/S /Movie, ISO 32000 §12.6.4.10) plays embedded video.",
|
|
28369
|
+
objectRef: refStr
|
|
28370
|
+
});
|
|
28371
|
+
break;
|
|
28372
|
+
case "Sound":
|
|
28373
|
+
findings.push({
|
|
28374
|
+
category: "Sound",
|
|
28375
|
+
severity: "low",
|
|
28376
|
+
detail: "Sound action (/S /Sound, ISO 32000 §12.6.4.8) plays embedded audio.",
|
|
28377
|
+
objectRef: refStr
|
|
28378
|
+
});
|
|
28379
|
+
break;
|
|
28380
|
+
default: break;
|
|
28381
|
+
}
|
|
28382
|
+
}
|
|
28383
|
+
/** Append the target URL of a /URI action to the detail string, if present. */
|
|
28384
|
+
function describeUri(dict) {
|
|
28385
|
+
const uri = stringValue(dict.get("/URI"));
|
|
28386
|
+
return uri !== void 0 ? `→ "${uri}"` : "";
|
|
28387
|
+
}
|
|
28388
|
+
/**
|
|
28389
|
+
* Best-effort raw-byte fallback used only when the PDF cannot be parsed.
|
|
28390
|
+
*
|
|
28391
|
+
* To stay precise we match constructs only in their *structural* dictionary
|
|
28392
|
+
* form — action subtypes as `/S /Type` (optionally without the inner space)
|
|
28393
|
+
* and `/OpenAction`/`/AA`/`/JavaScript` as dictionary keys (a `/` immediately
|
|
28394
|
+
* followed by the token and then a PDF delimiter). This avoids flagging the
|
|
28395
|
+
* same words when they appear as content-stream text or inside a literal
|
|
28396
|
+
* string. It is heuristic and clearly less reliable than the graph walk.
|
|
28397
|
+
*/
|
|
28398
|
+
function scanRawBytesFallback(pdf, findings) {
|
|
28399
|
+
const text = new TextDecoder("latin1").decode(pdf);
|
|
28400
|
+
const keyForm = (key) => new RegExp(`/${key}(?=[\\s/(<\\[\\]>]|$)`);
|
|
28401
|
+
if (keyForm("OpenAction").test(text)) findings.push({
|
|
28402
|
+
category: "OpenAction",
|
|
28403
|
+
severity: "medium",
|
|
28404
|
+
detail: "Raw scan: /OpenAction key present (ISO 32000 §12.6.3)."
|
|
28405
|
+
});
|
|
28406
|
+
if (keyForm("AA").test(text)) findings.push({
|
|
28407
|
+
category: "AA",
|
|
28408
|
+
severity: "medium",
|
|
28409
|
+
detail: "Raw scan: /AA additional-actions key present (ISO 32000 §12.6.2)."
|
|
28410
|
+
});
|
|
28411
|
+
if (keyForm("JavaScript").test(text) || /\/S\s*\/JavaScript\b/.test(text)) findings.push({
|
|
28412
|
+
category: "JavaScript",
|
|
28413
|
+
severity: "high",
|
|
28414
|
+
detail: "Raw scan: JavaScript action/name tree present (ISO 32000 §12.6.4.16)."
|
|
28415
|
+
});
|
|
28416
|
+
if (/\/S\s*\/Launch\b/.test(text)) findings.push({
|
|
28417
|
+
category: "Launch",
|
|
28418
|
+
severity: "high",
|
|
28419
|
+
detail: "Raw scan: /S /Launch action present (ISO 32000 §12.6.4.5)."
|
|
28420
|
+
});
|
|
28421
|
+
if (/\/S\s*\/URI\b/.test(text)) findings.push({
|
|
28422
|
+
category: "URI",
|
|
28423
|
+
severity: "medium",
|
|
28424
|
+
detail: "Raw scan: /S /URI action present (ISO 32000 §12.6.4.7)."
|
|
28425
|
+
});
|
|
28426
|
+
if (/\/S\s*\/SubmitForm\b/.test(text)) findings.push({
|
|
28427
|
+
category: "SubmitForm",
|
|
28428
|
+
severity: "medium",
|
|
28429
|
+
detail: "Raw scan: /S /SubmitForm action present (ISO 32000 §12.7.5.2)."
|
|
28430
|
+
});
|
|
28431
|
+
if (/\/S\s*\/ImportData\b/.test(text)) findings.push({
|
|
28432
|
+
category: "ImportData",
|
|
28433
|
+
severity: "low",
|
|
28434
|
+
detail: "Raw scan: /S /ImportData action present (ISO 32000 §12.7.5.4)."
|
|
28435
|
+
});
|
|
28436
|
+
if (/\/S\s*\/GoToR\b/.test(text)) findings.push({
|
|
28437
|
+
category: "GoToR",
|
|
28438
|
+
severity: "low",
|
|
28439
|
+
detail: "Raw scan: /S /GoToR remote go-to present (ISO 32000 §12.6.4.3)."
|
|
28440
|
+
});
|
|
28441
|
+
if (/\/Subtype\s*\/RichMedia\b/.test(text) || keyForm("RichMedia").test(text)) findings.push({
|
|
28442
|
+
category: "RichMedia",
|
|
28443
|
+
severity: "low",
|
|
28444
|
+
detail: "Raw scan: RichMedia annotation present (ISO 32000-2 §13.6)."
|
|
28445
|
+
});
|
|
28446
|
+
if (/\/Subtype\s*\/Movie\b/.test(text) || /\/S\s*\/Movie\b/.test(text)) findings.push({
|
|
28447
|
+
category: "Movie",
|
|
28448
|
+
severity: "low",
|
|
28449
|
+
detail: "Raw scan: Movie content/action present (ISO 32000 §13.4)."
|
|
28450
|
+
});
|
|
28451
|
+
if (/\/Subtype\s*\/Sound\b/.test(text) || /\/S\s*\/Sound\b/.test(text)) findings.push({
|
|
28452
|
+
category: "Sound",
|
|
28453
|
+
severity: "low",
|
|
28454
|
+
detail: "Raw scan: Sound content/action present (ISO 32000 §13.3)."
|
|
28455
|
+
});
|
|
28456
|
+
if (keyForm("XFA").test(text)) findings.push({
|
|
28457
|
+
category: "XFA",
|
|
28458
|
+
severity: "low",
|
|
28459
|
+
detail: "Raw scan: /XFA form present (ISO 32000 §12.7.8)."
|
|
28460
|
+
});
|
|
28461
|
+
}
|
|
28462
|
+
/** Maximum severity across findings, or `'none'` when there are none. */
|
|
28463
|
+
function aggregateRisk(findings) {
|
|
28464
|
+
let maxRank = 0;
|
|
28465
|
+
for (const f of findings) {
|
|
28466
|
+
const rank = SEVERITY_RANK[f.severity];
|
|
28467
|
+
if (rank > maxRank) maxRank = rank;
|
|
28468
|
+
}
|
|
28469
|
+
if (maxRank === 0) return "none";
|
|
28470
|
+
return RANK_SEVERITY[maxRank - 1];
|
|
28471
|
+
}
|
|
28472
|
+
//#endregion
|
|
28473
|
+
//#region src/security/sanitize.ts
|
|
28474
|
+
/**
|
|
28475
|
+
* @module security/sanitize
|
|
28476
|
+
*
|
|
28477
|
+
* PDF sanitizer — produce a cleaned copy of a PDF with active / hidden
|
|
28478
|
+
* content neutralised.
|
|
28479
|
+
*
|
|
28480
|
+
* The sanitizer removes four classes of potentially dangerous or
|
|
28481
|
+
* privacy-leaking content from a document, each independently toggleable:
|
|
28482
|
+
*
|
|
28483
|
+
* - **javascript** — document-level JavaScript: the catalog's
|
|
28484
|
+
* `/Names → /JavaScript` name tree (ISO 32000-1 §7.7.4 / §12.6.4.16)
|
|
28485
|
+
* and the catalog's `/AA` (document additional-actions) dictionary
|
|
28486
|
+
* (§12.6.3) plus any page-level `/AA`.
|
|
28487
|
+
* - **openActions** — the catalog's auto-run `/OpenAction` (§12.3.4 / §12.6.4).
|
|
28488
|
+
* - **embeddedFiles** — embedded file attachments: the catalog's
|
|
28489
|
+
* `/Names → /EmbeddedFiles` name tree (§7.7.4 / §7.11.4) and the
|
|
28490
|
+
* PDF 2.0 catalog `/AF` (associated files) array (§7.11.4).
|
|
28491
|
+
* - **metadata** — the XMP `/Metadata` stream (§14.3.2) and the document
|
|
28492
|
+
* information `/Info` dictionary (§14.3.3).
|
|
28493
|
+
*
|
|
28494
|
+
* Implementation strategy (verified against this library's parser/writer):
|
|
28495
|
+
* the cleaned bytes are produced by mutating the *parsed object graph* in
|
|
28496
|
+
* place and re-serializing the registry directly through
|
|
28497
|
+
* {@link serializePdf}, **without** rebuilding the catalog. This is what
|
|
28498
|
+
* lets a disabled option genuinely preserve its content — the original
|
|
28499
|
+
* catalog (minus the removed references) is written verbatim.
|
|
28500
|
+
*
|
|
28501
|
+
* Removing a reference from the catalog is not sufficient on its own: the
|
|
28502
|
+
* underlying object (a JavaScript action dictionary, an `/EmbeddedFile`
|
|
28503
|
+
* stream, an XMP metadata stream, …) would otherwise remain physically
|
|
28504
|
+
* present in the output as an orphan. To guarantee the payload bytes are
|
|
28505
|
+
* actually gone, after deleting references this module runs the registry's
|
|
28506
|
+
* reachability filter from the catalog + info roots, dropping every object
|
|
28507
|
+
* no longer referenced.
|
|
28508
|
+
*
|
|
28509
|
+
* @packageDocumentation
|
|
28510
|
+
*/
|
|
28511
|
+
/**
|
|
28512
|
+
* Resolve a {@link PdfObject} that may be an indirect reference to its
|
|
28513
|
+
* underlying value using the registry. Non-refs are returned as-is.
|
|
28514
|
+
* Returns `undefined` if a ref cannot be resolved.
|
|
28515
|
+
*/
|
|
28516
|
+
function resolve$2(obj, registry) {
|
|
28517
|
+
if (obj === void 0) return void 0;
|
|
28518
|
+
if (obj instanceof require_pdfObjects.PdfRef) return registry.resolve(obj);
|
|
28519
|
+
return obj;
|
|
28520
|
+
}
|
|
28521
|
+
/** Resolve `obj` and return it only if it is a {@link PdfDict}. */
|
|
28522
|
+
function asDict(obj, registry) {
|
|
28523
|
+
const resolved = resolve$2(obj, registry);
|
|
28524
|
+
return resolved instanceof require_pdfObjects.PdfDict ? resolved : void 0;
|
|
28525
|
+
}
|
|
28526
|
+
/** Read a {@link PdfString} value, resolving an indirect reference if needed. */
|
|
28527
|
+
function asStringValue(obj, registry) {
|
|
28528
|
+
const resolved = resolve$2(obj, registry);
|
|
28529
|
+
return resolved instanceof require_pdfObjects.PdfString ? resolved.value : void 0;
|
|
28530
|
+
}
|
|
28531
|
+
/**
|
|
28532
|
+
* Collect every distinct `/Type /Catalog` dictionary registered in the
|
|
28533
|
+
* document, paired with its reference. A well-formed PDF has exactly one,
|
|
28534
|
+
* but malformed or incrementally-updated files can register more than one;
|
|
28535
|
+
* sanitizing all of them is the conservative, correct choice. The first
|
|
28536
|
+
* entry is treated as the document root.
|
|
28537
|
+
*/
|
|
28538
|
+
function findCatalogEntries(registry) {
|
|
28539
|
+
const byObjNum = /* @__PURE__ */ new Map();
|
|
28540
|
+
for (const { ref, object } of registry) if (object instanceof require_pdfObjects.PdfDict) {
|
|
28541
|
+
const type = object.get("/Type");
|
|
28542
|
+
if (type instanceof require_pdfObjects.PdfName && type.value === "/Catalog") {
|
|
28543
|
+
if (!byObjNum.has(ref.objectNumber)) byObjNum.set(ref.objectNumber, {
|
|
28544
|
+
ref,
|
|
28545
|
+
dict: object
|
|
28546
|
+
});
|
|
28547
|
+
}
|
|
28548
|
+
}
|
|
28549
|
+
return [...byObjNum.values()];
|
|
28550
|
+
}
|
|
28551
|
+
/** Collect every distinct `/Type /Page` dictionary in the registry. */
|
|
28552
|
+
function findPages(registry) {
|
|
28553
|
+
const pages = /* @__PURE__ */ new Set();
|
|
28554
|
+
for (const { object } of registry) if (object instanceof require_pdfObjects.PdfDict) {
|
|
28555
|
+
const type = object.get("/Type");
|
|
28556
|
+
if (type instanceof require_pdfObjects.PdfName && type.value === "/Page") pages.add(object);
|
|
28557
|
+
}
|
|
28558
|
+
return [...pages];
|
|
28559
|
+
}
|
|
28560
|
+
/**
|
|
28561
|
+
* Determine whether a PDF name tree (ISO 32000-1 §7.7.4) contains at least
|
|
28562
|
+
* one entry. A name-tree node holds either a `/Names` array of
|
|
28563
|
+
* `[key value key value …]` pairs (leaf) or a `/Kids` array of child node
|
|
28564
|
+
* references (intermediate). The tree is walked recursively with a visited
|
|
28565
|
+
* guard against cyclic references in malformed files.
|
|
28566
|
+
*/
|
|
28567
|
+
function nameTreeHasEntries(node, registry, visited = /* @__PURE__ */ new Set()) {
|
|
28568
|
+
const dict = asDict(node, registry);
|
|
28569
|
+
if (dict === void 0 || visited.has(dict)) return false;
|
|
28570
|
+
visited.add(dict);
|
|
28571
|
+
const names = resolve$2(dict.get("/Names"), registry);
|
|
28572
|
+
if (names instanceof require_pdfObjects.PdfArray && names.length >= 2) return true;
|
|
28573
|
+
const kids = resolve$2(dict.get("/Kids"), registry);
|
|
28574
|
+
if (kids instanceof require_pdfObjects.PdfArray) {
|
|
28575
|
+
for (const kid of kids.items) if (nameTreeHasEntries(kid, registry, visited)) return true;
|
|
28576
|
+
}
|
|
28577
|
+
return false;
|
|
28578
|
+
}
|
|
28579
|
+
/** Return the catalog's resolved `/Names` sub-dictionary, if present. */
|
|
28580
|
+
function getNamesDict(catalog, registry) {
|
|
28581
|
+
return asDict(catalog.get("/Names"), registry);
|
|
28582
|
+
}
|
|
28583
|
+
/**
|
|
28584
|
+
* Determine whether an additional-actions (`/AA`) dictionary contains any
|
|
28585
|
+
* action whose `/S` (action type) is `/JavaScript`. Each value in an `/AA`
|
|
28586
|
+
* dictionary is itself an action dictionary (§12.6.3).
|
|
28587
|
+
*/
|
|
28588
|
+
function aaHasJavaScript(aa, registry) {
|
|
28589
|
+
if (aa === void 0) return false;
|
|
28590
|
+
for (const [, value] of aa) {
|
|
28591
|
+
const action = asDict(value, registry);
|
|
28592
|
+
if (action === void 0) continue;
|
|
28593
|
+
const s = action.get("/S");
|
|
28594
|
+
if (s instanceof require_pdfObjects.PdfName && s.value === "/JavaScript") return true;
|
|
28595
|
+
}
|
|
28596
|
+
return false;
|
|
28597
|
+
}
|
|
28598
|
+
/**
|
|
28599
|
+
* Descriptive `/Info` keys that constitute identifying metadata. The
|
|
28600
|
+
* auto-generated `/Producer`, `/CreationDate`, and `/ModDate` are NOT
|
|
28601
|
+
* treated as "present" for reporting purposes: a freshly created document
|
|
28602
|
+
* carries those by default and must still be considered clean. (When
|
|
28603
|
+
* metadata removal runs, the entire `/Info` dict is dropped regardless.)
|
|
28604
|
+
*/
|
|
28605
|
+
const DESCRIPTIVE_INFO_KEYS = [
|
|
28606
|
+
"/Title",
|
|
28607
|
+
"/Author",
|
|
28608
|
+
"/Subject",
|
|
28609
|
+
"/Keywords",
|
|
28610
|
+
"/Creator"
|
|
28611
|
+
];
|
|
28612
|
+
/**
|
|
28613
|
+
* Locate the trailer's `/Info` dictionary in the registry. The parser does
|
|
28614
|
+
* not expose the trailer publicly, so the Info dict is identified
|
|
28615
|
+
* structurally: it is a dictionary that is NOT a catalog/page/pages node
|
|
28616
|
+
* and carries at least one recognised document-information key.
|
|
28617
|
+
*/
|
|
28618
|
+
function findInfoDict(registry, catalogs) {
|
|
28619
|
+
const catalogDicts = new Set(catalogs.map((c) => c.dict));
|
|
28620
|
+
const INFO_KEYS = [
|
|
28621
|
+
"/Title",
|
|
28622
|
+
"/Author",
|
|
28623
|
+
"/Subject",
|
|
28624
|
+
"/Keywords",
|
|
28625
|
+
"/Creator",
|
|
28626
|
+
"/Producer",
|
|
28627
|
+
"/CreationDate",
|
|
28628
|
+
"/ModDate",
|
|
28629
|
+
"/Trapped"
|
|
28630
|
+
];
|
|
28631
|
+
for (const { object } of registry) {
|
|
28632
|
+
if (!(object instanceof require_pdfObjects.PdfDict) || catalogDicts.has(object)) continue;
|
|
28633
|
+
const type = object.get("/Type");
|
|
28634
|
+
if (type instanceof require_pdfObjects.PdfName) {
|
|
28635
|
+
const v = type.value;
|
|
28636
|
+
if (v === "/Catalog" || v === "/Pages" || v === "/Page" || v === "/Font") continue;
|
|
28637
|
+
}
|
|
28638
|
+
if (INFO_KEYS.some((k) => object.has(k))) return object;
|
|
28639
|
+
}
|
|
28640
|
+
}
|
|
28641
|
+
/** Read descriptive metadata from an `/Info` dict into a typed bag. */
|
|
28642
|
+
function readInfoMetadata(info, registry) {
|
|
28643
|
+
const meta = {};
|
|
28644
|
+
if (info === void 0) return meta;
|
|
28645
|
+
const title = asStringValue(info.get("/Title"), registry);
|
|
28646
|
+
if (title !== void 0) meta.title = title;
|
|
28647
|
+
const author = asStringValue(info.get("/Author"), registry);
|
|
28648
|
+
if (author !== void 0) meta.author = author;
|
|
28649
|
+
const subject = asStringValue(info.get("/Subject"), registry);
|
|
28650
|
+
if (subject !== void 0) meta.subject = subject;
|
|
28651
|
+
const keywords = asStringValue(info.get("/Keywords"), registry);
|
|
28652
|
+
if (keywords !== void 0) meta.keywords = keywords;
|
|
28653
|
+
const creator = asStringValue(info.get("/Creator"), registry);
|
|
28654
|
+
if (creator !== void 0) meta.creator = creator;
|
|
28655
|
+
const producer = asStringValue(info.get("/Producer"), registry);
|
|
28656
|
+
if (producer !== void 0) meta.producer = producer;
|
|
28657
|
+
return meta;
|
|
28658
|
+
}
|
|
28659
|
+
/**
|
|
28660
|
+
* Produce a cleaned copy of a PDF with active / hidden content neutralised.
|
|
28661
|
+
*
|
|
28662
|
+
* Loads the PDF, removes each enabled class of content from the parsed
|
|
28663
|
+
* object graph, prunes the orphaned objects, re-serializes the cleaned
|
|
28664
|
+
* document, and returns the new bytes alongside a report listing the
|
|
28665
|
+
* classes that were actually present and removed.
|
|
28666
|
+
*
|
|
28667
|
+
* @param pdf The source PDF bytes.
|
|
28668
|
+
* @param options Which classes to remove (each defaults to `true`).
|
|
28669
|
+
* @returns The cleaned PDF bytes and a {@link SanitizeReport}.
|
|
28670
|
+
*
|
|
28671
|
+
* @example
|
|
28672
|
+
* ```ts
|
|
28673
|
+
* const { pdf, report } = await sanitizePdf(bytes);
|
|
28674
|
+
* // report.removed === ['javascript', 'embeddedFiles', 'metadata']
|
|
28675
|
+
* ```
|
|
28676
|
+
*/
|
|
28677
|
+
async function sanitizePdf(pdf, options) {
|
|
28678
|
+
const removeJavaScript = options?.javascript ?? true;
|
|
28679
|
+
const removeOpenActions = options?.openActions ?? true;
|
|
28680
|
+
const removeEmbeddedFiles = options?.embeddedFiles ?? true;
|
|
28681
|
+
const removeMetadata = options?.metadata ?? true;
|
|
28682
|
+
const registry = (await require_pdfDocument.loadPdf(pdf)).getRegistry();
|
|
28683
|
+
const catalogs = findCatalogEntries(registry);
|
|
28684
|
+
if (catalogs.length === 0) return {
|
|
28685
|
+
pdf,
|
|
28686
|
+
report: { removed: [] }
|
|
28687
|
+
};
|
|
28688
|
+
const pages = findPages(registry);
|
|
28689
|
+
const infoDict = findInfoDict(registry, catalogs);
|
|
28690
|
+
const removed = /* @__PURE__ */ new Set();
|
|
28691
|
+
if (removeJavaScript) {
|
|
28692
|
+
for (const { dict: catalog } of catalogs) {
|
|
28693
|
+
const names = getNamesDict(catalog, registry);
|
|
28694
|
+
if (names !== void 0 && nameTreeHasEntries(names.get("/JavaScript"), registry)) removed.add("javascript");
|
|
28695
|
+
if (aaHasJavaScript(asDict(catalog.get("/AA"), registry), registry)) removed.add("javascript");
|
|
28696
|
+
}
|
|
28697
|
+
for (const page of pages) if (aaHasJavaScript(asDict(page.get("/AA"), registry), registry)) removed.add("javascript");
|
|
28698
|
+
for (const { dict: catalog } of catalogs) {
|
|
28699
|
+
const names = getNamesDict(catalog, registry);
|
|
28700
|
+
if (names !== void 0) {
|
|
28701
|
+
names.delete("/JavaScript");
|
|
28702
|
+
if (names.size === 0) catalog.delete("/Names");
|
|
28703
|
+
}
|
|
28704
|
+
catalog.delete("/AA");
|
|
28705
|
+
}
|
|
28706
|
+
for (const page of pages) page.delete("/AA");
|
|
28707
|
+
}
|
|
28708
|
+
if (removeOpenActions) {
|
|
28709
|
+
for (const { dict: catalog } of catalogs) if (catalog.has("/OpenAction")) {
|
|
28710
|
+
removed.add("openActions");
|
|
28711
|
+
catalog.delete("/OpenAction");
|
|
28712
|
+
}
|
|
28713
|
+
}
|
|
28714
|
+
if (removeEmbeddedFiles) {
|
|
28715
|
+
for (const { dict: catalog } of catalogs) {
|
|
28716
|
+
const names = getNamesDict(catalog, registry);
|
|
28717
|
+
if (names !== void 0 && nameTreeHasEntries(names.get("/EmbeddedFiles"), registry)) removed.add("embeddedFiles");
|
|
28718
|
+
const af = resolve$2(catalog.get("/AF"), registry);
|
|
28719
|
+
if (af instanceof require_pdfObjects.PdfArray && af.length > 0) removed.add("embeddedFiles");
|
|
28720
|
+
}
|
|
28721
|
+
for (const { dict: catalog } of catalogs) {
|
|
28722
|
+
const names = getNamesDict(catalog, registry);
|
|
28723
|
+
if (names !== void 0) {
|
|
28724
|
+
names.delete("/EmbeddedFiles");
|
|
28725
|
+
if (names.size === 0) catalog.delete("/Names");
|
|
28726
|
+
}
|
|
28727
|
+
catalog.delete("/AF");
|
|
28728
|
+
}
|
|
28729
|
+
}
|
|
28730
|
+
const preservedMeta = readInfoMetadata(infoDict, registry);
|
|
28731
|
+
if (removeMetadata) {
|
|
28732
|
+
let metadataPresent = false;
|
|
28733
|
+
for (const { dict: catalog } of catalogs) if (resolve$2(catalog.get("/Metadata"), registry) instanceof require_pdfObjects.PdfStream) metadataPresent = true;
|
|
28734
|
+
if (infoDict !== void 0 && DESCRIPTIVE_INFO_KEYS.some((k) => infoDict.has(k))) metadataPresent = true;
|
|
28735
|
+
if (metadataPresent) removed.add("metadata");
|
|
28736
|
+
for (const { dict: catalog } of catalogs) catalog.delete("/Metadata");
|
|
28737
|
+
if (infoDict !== void 0) for (const [key] of [...infoDict]) infoDict.delete(key);
|
|
28738
|
+
}
|
|
28739
|
+
const root = catalogs[0];
|
|
28740
|
+
const pagesRef = root.dict.get("/Pages");
|
|
28741
|
+
let infoRef;
|
|
28742
|
+
if (removeMetadata) infoRef = require_pdfDocument.buildInfoDict({}, registry);
|
|
28743
|
+
else infoRef = require_pdfDocument.buildInfoDict(preservedMeta, registry);
|
|
28744
|
+
const structure = {
|
|
28745
|
+
catalogRef: root.ref,
|
|
28746
|
+
infoRef,
|
|
28747
|
+
pagesRef: pagesRef instanceof require_pdfObjects.PdfRef ? pagesRef : root.ref
|
|
28748
|
+
};
|
|
28749
|
+
registry.filterReachable([structure.catalogRef, structure.infoRef]);
|
|
28750
|
+
return {
|
|
28751
|
+
pdf: await require_pdfDocument.serializePdf(registry, structure, { compress: true }),
|
|
28752
|
+
report: { removed: [...removed] }
|
|
28753
|
+
};
|
|
28754
|
+
}
|
|
28755
|
+
//#endregion
|
|
28756
|
+
//#region src/security/redactionVerifier.ts
|
|
28757
|
+
/**
|
|
28758
|
+
* @module security/redactionVerifier
|
|
28759
|
+
*
|
|
28760
|
+
* Detect FAILED / fake redactions — text that is still extractable underneath
|
|
28761
|
+
* a redaction region.
|
|
28762
|
+
*
|
|
28763
|
+
* A *secure* redaction physically removes the underlying text-showing
|
|
28764
|
+
* operators from the page content stream (see `redactRegions` in
|
|
28765
|
+
* `../render/redactContent.js`), so the redacted text cannot be recovered by
|
|
28766
|
+
* copy/paste, text extraction, or raw stream inspection. A *fake* redaction
|
|
28767
|
+
* merely paints an opaque black box on top of the page while the original text
|
|
28768
|
+
* remains in the byte stream — a genuine, well-documented security failure
|
|
28769
|
+
* (the "select-all-and-copy" leak). This verifier finds those leaks.
|
|
28770
|
+
*
|
|
28771
|
+
* ## How it works
|
|
28772
|
+
*
|
|
28773
|
+
* For each page named by a region:
|
|
28774
|
+
* 1. The page's content stream is decoded
|
|
28775
|
+
* (`PdfPage.getContentStream()`) and parsed (`parseContentStream`).
|
|
28776
|
+
* 2. `extractTextWithPositions` replays the text/graphics-state machine and
|
|
28777
|
+
* yields one positioned {@link import('../parser/textExtractor.js').TextItem}
|
|
28778
|
+
* per shown text run, with its page-space origin and size.
|
|
28779
|
+
* 3. Every text item whose bounding box intersects a region rectangle is
|
|
28780
|
+
* reported as a leak. `clean` is true iff there are no leaks.
|
|
28781
|
+
*
|
|
28782
|
+
* ## Coordinate / encoding conventions (confirmed, not assumed)
|
|
28783
|
+
*
|
|
28784
|
+
* - **Space:** PDF user space — the coordinate space produced by
|
|
28785
|
+
* `extractTextWithPositions`. Origin is **bottom-left**, the y-axis points
|
|
28786
|
+
* **up**, and units are **PDF points** (1/72 inch). This matches
|
|
28787
|
+
* `RedactRect` in `../render/redactContent.js` ("page space, PDF points,
|
|
28788
|
+
* y-up, lower-left origin"), so a {@link RedactionRegion} uses exactly the
|
|
28789
|
+
* same convention: `(x, y)` is the lower-left corner and `width` / `height`
|
|
28790
|
+
* extend in the +x / +y directions.
|
|
28791
|
+
* - **Glyph box:** a `TextItem` reports `(x, y)` = the text origin
|
|
28792
|
+
* (baseline-left), `width` extending +x, and `height` (equal to the font
|
|
28793
|
+
* size) extending +y. The run's axis-aligned box is therefore
|
|
28794
|
+
* `[x, y, x + width, y + height]`. Note `width`/`height` are heuristic
|
|
28795
|
+
* estimates from `extractTextWithPositions` (0.5·fontSize per character;
|
|
28796
|
+
* height = fontSize) because per-glyph font metrics are not available there;
|
|
28797
|
+
* intersection testing tolerates this by treating any overlap as a leak.
|
|
28798
|
+
* - **Text decoding:** strings are decoded by `extractTextWithPositions` using
|
|
28799
|
+
* the font's `/ToUnicode` CMap when present, else WinAnsiEncoding (1-byte) or
|
|
28800
|
+
* 2-byte Identity-H for CID fonts — see `../parser/textExtractor.js`.
|
|
28801
|
+
*
|
|
28802
|
+
* ## Region auto-detection is intentionally NOT performed
|
|
28803
|
+
*
|
|
28804
|
+
* The `regions` argument is **required**. Deriving candidate regions from the
|
|
28805
|
+
* PDF itself is not reliable through the public API and is not fabricated here:
|
|
28806
|
+
* - Filled black rectangles in the content stream are indistinguishable from
|
|
28807
|
+
* legitimate black-filled graphics (chart bars, rules, design elements), so
|
|
28808
|
+
* a heuristic would produce false positives/negatives.
|
|
28809
|
+
* - `/Redact` annotations (ISO 32000-2 §12.5.6.23) *do* carry explicit
|
|
28810
|
+
* `/Rect` / `/QuadPoints` regions, but a loaded page's original annotation
|
|
28811
|
+
* dictionaries are not exposed through a stable public `PdfPage` accessor,
|
|
28812
|
+
* so reading them here is not possible without reaching into private state.
|
|
28813
|
+
* Callers must therefore pass the regions they redacted (or believe were
|
|
28814
|
+
* redacted). When `regions` is omitted or empty, {@link verifyRedactions}
|
|
28815
|
+
* throws.
|
|
28816
|
+
*
|
|
28817
|
+
* @packageDocumentation
|
|
28818
|
+
*/
|
|
28819
|
+
/**
|
|
28820
|
+
* Axis-aligned bounding box of a text run in page space (bottom-left origin).
|
|
28821
|
+
* `width`/`height` may be zero (empty text); such runs never intersect.
|
|
28822
|
+
*/
|
|
28823
|
+
function itemBox(item) {
|
|
28824
|
+
const x0 = item.x;
|
|
28825
|
+
const y0 = item.y;
|
|
28826
|
+
const x1 = item.x + item.width;
|
|
28827
|
+
const y1 = item.y + item.height;
|
|
28828
|
+
return {
|
|
28829
|
+
minX: Math.min(x0, x1),
|
|
28830
|
+
minY: Math.min(y0, y1),
|
|
28831
|
+
maxX: Math.max(x0, x1),
|
|
28832
|
+
maxY: Math.max(y0, y1)
|
|
28833
|
+
};
|
|
28834
|
+
}
|
|
28835
|
+
/**
|
|
28836
|
+
* Whether a text run's box overlaps a region rectangle.
|
|
28837
|
+
*
|
|
28838
|
+
* Uses the standard separating-axis test for axis-aligned rectangles: they
|
|
28839
|
+
* overlap unless one is entirely to the left/right/above/below the other.
|
|
28840
|
+
* Touching edges count as overlap (inclusive bounds), matching the intersection
|
|
28841
|
+
* test in `../render/redactContent.js`.
|
|
28842
|
+
*/
|
|
28843
|
+
function intersects$1(box, region) {
|
|
28844
|
+
const rMinX = region.x;
|
|
28845
|
+
const rMinY = region.y;
|
|
28846
|
+
const rMaxX = region.x + region.width;
|
|
28847
|
+
const rMaxY = region.y + region.height;
|
|
28848
|
+
return box.minX <= rMaxX && box.maxX >= rMinX && box.minY <= rMaxY && box.maxY >= rMinY;
|
|
28849
|
+
}
|
|
28850
|
+
/**
|
|
28851
|
+
* Verify that the given regions of a PDF contain no still-extractable text,
|
|
28852
|
+
* detecting fake / failed redactions.
|
|
28853
|
+
*
|
|
28854
|
+
* For each region, the corresponding page's text is extracted with positions
|
|
28855
|
+
* and any text run whose bounding box intersects the region is reported as a
|
|
28856
|
+
* {@link RedactionLeak}. A clean result means no text was found under any
|
|
28857
|
+
* region (the redactions truly removed the content, or there was none to begin
|
|
28858
|
+
* with).
|
|
28859
|
+
*
|
|
28860
|
+
* @param pdf The PDF file bytes to inspect.
|
|
28861
|
+
* @param regions The regions to check (REQUIRED — see the module docs for why
|
|
28862
|
+
* automatic region detection is not performed). Coordinates are
|
|
28863
|
+
* in PDF user space: origin bottom-left, y-up, units in points;
|
|
28864
|
+
* `(x, y)` is the lower-left corner.
|
|
28865
|
+
* @returns A report listing every leak; `clean` is true iff there are
|
|
28866
|
+
* none.
|
|
28867
|
+
* @throws `TypeError` if `regions` is omitted or empty.
|
|
28868
|
+
*
|
|
28869
|
+
* @example
|
|
28870
|
+
* ```ts
|
|
28871
|
+
* const report = await verifyRedactions(pdfBytes, [
|
|
28872
|
+
* { page: 0, x: 45, y: 545, width: 120, height: 25 },
|
|
28873
|
+
* ]);
|
|
28874
|
+
* if (!report.clean) {
|
|
28875
|
+
* for (const leak of report.leaks) {
|
|
28876
|
+
* console.warn(`Leak on page ${leak.page}: "${leak.text}"`);
|
|
28877
|
+
* }
|
|
28878
|
+
* }
|
|
28879
|
+
* ```
|
|
28880
|
+
*/
|
|
28881
|
+
async function verifyRedactions(pdf, regions) {
|
|
28882
|
+
if (regions === void 0 || regions.length === 0) throw new TypeError("verifyRedactions requires an explicit, non-empty `regions` array. Automatic redaction-region detection is not performed: black-filled rectangles are indistinguishable from legitimate graphics, and a loaded page's original /Redact annotations are not exposed through a stable public API. Pass the regions you redacted (PDF points, bottom-left origin, y-up).");
|
|
28883
|
+
const doc = await require_pdfDocument.loadPdf(pdf);
|
|
28884
|
+
const pageCount = doc.getPageCount();
|
|
28885
|
+
const itemsByPage = /* @__PURE__ */ new Map();
|
|
28886
|
+
const getItems = (pageIndex) => {
|
|
28887
|
+
const cached = itemsByPage.get(pageIndex);
|
|
28888
|
+
if (cached !== void 0) return cached;
|
|
28889
|
+
let items = [];
|
|
28890
|
+
if (pageIndex >= 0 && pageIndex < pageCount) {
|
|
28891
|
+
const page = doc.getPage(pageIndex);
|
|
28892
|
+
items = require_compressionAnalysis.extractTextWithPositions(require_compressionAnalysis.parseContentStream(page.getContentStream()), page.getOriginalResources());
|
|
28893
|
+
}
|
|
28894
|
+
itemsByPage.set(pageIndex, items);
|
|
28895
|
+
return items;
|
|
28896
|
+
};
|
|
28897
|
+
const leaks = [];
|
|
28898
|
+
for (const region of regions) {
|
|
28899
|
+
if (region.width <= 0 || region.height <= 0) continue;
|
|
28900
|
+
const items = getItems(region.page);
|
|
28901
|
+
for (const item of items) {
|
|
28902
|
+
if (item.text.length === 0) continue;
|
|
28903
|
+
const box = itemBox(item);
|
|
28904
|
+
if (box.maxX === box.minX && box.maxY === box.minY) continue;
|
|
28905
|
+
if (intersects$1(box, region)) leaks.push({
|
|
28906
|
+
page: region.page,
|
|
28907
|
+
text: item.text,
|
|
28908
|
+
x: item.x,
|
|
28909
|
+
y: item.y
|
|
28910
|
+
});
|
|
28911
|
+
}
|
|
28912
|
+
}
|
|
28913
|
+
return {
|
|
28914
|
+
leaks,
|
|
28915
|
+
clean: leaks.length === 0,
|
|
28916
|
+
regionsChecked: regions.length
|
|
28917
|
+
};
|
|
28918
|
+
}
|
|
28919
|
+
//#endregion
|
|
28920
|
+
//#region src/security/encryptionInspector.ts
|
|
28921
|
+
const BIT_PRINT = 4;
|
|
28922
|
+
const BIT_MODIFY = 8;
|
|
28923
|
+
const BIT_COPY = 16;
|
|
28924
|
+
const BIT_ANNOTATE = 32;
|
|
28925
|
+
const BIT_FILL_FORMS = 256;
|
|
28926
|
+
const BIT_ASSEMBLE = 1024;
|
|
28927
|
+
const BIT_PRINT_HIGH_RES = 2048;
|
|
28928
|
+
/**
|
|
28929
|
+
* Inspect a PDF's encryption + permission posture.
|
|
28930
|
+
*
|
|
28931
|
+
* @param pdf The raw PDF file bytes.
|
|
28932
|
+
* @returns A {@link EncryptionReport}. If the trailer has no
|
|
28933
|
+
* `/Encrypt` entry (or the file is unparseable) the report is
|
|
28934
|
+
* `{ encrypted: false }`.
|
|
28935
|
+
*/
|
|
28936
|
+
async function inspectEncryption(pdf) {
|
|
28937
|
+
const located = await locateEncryptDict(pdf);
|
|
28938
|
+
if (located === void 0) return { encrypted: false };
|
|
28939
|
+
const { encryptDict, fileId, encryptRef } = located;
|
|
28940
|
+
const filterName = getName(encryptDict, "/Filter");
|
|
28941
|
+
let handler;
|
|
28942
|
+
if (filterName === "/Standard") handler = "password";
|
|
28943
|
+
else if (filterName === "/Adobe.PubSec") handler = "publicKey";
|
|
28944
|
+
const version = getNumber(encryptDict, "/V");
|
|
28945
|
+
const revision = getNumber(encryptDict, "/R");
|
|
28946
|
+
const { method, keyBits } = decodeAlgorithm(encryptDict, version);
|
|
28947
|
+
let permissions;
|
|
28948
|
+
const pValue = getNumber(encryptDict, "/P");
|
|
28949
|
+
if (handler === "password" && pValue !== void 0) permissions = decodePermissionBits(pValue);
|
|
28950
|
+
let emptyUserPassword;
|
|
28951
|
+
if (handler === "password") emptyUserPassword = await testEmptyUserPassword(encryptDict, fileId);
|
|
28952
|
+
const report = { encrypted: true };
|
|
28953
|
+
if (method !== void 0) report.method = method;
|
|
28954
|
+
if (keyBits !== void 0) report.keyBits = keyBits;
|
|
28955
|
+
if (version !== void 0) report.version = version;
|
|
28956
|
+
if (revision !== void 0) report.revision = revision;
|
|
28957
|
+
if (handler !== void 0) report.handler = handler;
|
|
28958
|
+
if (emptyUserPassword !== void 0) report.emptyUserPassword = emptyUserPassword;
|
|
28959
|
+
if (permissions !== void 0) report.permissions = permissions;
|
|
28960
|
+
return report;
|
|
28961
|
+
}
|
|
28962
|
+
/**
|
|
28963
|
+
* Parse just enough of the PDF (header + xref + trailer) to resolve the
|
|
28964
|
+
* `/Encrypt` dictionary and the first element of the `/ID` array.
|
|
28965
|
+
*
|
|
28966
|
+
* Returns `undefined` when the file has no `/Encrypt` entry or when it
|
|
28967
|
+
* cannot be parsed far enough to find one.
|
|
28968
|
+
*/
|
|
28969
|
+
async function locateEncryptDict(pdf) {
|
|
28970
|
+
if (pdf.length < 8) return void 0;
|
|
28971
|
+
let encryptRef;
|
|
28972
|
+
let fileIdFirst;
|
|
28973
|
+
let entry;
|
|
28974
|
+
let objectParser;
|
|
28975
|
+
try {
|
|
28976
|
+
objectParser = new require_pdfDocument.PdfObjectParser(new require_pdfDocument.PdfLexer(pdf), new require_pdfObjects.PdfObjectRegistry());
|
|
28977
|
+
const { entries, trailer } = await new require_pdfDocument.XrefParser(pdf, objectParser).parseXref();
|
|
28978
|
+
encryptRef = trailer.encryptRef;
|
|
28979
|
+
if (encryptRef === void 0) return void 0;
|
|
28980
|
+
fileIdFirst = trailer.id?.[0] ?? /* @__PURE__ */ new Uint8Array(0);
|
|
28981
|
+
entry = entries.get(encryptRef.objectNumber);
|
|
28982
|
+
} catch {
|
|
28983
|
+
return;
|
|
28984
|
+
}
|
|
28985
|
+
if (entry === void 0 || entry.type !== "in-use") return void 0;
|
|
28986
|
+
let encryptObj;
|
|
28987
|
+
try {
|
|
28988
|
+
const { object } = objectParser.parseIndirectObjectAt(entry.offset);
|
|
28989
|
+
encryptObj = object;
|
|
28990
|
+
} catch {
|
|
28991
|
+
return;
|
|
28992
|
+
}
|
|
28993
|
+
if (encryptObj.kind !== "dict") return void 0;
|
|
28994
|
+
return {
|
|
28995
|
+
encryptDict: encryptObj,
|
|
28996
|
+
encryptRef,
|
|
28997
|
+
fileId: fileIdFirst
|
|
28998
|
+
};
|
|
28999
|
+
}
|
|
29000
|
+
/**
|
|
29001
|
+
* Decode the bulk cipher and key length from an encryption dictionary.
|
|
29002
|
+
*
|
|
29003
|
+
* - V=1: 40-bit RC4 (Table 20/21).
|
|
29004
|
+
* - V=2: RC4 with `/Length` bits.
|
|
29005
|
+
* - V=4: inspect the crypt filter `/CFM` of the filter named by `/StmF`
|
|
29006
|
+
* (falling back to `/StdCF`): `/V2` → RC4, `/AESV2` → 128-bit AES,
|
|
29007
|
+
* `/AESV3` → 256-bit AES, `/Identity`/`/None` → no encryption.
|
|
29008
|
+
* - V=5: 256-bit AES (`/AESV3`).
|
|
29009
|
+
*/
|
|
29010
|
+
function decodeAlgorithm(dict, version) {
|
|
29011
|
+
const lengthBits = getNumber(dict, "/Length");
|
|
29012
|
+
if (version === 1) return {
|
|
29013
|
+
method: "rc4",
|
|
29014
|
+
keyBits: 40
|
|
29015
|
+
};
|
|
29016
|
+
if (version === 2) return {
|
|
29017
|
+
method: "rc4",
|
|
29018
|
+
keyBits: lengthBits ?? 40
|
|
29019
|
+
};
|
|
29020
|
+
if (version === 5) return {
|
|
29021
|
+
method: "aes",
|
|
29022
|
+
keyBits: 256
|
|
29023
|
+
};
|
|
29024
|
+
if (version === 4) {
|
|
29025
|
+
const cfm = resolveCryptFilterMethod(dict);
|
|
29026
|
+
if (cfm === "/AESV3") return {
|
|
29027
|
+
method: "aes",
|
|
29028
|
+
keyBits: 256
|
|
29029
|
+
};
|
|
29030
|
+
if (cfm === "/AESV2") return {
|
|
29031
|
+
method: "aes",
|
|
29032
|
+
keyBits: lengthBits ?? 128
|
|
29033
|
+
};
|
|
29034
|
+
if (cfm === "/V2") return {
|
|
29035
|
+
method: "rc4",
|
|
29036
|
+
keyBits: lengthBits ?? 128
|
|
29037
|
+
};
|
|
29038
|
+
return {
|
|
29039
|
+
method: void 0,
|
|
29040
|
+
keyBits: lengthBits ?? void 0
|
|
29041
|
+
};
|
|
29042
|
+
}
|
|
29043
|
+
return {
|
|
29044
|
+
method: void 0,
|
|
29045
|
+
keyBits: lengthBits ?? void 0
|
|
29046
|
+
};
|
|
29047
|
+
}
|
|
29048
|
+
/**
|
|
29049
|
+
* For V=4 documents, resolve the `/CFM` (crypt filter method) name of the
|
|
29050
|
+
* stream crypt filter. Looks up `/StmF` in `/CF`, then falls back to the
|
|
29051
|
+
* conventional `/StdCF` entry.
|
|
29052
|
+
*/
|
|
29053
|
+
function resolveCryptFilterMethod(dict) {
|
|
29054
|
+
const cf = dict.get("/CF");
|
|
29055
|
+
if (cf === void 0 || cf.kind !== "dict") return void 0;
|
|
29056
|
+
const cfDict = cf;
|
|
29057
|
+
let filterName = getName(dict, "/StmF");
|
|
29058
|
+
if (filterName === "/Identity") return "/Identity";
|
|
29059
|
+
let stdCf = filterName !== void 0 ? cfDict.get(filterName) : void 0;
|
|
29060
|
+
if (stdCf === void 0 || stdCf.kind !== "dict") {
|
|
29061
|
+
stdCf = cfDict.get("/StdCF");
|
|
29062
|
+
filterName = "/StdCF";
|
|
29063
|
+
}
|
|
29064
|
+
if (stdCf === void 0 || stdCf.kind !== "dict") return void 0;
|
|
29065
|
+
return getName(stdCf, "/CFM");
|
|
29066
|
+
}
|
|
29067
|
+
/**
|
|
29068
|
+
* Decode the 32-bit `/P` integer into individual permission flags.
|
|
29069
|
+
*
|
|
29070
|
+
* The `/P` value is a *signed* 32-bit integer; bitwise AND in JavaScript
|
|
29071
|
+
* operates on the 32-bit two's-complement representation, so masks apply
|
|
29072
|
+
* correctly even when `/P` is negative (the usual case, since reserved
|
|
29073
|
+
* high bits are set to 1).
|
|
29074
|
+
*/
|
|
29075
|
+
function decodePermissionBits(p) {
|
|
29076
|
+
const has = (mask) => (p & mask) !== 0;
|
|
29077
|
+
const copy = has(BIT_COPY);
|
|
29078
|
+
return {
|
|
29079
|
+
print: has(BIT_PRINT),
|
|
29080
|
+
modify: has(BIT_MODIFY),
|
|
29081
|
+
copy,
|
|
29082
|
+
annotate: has(BIT_ANNOTATE),
|
|
29083
|
+
fillForms: has(BIT_FILL_FORMS),
|
|
29084
|
+
extract: copy,
|
|
29085
|
+
assemble: has(BIT_ASSEMBLE),
|
|
29086
|
+
printHighRes: has(BIT_PRINT_HIGH_RES)
|
|
29087
|
+
};
|
|
29088
|
+
}
|
|
29089
|
+
/**
|
|
29090
|
+
* Test whether the empty user password (`""`) authenticates against the
|
|
29091
|
+
* standard security handler. Reuses {@link PdfEncryptionHandler.fromEncryptDict},
|
|
29092
|
+
* which throws when the password is incorrect; a successful return means
|
|
29093
|
+
* the empty password derived a valid file key.
|
|
29094
|
+
*
|
|
29095
|
+
* Returns `undefined` when the test cannot be performed (e.g. the handler
|
|
29096
|
+
* implementation does not support the document's /V/R).
|
|
29097
|
+
*/
|
|
29098
|
+
async function testEmptyUserPassword(dict, fileId) {
|
|
29099
|
+
try {
|
|
29100
|
+
await require_pdfDocument.PdfEncryptionHandler.fromEncryptDict(dict, fileId, "");
|
|
29101
|
+
return true;
|
|
29102
|
+
} catch (err) {
|
|
29103
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
29104
|
+
if (/password/i.test(message)) return false;
|
|
29105
|
+
return;
|
|
29106
|
+
}
|
|
29107
|
+
}
|
|
29108
|
+
/** Read a `/Name` value (including the leading `/`) from a dict. */
|
|
29109
|
+
function getName(dict, key) {
|
|
29110
|
+
const obj = dict.get(key);
|
|
29111
|
+
if (obj !== void 0 && obj.kind === "name") return obj.value;
|
|
29112
|
+
}
|
|
29113
|
+
/** Read a numeric value from a dict. */
|
|
29114
|
+
function getNumber(dict, key) {
|
|
29115
|
+
const obj = dict.get(key);
|
|
29116
|
+
if (obj !== void 0 && obj.kind === "number") return obj.value;
|
|
29117
|
+
}
|
|
29118
|
+
//#endregion
|
|
27844
29119
|
//#region src/render/matrix.ts
|
|
27845
29120
|
/** The identity transform. */
|
|
27846
29121
|
function identity() {
|
|
@@ -31649,15 +32924,15 @@ async function initWasm(options) {
|
|
|
31649
32924
|
if (wasmInitialized) return;
|
|
31650
32925
|
const inits = [];
|
|
31651
32926
|
if (options.deflate || options.deflateWasm) inits.push((async () => {
|
|
31652
|
-
const { initDeflateWasm } = await Promise.resolve().then(() => require("./pdfDocument-
|
|
32927
|
+
const { initDeflateWasm } = await Promise.resolve().then(() => require("./pdfDocument-D5qz7duP.cjs")).then((n) => n.libdeflateWasm_exports);
|
|
31653
32928
|
await initDeflateWasm(options.deflateWasm);
|
|
31654
32929
|
})());
|
|
31655
32930
|
if (options.png || options.pngWasm) inits.push((async () => {
|
|
31656
|
-
const { initPngWasm } = await Promise.resolve().then(() => require("./pdfDocument-
|
|
32931
|
+
const { initPngWasm } = await Promise.resolve().then(() => require("./pdfDocument-D5qz7duP.cjs")).then((n) => n.pngEmbed_exports);
|
|
31657
32932
|
await initPngWasm(options.pngWasm);
|
|
31658
32933
|
})());
|
|
31659
32934
|
if (options.fonts || options.fontWasm) inits.push((async () => {
|
|
31660
|
-
const { initSubsetWasm } = await Promise.resolve().then(() => require("./pdfDocument-
|
|
32935
|
+
const { initSubsetWasm } = await Promise.resolve().then(() => require("./pdfDocument-D5qz7duP.cjs")).then((n) => n.fontSubset_exports);
|
|
31661
32936
|
await initSubsetWasm(options.fontWasm);
|
|
31662
32937
|
})());
|
|
31663
32938
|
if (options.jpeg || options.jpegWasm) inits.push((async () => {
|
|
@@ -32166,6 +33441,12 @@ Object.defineProperty(exports, "buildCalRGB", {
|
|
|
32166
33441
|
return buildCalRGB;
|
|
32167
33442
|
}
|
|
32168
33443
|
});
|
|
33444
|
+
Object.defineProperty(exports, "buildCertPath", {
|
|
33445
|
+
enumerable: true,
|
|
33446
|
+
get: function() {
|
|
33447
|
+
return buildCertPath;
|
|
33448
|
+
}
|
|
33449
|
+
});
|
|
32169
33450
|
Object.defineProperty(exports, "buildCertificateChain", {
|
|
32170
33451
|
enumerable: true,
|
|
32171
33452
|
get: function() {
|
|
@@ -33066,6 +34347,12 @@ Object.defineProperty(exports, "injectJpegMetadata", {
|
|
|
33066
34347
|
return injectJpegMetadata;
|
|
33067
34348
|
}
|
|
33068
34349
|
});
|
|
34350
|
+
Object.defineProperty(exports, "inspectEncryption", {
|
|
34351
|
+
enumerable: true,
|
|
34352
|
+
get: function() {
|
|
34353
|
+
return inspectEncryption;
|
|
34354
|
+
}
|
|
34355
|
+
});
|
|
33069
34356
|
Object.defineProperty(exports, "interpretContentStream", {
|
|
33070
34357
|
enumerable: true,
|
|
33071
34358
|
get: function() {
|
|
@@ -33438,6 +34725,12 @@ Object.defineProperty(exports, "sampleShadingColor", {
|
|
|
33438
34725
|
return sampleShadingColor;
|
|
33439
34726
|
}
|
|
33440
34727
|
});
|
|
34728
|
+
Object.defineProperty(exports, "sanitizePdf", {
|
|
34729
|
+
enumerable: true,
|
|
34730
|
+
get: function() {
|
|
34731
|
+
return sanitizePdf;
|
|
34732
|
+
}
|
|
34733
|
+
});
|
|
33441
34734
|
Object.defineProperty(exports, "saveDocumentIncremental", {
|
|
33442
34735
|
enumerable: true,
|
|
33443
34736
|
get: function() {
|
|
@@ -33456,6 +34749,12 @@ Object.defineProperty(exports, "saveIncrementalWithSignaturePreservation", {
|
|
|
33456
34749
|
return saveIncrementalWithSignaturePreservation;
|
|
33457
34750
|
}
|
|
33458
34751
|
});
|
|
34752
|
+
Object.defineProperty(exports, "scanPdfThreats", {
|
|
34753
|
+
enumerable: true,
|
|
34754
|
+
get: function() {
|
|
34755
|
+
return scanPdfThreats;
|
|
34756
|
+
}
|
|
34757
|
+
});
|
|
33459
34758
|
Object.defineProperty(exports, "searchTextItems", {
|
|
33460
34759
|
enumerable: true,
|
|
33461
34760
|
get: function() {
|
|
@@ -33732,6 +35031,12 @@ Object.defineProperty(exports, "verifyOfflineRevocation", {
|
|
|
33732
35031
|
return verifyOfflineRevocation;
|
|
33733
35032
|
}
|
|
33734
35033
|
});
|
|
35034
|
+
Object.defineProperty(exports, "verifyRedactions", {
|
|
35035
|
+
enumerable: true,
|
|
35036
|
+
get: function() {
|
|
35037
|
+
return verifyRedactions;
|
|
35038
|
+
}
|
|
35039
|
+
});
|
|
33735
35040
|
Object.defineProperty(exports, "verifySignatureDetailed", {
|
|
33736
35041
|
enumerable: true,
|
|
33737
35042
|
get: function() {
|
|
@@ -33757,4 +35062,4 @@ Object.defineProperty(exports, "wrapText", {
|
|
|
33757
35062
|
}
|
|
33758
35063
|
});
|
|
33759
35064
|
|
|
33760
|
-
//# sourceMappingURL=src-
|
|
35065
|
+
//# sourceMappingURL=src-C-mBizNU.cjs.map
|