@embedpdf/engine-core 3.0.0-next.11 → 3.0.0-next.13
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/dist/{AbortError-CRSooKsk.cjs → AbortError-Cq2igpZ6.cjs} +2 -2
- package/dist/{AbortError-CRSooKsk.cjs.map → AbortError-Cq2igpZ6.cjs.map} +1 -1
- package/dist/{AbortError-8-9JAX1Y.js → AbortError-DHqPBnYc.js} +2 -2
- package/dist/{AbortError-8-9JAX1Y.js.map → AbortError-DHqPBnYc.js.map} +1 -1
- package/dist/{AnnotationListSnapshot-C1U3XUxP.d.cts → AnnotationListSnapshot-D3mFut1e.d.cts} +1323 -214
- package/dist/{AnnotationListSnapshot-C1U3XUxP.d.ts → AnnotationListSnapshot-D3mFut1e.d.ts} +1323 -214
- package/dist/{DocumentSecurityService-6rhztZlz.d.cts → DocumentSecurityService-ClTZc0zO.d.cts} +2 -2
- package/dist/{DocumentSecurityService-7nL9aY75.d.ts → DocumentSecurityService-_JRjqvoI.d.ts} +2 -2
- package/dist/{Engine-D3868xIU.d.cts → Engine-Bx0NOfmd.d.cts} +219 -11
- package/dist/{Engine-CWnAKccD.d.ts → Engine-DxtpCYcP.d.ts} +219 -11
- package/dist/{PageInsertBlankInput-CzShuSJI.cjs → PageInsertBlankInput-CqXBmFhN.cjs} +37 -2
- package/dist/PageInsertBlankInput-CqXBmFhN.cjs.map +1 -0
- package/dist/{PageInsertBlankInput-WKY0tr6S.js → PageInsertBlankInput-cHq9-i-z.js} +37 -2
- package/dist/PageInsertBlankInput-cHq9-i-z.js.map +1 -0
- package/dist/{builders-Do6_4KnS.cjs → builders-Bqm8yTpi.cjs} +1483 -11
- package/dist/builders-Bqm8yTpi.cjs.map +1 -0
- package/dist/{builders-Cdz34nbF.js → builders-Dqo2X-dt.js} +1364 -12
- package/dist/builders-Dqo2X-dt.js.map +1 -0
- package/dist/conformance.cjs +1630 -107
- package/dist/conformance.cjs.map +1 -1
- package/dist/conformance.d.cts +117 -3
- package/dist/conformance.d.ts +117 -3
- package/dist/conformance.js +1627 -108
- package/dist/conformance.js.map +1 -1
- package/dist/{fold-C7I5GpBz.cjs → fold-2V29cxCV.cjs} +2 -2
- package/dist/{fold-C7I5GpBz.cjs.map → fold-2V29cxCV.cjs.map} +1 -1
- package/dist/{fold-BfyxxwS6.js → fold-CdEJTBTn.js} +2 -2
- package/dist/{fold-BfyxxwS6.js.map → fold-CdEJTBTn.js.map} +1 -1
- package/dist/index.cjs +33 -4
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -5
- package/dist/{resolver-D-jumvgM.cjs → resolver-CYVNV4hn.cjs} +216 -10
- package/dist/resolver-CYVNV4hn.cjs.map +1 -0
- package/dist/{resolver-CHG4waXL.js → resolver-inAVzsWt.js} +157 -11
- package/dist/resolver-inAVzsWt.js.map +1 -0
- package/dist/runtime.cjs +34 -5
- package/dist/runtime.d.cts +253 -5
- package/dist/runtime.d.ts +253 -5
- package/dist/runtime.js +6 -6
- package/dist/{schemas-XGrNVMDd.js → schemas-BLARNCgx.js} +469 -7
- package/dist/schemas-BLARNCgx.js.map +1 -0
- package/dist/{schemas-CS5iPpX5.cjs → schemas-DIqFMmrC.cjs} +672 -6
- package/dist/schemas-DIqFMmrC.cjs.map +1 -0
- package/dist/wire.cjs +250 -12
- package/dist/wire.cjs.map +1 -1
- package/dist/wire.d.cts +444 -4
- package/dist/wire.d.ts +444 -4
- package/dist/wire.js +214 -13
- package/dist/wire.js.map +1 -1
- package/package.json +1 -1
- package/dist/PageInsertBlankInput-CzShuSJI.cjs.map +0 -1
- package/dist/PageInsertBlankInput-WKY0tr6S.js.map +0 -1
- package/dist/builders-Cdz34nbF.js.map +0 -1
- package/dist/builders-Do6_4KnS.cjs.map +0 -1
- package/dist/resolver-CHG4waXL.js.map +0 -1
- package/dist/resolver-D-jumvgM.cjs.map +0 -1
- package/dist/schemas-CS5iPpX5.cjs.map +0 -1
- package/dist/schemas-XGrNVMDd.js.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
const require_fold = require("./fold-
|
|
2
|
-
const require_PageInsertBlankInput = require("./PageInsertBlankInput-
|
|
1
|
+
const require_fold = require("./fold-2V29cxCV.cjs");
|
|
2
|
+
const require_PageInsertBlankInput = require("./PageInsertBlankInput-CqXBmFhN.cjs");
|
|
3
|
+
const require_resolver = require("./resolver-CYVNV4hn.cjs");
|
|
3
4
|
//#region src/dto/PdfAction.ts
|
|
4
5
|
/**
|
|
5
6
|
* The one Table-240 decode, shared by the reader, the schema's producers,
|
|
@@ -43,6 +44,18 @@ function createPageImageHandle(result, blobSource) {
|
|
|
43
44
|
//#region src/dto/PdfSaveMode.ts
|
|
44
45
|
const DEFAULT_PDF_SAVE_MODE = "incremental";
|
|
45
46
|
//#endregion
|
|
47
|
+
//#region src/dto/RichText.ts
|
|
48
|
+
/** The plain-text projection of a rich document: paragraphs joined by `\r`,
|
|
49
|
+
* runs concatenated — what the engine writes to `/Contents`. */
|
|
50
|
+
function richTextPlainText(doc) {
|
|
51
|
+
return doc.paragraphs.map((p) => p.runs.map((r) => r.text).join("")).join("\r");
|
|
52
|
+
}
|
|
53
|
+
/** Plain text as rich paragraphs: one per line break, one run each, no
|
|
54
|
+
* overrides (the engine's own `/Contents` fallback shape). */
|
|
55
|
+
function richTextParagraphsFromPlainText(text) {
|
|
56
|
+
return text.split(/\r\n|\r|\n/).map((line) => ({ runs: [{ text: line }] }));
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
46
59
|
//#region src/identity/PageObjectNumber.ts
|
|
47
60
|
/**
|
|
48
61
|
* Type guard for the rare malformed-PDF case where a page is a direct
|
|
@@ -139,19 +152,25 @@ function isBlob(value) {
|
|
|
139
152
|
return typeof Blob !== "undefined" && value instanceof Blob;
|
|
140
153
|
}
|
|
141
154
|
/**
|
|
142
|
-
*
|
|
143
|
-
* when the view
|
|
155
|
+
* Copy a Uint8Array view into a fresh, exactly-sized ArrayBuffer the call
|
|
156
|
+
* owns. Always copies — even when the view spans its whole buffer — because
|
|
157
|
+
* the result may be transferred to a worker (detaching it), and the view is
|
|
158
|
+
* the caller's. Returning `view.buffer` here would let one transfer zero
|
|
159
|
+
* the caller's bytes (a stamp library's Uint8Array became single-use).
|
|
160
|
+
* Callers wanting a zero-copy hand-off need an explicit "give" form, as
|
|
161
|
+
* `pages.insert` has for bare ArrayBuffers; `BinarySource` has none.
|
|
144
162
|
*/
|
|
145
|
-
function
|
|
146
|
-
|
|
147
|
-
|
|
163
|
+
function toOwnedArrayBuffer(view) {
|
|
164
|
+
const copy = new ArrayBuffer(view.byteLength);
|
|
165
|
+
new Uint8Array(copy).set(view);
|
|
166
|
+
return copy;
|
|
148
167
|
}
|
|
149
168
|
/**
|
|
150
169
|
* Resolve any accepted `BinarySource` form into a `WireResource`.
|
|
151
170
|
* Async because Blob bytes can only be read asynchronously.
|
|
152
171
|
*/
|
|
153
172
|
async function resolveBinarySource(source) {
|
|
154
|
-
if (source instanceof Uint8Array) return { bytes:
|
|
173
|
+
if (source instanceof Uint8Array) return { bytes: toOwnedArrayBuffer(source) };
|
|
155
174
|
if (isBlob(source)) {
|
|
156
175
|
const blobName = blobFileName(source);
|
|
157
176
|
return {
|
|
@@ -162,7 +181,7 @@ async function resolveBinarySource(source) {
|
|
|
162
181
|
}
|
|
163
182
|
const { data, mimeType, name } = source;
|
|
164
183
|
const inner = data instanceof Uint8Array ? {
|
|
165
|
-
bytes:
|
|
184
|
+
bytes: toOwnedArrayBuffer(data),
|
|
166
185
|
mimeType: void 0,
|
|
167
186
|
name: void 0
|
|
168
187
|
} : {
|
|
@@ -849,6 +868,1336 @@ function decodeFieldRefKey(key) {
|
|
|
849
868
|
return null;
|
|
850
869
|
}
|
|
851
870
|
//#endregion
|
|
871
|
+
//#region src/signature/analysis/pdf-value.ts
|
|
872
|
+
/**
|
|
873
|
+
* Parse the fork's canonical shallow serialisation:
|
|
874
|
+
* dict <</Key value/Key2 value>> (keys sorted, `#xx`-escaped)
|
|
875
|
+
* array [item item]
|
|
876
|
+
* ref n 0 R
|
|
877
|
+
* stream stream(length,sha256hex)<<dict>>
|
|
878
|
+
* name /Name (with #xx escapes)
|
|
879
|
+
* string (literal with backslash escapes) or <hex>
|
|
880
|
+
* number -12.5 bool true|false null
|
|
881
|
+
*/
|
|
882
|
+
function parsePdfValue(text) {
|
|
883
|
+
const parser = new Parser(text);
|
|
884
|
+
const value = parser.value();
|
|
885
|
+
parser.skipSpaces();
|
|
886
|
+
if (!parser.atEnd()) throw new Error(`trailing input at ${parser.pos}: ${text.slice(parser.pos, parser.pos + 20)}`);
|
|
887
|
+
return value;
|
|
888
|
+
}
|
|
889
|
+
var Parser = class {
|
|
890
|
+
constructor(text) {
|
|
891
|
+
this.text = text;
|
|
892
|
+
require_fold._defineProperty(this, "pos", 0);
|
|
893
|
+
}
|
|
894
|
+
atEnd() {
|
|
895
|
+
return this.pos >= this.text.length;
|
|
896
|
+
}
|
|
897
|
+
skipSpaces() {
|
|
898
|
+
while (this.pos < this.text.length && this.text[this.pos] === " ") this.pos++;
|
|
899
|
+
}
|
|
900
|
+
value() {
|
|
901
|
+
this.skipSpaces();
|
|
902
|
+
const t = this.text;
|
|
903
|
+
if (t.startsWith("<<", this.pos)) return this.dict();
|
|
904
|
+
if (t.startsWith("stream(", this.pos)) return this.stream();
|
|
905
|
+
if (t[this.pos] === "[") return this.array();
|
|
906
|
+
if (t[this.pos] === "/") return {
|
|
907
|
+
t: "name",
|
|
908
|
+
v: this.name()
|
|
909
|
+
};
|
|
910
|
+
if (t[this.pos] === "(") return {
|
|
911
|
+
t: "string",
|
|
912
|
+
v: this.literalString()
|
|
913
|
+
};
|
|
914
|
+
if (t[this.pos] === "<") return {
|
|
915
|
+
t: "string",
|
|
916
|
+
v: this.hexString()
|
|
917
|
+
};
|
|
918
|
+
if (t.startsWith("true", this.pos)) {
|
|
919
|
+
this.pos += 4;
|
|
920
|
+
return {
|
|
921
|
+
t: "bool",
|
|
922
|
+
v: true
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
if (t.startsWith("false", this.pos)) {
|
|
926
|
+
this.pos += 5;
|
|
927
|
+
return {
|
|
928
|
+
t: "bool",
|
|
929
|
+
v: false
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
if (t.startsWith("null", this.pos)) {
|
|
933
|
+
this.pos += 4;
|
|
934
|
+
return { t: "null" };
|
|
935
|
+
}
|
|
936
|
+
const ref = /^(\d+) 0 R(?![0-9A-Za-z])/.exec(t.slice(this.pos));
|
|
937
|
+
if (ref) {
|
|
938
|
+
this.pos += ref[0].length;
|
|
939
|
+
return {
|
|
940
|
+
t: "ref",
|
|
941
|
+
num: Number(ref[1])
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
const num = /^[+-]?(?:\d+\.?\d*|\.\d+)/.exec(t.slice(this.pos));
|
|
945
|
+
if (num) {
|
|
946
|
+
this.pos += num[0].length;
|
|
947
|
+
return {
|
|
948
|
+
t: "number",
|
|
949
|
+
v: Number(num[0])
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
throw new Error(`unexpected input at ${this.pos}: ${t.slice(this.pos, this.pos + 20)}`);
|
|
953
|
+
}
|
|
954
|
+
dict() {
|
|
955
|
+
return {
|
|
956
|
+
t: "dict",
|
|
957
|
+
entries: this.dictEntries()
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
dictEntries() {
|
|
961
|
+
this.expect("<<");
|
|
962
|
+
const entries = {};
|
|
963
|
+
for (;;) {
|
|
964
|
+
this.skipSpaces();
|
|
965
|
+
if (this.text.startsWith(">>", this.pos)) {
|
|
966
|
+
this.pos += 2;
|
|
967
|
+
return entries;
|
|
968
|
+
}
|
|
969
|
+
if (this.text[this.pos] !== "/") throw new Error(`expected a key at ${this.pos}`);
|
|
970
|
+
const key = this.name();
|
|
971
|
+
this.skipSpaces();
|
|
972
|
+
entries[key] = this.value();
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
stream() {
|
|
976
|
+
const m = /^stream\((\d+),([0-9a-fA-F]*)\)/.exec(this.text.slice(this.pos));
|
|
977
|
+
if (!m) throw new Error(`malformed stream signature at ${this.pos}`);
|
|
978
|
+
this.pos += m[0].length;
|
|
979
|
+
const dict = this.dictEntries();
|
|
980
|
+
return {
|
|
981
|
+
t: "stream",
|
|
982
|
+
length: Number(m[1]),
|
|
983
|
+
sha256: m[2].toLowerCase(),
|
|
984
|
+
dict
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
array() {
|
|
988
|
+
this.expect("[");
|
|
989
|
+
const items = [];
|
|
990
|
+
for (;;) {
|
|
991
|
+
this.skipSpaces();
|
|
992
|
+
if (this.text[this.pos] === "]") {
|
|
993
|
+
this.pos++;
|
|
994
|
+
return {
|
|
995
|
+
t: "array",
|
|
996
|
+
items
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
items.push(this.value());
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
name() {
|
|
1003
|
+
this.expect("/");
|
|
1004
|
+
let out = "";
|
|
1005
|
+
while (this.pos < this.text.length) {
|
|
1006
|
+
const ch = this.text[this.pos];
|
|
1007
|
+
if (ch === " " || ch === "/" || ch === "[" || ch === "]" || ch === "<" || ch === ">" || ch === "(" || ch === ")") break;
|
|
1008
|
+
if (ch === "#" && /^[0-9A-Fa-f]{2}/.test(this.text.slice(this.pos + 1, this.pos + 3))) {
|
|
1009
|
+
out += String.fromCharCode(parseInt(this.text.slice(this.pos + 1, this.pos + 3), 16));
|
|
1010
|
+
this.pos += 3;
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
out += ch;
|
|
1014
|
+
this.pos++;
|
|
1015
|
+
}
|
|
1016
|
+
return out;
|
|
1017
|
+
}
|
|
1018
|
+
literalString() {
|
|
1019
|
+
this.expect("(");
|
|
1020
|
+
let depth = 1;
|
|
1021
|
+
let out = "";
|
|
1022
|
+
while (this.pos < this.text.length) {
|
|
1023
|
+
const ch = this.text[this.pos++];
|
|
1024
|
+
if (ch === "\\") {
|
|
1025
|
+
const next = this.text[this.pos++];
|
|
1026
|
+
switch (next) {
|
|
1027
|
+
case "n":
|
|
1028
|
+
out += "\n";
|
|
1029
|
+
break;
|
|
1030
|
+
case "r":
|
|
1031
|
+
out += "\r";
|
|
1032
|
+
break;
|
|
1033
|
+
case "t":
|
|
1034
|
+
out += " ";
|
|
1035
|
+
break;
|
|
1036
|
+
case "b":
|
|
1037
|
+
out += "\b";
|
|
1038
|
+
break;
|
|
1039
|
+
case "f":
|
|
1040
|
+
out += "\f";
|
|
1041
|
+
break;
|
|
1042
|
+
case "\n": break;
|
|
1043
|
+
case "\r":
|
|
1044
|
+
if (this.text[this.pos] === "\n") this.pos++;
|
|
1045
|
+
break;
|
|
1046
|
+
default: {
|
|
1047
|
+
const oct = /^[0-7]{1,3}/.exec(this.text.slice(this.pos - 1));
|
|
1048
|
+
if (oct) {
|
|
1049
|
+
out += String.fromCharCode(parseInt(oct[0], 8));
|
|
1050
|
+
this.pos += oct[0].length - 1;
|
|
1051
|
+
} else out += next;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
if (ch === "(") depth++;
|
|
1057
|
+
if (ch === ")") {
|
|
1058
|
+
depth--;
|
|
1059
|
+
if (depth === 0) return out;
|
|
1060
|
+
}
|
|
1061
|
+
out += ch;
|
|
1062
|
+
}
|
|
1063
|
+
throw new Error("unterminated string");
|
|
1064
|
+
}
|
|
1065
|
+
hexString() {
|
|
1066
|
+
this.expect("<");
|
|
1067
|
+
const end = this.text.indexOf(">", this.pos);
|
|
1068
|
+
if (end < 0) throw new Error("unterminated hex string");
|
|
1069
|
+
const hex = this.text.slice(this.pos, end).replace(/\s+/g, "");
|
|
1070
|
+
this.pos = end + 1;
|
|
1071
|
+
let out = "";
|
|
1072
|
+
for (let i = 0; i < hex.length; i += 2) out += String.fromCharCode(parseInt(hex.slice(i, i + 2).padEnd(2, "0"), 16));
|
|
1073
|
+
return out;
|
|
1074
|
+
}
|
|
1075
|
+
expect(token) {
|
|
1076
|
+
if (!this.text.startsWith(token, this.pos)) throw new Error(`expected ${token} at ${this.pos}`);
|
|
1077
|
+
this.pos += token.length;
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
/** Structural equality of two parsed values. */
|
|
1081
|
+
function pdfValueEquals(a, b) {
|
|
1082
|
+
return stableStringify(a) === stableStringify(b);
|
|
1083
|
+
}
|
|
1084
|
+
function stableStringify(value) {
|
|
1085
|
+
if (value === null) return "null";
|
|
1086
|
+
switch (value.t) {
|
|
1087
|
+
case "dict": return `<<${Object.keys(value.entries).sort().map((k) => `/${k} ${stableStringify(value.entries[k])}`).join("")}>>`;
|
|
1088
|
+
case "stream": return `stream(${value.length},${value.sha256})${stableStringify({
|
|
1089
|
+
t: "dict",
|
|
1090
|
+
entries: value.dict
|
|
1091
|
+
})}`;
|
|
1092
|
+
case "array": return `[${value.items.map(stableStringify).join(" ")}]`;
|
|
1093
|
+
case "ref": return `${value.num} 0 R`;
|
|
1094
|
+
case "name": return `/${encodeName(value.v)}`;
|
|
1095
|
+
case "string": return `(${value.v.replace(/[\\()]/g, (ch) => `\\${ch}`)})`;
|
|
1096
|
+
case "number": return String(value.v);
|
|
1097
|
+
case "bool": return value.v ? "true" : "false";
|
|
1098
|
+
case "null": return "null";
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
function dictEntries(value) {
|
|
1102
|
+
if (!value) return null;
|
|
1103
|
+
if (value.t === "dict") return value.entries;
|
|
1104
|
+
if (value.t === "stream") return value.dict;
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
1107
|
+
/** Keys whose value differs (added, removed, or changed) between two dictionaries. */
|
|
1108
|
+
function changedKeys(a, b) {
|
|
1109
|
+
const ea = dictEntries(a) ?? {};
|
|
1110
|
+
const eb = dictEntries(b) ?? {};
|
|
1111
|
+
const out = /* @__PURE__ */ new Set();
|
|
1112
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(ea), ...Object.keys(eb)])) if (!pdfValueEquals(ea[key] ?? null, eb[key] ?? null)) out.add(key);
|
|
1113
|
+
return out;
|
|
1114
|
+
}
|
|
1115
|
+
function refsOf(value) {
|
|
1116
|
+
if (!value) return [];
|
|
1117
|
+
if (value.t === "ref") return [value.num];
|
|
1118
|
+
if (value.t === "array") return value.items.flatMap(refsOf);
|
|
1119
|
+
return [];
|
|
1120
|
+
}
|
|
1121
|
+
/** `#xx`-escape a name the way the PDF writer does: delimiters, whitespace, `#`, and non-printables. */
|
|
1122
|
+
function encodeName(name) {
|
|
1123
|
+
let out = "";
|
|
1124
|
+
for (const ch of name) {
|
|
1125
|
+
const code = ch.charCodeAt(0);
|
|
1126
|
+
if (code < 33 || code > 126 || "#/[]<>(){}%".includes(ch)) out += `#${code.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
1127
|
+
else out += ch;
|
|
1128
|
+
}
|
|
1129
|
+
return out;
|
|
1130
|
+
}
|
|
1131
|
+
//#endregion
|
|
1132
|
+
//#region src/signature/analysis/verdict.ts
|
|
1133
|
+
const RANK = {
|
|
1134
|
+
unchanged: 0,
|
|
1135
|
+
permitted: 1,
|
|
1136
|
+
indeterminate: 2,
|
|
1137
|
+
forbidden: 3
|
|
1138
|
+
};
|
|
1139
|
+
/** The worst of several verdicts (a certification replay's steps). */
|
|
1140
|
+
function worstVerdict(steps) {
|
|
1141
|
+
let worst = "unchanged";
|
|
1142
|
+
for (const s of steps) if (RANK[s.verdict] > RANK[worst]) worst = s.verdict;
|
|
1143
|
+
return worst;
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* One aggregation rule everywhere: a proven violation decides, whatever
|
|
1147
|
+
* else could not be established; missing evidence never becomes a pass.
|
|
1148
|
+
*/
|
|
1149
|
+
function conclude(findings, hasEffectiveChanges) {
|
|
1150
|
+
if (findings.some((f) => f.verdict === "forbidden")) return "forbidden";
|
|
1151
|
+
if (findings.some((f) => f.verdict === "incomplete")) return "indeterminate";
|
|
1152
|
+
return hasEffectiveChanges ? "permitted" : "unchanged";
|
|
1153
|
+
}
|
|
1154
|
+
/** The finding that explains a verdict: the violation, else the gap, else the most notable allowance. */
|
|
1155
|
+
function primaryFinding(findings) {
|
|
1156
|
+
return findings.find((f) => f.verdict === "forbidden") ?? findings.find((f) => f.verdict === "incomplete") ?? findings.find((f) => f.verdict === "permitted");
|
|
1157
|
+
}
|
|
1158
|
+
function assessmentOf(step) {
|
|
1159
|
+
const primary = primaryFinding(step.findings);
|
|
1160
|
+
return {
|
|
1161
|
+
verdict: step.verdict,
|
|
1162
|
+
complete: !step.findings.some((f) => f.verdict === "incomplete"),
|
|
1163
|
+
...primary ? { primary } : {},
|
|
1164
|
+
findings: step.findings
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* A certification window: the net verdict and every replayed step, under
|
|
1169
|
+
* the same rule. A step's violation is a violation of the certification
|
|
1170
|
+
* even when the final state no longer shows it (corpus v3/83).
|
|
1171
|
+
*/
|
|
1172
|
+
function combine(net, steps) {
|
|
1173
|
+
const findings = [...net.findings];
|
|
1174
|
+
for (const s of steps) {
|
|
1175
|
+
if (s === net) continue;
|
|
1176
|
+
for (const f of s.findings) {
|
|
1177
|
+
if (f.verdict === "permitted") continue;
|
|
1178
|
+
findings.push({
|
|
1179
|
+
...f,
|
|
1180
|
+
detail: `revision ${s.newer}: ${f.detail ?? f.rule}`
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
const verdict = conclude(findings, net.verdict !== "unchanged" || steps.some((s) => s.verdict !== "unchanged"));
|
|
1185
|
+
const primary = primaryFinding(findings);
|
|
1186
|
+
return {
|
|
1187
|
+
verdict,
|
|
1188
|
+
complete: !findings.some((f) => f.verdict === "incomplete"),
|
|
1189
|
+
...primary ? { primary } : {},
|
|
1190
|
+
findings
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
//#endregion
|
|
1194
|
+
//#region src/signature/analysis/evaluate.ts
|
|
1195
|
+
const SIDES = ["old", "new"];
|
|
1196
|
+
const TRAILER_KEYS = /* @__PURE__ */ new Set([
|
|
1197
|
+
"Size",
|
|
1198
|
+
"Prev",
|
|
1199
|
+
"XRefStm",
|
|
1200
|
+
"Info",
|
|
1201
|
+
"ID",
|
|
1202
|
+
"Root",
|
|
1203
|
+
"Type",
|
|
1204
|
+
"W",
|
|
1205
|
+
"Index",
|
|
1206
|
+
"Filter",
|
|
1207
|
+
"DecodeParms",
|
|
1208
|
+
"Length",
|
|
1209
|
+
"Encrypt"
|
|
1210
|
+
]);
|
|
1211
|
+
const CATALOG_KEYS = /* @__PURE__ */ new Set([
|
|
1212
|
+
"Metadata",
|
|
1213
|
+
"Extensions",
|
|
1214
|
+
"Version",
|
|
1215
|
+
"DSS",
|
|
1216
|
+
"AcroForm",
|
|
1217
|
+
"Perms"
|
|
1218
|
+
]);
|
|
1219
|
+
const ACROFORM_KEYS = /* @__PURE__ */ new Set([
|
|
1220
|
+
"DR",
|
|
1221
|
+
"DA",
|
|
1222
|
+
"Q",
|
|
1223
|
+
"SigFlags",
|
|
1224
|
+
"NeedAppearances",
|
|
1225
|
+
"Fields"
|
|
1226
|
+
]);
|
|
1227
|
+
/**
|
|
1228
|
+
* What a form field may change after a signature, by the level in force.
|
|
1229
|
+
* Every key here has a corpus case behind it (`signature-compat`); a key
|
|
1230
|
+
* not listed is forbidden until one does.
|
|
1231
|
+
*
|
|
1232
|
+
* annotate (an approval signature: "Form Fill-in, Signing and Commenting
|
|
1233
|
+
* are allowed"): everything but the field's identity and its place in
|
|
1234
|
+
* the tree. v1/08 /V+/AP, 09 /Ff, 10 /Rect, 11 /F, 18 /TU, 21 /DA; 13 /T
|
|
1235
|
+
* forbidden. /AA (v1/12) and /A are accepted by Acrobat and deliberately
|
|
1236
|
+
* NOT listed: an action on a signed form is executable content.
|
|
1237
|
+
*
|
|
1238
|
+
* fill (a P=2 certification): value and appearance. v1/26 /V+/AP, v3/64
|
|
1239
|
+
* /Ff (ReadOnly added), 65 /DA, 68 /AP stream; v3/66 /TU, 67 /F, v2/50
|
|
1240
|
+
* /Rect forbidden.
|
|
1241
|
+
*/
|
|
1242
|
+
const FIELD_KEYS_BY_LEVEL = {
|
|
1243
|
+
annotate: /* @__PURE__ */ new Set([
|
|
1244
|
+
"V",
|
|
1245
|
+
"AS",
|
|
1246
|
+
"AP",
|
|
1247
|
+
"I",
|
|
1248
|
+
"DA",
|
|
1249
|
+
"Ff",
|
|
1250
|
+
"RV",
|
|
1251
|
+
"MK",
|
|
1252
|
+
"Q",
|
|
1253
|
+
"TU",
|
|
1254
|
+
"F",
|
|
1255
|
+
"Rect",
|
|
1256
|
+
"BS",
|
|
1257
|
+
"Opt",
|
|
1258
|
+
"DV",
|
|
1259
|
+
"MaxLen"
|
|
1260
|
+
]),
|
|
1261
|
+
fill: /* @__PURE__ */ new Set([
|
|
1262
|
+
"V",
|
|
1263
|
+
"AS",
|
|
1264
|
+
"AP",
|
|
1265
|
+
"I",
|
|
1266
|
+
"RV",
|
|
1267
|
+
"DA",
|
|
1268
|
+
"Ff"
|
|
1269
|
+
])
|
|
1270
|
+
};
|
|
1271
|
+
/** A separate widget dictionary: the presentation subset of the same tables. */
|
|
1272
|
+
const WIDGET_KEYS_BY_LEVEL = {
|
|
1273
|
+
annotate: /* @__PURE__ */ new Set([
|
|
1274
|
+
"AS",
|
|
1275
|
+
"AP",
|
|
1276
|
+
"DA",
|
|
1277
|
+
"MK",
|
|
1278
|
+
"Q",
|
|
1279
|
+
"TU",
|
|
1280
|
+
"F",
|
|
1281
|
+
"Rect",
|
|
1282
|
+
"BS"
|
|
1283
|
+
]),
|
|
1284
|
+
fill: /* @__PURE__ */ new Set([
|
|
1285
|
+
"AS",
|
|
1286
|
+
"AP",
|
|
1287
|
+
"DA"
|
|
1288
|
+
])
|
|
1289
|
+
};
|
|
1290
|
+
/**
|
|
1291
|
+
* Signing an EXISTING field: its value, appearance and ReadOnly at any
|
|
1292
|
+
* level; after an approval signature also its tooltip and a /Lock installed
|
|
1293
|
+
* with the signature (v1/18, 19: both valid; under P=2 the combination is
|
|
1294
|
+
* rejected, v3/69, so neither is allowed at `fill`).
|
|
1295
|
+
*/
|
|
1296
|
+
const SIGNED_FIELD_KEYS_BY_LEVEL = {
|
|
1297
|
+
annotate: /* @__PURE__ */ new Set([
|
|
1298
|
+
"V",
|
|
1299
|
+
"AP",
|
|
1300
|
+
"AS",
|
|
1301
|
+
"Ff",
|
|
1302
|
+
"TU",
|
|
1303
|
+
"Lock"
|
|
1304
|
+
]),
|
|
1305
|
+
fill: /* @__PURE__ */ new Set([
|
|
1306
|
+
"V",
|
|
1307
|
+
"AP",
|
|
1308
|
+
"AS",
|
|
1309
|
+
"Ff"
|
|
1310
|
+
])
|
|
1311
|
+
};
|
|
1312
|
+
const SIGNED_WIDGET_KEYS = /* @__PURE__ */ new Set(["AP", "AS"]);
|
|
1313
|
+
const FF_READ_ONLY = 1;
|
|
1314
|
+
function fieldKeysFor(level) {
|
|
1315
|
+
return level === "annotate" ? FIELD_KEYS_BY_LEVEL.annotate : FIELD_KEYS_BY_LEVEL.fill;
|
|
1316
|
+
}
|
|
1317
|
+
function widgetKeysFor(level) {
|
|
1318
|
+
return level === "annotate" ? WIDGET_KEYS_BY_LEVEL.annotate : WIDGET_KEYS_BY_LEVEL.fill;
|
|
1319
|
+
}
|
|
1320
|
+
function signedFieldKeysFor(level) {
|
|
1321
|
+
return level === "annotate" ? SIGNED_FIELD_KEYS_BY_LEVEL.annotate : SIGNED_FIELD_KEYS_BY_LEVEL.fill;
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Rules whose permitted findings do not make a revision a CHANGE of the
|
|
1325
|
+
* document: an object written again with the sealed value, an object nobody
|
|
1326
|
+
* references, a cross-reference container, the trailer's own bookkeeping.
|
|
1327
|
+
* A window with nothing else is `unchanged` (Acrobat: "not modified").
|
|
1328
|
+
*/
|
|
1329
|
+
const NON_EFFECTIVE_RULES = /* @__PURE__ */ new Set([
|
|
1330
|
+
"identical-rewrite",
|
|
1331
|
+
"orphan",
|
|
1332
|
+
"xref-container",
|
|
1333
|
+
"trailer"
|
|
1334
|
+
]);
|
|
1335
|
+
/** The level a step runs at, and the locks it enforces: what the OLDER revision's signatures established. */
|
|
1336
|
+
function restrictionsOf(before) {
|
|
1337
|
+
const protection = require_resolver.deriveProtection(before.signatures);
|
|
1338
|
+
return {
|
|
1339
|
+
level: protection.judged ?? "annotate",
|
|
1340
|
+
locks: protection.fieldLocks
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Judge one pairwise step. Every reference to a changed object, in the
|
|
1345
|
+
* older AND the newer revision, must be claimed by a rule that inspected
|
|
1346
|
+
* that use; an unclaimed reference is `unexplained` and forbidden. A
|
|
1347
|
+
* truncated value is `incomplete`, never permitted. Pure: JSON in, JSON
|
|
1348
|
+
* out, the same code in the browser worker, on the server and in tests.
|
|
1349
|
+
*/
|
|
1350
|
+
function evaluateStep(input) {
|
|
1351
|
+
const changes = withEvidenceCheck(input.changes);
|
|
1352
|
+
const { level, locks } = input.restrictions ?? restrictionsOf(input.before);
|
|
1353
|
+
const levelInForce = input.levelOverride ?? level;
|
|
1354
|
+
const ctx = new StepContext({
|
|
1355
|
+
...input,
|
|
1356
|
+
changes
|
|
1357
|
+
}, levelInForce, locks);
|
|
1358
|
+
ruleRevisionHealth(ctx);
|
|
1359
|
+
ruleXrefContainer(ctx);
|
|
1360
|
+
ruleIdenticalRewrite(ctx);
|
|
1361
|
+
ruleFieldLock(ctx);
|
|
1362
|
+
ruleTrailer(ctx);
|
|
1363
|
+
ruleInfo(ctx);
|
|
1364
|
+
ruleMetadata(ctx);
|
|
1365
|
+
ruleCatalog(ctx);
|
|
1366
|
+
ruleAcroForm(ctx);
|
|
1367
|
+
ruleDss(ctx);
|
|
1368
|
+
ruleSignatureFieldAdded(ctx);
|
|
1369
|
+
ruleSignatureAdded(ctx);
|
|
1370
|
+
ruleFormFill(ctx);
|
|
1371
|
+
ruleAnnotation(ctx);
|
|
1372
|
+
ruleUnexplained(ctx);
|
|
1373
|
+
const verdict = conclude(ctx.findings, ctx.findings.some((f) => f.verdict === "permitted" && !NON_EFFECTIVE_RULES.has(f.rule)));
|
|
1374
|
+
return {
|
|
1375
|
+
older: input.older,
|
|
1376
|
+
newer: input.newer,
|
|
1377
|
+
levelInForce,
|
|
1378
|
+
locks,
|
|
1379
|
+
verdict,
|
|
1380
|
+
findings: ctx.findings,
|
|
1381
|
+
changes
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* The evaluator never trusts its transport to be honest about gaps: a side
|
|
1386
|
+
* that exists must carry a value, and an object whose value is missing is
|
|
1387
|
+
* judged as truncated, whatever the flag says. Cross-reference containers
|
|
1388
|
+
* are exempt (their members are judged on their own).
|
|
1389
|
+
*/
|
|
1390
|
+
function withEvidenceCheck(changes) {
|
|
1391
|
+
return changes.map((c) => {
|
|
1392
|
+
if (c.value.truncated || c.kind === "xref" || c.kind === "objstm") return c;
|
|
1393
|
+
const missingOld = c.present.old && (c.value.old === null || c.read?.old === "failed");
|
|
1394
|
+
const missingNew = c.present.new && (c.value.new === null || c.read?.new === "failed");
|
|
1395
|
+
return missingOld || missingNew ? {
|
|
1396
|
+
...c,
|
|
1397
|
+
value: {
|
|
1398
|
+
...c.value,
|
|
1399
|
+
truncated: true
|
|
1400
|
+
}
|
|
1401
|
+
} : c;
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* The transport's verdict on each revision as a whole. A sealed revision
|
|
1406
|
+
* Acrobat cannot judge later changes to (sparse cross-reference table, a
|
|
1407
|
+
* reachable bare-reference object) makes every step over it indeterminate:
|
|
1408
|
+
* our verdict would otherwise disagree with the validator recipients use,
|
|
1409
|
+
* and for a reason that has nothing to do with the change. An incomplete
|
|
1410
|
+
* reachability walk on either side leaves every use unproven.
|
|
1411
|
+
*/
|
|
1412
|
+
function ruleRevisionHealth(ctx) {
|
|
1413
|
+
const health = ctx.input.health;
|
|
1414
|
+
if (!health) return;
|
|
1415
|
+
const base = health.old;
|
|
1416
|
+
if (base.sparseXref || base.bareReferences > 0) {
|
|
1417
|
+
const why = [base.sparseXref ? "its cross-reference table has no entry for some object numbers below /Size" : null, base.bareReferences > 0 ? `${base.bareReferences} reachable indirect object(s) consist of a bare reference` : null].filter((x) => x !== null).join("; ");
|
|
1418
|
+
ctx.findings.push({
|
|
1419
|
+
rule: "base-unverifiable",
|
|
1420
|
+
verdict: "incomplete",
|
|
1421
|
+
objectNumber: 0,
|
|
1422
|
+
detail: `the signed revision cannot be verified against later changes by Acrobat (${why}); no verdict is given rather than one Acrobat would contradict`
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
for (const side of SIDES) if (!health[side].referrersComplete) ctx.findings.push({
|
|
1426
|
+
rule: "unexplained",
|
|
1427
|
+
verdict: "incomplete",
|
|
1428
|
+
objectNumber: 0,
|
|
1429
|
+
detail: `the ${side === "old" ? "older" : "newer"} revision's reachability walk hit its budget; not every use of a changed object is known`
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Same effective value on both sides: same canonical serialisation (keys
|
|
1434
|
+
* sorted, whitespace normalised - Acrobat compares dictionaries by value,
|
|
1435
|
+
* corpus v3/61-62), same stream data (by bytes, v2/45), and both sides
|
|
1436
|
+
* actually read. A failed read serialises as "null" and would match another
|
|
1437
|
+
* failed read - evidence of nothing.
|
|
1438
|
+
*/
|
|
1439
|
+
function sameEffectiveValue(c) {
|
|
1440
|
+
return c.change === "modified" && !c.value.truncated && c.read?.old !== "failed" && c.read?.new !== "failed" && c.raw.old !== null && c.raw.new !== null && c.raw.old === c.raw.new && !c.streamDataChanged;
|
|
1441
|
+
}
|
|
1442
|
+
function indexRoles(s) {
|
|
1443
|
+
const fieldByObj = /* @__PURE__ */ new Map();
|
|
1444
|
+
const widgetToField = /* @__PURE__ */ new Map();
|
|
1445
|
+
const sigFields = /* @__PURE__ */ new Set();
|
|
1446
|
+
for (const f of s.fields) {
|
|
1447
|
+
fieldByObj.set(f.objectNumber, f);
|
|
1448
|
+
if (f.family === "signature") sigFields.add(f.objectNumber);
|
|
1449
|
+
for (const w of f.widgets) widgetToField.set(w, f.objectNumber);
|
|
1450
|
+
}
|
|
1451
|
+
const signedFields = /* @__PURE__ */ new Set();
|
|
1452
|
+
for (const sig of s.signatures) if (sig.signed && sig.field.kind === "objectNumber") signedFields.add(sig.field.fieldObjectNumber);
|
|
1453
|
+
return {
|
|
1454
|
+
fieldByObj,
|
|
1455
|
+
widgetToField,
|
|
1456
|
+
pages: new Set(s.pages),
|
|
1457
|
+
signedFields,
|
|
1458
|
+
sigFields
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
function edgeKey(e) {
|
|
1462
|
+
return `${e.parent}:${e.label}`;
|
|
1463
|
+
}
|
|
1464
|
+
var StepContext = class {
|
|
1465
|
+
constructor(input, level, locks) {
|
|
1466
|
+
this.input = input;
|
|
1467
|
+
this.level = level;
|
|
1468
|
+
this.locks = locks;
|
|
1469
|
+
require_fold._defineProperty(this, "findings", []);
|
|
1470
|
+
require_fold._defineProperty(this, "byNum", /* @__PURE__ */ new Map());
|
|
1471
|
+
require_fold._defineProperty(this, "before", void 0);
|
|
1472
|
+
require_fold._defineProperty(this, "after", void 0);
|
|
1473
|
+
require_fold._defineProperty(this, "claims", /* @__PURE__ */ new Map());
|
|
1474
|
+
require_fold._defineProperty(this, "locked", /* @__PURE__ */ new Set());
|
|
1475
|
+
for (const c of input.changes) this.byNum.set(c.objectNumber, c);
|
|
1476
|
+
this.before = indexRoles(input.before);
|
|
1477
|
+
this.after = indexRoles(input.after);
|
|
1478
|
+
}
|
|
1479
|
+
get changes() {
|
|
1480
|
+
return this.input.changes;
|
|
1481
|
+
}
|
|
1482
|
+
allows(needed) {
|
|
1483
|
+
return require_resolver.levelAllows(this.level, needed);
|
|
1484
|
+
}
|
|
1485
|
+
claimEdge(c, side, edge, rule, allow = true) {
|
|
1486
|
+
if (this.locked.has(c.objectNumber)) return;
|
|
1487
|
+
const key = `${c.objectNumber}:${side}:${edgeKey(edge)}`;
|
|
1488
|
+
if (!this.claims.has(key)) this.claims.set(key, {
|
|
1489
|
+
rule,
|
|
1490
|
+
allow
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
claimAll(c, rule, allow = true) {
|
|
1494
|
+
for (const side of SIDES) for (const e of c.usage[side]) this.claimEdge(c, side, e, rule, allow);
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Claim the references that are the same in both revisions; a reference
|
|
1498
|
+
* gained or lost stays unexplained. `allow: false` explains a condemned
|
|
1499
|
+
* object's edges (one cause in the report, not a cascade) without letting
|
|
1500
|
+
* anything hang off it.
|
|
1501
|
+
*/
|
|
1502
|
+
claimStable(c, rule, allow = true) {
|
|
1503
|
+
const oldKeys = new Set(c.usage.old.map(edgeKey));
|
|
1504
|
+
const newKeys = new Set(c.usage.new.map(edgeKey));
|
|
1505
|
+
for (const e of c.usage.old) if (newKeys.has(edgeKey(e))) this.claimEdge(c, "old", e, rule, allow);
|
|
1506
|
+
for (const e of c.usage.new) if (oldKeys.has(edgeKey(e))) this.claimEdge(c, "new", e, rule, allow);
|
|
1507
|
+
}
|
|
1508
|
+
isClaimed(c, side, edge) {
|
|
1509
|
+
return this.claims.has(`${c.objectNumber}:${side}:${edgeKey(edge)}`);
|
|
1510
|
+
}
|
|
1511
|
+
/** The use was inspected by a rule that allowed it. */
|
|
1512
|
+
isAllowed(c, side, edge) {
|
|
1513
|
+
return this.claims.get(`${c.objectNumber}:${side}:${edgeKey(edge)}`)?.allow === true;
|
|
1514
|
+
}
|
|
1515
|
+
permitted(c, rule, detail) {
|
|
1516
|
+
if (this.locked.has(c.objectNumber)) return;
|
|
1517
|
+
this.findings.push({
|
|
1518
|
+
rule,
|
|
1519
|
+
verdict: "permitted",
|
|
1520
|
+
objectNumber: c.objectNumber,
|
|
1521
|
+
detail
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* A violation is only proven on evidence: a rule condemning an object
|
|
1526
|
+
* whose value could not be read or inspected is reporting what it could
|
|
1527
|
+
* not see, and that is `incomplete`, never `forbidden`.
|
|
1528
|
+
*/
|
|
1529
|
+
forbidden(c, rule, detail, edge) {
|
|
1530
|
+
this.findings.push({
|
|
1531
|
+
rule,
|
|
1532
|
+
verdict: c.value.truncated ? "incomplete" : "forbidden",
|
|
1533
|
+
objectNumber: c.objectNumber,
|
|
1534
|
+
edge: edge ? edgeKey(edge) : void 0,
|
|
1535
|
+
detail: c.value.truncated ? `${detail} (judged on missing evidence: not proven)` : detail
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Claim every reference under a set of parents (an appearance subtree,
|
|
1540
|
+
* the DSS): objects whose references come from `parents` through labels
|
|
1541
|
+
* `accept`, then everything referenced from those objects, and so on.
|
|
1542
|
+
* Runs per side, because a replaced appearance stream exists on one
|
|
1543
|
+
* side only.
|
|
1544
|
+
*/
|
|
1545
|
+
claimSubtree(side, roots, accept, rule) {
|
|
1546
|
+
const joined = /* @__PURE__ */ new Set();
|
|
1547
|
+
let grew = true;
|
|
1548
|
+
while (grew) {
|
|
1549
|
+
grew = false;
|
|
1550
|
+
for (const c of this.changes) {
|
|
1551
|
+
if (roots.has(c.objectNumber) || joined.has(c.objectNumber)) continue;
|
|
1552
|
+
if (this.locked.has(c.objectNumber)) continue;
|
|
1553
|
+
const edges = c.usage[side];
|
|
1554
|
+
if (edges.length === 0) continue;
|
|
1555
|
+
const inside = edges.filter((e) => roots.has(e.parent) && accept(e.label) || joined.has(e.parent));
|
|
1556
|
+
if (inside.length === 0) continue;
|
|
1557
|
+
for (const e of inside) this.claimEdge(c, side, e, rule);
|
|
1558
|
+
if (edges.every((e) => inside.includes(e) || this.isAllowed(c, side, e))) {
|
|
1559
|
+
joined.add(c.objectNumber);
|
|
1560
|
+
grew = true;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
isIdentical(c) {
|
|
1566
|
+
return sameEffectiveValue(c);
|
|
1567
|
+
}
|
|
1568
|
+
/** Signatures signed in the newer revision but not the older: this step's signing events. */
|
|
1569
|
+
newlySigned() {
|
|
1570
|
+
const out = [];
|
|
1571
|
+
for (const sig of this.input.after.signatures) {
|
|
1572
|
+
if (!sig.signed || sig.field.kind !== "objectNumber") continue;
|
|
1573
|
+
if (this.before.signedFields.has(sig.field.fieldObjectNumber)) continue;
|
|
1574
|
+
out.push({
|
|
1575
|
+
fieldObjectNumber: sig.field.fieldObjectNumber,
|
|
1576
|
+
sig
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
return out;
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
function ruleXrefContainer(ctx) {
|
|
1583
|
+
for (const c of ctx.changes) if (c.kind === "xref" || c.kind === "objstm") {
|
|
1584
|
+
ctx.claimAll(c, "xref-container");
|
|
1585
|
+
ctx.permitted(c, "xref-container", "cross-reference container; members are judged on their own");
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* An object written again with the value the sealed revision holds is not
|
|
1590
|
+
* a modification: Acrobat compares objects by value and reports such a
|
|
1591
|
+
* revision as "not modified" for every role tried - document information,
|
|
1592
|
+
* a custom catalog entry, a page, the signed signature's own widget, a text
|
|
1593
|
+
* field, an annotation, the encryption dictionary, reserialised with keys
|
|
1594
|
+
* reordered, under a certification, under a field lock (corpus v1/02-07,
|
|
1595
|
+
* 23, v2/47, v3/54-62, 70, 71, 77). Under P=1 nothing is established and
|
|
1596
|
+
* the rewrite stays forbidden. Claimed here so the object-kind rules skip
|
|
1597
|
+
* it; the verdict treats it as no effective change.
|
|
1598
|
+
*/
|
|
1599
|
+
function ruleIdenticalRewrite(ctx) {
|
|
1600
|
+
for (const c of ctx.changes) {
|
|
1601
|
+
if (!ctx.isIdentical(c)) continue;
|
|
1602
|
+
if (ctx.allows("fill")) {
|
|
1603
|
+
ctx.claimAll(c, "identical-rewrite");
|
|
1604
|
+
ctx.permitted(c, "identical-rewrite", "written again with the sealed value");
|
|
1605
|
+
} else {
|
|
1606
|
+
ctx.claimAll(c, "identical-rewrite", false);
|
|
1607
|
+
ctx.forbidden(c, "identical-rewrite", `written again with the sealed value under level '${ctx.level}' (not established for this level)`);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
function ruleTrailer(ctx) {
|
|
1612
|
+
const c = ctx.byNum.get(0);
|
|
1613
|
+
if (!c) return;
|
|
1614
|
+
const changed = changedKeys(c.value.old, c.value.new);
|
|
1615
|
+
const bad = [...changed].filter((k) => !TRAILER_KEYS.has(k));
|
|
1616
|
+
const oldRoot = dictEntries(c.value.old)?.Root;
|
|
1617
|
+
const newRoot = dictEntries(c.value.new)?.Root;
|
|
1618
|
+
if (bad.length > 0) {
|
|
1619
|
+
ctx.forbidden(c, "trailer", `trailer keys changed: ${bad.join(", ")}`);
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
if (changed.has("Root") && !pdfValueEquals(oldRoot ?? null, newRoot ?? null)) {
|
|
1623
|
+
ctx.forbidden(c, "trailer", "the trailer points at a different catalog");
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
if (changed.has("Encrypt") && !pdfValueEquals(dictEntries(c.value.old)?.Encrypt ?? null, dictEntries(c.value.new)?.Encrypt ?? null)) {
|
|
1627
|
+
ctx.forbidden(c, "trailer", "the trailer points at a different encryption dictionary");
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
ctx.claimAll(c, "trailer");
|
|
1631
|
+
ctx.permitted(c, "trailer");
|
|
1632
|
+
}
|
|
1633
|
+
function onlyEdges(c, test) {
|
|
1634
|
+
const all = [...c.usage.old, ...c.usage.new];
|
|
1635
|
+
return all.length > 0 && all.every(test);
|
|
1636
|
+
}
|
|
1637
|
+
function ruleInfo(ctx) {
|
|
1638
|
+
for (const c of ctx.changes) {
|
|
1639
|
+
if (ctx.isIdentical(c)) continue;
|
|
1640
|
+
if (c.objectNumber !== 0 && onlyEdges(c, (e) => e.parent === 0 && e.label === "Info")) {
|
|
1641
|
+
ctx.claimAll(c, "info");
|
|
1642
|
+
ctx.permitted(c, "info", "document information dictionary");
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
function ruleMetadata(ctx) {
|
|
1647
|
+
const roots = /* @__PURE__ */ new Set([ctx.input.before.root, ctx.input.after.root]);
|
|
1648
|
+
for (const c of ctx.changes) {
|
|
1649
|
+
if (ctx.isIdentical(c)) continue;
|
|
1650
|
+
if (onlyEdges(c, (e) => roots.has(e.parent) && e.label === "Metadata")) {
|
|
1651
|
+
ctx.claimAll(c, "metadata");
|
|
1652
|
+
ctx.permitted(c, "metadata", "XMP metadata stream");
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
function ruleCatalog(ctx) {
|
|
1657
|
+
const root = ctx.input.after.root;
|
|
1658
|
+
const c = ctx.byNum.get(root);
|
|
1659
|
+
if (!c || ctx.isIdentical(c)) return;
|
|
1660
|
+
const changed = changedKeys(c.value.old, c.value.new);
|
|
1661
|
+
const bad = [...changed].filter((k) => !CATALOG_KEYS.has(k));
|
|
1662
|
+
if (bad.length > 0) {
|
|
1663
|
+
ctx.forbidden(c, "catalog-housekeeping", `catalog keys changed: ${bad.join(", ")}`);
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
if (changed.has("AcroForm") && !ctx.input.after.acroForm && !ctx.input.before.acroForm) {
|
|
1667
|
+
const problem = acroFormProblem(ctx, dictEntries(c.value.old)?.AcroForm ?? null, dictEntries(c.value.new)?.AcroForm ?? null);
|
|
1668
|
+
if (problem) {
|
|
1669
|
+
ctx.forbidden(c, "acroform-housekeeping", problem);
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
for (const side of SIDES) ctx.claimSubtree(side, /* @__PURE__ */ new Set([root]), (label) => label.startsWith("AcroForm/DR/"), "acroform-housekeeping");
|
|
1673
|
+
}
|
|
1674
|
+
if (changed.has("Perms")) {
|
|
1675
|
+
const after = require_resolver.deriveProtection(ctx.input.after.signatures);
|
|
1676
|
+
const before = require_resolver.deriveProtection(ctx.input.before.signatures);
|
|
1677
|
+
if (!after.certification || before.judged !== null) {
|
|
1678
|
+
ctx.forbidden(c, "catalog-housekeeping", "/Perms may only appear with the certification signature, in the first signed revision");
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
ctx.claimAll(c, "catalog-housekeeping");
|
|
1683
|
+
ctx.permitted(c, "catalog-housekeeping");
|
|
1684
|
+
}
|
|
1685
|
+
function ruleAcroForm(ctx) {
|
|
1686
|
+
const acro = ctx.input.after.acroForm || ctx.input.before.acroForm;
|
|
1687
|
+
if (!acro) return;
|
|
1688
|
+
const c = ctx.byNum.get(acro);
|
|
1689
|
+
if (!c || ctx.isIdentical(c)) return;
|
|
1690
|
+
const problem = acroFormProblem(ctx, c.value.old, c.value.new);
|
|
1691
|
+
if (problem) {
|
|
1692
|
+
ctx.forbidden(c, "acroform-housekeeping", problem);
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
ctx.claimAll(c, "acroform-housekeeping");
|
|
1696
|
+
ctx.permitted(c, "acroform-housekeeping");
|
|
1697
|
+
for (const side of SIDES) ctx.claimSubtree(side, /* @__PURE__ */ new Set([acro]), (label) => label.startsWith("DR/"), "acroform-housekeeping");
|
|
1698
|
+
}
|
|
1699
|
+
/** What is wrong with an AcroForm dictionary change, or null when it is housekeeping. */
|
|
1700
|
+
function acroFormProblem(ctx, oldValue, newValue) {
|
|
1701
|
+
const changed = changedKeys(oldValue, newValue);
|
|
1702
|
+
const bad = [...changed].filter((k) => !ACROFORM_KEYS.has(k));
|
|
1703
|
+
if (bad.length > 0) return `AcroForm keys changed: ${bad.join(", ")}`;
|
|
1704
|
+
if (changed.has("Fields")) {
|
|
1705
|
+
const oldRefs = refsOf(dictEntries(oldValue)?.Fields);
|
|
1706
|
+
const newRefs = refsOf(dictEntries(newValue)?.Fields);
|
|
1707
|
+
if (oldRefs.length > newRefs.length || oldRefs.some((n, i) => newRefs[i] !== n)) return "/AcroForm /Fields is not append-only";
|
|
1708
|
+
const newSigFields = newSignatureFields(ctx);
|
|
1709
|
+
const strangers = newRefs.slice(oldRefs.length).filter((n) => !newSigFields.has(n));
|
|
1710
|
+
if (strangers.length > 0) return `/AcroForm /Fields gained non-signature fields: ${strangers.join(", ")}`;
|
|
1711
|
+
}
|
|
1712
|
+
return null;
|
|
1713
|
+
}
|
|
1714
|
+
/** The refs appended to an array-valued key when the old array is a prefix of the new one; null otherwise. */
|
|
1715
|
+
function appendedRefs(c, key) {
|
|
1716
|
+
const oldItems = dictEntries(c.value.old)?.[key];
|
|
1717
|
+
const newItems = dictEntries(c.value.new)?.[key];
|
|
1718
|
+
const oldRefs = refsOf(oldItems);
|
|
1719
|
+
const newRefs = refsOf(newItems);
|
|
1720
|
+
if (oldItems && oldItems.t !== "array") return null;
|
|
1721
|
+
if (!newItems || newItems.t !== "array") return null;
|
|
1722
|
+
if (oldRefs.length > newRefs.length) return null;
|
|
1723
|
+
for (let i = 0; i < oldRefs.length; i++) if (oldRefs[i] !== newRefs[i]) return null;
|
|
1724
|
+
return newRefs.slice(oldRefs.length);
|
|
1725
|
+
}
|
|
1726
|
+
function newSignatureFields(ctx) {
|
|
1727
|
+
const out = /* @__PURE__ */ new Set();
|
|
1728
|
+
for (const n of ctx.after.sigFields) if (!ctx.before.fieldByObj.has(n)) out.add(n);
|
|
1729
|
+
return out;
|
|
1730
|
+
}
|
|
1731
|
+
function ruleDss(ctx) {
|
|
1732
|
+
const roots = /* @__PURE__ */ new Set([ctx.input.before.root, ctx.input.after.root]);
|
|
1733
|
+
for (const side of SIDES) ctx.claimSubtree(side, roots, (label) => label === "DSS" || label.startsWith("DSS/"), "dss");
|
|
1734
|
+
for (const c of ctx.changes) if (c.usage.new.some((e) => roots.has(e.parent) && e.label === "DSS") && !ctx.findings.some((f) => f.objectNumber === c.objectNumber)) ctx.permitted(c, "dss", "document security store");
|
|
1735
|
+
}
|
|
1736
|
+
function ruleSignatureFieldAdded(ctx) {
|
|
1737
|
+
const added = newSignatureFields(ctx);
|
|
1738
|
+
if (added.size === 0) return;
|
|
1739
|
+
const rule = "signature-field-added";
|
|
1740
|
+
if (!ctx.allows("fill")) {
|
|
1741
|
+
for (const n of added) {
|
|
1742
|
+
const c = ctx.byNum.get(n);
|
|
1743
|
+
if (c) ctx.forbidden(c, rule, `a signature field was added under level '${ctx.level}'`);
|
|
1744
|
+
}
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
const newWidgets = /* @__PURE__ */ new Set();
|
|
1748
|
+
for (const n of added) {
|
|
1749
|
+
const field = ctx.after.fieldByObj.get(n);
|
|
1750
|
+
const c = ctx.byNum.get(n);
|
|
1751
|
+
if (c) {
|
|
1752
|
+
ctx.claimAll(c, rule);
|
|
1753
|
+
ctx.permitted(c, rule, `new signature field "${field.name}"`);
|
|
1754
|
+
}
|
|
1755
|
+
for (const w of field.widgets) {
|
|
1756
|
+
newWidgets.add(w);
|
|
1757
|
+
if (w === n) continue;
|
|
1758
|
+
const wc = ctx.byNum.get(w);
|
|
1759
|
+
if (wc) {
|
|
1760
|
+
ctx.claimAll(wc, rule);
|
|
1761
|
+
ctx.permitted(wc, rule, `widget of new signature field "${field.name}"`);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
for (const p of ctx.after.pages) {
|
|
1766
|
+
const c = ctx.byNum.get(p);
|
|
1767
|
+
if (!c || ctx.isIdentical(c)) continue;
|
|
1768
|
+
const changed = changedKeys(c.value.old, c.value.new);
|
|
1769
|
+
if (changed.size !== 1 || !changed.has("Annots")) continue;
|
|
1770
|
+
const appended = appendedRefs(c, "Annots");
|
|
1771
|
+
if (appended && appended.every((n) => newWidgets.has(n))) {
|
|
1772
|
+
ctx.claimAll(c, rule);
|
|
1773
|
+
ctx.permitted(c, rule, "page /Annots gained the new signature widget");
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
for (const c of ctx.changes) {
|
|
1777
|
+
if (c.kind !== "dictionary" || ctx.isIdentical(c)) continue;
|
|
1778
|
+
const changed = changedKeys(c.value.old, c.value.new);
|
|
1779
|
+
if (changed.size !== 1 || !changed.has("Kids")) continue;
|
|
1780
|
+
const appended = appendedRefs(c, "Kids");
|
|
1781
|
+
if (appended && appended.length > 0 && appended.every((n) => added.has(n) || newWidgets.has(n))) {
|
|
1782
|
+
ctx.claimAll(c, rule);
|
|
1783
|
+
ctx.permitted(c, rule, "field /Kids gained the new signature field");
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
function ruleSignatureAdded(ctx) {
|
|
1788
|
+
const rule = "signature-added";
|
|
1789
|
+
const events = ctx.newlySigned();
|
|
1790
|
+
if (events.length === 0) return;
|
|
1791
|
+
const addedThisStep = newSignatureFields(ctx);
|
|
1792
|
+
const beforeProtection = require_resolver.deriveProtection(ctx.input.before.signatures);
|
|
1793
|
+
const afterProtection = require_resolver.deriveProtection(ctx.input.after.signatures);
|
|
1794
|
+
const parents = /* @__PURE__ */ new Set();
|
|
1795
|
+
const values = /* @__PURE__ */ new Set();
|
|
1796
|
+
for (const { fieldObjectNumber, sig } of events) {
|
|
1797
|
+
const needed = sig.kind === "timestamp" ? "lta" : "fill";
|
|
1798
|
+
if (!ctx.allows(needed)) {
|
|
1799
|
+
const c = ctx.byNum.get(fieldObjectNumber);
|
|
1800
|
+
if (c) ctx.forbidden(c, rule, `a ${sig.kind} was added under level '${ctx.level}'`);
|
|
1801
|
+
continue;
|
|
1802
|
+
}
|
|
1803
|
+
if (sig.catalogCertification && beforeProtection.judged !== null) {
|
|
1804
|
+
const c = ctx.byNum.get(fieldObjectNumber);
|
|
1805
|
+
if (c) ctx.forbidden(c, rule, "a certification signature after another signature");
|
|
1806
|
+
continue;
|
|
1807
|
+
}
|
|
1808
|
+
const field = ctx.after.fieldByObj.get(fieldObjectNumber);
|
|
1809
|
+
parents.add(fieldObjectNumber);
|
|
1810
|
+
for (const w of field?.widgets ?? []) parents.add(w);
|
|
1811
|
+
const fieldIsNew = addedThisStep.has(fieldObjectNumber);
|
|
1812
|
+
const allowedFieldKeys = beforeProtection.judged === null ? /* @__PURE__ */ new Set([...signedFieldKeysFor(ctx.level), "Lock"]) : signedFieldKeysFor(ctx.level);
|
|
1813
|
+
const fc = ctx.byNum.get(fieldObjectNumber);
|
|
1814
|
+
if (fc && fieldIsNew) ctx.permitted(fc, rule, `new field "${sig.fieldName}" signed`);
|
|
1815
|
+
else if (fc && !ctx.isIdentical(fc)) {
|
|
1816
|
+
const changed = changedKeys(fc.value.old, fc.value.new);
|
|
1817
|
+
const bad = [...changed].filter((k) => !allowedFieldKeys.has(k));
|
|
1818
|
+
if (bad.length > 0) {
|
|
1819
|
+
ctx.forbidden(fc, rule, `signature field keys changed: ${bad.join(", ")}`);
|
|
1820
|
+
ctx.claimStable(fc, rule, false);
|
|
1821
|
+
} else if (changed.has("Ff") && !readOnlyOnlyChange(fc, ctx.before.fieldByObj.get(fieldObjectNumber)?.flags)) {
|
|
1822
|
+
ctx.forbidden(fc, rule, "signature field flags changed beyond ReadOnly");
|
|
1823
|
+
ctx.claimStable(fc, rule, false);
|
|
1824
|
+
} else {
|
|
1825
|
+
ctx.claimStable(fc, rule);
|
|
1826
|
+
ctx.permitted(fc, rule, `field "${sig.fieldName}" signed`);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
for (const w of field?.widgets ?? []) {
|
|
1830
|
+
if (w === fieldObjectNumber || fieldIsNew) continue;
|
|
1831
|
+
const wc = ctx.byNum.get(w);
|
|
1832
|
+
if (!wc || ctx.isIdentical(wc)) continue;
|
|
1833
|
+
const bad = [...changedKeys(wc.value.old, wc.value.new)].filter((k) => !SIGNED_WIDGET_KEYS.has(k));
|
|
1834
|
+
if (bad.length > 0) {
|
|
1835
|
+
ctx.forbidden(wc, rule, `signature widget keys changed: ${bad.join(", ")}`);
|
|
1836
|
+
ctx.claimStable(wc, rule, false);
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
ctx.claimStable(wc, rule);
|
|
1840
|
+
ctx.permitted(wc, rule, "signature widget appearance");
|
|
1841
|
+
}
|
|
1842
|
+
for (const c of ctx.changes) {
|
|
1843
|
+
if (c.change !== "added") continue;
|
|
1844
|
+
const asValue = c.usage.new.filter((e) => e.parent === fieldObjectNumber && e.label === "V");
|
|
1845
|
+
const asLock = c.usage.new.filter((e) => e.parent === fieldObjectNumber && e.label === "Lock");
|
|
1846
|
+
const type = dictEntries(c.value.new)?.Type;
|
|
1847
|
+
if (asValue.length > 0) {
|
|
1848
|
+
if (!(type?.t === "name" && (type.v === "Sig" || type.v === "DocTimeStamp")) && type !== void 0) continue;
|
|
1849
|
+
for (const e of c.usage.new) if (e.parent === fieldObjectNumber && e.label === "V" || e.label === "DocMDP") ctx.claimEdge(c, "new", e, rule);
|
|
1850
|
+
ctx.permitted(c, rule, "signature value");
|
|
1851
|
+
values.add(c.objectNumber);
|
|
1852
|
+
} else if (asLock.length > 0) {
|
|
1853
|
+
if (!(type === void 0 || type.t === "name" && type.v === "SigFieldLock")) continue;
|
|
1854
|
+
for (const e of asLock) ctx.claimEdge(c, "new", e, rule);
|
|
1855
|
+
ctx.permitted(c, rule, "field lock installed with the signature");
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
if (afterProtection.certification && beforeProtection.judged === null) {
|
|
1860
|
+
const root = ctx.input.after.root;
|
|
1861
|
+
for (const c of ctx.changes) if (c.change === "added" && onlyEdges(c, (e) => e.parent === root && e.label === "Perms")) {
|
|
1862
|
+
ctx.claimAll(c, rule);
|
|
1863
|
+
ctx.permitted(c, rule, "certification permissions");
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
for (const side of SIDES) ctx.claimSubtree(side, parents, (label) => label === "AP" || label.startsWith("AP/"), rule);
|
|
1867
|
+
if (values.size > 0) ctx.claimSubtree("new", values, () => true, rule);
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Did `/Ff` change by gaining ReadOnly and nothing else? `/Ff` is
|
|
1871
|
+
* inheritable, and a lock writes `effective | ReadOnly` into the terminal
|
|
1872
|
+
* dictionary, so a terminal that carried no `/Ff` of its own is judged
|
|
1873
|
+
* against the effective flags the older revision's form model reported.
|
|
1874
|
+
*/
|
|
1875
|
+
function readOnlyOnlyChange(c, effectiveBefore) {
|
|
1876
|
+
const oldFf = dictEntries(c.value.old)?.Ff;
|
|
1877
|
+
const newFf = dictEntries(c.value.new)?.Ff;
|
|
1878
|
+
const before = oldFf?.t === "number" ? oldFf.v : effectiveBefore ?? 0;
|
|
1879
|
+
const after = newFf?.t === "number" ? newFf.v : 0;
|
|
1880
|
+
return (before | FF_READ_ONLY) === after;
|
|
1881
|
+
}
|
|
1882
|
+
function ruleFormFill(ctx) {
|
|
1883
|
+
const rule = "form-fill";
|
|
1884
|
+
const fieldKeys = fieldKeysFor(ctx.level);
|
|
1885
|
+
const widgetKeys = widgetKeysFor(ctx.level);
|
|
1886
|
+
const parents = /* @__PURE__ */ new Set();
|
|
1887
|
+
const fields = new Map([...ctx.before.fieldByObj, ...ctx.after.fieldByObj]);
|
|
1888
|
+
for (const [num, field] of fields) {
|
|
1889
|
+
if (field.family === "signature") continue;
|
|
1890
|
+
const fc = ctx.byNum.get(num);
|
|
1891
|
+
const widgetChanges = field.widgets.filter((w) => w !== num).map((w) => ctx.byNum.get(w)).filter(Boolean);
|
|
1892
|
+
const owners = /* @__PURE__ */ new Set([num, ...field.widgets]);
|
|
1893
|
+
const isAp = (label) => label === "AP" || label.startsWith("AP/");
|
|
1894
|
+
const appearanceOnly = !fc && widgetChanges.length === 0 ? ctx.changes.filter((c) => SIDES.some((side) => c.usage[side].some((e) => owners.has(e.parent) && isAp(e.label))) && SIDES.every((side) => c.usage[side].every((e) => !isAp(e.label) || owners.has(e.parent)))) : [];
|
|
1895
|
+
if (!fc && widgetChanges.length === 0 && appearanceOnly.length === 0) continue;
|
|
1896
|
+
if (!ctx.allows("fill")) {
|
|
1897
|
+
for (const c of [
|
|
1898
|
+
fc,
|
|
1899
|
+
...widgetChanges,
|
|
1900
|
+
...appearanceOnly
|
|
1901
|
+
]) if (c && !ctx.isIdentical(c)) ctx.forbidden(c, rule, `form fill under level '${ctx.level}'`);
|
|
1902
|
+
continue;
|
|
1903
|
+
}
|
|
1904
|
+
if (appearanceOnly.length > 0) {
|
|
1905
|
+
for (const c of appearanceOnly) ctx.permitted(c, rule, `appearance of field "${field.name}" regenerated`);
|
|
1906
|
+
parents.add(num);
|
|
1907
|
+
for (const w of field.widgets) parents.add(w);
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
let ok = true;
|
|
1911
|
+
if (fc && !ctx.isIdentical(fc)) {
|
|
1912
|
+
const changed = changedKeys(fc.value.old, fc.value.new);
|
|
1913
|
+
const bad = [...changed].filter((k) => !fieldKeys.has(k));
|
|
1914
|
+
if (bad.length > 0) {
|
|
1915
|
+
ctx.forbidden(fc, rule, `field "${field.name}" keys changed: ${bad.join(", ")}`);
|
|
1916
|
+
ctx.claimStable(fc, rule, false);
|
|
1917
|
+
ok = false;
|
|
1918
|
+
} else if (changed.has("Ff") && !readOnlyOnlyChange(fc, ctx.before.fieldByObj.get(num)?.flags)) {
|
|
1919
|
+
ctx.forbidden(fc, rule, `field "${field.name}" flags changed beyond adding ReadOnly`);
|
|
1920
|
+
ctx.claimStable(fc, rule, false);
|
|
1921
|
+
ok = false;
|
|
1922
|
+
} else {
|
|
1923
|
+
ctx.claimStable(fc, rule);
|
|
1924
|
+
const properties = [...changed].filter((k) => k !== "V" && k !== "AP" && k !== "AS" && k !== "I" && k !== "RV");
|
|
1925
|
+
ctx.permitted(fc, rule, properties.length > 0 ? `field "${field.name}" ${changed.has("V") || changed.has("AS") ? "filled, " : ""}properties changed: ${properties.join(", ")}` : `field "${field.name}" filled`);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
for (const wc of widgetChanges) {
|
|
1929
|
+
if (ctx.isIdentical(wc)) continue;
|
|
1930
|
+
const bad = [...changedKeys(wc.value.old, wc.value.new)].filter((k) => !widgetKeys.has(k));
|
|
1931
|
+
if (bad.length > 0) {
|
|
1932
|
+
ctx.forbidden(wc, rule, `widget of "${field.name}" keys changed: ${bad.join(", ")}`);
|
|
1933
|
+
ctx.claimStable(wc, rule, false);
|
|
1934
|
+
ok = false;
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1937
|
+
ctx.claimStable(wc, rule);
|
|
1938
|
+
ctx.permitted(wc, rule, `widget of "${field.name}" appearance`);
|
|
1939
|
+
}
|
|
1940
|
+
if (ok) {
|
|
1941
|
+
parents.add(num);
|
|
1942
|
+
for (const w of field.widgets) parents.add(w);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
for (const side of SIDES) ctx.claimSubtree(side, parents, (label) => label === "AP" || label.startsWith("AP/"), rule);
|
|
1946
|
+
}
|
|
1947
|
+
function ruleAnnotation(ctx) {
|
|
1948
|
+
const rule = "annotation";
|
|
1949
|
+
const pages = /* @__PURE__ */ new Set([...ctx.before.pages, ...ctx.after.pages]);
|
|
1950
|
+
const widgets = /* @__PURE__ */ new Set([...ctx.before.widgetToField.keys(), ...ctx.after.widgetToField.keys()]);
|
|
1951
|
+
const isWidget = (c) => {
|
|
1952
|
+
if (widgets.has(c.objectNumber)) return true;
|
|
1953
|
+
const sub = dictEntries(c.value.new ?? c.value.old)?.Subtype;
|
|
1954
|
+
return sub?.t === "name" && sub.v === "Widget";
|
|
1955
|
+
};
|
|
1956
|
+
const annots = [];
|
|
1957
|
+
for (const c of ctx.changes) {
|
|
1958
|
+
if (c.kind !== "dictionary" || ctx.isIdentical(c)) continue;
|
|
1959
|
+
if ([...c.usage.old, ...c.usage.new].some((e) => pages.has(e.parent) && e.label.startsWith("Annots/")) && !isWidget(c)) annots.push(c);
|
|
1960
|
+
}
|
|
1961
|
+
if (annots.length === 0) {}
|
|
1962
|
+
if (!ctx.allows("annotate")) {
|
|
1963
|
+
for (const c of annots) ctx.forbidden(c, rule, `annotation change under level '${ctx.level}'`);
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
const parents = /* @__PURE__ */ new Set();
|
|
1967
|
+
for (const c of annots) {
|
|
1968
|
+
ctx.claimAll(c, rule);
|
|
1969
|
+
ctx.permitted(c, rule, `${c.change} annotation`);
|
|
1970
|
+
parents.add(c.objectNumber);
|
|
1971
|
+
}
|
|
1972
|
+
for (const p of pages) {
|
|
1973
|
+
const c = ctx.byNum.get(p);
|
|
1974
|
+
if (!c || ctx.isIdentical(c)) continue;
|
|
1975
|
+
const changed = changedKeys(c.value.old, c.value.new);
|
|
1976
|
+
if (changed.size === 1 && changed.has("Annots")) {
|
|
1977
|
+
const removed = refsOf(dictEntries(c.value.old)?.Annots).filter((n) => !refsOf(dictEntries(c.value.new)?.Annots).includes(n));
|
|
1978
|
+
const added = refsOf(dictEntries(c.value.new)?.Annots).filter((n) => !refsOf(dictEntries(c.value.old)?.Annots).includes(n));
|
|
1979
|
+
if ([...removed, ...added].some((n) => widgets.has(n) && !ctx.findings.some((f) => f.objectNumber === n && f.verdict === "permitted"))) {
|
|
1980
|
+
ctx.forbidden(c, rule, "page /Annots added or removed a form widget");
|
|
1981
|
+
continue;
|
|
1982
|
+
}
|
|
1983
|
+
ctx.claimAll(c, rule);
|
|
1984
|
+
ctx.permitted(c, rule, "page /Annots changed");
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
for (const side of SIDES) ctx.claimSubtree(side, parents, (label) => label === "AP" || label.startsWith("AP/") || label === "Popup" || label === "IRT", rule);
|
|
1988
|
+
}
|
|
1989
|
+
function ruleFieldLock(ctx) {
|
|
1990
|
+
if (ctx.locks.length === 0) return;
|
|
1991
|
+
const lockedFields = ctx.input.before.fields.filter((f) => ctx.locks.some((l) => require_resolver.lockCovers(l.spec, f.name)));
|
|
1992
|
+
const lockedObjects = /* @__PURE__ */ new Set();
|
|
1993
|
+
for (const f of lockedFields) {
|
|
1994
|
+
lockedObjects.add(f.objectNumber);
|
|
1995
|
+
for (const w of f.widgets) lockedObjects.add(w);
|
|
1996
|
+
}
|
|
1997
|
+
const frontier = new Set(lockedObjects);
|
|
1998
|
+
let grew = true;
|
|
1999
|
+
while (grew) {
|
|
2000
|
+
grew = false;
|
|
2001
|
+
for (const c of ctx.changes) {
|
|
2002
|
+
if (frontier.has(c.objectNumber)) continue;
|
|
2003
|
+
if (c.usage.old.length > 0 && c.usage.old.every((e) => frontier.has(e.parent))) {
|
|
2004
|
+
frontier.add(c.objectNumber);
|
|
2005
|
+
grew = true;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
for (const c of ctx.changes) {
|
|
2010
|
+
if (!frontier.has(c.objectNumber) || ctx.isIdentical(c)) continue;
|
|
2011
|
+
const field = lockedFields.find((f) => f.objectNumber === c.objectNumber || f.widgets.includes(c.objectNumber));
|
|
2012
|
+
if (field && field.objectNumber === c.objectNumber) {
|
|
2013
|
+
const changed = changedKeys(c.value.old, c.value.new);
|
|
2014
|
+
if (changed.size === 1 && changed.has("Ff") && readOnlyOnlyChange(c, ctx.before.fieldByObj.get(c.objectNumber)?.flags)) {
|
|
2015
|
+
ctx.claimStable(c, "field-lock");
|
|
2016
|
+
ctx.permitted(c, "field-lock", `ReadOnly set on locked field "${field.name}"`);
|
|
2017
|
+
continue;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
ctx.locked.add(c.objectNumber);
|
|
2021
|
+
ctx.forbidden(c, "field-lock", field ? `field "${field.name}" is locked by the signature` : "part of a locked field changed");
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
function ruleUnexplained(ctx) {
|
|
2025
|
+
for (const c of ctx.changes) {
|
|
2026
|
+
if (c.value.truncated) {
|
|
2027
|
+
ctx.findings.push({
|
|
2028
|
+
rule: "unexplained",
|
|
2029
|
+
verdict: "incomplete",
|
|
2030
|
+
objectNumber: c.objectNumber,
|
|
2031
|
+
detail: "value too large to inspect"
|
|
2032
|
+
});
|
|
2033
|
+
continue;
|
|
2034
|
+
}
|
|
2035
|
+
if (c.usageIncomplete) {
|
|
2036
|
+
ctx.findings.push({
|
|
2037
|
+
rule: "unexplained",
|
|
2038
|
+
verdict: "incomplete",
|
|
2039
|
+
objectNumber: c.objectNumber,
|
|
2040
|
+
detail: "references could not be resolved within budget (depth, fan-out or total reads)"
|
|
2041
|
+
});
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
if (ctx.locked.has(c.objectNumber)) continue;
|
|
2045
|
+
for (const side of SIDES) for (const e of c.usage[side]) if (!ctx.isClaimed(c, side, e)) ctx.forbidden(c, "unexplained", `${side === "old" ? "was" : "is"} referenced from object ${e.parent} at ${e.label} and no rule explains it`, e);
|
|
2046
|
+
if (c.usage.old.length === 0 && c.usage.new.length === 0 && !ctx.findings.some((f) => f.objectNumber === c.objectNumber)) ctx.permitted(c, "orphan", "referenced from nowhere reachable");
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
//#endregion
|
|
2050
|
+
//#region src/signature/analysis/restrictions.ts
|
|
2051
|
+
/**
|
|
2052
|
+
* The restrictions a window is judged under, read from the SEALED
|
|
2053
|
+
* revision's structure (what that revision declared, not what the final
|
|
2054
|
+
* file says).
|
|
2055
|
+
*
|
|
2056
|
+
* - `judged` given (a signature-anchored analysis): its own FieldMDP and
|
|
2057
|
+
* /Lock bind the window; other signatures' locks do not (corpus v3/85:
|
|
2058
|
+
* a second signature is valid with a fill of a field the first locked).
|
|
2059
|
+
* Every certification's level, own or earlier, applies (inherited:
|
|
2060
|
+
* conservative until observed otherwise), as does a /Lock's /P.
|
|
2061
|
+
* - no `judged` (a revision-anchored analysis): every signed signature's
|
|
2062
|
+
* locks and levels, as before.
|
|
2063
|
+
*/
|
|
2064
|
+
function restrictionsFor(before, judged) {
|
|
2065
|
+
const anchors = [];
|
|
2066
|
+
const locks = [];
|
|
2067
|
+
let enforced = null;
|
|
2068
|
+
let signed = false;
|
|
2069
|
+
for (const sig of before.signatures) {
|
|
2070
|
+
if (!sig.signed || sig.revisionIndex === null) continue;
|
|
2071
|
+
signed = true;
|
|
2072
|
+
const own = judged !== null && sig.index === judged.index;
|
|
2073
|
+
if (sig.catalogCertification && sig.docMdp !== null) {
|
|
2074
|
+
anchors.push({
|
|
2075
|
+
signatureIndex: sig.index,
|
|
2076
|
+
revisionIndex: sig.revisionIndex,
|
|
2077
|
+
source: "docmdp",
|
|
2078
|
+
own,
|
|
2079
|
+
permission: sig.docMdp
|
|
2080
|
+
});
|
|
2081
|
+
enforced = require_resolver.minLevel(enforced ?? "annotate", require_resolver.levelFromPermission(sig.docMdp));
|
|
2082
|
+
}
|
|
2083
|
+
if (sig.lock?.permission !== void 0) enforced = require_resolver.minLevel(enforced ?? "annotate", require_resolver.levelFromPermission(sig.lock.permission));
|
|
2084
|
+
if (judged !== null && !own) continue;
|
|
2085
|
+
if (sig.fieldMdp) {
|
|
2086
|
+
anchors.push({
|
|
2087
|
+
signatureIndex: sig.index,
|
|
2088
|
+
revisionIndex: sig.revisionIndex,
|
|
2089
|
+
source: "fieldmdp",
|
|
2090
|
+
own,
|
|
2091
|
+
fields: sig.fieldMdp
|
|
2092
|
+
});
|
|
2093
|
+
locks.push({
|
|
2094
|
+
signatureIndex: sig.index,
|
|
2095
|
+
source: "fieldmdp",
|
|
2096
|
+
spec: sig.fieldMdp
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
if (sig.lock) {
|
|
2100
|
+
anchors.push({
|
|
2101
|
+
signatureIndex: sig.index,
|
|
2102
|
+
revisionIndex: sig.revisionIndex,
|
|
2103
|
+
source: "lock",
|
|
2104
|
+
own,
|
|
2105
|
+
fields: sig.lock
|
|
2106
|
+
});
|
|
2107
|
+
locks.push({
|
|
2108
|
+
signatureIndex: sig.index,
|
|
2109
|
+
source: "lock",
|
|
2110
|
+
spec: sig.lock
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
return {
|
|
2115
|
+
anchors,
|
|
2116
|
+
level: signed ? enforced ?? "annotate" : "annotate",
|
|
2117
|
+
locks
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
//#endregion
|
|
2121
|
+
//#region src/signature/analysis/resolve.ts
|
|
2122
|
+
const DEFAULT_EDGE_RESOLVER_BUDGET = {
|
|
2123
|
+
maxDepth: 16,
|
|
2124
|
+
maxEdgesPerObject: 256,
|
|
2125
|
+
maxReads: 2e4
|
|
2126
|
+
};
|
|
2127
|
+
const USAGE_INCOMPLETE = "incomplete";
|
|
2128
|
+
/**
|
|
2129
|
+
* Resolves the raw inbound edges of an object (from the fork's referrer
|
|
2130
|
+
* index, which covers every reachable object of a revision) into edges
|
|
2131
|
+
* from *anchors*: the changed objects and the structural objects of the
|
|
2132
|
+
* revision (trailer, catalog, AcroForm, page tree root, pages, fields,
|
|
2133
|
+
* widgets). An edge from an unchanged, non-structural parent is replaced by
|
|
2134
|
+
* that parent's own anchored edges with the labels joined, so an
|
|
2135
|
+
* appearance stream under an unchanged indirect `/AP` dictionary reads
|
|
2136
|
+
* `widget:AP/N` rather than `apDict:N`.
|
|
2137
|
+
*
|
|
2138
|
+
* A graph traversal, not a path enumeration: results are memoised per
|
|
2139
|
+
* object, so a lattice of shared unchanged objects costs its size, not the
|
|
2140
|
+
* number of paths through it. Cycle edges contribute no anchors and are
|
|
2141
|
+
* skipped; a result computed while skipping one is not memoised.
|
|
2142
|
+
*/
|
|
2143
|
+
var EdgeResolver = class {
|
|
2144
|
+
constructor(read, anchors, budget = DEFAULT_EDGE_RESOLVER_BUDGET) {
|
|
2145
|
+
this.read = read;
|
|
2146
|
+
this.anchors = anchors;
|
|
2147
|
+
this.budget = budget;
|
|
2148
|
+
require_fold._defineProperty(this, "memo", /* @__PURE__ */ new Map());
|
|
2149
|
+
require_fold._defineProperty(this, "reads", 0);
|
|
2150
|
+
require_fold._defineProperty(this, "exhausted", false);
|
|
2151
|
+
}
|
|
2152
|
+
/** Anchored edges of `objectNumber`, or `incomplete` when a budget was exceeded. */
|
|
2153
|
+
resolve(objectNumber) {
|
|
2154
|
+
if (this.exhausted) return USAGE_INCOMPLETE;
|
|
2155
|
+
const result = this.resolveInner(objectNumber, /* @__PURE__ */ new Set());
|
|
2156
|
+
return result === null ? USAGE_INCOMPLETE : result.edges;
|
|
2157
|
+
}
|
|
2158
|
+
resolveInner(n, active) {
|
|
2159
|
+
const hit = this.memo.get(n);
|
|
2160
|
+
if (hit) return hit;
|
|
2161
|
+
if (active.size >= this.budget.maxDepth) return null;
|
|
2162
|
+
if (this.reads >= this.budget.maxReads) {
|
|
2163
|
+
this.exhausted = true;
|
|
2164
|
+
return null;
|
|
2165
|
+
}
|
|
2166
|
+
this.reads += 1;
|
|
2167
|
+
const raw = this.read(n);
|
|
2168
|
+
active.add(n);
|
|
2169
|
+
const edges = [];
|
|
2170
|
+
let cyclic = false;
|
|
2171
|
+
try {
|
|
2172
|
+
for (const e of raw) {
|
|
2173
|
+
if (this.anchors.has(e.parent)) edges.push(e);
|
|
2174
|
+
else if (active.has(e.parent)) {
|
|
2175
|
+
cyclic = true;
|
|
2176
|
+
continue;
|
|
2177
|
+
} else {
|
|
2178
|
+
const up = this.resolveInner(e.parent, active);
|
|
2179
|
+
if (up === null) return null;
|
|
2180
|
+
cyclic || (cyclic = up.cyclic);
|
|
2181
|
+
for (const u of up.edges) edges.push({
|
|
2182
|
+
parent: u.parent,
|
|
2183
|
+
label: `${u.label}/${e.label}`,
|
|
2184
|
+
via: [...u.via ?? [], e.parent]
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
if (edges.length > this.budget.maxEdgesPerObject) return null;
|
|
2188
|
+
}
|
|
2189
|
+
} finally {
|
|
2190
|
+
active.delete(n);
|
|
2191
|
+
}
|
|
2192
|
+
const resolution = {
|
|
2193
|
+
edges,
|
|
2194
|
+
cyclic
|
|
2195
|
+
};
|
|
2196
|
+
if (!cyclic) this.memo.set(n, resolution);
|
|
2197
|
+
return resolution;
|
|
2198
|
+
}
|
|
2199
|
+
};
|
|
2200
|
+
//#endregion
|
|
852
2201
|
//#region src/search/literal.ts
|
|
853
2202
|
/**
|
|
854
2203
|
* The fold flavor a literal query needs. Haystack and needle MUST be
|
|
@@ -1759,17 +3108,32 @@ function materializePdfPermissions(b) {
|
|
|
1759
3108
|
}
|
|
1760
3109
|
if (b.bit11) out.add("doc.pages.assemble");
|
|
1761
3110
|
if (b.bit6) out.add("doc.annotate.modify");
|
|
1762
|
-
if (b.bit6 || b.bit9)
|
|
3111
|
+
if (b.bit6 || b.bit9) {
|
|
3112
|
+
out.add("doc.forms.fill");
|
|
3113
|
+
out.add("doc.sign");
|
|
3114
|
+
}
|
|
1763
3115
|
if (b.bit6 && b.bit4) out.add("doc.forms.modify");
|
|
1764
3116
|
return [...out];
|
|
1765
3117
|
}
|
|
1766
3118
|
//#endregion
|
|
3119
|
+
Object.defineProperty(exports, "DEFAULT_EDGE_RESOLVER_BUDGET", {
|
|
3120
|
+
enumerable: true,
|
|
3121
|
+
get: function() {
|
|
3122
|
+
return DEFAULT_EDGE_RESOLVER_BUDGET;
|
|
3123
|
+
}
|
|
3124
|
+
});
|
|
1767
3125
|
Object.defineProperty(exports, "DEFAULT_PDF_SAVE_MODE", {
|
|
1768
3126
|
enumerable: true,
|
|
1769
3127
|
get: function() {
|
|
1770
3128
|
return DEFAULT_PDF_SAVE_MODE;
|
|
1771
3129
|
}
|
|
1772
3130
|
});
|
|
3131
|
+
Object.defineProperty(exports, "EdgeResolver", {
|
|
3132
|
+
enumerable: true,
|
|
3133
|
+
get: function() {
|
|
3134
|
+
return EdgeResolver;
|
|
3135
|
+
}
|
|
3136
|
+
});
|
|
1773
3137
|
Object.defineProperty(exports, "NO_ANNOTATION_FLAGS", {
|
|
1774
3138
|
enumerable: true,
|
|
1775
3139
|
get: function() {
|
|
@@ -1800,12 +3164,24 @@ Object.defineProperty(exports, "UNKNOWN_WEAK_ANNOTATION_STATE", {
|
|
|
1800
3164
|
return UNKNOWN_WEAK_ANNOTATION_STATE;
|
|
1801
3165
|
}
|
|
1802
3166
|
});
|
|
3167
|
+
Object.defineProperty(exports, "USAGE_INCOMPLETE", {
|
|
3168
|
+
enumerable: true,
|
|
3169
|
+
get: function() {
|
|
3170
|
+
return USAGE_INCOMPLETE;
|
|
3171
|
+
}
|
|
3172
|
+
});
|
|
1803
3173
|
Object.defineProperty(exports, "appearanceImpactOf", {
|
|
1804
3174
|
enumerable: true,
|
|
1805
3175
|
get: function() {
|
|
1806
3176
|
return appearanceImpactOf;
|
|
1807
3177
|
}
|
|
1808
3178
|
});
|
|
3179
|
+
Object.defineProperty(exports, "assessmentOf", {
|
|
3180
|
+
enumerable: true,
|
|
3181
|
+
get: function() {
|
|
3182
|
+
return assessmentOf;
|
|
3183
|
+
}
|
|
3184
|
+
});
|
|
1809
3185
|
Object.defineProperty(exports, "buildCommentThreads", {
|
|
1810
3186
|
enumerable: true,
|
|
1811
3187
|
get: function() {
|
|
@@ -1842,6 +3218,12 @@ Object.defineProperty(exports, "caps", {
|
|
|
1842
3218
|
return caps;
|
|
1843
3219
|
}
|
|
1844
3220
|
});
|
|
3221
|
+
Object.defineProperty(exports, "changedKeys", {
|
|
3222
|
+
enumerable: true,
|
|
3223
|
+
get: function() {
|
|
3224
|
+
return changedKeys;
|
|
3225
|
+
}
|
|
3226
|
+
});
|
|
1845
3227
|
Object.defineProperty(exports, "changesAnnotationList", {
|
|
1846
3228
|
enumerable: true,
|
|
1847
3229
|
get: function() {
|
|
@@ -1860,6 +3242,18 @@ Object.defineProperty(exports, "collab", {
|
|
|
1860
3242
|
return collab;
|
|
1861
3243
|
}
|
|
1862
3244
|
});
|
|
3245
|
+
Object.defineProperty(exports, "combine", {
|
|
3246
|
+
enumerable: true,
|
|
3247
|
+
get: function() {
|
|
3248
|
+
return combine;
|
|
3249
|
+
}
|
|
3250
|
+
});
|
|
3251
|
+
Object.defineProperty(exports, "conclude", {
|
|
3252
|
+
enumerable: true,
|
|
3253
|
+
get: function() {
|
|
3254
|
+
return conclude;
|
|
3255
|
+
}
|
|
3256
|
+
});
|
|
1863
3257
|
Object.defineProperty(exports, "createPageImageHandle", {
|
|
1864
3258
|
enumerable: true,
|
|
1865
3259
|
get: function() {
|
|
@@ -1890,6 +3284,12 @@ Object.defineProperty(exports, "decodeSubmitFormFlags", {
|
|
|
1890
3284
|
return decodeSubmitFormFlags;
|
|
1891
3285
|
}
|
|
1892
3286
|
});
|
|
3287
|
+
Object.defineProperty(exports, "dictEntries", {
|
|
3288
|
+
enumerable: true,
|
|
3289
|
+
get: function() {
|
|
3290
|
+
return dictEntries;
|
|
3291
|
+
}
|
|
3292
|
+
});
|
|
1893
3293
|
Object.defineProperty(exports, "encodeFieldRefKey", {
|
|
1894
3294
|
enumerable: true,
|
|
1895
3295
|
get: function() {
|
|
@@ -1902,6 +3302,12 @@ Object.defineProperty(exports, "encodeStableIdKey", {
|
|
|
1902
3302
|
return encodeStableIdKey;
|
|
1903
3303
|
}
|
|
1904
3304
|
});
|
|
3305
|
+
Object.defineProperty(exports, "evaluateStep", {
|
|
3306
|
+
enumerable: true,
|
|
3307
|
+
get: function() {
|
|
3308
|
+
return evaluateStep;
|
|
3309
|
+
}
|
|
3310
|
+
});
|
|
1905
3311
|
Object.defineProperty(exports, "expandTextRangeToLine", {
|
|
1906
3312
|
enumerable: true,
|
|
1907
3313
|
get: function() {
|
|
@@ -1986,30 +3392,84 @@ Object.defineProperty(exports, "normalizeAttachmentFileSource", {
|
|
|
1986
3392
|
return normalizeAttachmentFileSource;
|
|
1987
3393
|
}
|
|
1988
3394
|
});
|
|
3395
|
+
Object.defineProperty(exports, "parsePdfValue", {
|
|
3396
|
+
enumerable: true,
|
|
3397
|
+
get: function() {
|
|
3398
|
+
return parsePdfValue;
|
|
3399
|
+
}
|
|
3400
|
+
});
|
|
1989
3401
|
Object.defineProperty(exports, "pdfPermissions", {
|
|
1990
3402
|
enumerable: true,
|
|
1991
3403
|
get: function() {
|
|
1992
3404
|
return pdfPermissions;
|
|
1993
3405
|
}
|
|
1994
3406
|
});
|
|
3407
|
+
Object.defineProperty(exports, "pdfValueEquals", {
|
|
3408
|
+
enumerable: true,
|
|
3409
|
+
get: function() {
|
|
3410
|
+
return pdfValueEquals;
|
|
3411
|
+
}
|
|
3412
|
+
});
|
|
3413
|
+
Object.defineProperty(exports, "primaryFinding", {
|
|
3414
|
+
enumerable: true,
|
|
3415
|
+
get: function() {
|
|
3416
|
+
return primaryFinding;
|
|
3417
|
+
}
|
|
3418
|
+
});
|
|
1995
3419
|
Object.defineProperty(exports, "refKey", {
|
|
1996
3420
|
enumerable: true,
|
|
1997
3421
|
get: function() {
|
|
1998
3422
|
return refKey;
|
|
1999
3423
|
}
|
|
2000
3424
|
});
|
|
3425
|
+
Object.defineProperty(exports, "refsOf", {
|
|
3426
|
+
enumerable: true,
|
|
3427
|
+
get: function() {
|
|
3428
|
+
return refsOf;
|
|
3429
|
+
}
|
|
3430
|
+
});
|
|
2001
3431
|
Object.defineProperty(exports, "resolveBinarySource", {
|
|
2002
3432
|
enumerable: true,
|
|
2003
3433
|
get: function() {
|
|
2004
3434
|
return resolveBinarySource;
|
|
2005
3435
|
}
|
|
2006
3436
|
});
|
|
3437
|
+
Object.defineProperty(exports, "restrictionsFor", {
|
|
3438
|
+
enumerable: true,
|
|
3439
|
+
get: function() {
|
|
3440
|
+
return restrictionsFor;
|
|
3441
|
+
}
|
|
3442
|
+
});
|
|
3443
|
+
Object.defineProperty(exports, "restrictionsOf", {
|
|
3444
|
+
enumerable: true,
|
|
3445
|
+
get: function() {
|
|
3446
|
+
return restrictionsOf;
|
|
3447
|
+
}
|
|
3448
|
+
});
|
|
2007
3449
|
Object.defineProperty(exports, "revisionTokensEqual", {
|
|
2008
3450
|
enumerable: true,
|
|
2009
3451
|
get: function() {
|
|
2010
3452
|
return revisionTokensEqual;
|
|
2011
3453
|
}
|
|
2012
3454
|
});
|
|
3455
|
+
Object.defineProperty(exports, "richTextParagraphsFromPlainText", {
|
|
3456
|
+
enumerable: true,
|
|
3457
|
+
get: function() {
|
|
3458
|
+
return richTextParagraphsFromPlainText;
|
|
3459
|
+
}
|
|
3460
|
+
});
|
|
3461
|
+
Object.defineProperty(exports, "richTextPlainText", {
|
|
3462
|
+
enumerable: true,
|
|
3463
|
+
get: function() {
|
|
3464
|
+
return richTextPlainText;
|
|
3465
|
+
}
|
|
3466
|
+
});
|
|
3467
|
+
Object.defineProperty(exports, "sameEffectiveValue", {
|
|
3468
|
+
enumerable: true,
|
|
3469
|
+
get: function() {
|
|
3470
|
+
return sameEffectiveValue;
|
|
3471
|
+
}
|
|
3472
|
+
});
|
|
2013
3473
|
Object.defineProperty(exports, "searchContentEpoch", {
|
|
2014
3474
|
enumerable: true,
|
|
2015
3475
|
get: function() {
|
|
@@ -2046,6 +3506,12 @@ Object.defineProperty(exports, "sniffBinaryMetadata", {
|
|
|
2046
3506
|
return sniffBinaryMetadata;
|
|
2047
3507
|
}
|
|
2048
3508
|
});
|
|
3509
|
+
Object.defineProperty(exports, "stableStringify", {
|
|
3510
|
+
enumerable: true,
|
|
3511
|
+
get: function() {
|
|
3512
|
+
return stableStringify;
|
|
3513
|
+
}
|
|
3514
|
+
});
|
|
2049
3515
|
Object.defineProperty(exports, "textGlyphAt", {
|
|
2050
3516
|
enumerable: true,
|
|
2051
3517
|
get: function() {
|
|
@@ -2088,5 +3554,11 @@ Object.defineProperty(exports, "wordBefore", {
|
|
|
2088
3554
|
return wordBefore;
|
|
2089
3555
|
}
|
|
2090
3556
|
});
|
|
3557
|
+
Object.defineProperty(exports, "worstVerdict", {
|
|
3558
|
+
enumerable: true,
|
|
3559
|
+
get: function() {
|
|
3560
|
+
return worstVerdict;
|
|
3561
|
+
}
|
|
3562
|
+
});
|
|
2091
3563
|
|
|
2092
|
-
//# sourceMappingURL=builders-
|
|
3564
|
+
//# sourceMappingURL=builders-Bqm8yTpi.cjs.map
|