pdf-codec 1.4.2 → 1.5.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 +20 -12
- package/dist/content-write.cjs +2 -3
- package/dist/content-write.js +1 -2
- package/dist/crypto/aes.cjs +166 -0
- package/dist/crypto/aes.d.cts +6 -0
- package/dist/crypto/aes.d.ts +6 -0
- package/dist/crypto/aes.js +163 -0
- package/dist/crypto/md5.cjs +215 -0
- package/dist/crypto/md5.d.cts +4 -0
- package/dist/crypto/md5.d.ts +4 -0
- package/dist/crypto/md5.js +214 -0
- package/dist/crypto/rc4.cjs +29 -0
- package/dist/crypto/rc4.d.cts +4 -0
- package/dist/crypto/rc4.d.ts +4 -0
- package/dist/crypto/rc4.js +28 -0
- package/dist/crypto/sha2.cjs +261 -0
- package/dist/crypto/sha2.d.cts +6 -0
- package/dist/crypto/sha2.d.ts +6 -0
- package/dist/crypto/sha2.js +258 -0
- package/dist/diagnostics.cjs +7 -0
- package/dist/diagnostics.d.cts +4 -1
- package/dist/diagnostics.d.ts +4 -1
- package/dist/diagnostics.js +7 -1
- package/dist/document.cjs +38 -2
- package/dist/document.js +38 -2
- package/dist/encrypt.cjs +264 -0
- package/dist/encrypt.d.cts +11 -0
- package/dist/encrypt.d.ts +11 -0
- package/dist/encrypt.js +263 -0
- package/dist/filters.cjs +19 -1
- package/dist/filters.js +19 -1
- package/dist/image/ccitt.cjs +488 -0
- package/dist/image/ccitt.d.cts +17 -0
- package/dist/image/ccitt.d.ts +17 -0
- package/dist/image/ccitt.js +487 -0
- package/dist/images-read.cjs +1 -4
- package/dist/images-read.js +2 -5
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/interpret.cjs +173 -38
- package/dist/interpret.d.cts +27 -10
- package/dist/interpret.d.ts +27 -10
- package/dist/interpret.js +174 -39
- package/dist/math-font-write.cjs +1 -1
- package/dist/math-font-write.js +1 -1
- package/dist/{matrix-B3c2_a2f.d.cts → matrix-BHzJESll.d.cts} +2 -1
- package/dist/{matrix-B3c2_a2f.d.ts → matrix-BHzJESll.d.ts} +2 -1
- package/dist/matrix.cjs +2 -0
- package/dist/matrix.d.cts +2 -2
- package/dist/matrix.d.ts +2 -2
- package/dist/matrix.js +2 -1
- package/dist/objects.cjs +4 -0
- package/dist/objects.d.cts +2 -1
- package/dist/objects.d.ts +2 -1
- package/dist/objects.js +4 -1
- package/dist/read.cjs +43 -4
- package/dist/read.d.cts +1 -1
- package/dist/read.d.ts +1 -1
- package/dist/read.js +43 -4
- package/dist/write.cjs +1 -1
- package/dist/write.js +1 -1
- package/package.json +1 -1
package/dist/document.js
CHANGED
|
@@ -2,6 +2,7 @@ import { ByteReader } from "./bytes/reader.js";
|
|
|
2
2
|
import { nextToken } from "./lexer.js";
|
|
3
3
|
import { PdfEncryptedError, PdfParseError } from "./diagnostics.js";
|
|
4
4
|
import { asArray, asDict, asNumber, dictGet, isName, pdfNull } from "./objects.js";
|
|
5
|
+
import { createStandardDecryptor } from "./encrypt.js";
|
|
5
6
|
import { decodeStream } from "./filters.js";
|
|
6
7
|
import { parseIndirectObject, parseValue } from "./parse.js";
|
|
7
8
|
import { readXref } from "./xref.js";
|
|
@@ -13,11 +14,39 @@ const INHERITABLE_PAGE_KEYS = [
|
|
|
13
14
|
"CropBox",
|
|
14
15
|
"Rotate"
|
|
15
16
|
];
|
|
17
|
+
function firstFileId(trailer) {
|
|
18
|
+
const first = asArray(dictGet(trailer, "ID"))?.[0];
|
|
19
|
+
return first?.kind === "string" ? first.bytes : /* @__PURE__ */ new Uint8Array(0);
|
|
20
|
+
}
|
|
21
|
+
function decryptDict(dict, num, gen, decryptor) {
|
|
22
|
+
return {
|
|
23
|
+
kind: "dict",
|
|
24
|
+
entries: new Map(Array.from(dict.entries, ([key, entry]) => [key, decryptObject(entry, num, gen, decryptor)]))
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function decryptObject(value, num, gen, decryptor) {
|
|
28
|
+
if (value.kind === "string") return {
|
|
29
|
+
kind: "string",
|
|
30
|
+
bytes: decryptor.decryptString(value.bytes, num, gen),
|
|
31
|
+
hex: value.hex
|
|
32
|
+
};
|
|
33
|
+
if (value.kind === "array") return {
|
|
34
|
+
kind: "array",
|
|
35
|
+
items: value.items.map((item) => decryptObject(item, num, gen, decryptor))
|
|
36
|
+
};
|
|
37
|
+
if (value.kind === "dict") return decryptDict(value, num, gen, decryptor);
|
|
38
|
+
if (value.kind === "stream") return {
|
|
39
|
+
kind: "stream",
|
|
40
|
+
dict: decryptDict(value.dict, num, gen, decryptor),
|
|
41
|
+
raw: decryptor.decryptStream(value.raw, value.dict, num, gen)
|
|
42
|
+
};
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
16
45
|
function openPdfDocument(bytes, sink) {
|
|
17
46
|
const xref = readXref(bytes, sink);
|
|
18
|
-
if (dictGet(xref.trailer, "Encrypt") !== void 0) throw new PdfEncryptedError();
|
|
19
47
|
const objectCache = /* @__PURE__ */ new Map();
|
|
20
48
|
const objStmCache = /* @__PURE__ */ new Map();
|
|
49
|
+
let decryptor;
|
|
21
50
|
function fetchDirect(num, entry) {
|
|
22
51
|
const reader = new ByteReader(bytes);
|
|
23
52
|
reader.seek(entry.offset);
|
|
@@ -30,7 +59,8 @@ function openPdfDocument(bytes, sink) {
|
|
|
30
59
|
});
|
|
31
60
|
return pdfNull();
|
|
32
61
|
}
|
|
33
|
-
return indirect.value;
|
|
62
|
+
if (decryptor === void 0) return indirect.value;
|
|
63
|
+
return decryptObject(indirect.value, num, indirect.gen, decryptor);
|
|
34
64
|
}
|
|
35
65
|
function decodeObjectStream(streamObjNum) {
|
|
36
66
|
const cached = objStmCache.get(streamObjNum);
|
|
@@ -112,6 +142,12 @@ function openPdfDocument(bytes, sink) {
|
|
|
112
142
|
if (dict === void 0 || !isName(dictGet(dict, "Type"), "Catalog")) throw new PdfParseError("pdf/no-root", "no resolvable /Root catalog was found, even after cross-reference recovery");
|
|
113
143
|
return dict;
|
|
114
144
|
}
|
|
145
|
+
const encryptEntry = dictGet(xref.trailer, "Encrypt");
|
|
146
|
+
if (encryptEntry !== void 0) {
|
|
147
|
+
const encryptDict = resolveDict(encryptEntry);
|
|
148
|
+
if (encryptDict === void 0) throw new PdfEncryptedError("the trailer names an /Encrypt dictionary that does not resolve to a dictionary at all");
|
|
149
|
+
decryptor = createStandardDecryptor(encryptDict, firstFileId(xref.trailer), sink);
|
|
150
|
+
}
|
|
115
151
|
const catalog = requireCatalog(resolveDict(dictGet(xref.trailer, "Root")));
|
|
116
152
|
function pages() {
|
|
117
153
|
const pagesRoot = resolveDict(dictGet(catalog, "Pages"));
|
package/dist/encrypt.cjs
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_bytes_writer = require("./bytes/writer.cjs");
|
|
3
|
+
const require_diagnostics = require("./diagnostics.cjs");
|
|
4
|
+
const require_crypto_aes = require("./crypto/aes.cjs");
|
|
5
|
+
const require_crypto_md5 = require("./crypto/md5.cjs");
|
|
6
|
+
const require_crypto_rc4 = require("./crypto/rc4.cjs");
|
|
7
|
+
const require_crypto_sha2 = require("./crypto/sha2.cjs");
|
|
8
|
+
const require_objects = require("./objects.cjs");
|
|
9
|
+
//#region src/encrypt.ts
|
|
10
|
+
const PASSWORD_PADDING = new Uint8Array([
|
|
11
|
+
40,
|
|
12
|
+
191,
|
|
13
|
+
78,
|
|
14
|
+
94,
|
|
15
|
+
78,
|
|
16
|
+
117,
|
|
17
|
+
138,
|
|
18
|
+
65,
|
|
19
|
+
100,
|
|
20
|
+
0,
|
|
21
|
+
78,
|
|
22
|
+
86,
|
|
23
|
+
255,
|
|
24
|
+
250,
|
|
25
|
+
1,
|
|
26
|
+
8,
|
|
27
|
+
46,
|
|
28
|
+
46,
|
|
29
|
+
0,
|
|
30
|
+
182,
|
|
31
|
+
208,
|
|
32
|
+
104,
|
|
33
|
+
62,
|
|
34
|
+
128,
|
|
35
|
+
47,
|
|
36
|
+
12,
|
|
37
|
+
169,
|
|
38
|
+
254,
|
|
39
|
+
100,
|
|
40
|
+
83,
|
|
41
|
+
105,
|
|
42
|
+
122
|
|
43
|
+
]);
|
|
44
|
+
const AES_OBJECT_KEY_SALT = new Uint8Array([
|
|
45
|
+
115,
|
|
46
|
+
65,
|
|
47
|
+
108,
|
|
48
|
+
84
|
|
49
|
+
]);
|
|
50
|
+
const MD5_DIGEST_BYTES = 16;
|
|
51
|
+
const OBJECT_KEY_EXTRA_BYTES = 5;
|
|
52
|
+
const LEGACY_KEY_ITERATIONS = 50;
|
|
53
|
+
const ALGORITHM_5_ITERATIONS = 19;
|
|
54
|
+
const R6_MINIMUM_ROUNDS = 64;
|
|
55
|
+
const R6_ROUND_REPEATS = 64;
|
|
56
|
+
const R6_TERMINATION_MARGIN = 32;
|
|
57
|
+
const AESV3_KEY_BYTES = 32;
|
|
58
|
+
const RC4_40_KEY_BYTES = 5;
|
|
59
|
+
const AESV2_KEY_BYTES = 16;
|
|
60
|
+
const DEFAULT_KEY_BITS = 40;
|
|
61
|
+
const BITS_PER_BYTE = 8;
|
|
62
|
+
const MAX_RC4_KEY_BYTES = 16;
|
|
63
|
+
const ZERO_IV = /* @__PURE__ */ new Uint8Array(16);
|
|
64
|
+
function requireStringBytes(dict, key) {
|
|
65
|
+
const value = require_objects.dictGet(dict, key);
|
|
66
|
+
if (value?.kind !== "string") throw new require_diagnostics.PdfEncryptedError(`the /Encrypt dictionary's /${key} entry is missing or is not a direct string; this file's encryption cannot be read`);
|
|
67
|
+
return value.bytes;
|
|
68
|
+
}
|
|
69
|
+
function bytesEqual(a, b, length) {
|
|
70
|
+
if (a.length < length || b.length < length) return false;
|
|
71
|
+
for (let i = 0; i < length; i++) if (a[i] !== b[i]) return false;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
function permissionsBytes(p) {
|
|
75
|
+
const unsigned = Math.trunc(p) >>> 0;
|
|
76
|
+
return new Uint8Array([
|
|
77
|
+
unsigned & 255,
|
|
78
|
+
unsigned >>> 8 & 255,
|
|
79
|
+
unsigned >>> 16 & 255,
|
|
80
|
+
unsigned >>> 24 & 255
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
function computeLegacyFileKey(encryptDict, fileId, revision, keyBytes, encryptMetadata) {
|
|
84
|
+
const owner = requireStringBytes(encryptDict, "O");
|
|
85
|
+
const permissions = require_objects.asNumber(require_objects.dictGet(encryptDict, "P")) ?? 0;
|
|
86
|
+
const parts = [
|
|
87
|
+
PASSWORD_PADDING,
|
|
88
|
+
owner.subarray(0, PASSWORD_PADDING.length),
|
|
89
|
+
permissionsBytes(permissions),
|
|
90
|
+
fileId
|
|
91
|
+
];
|
|
92
|
+
if (revision >= 4 && !encryptMetadata) parts.push(new Uint8Array([
|
|
93
|
+
255,
|
|
94
|
+
255,
|
|
95
|
+
255,
|
|
96
|
+
255
|
|
97
|
+
]));
|
|
98
|
+
let digest = require_crypto_md5.md5(require_bytes_writer.concatBytes(parts));
|
|
99
|
+
if (revision >= 3) for (let i = 0; i < LEGACY_KEY_ITERATIONS; i++) digest = require_crypto_md5.md5(digest.subarray(0, keyBytes));
|
|
100
|
+
return digest.subarray(0, keyBytes);
|
|
101
|
+
}
|
|
102
|
+
function legacyUserPasswordVerifies(encryptDict, fileId, revision, fileKey) {
|
|
103
|
+
const storedUser = requireStringBytes(encryptDict, "U");
|
|
104
|
+
if (revision === 2) return bytesEqual(require_crypto_rc4.rc4(fileKey, PASSWORD_PADDING), storedUser, PASSWORD_PADDING.length);
|
|
105
|
+
let value = require_crypto_rc4.rc4(fileKey, require_crypto_md5.md5(require_bytes_writer.concatBytes([PASSWORD_PADDING, fileId])));
|
|
106
|
+
for (let i = 1; i <= ALGORITHM_5_ITERATIONS; i++) {
|
|
107
|
+
const roundKey = Uint8Array.from(fileKey, (byte) => byte ^ i);
|
|
108
|
+
value = require_crypto_rc4.rc4(roundKey, value);
|
|
109
|
+
}
|
|
110
|
+
return bytesEqual(value, storedUser, MD5_DIGEST_BYTES);
|
|
111
|
+
}
|
|
112
|
+
function hardenedHash(password, salt, userData, revision) {
|
|
113
|
+
let k = require_crypto_sha2.sha256(require_bytes_writer.concatBytes([
|
|
114
|
+
password,
|
|
115
|
+
salt,
|
|
116
|
+
userData
|
|
117
|
+
]));
|
|
118
|
+
if (revision === 5) return k;
|
|
119
|
+
let e = new Uint8Array([0]);
|
|
120
|
+
let round = 0;
|
|
121
|
+
while (round < R6_MINIMUM_ROUNDS || e[e.length - 1] > round - R6_TERMINATION_MARGIN) {
|
|
122
|
+
const roundInput = require_bytes_writer.concatBytes([
|
|
123
|
+
password,
|
|
124
|
+
k,
|
|
125
|
+
userData
|
|
126
|
+
]);
|
|
127
|
+
const k1 = new Uint8Array(roundInput.length * R6_ROUND_REPEATS);
|
|
128
|
+
for (let i = 0; i < R6_ROUND_REPEATS; i++) k1.set(roundInput, i * roundInput.length);
|
|
129
|
+
e = require_crypto_aes.aesCbcEncrypt(k.subarray(0, 16), k.subarray(16, 32), k1);
|
|
130
|
+
let sum = 0;
|
|
131
|
+
for (let i = 0; i < 16; i++) sum += e[i];
|
|
132
|
+
const selector = sum % 3;
|
|
133
|
+
k = selector === 0 ? require_crypto_sha2.sha256(e) : selector === 1 ? require_crypto_sha2.sha384(e) : require_crypto_sha2.sha512(e);
|
|
134
|
+
round++;
|
|
135
|
+
}
|
|
136
|
+
return k.subarray(0, AESV3_KEY_BYTES);
|
|
137
|
+
}
|
|
138
|
+
function computeAesV3FileKey(encryptDict, revision) {
|
|
139
|
+
const storedUser = requireStringBytes(encryptDict, "U");
|
|
140
|
+
const expectedLength = 48;
|
|
141
|
+
if (storedUser.length < expectedLength) throw new require_diagnostics.PdfEncryptedError(`the /Encrypt dictionary's /U entry is ${String(storedUser.length)} bytes; revision ${String(revision)} requires ${String(expectedLength)}`);
|
|
142
|
+
const empty = /* @__PURE__ */ new Uint8Array(0);
|
|
143
|
+
const validationSalt = storedUser.subarray(AESV3_KEY_BYTES, 40);
|
|
144
|
+
const keySalt = storedUser.subarray(40, expectedLength);
|
|
145
|
+
if (!bytesEqual(hardenedHash(empty, validationSalt, empty, revision), storedUser, AESV3_KEY_BYTES)) throw new require_diagnostics.PdfPasswordRequiredError();
|
|
146
|
+
const intermediateKey = hardenedHash(empty, keySalt, empty, revision);
|
|
147
|
+
const wrappedKey = requireStringBytes(encryptDict, "UE");
|
|
148
|
+
if (wrappedKey.length < AESV3_KEY_BYTES) throw new require_diagnostics.PdfEncryptedError(`the /Encrypt dictionary's /UE entry is ${String(wrappedKey.length)} bytes; revision ${String(revision)} requires ${String(AESV3_KEY_BYTES)}`);
|
|
149
|
+
return require_crypto_aes.aesCbcDecrypt(intermediateKey, ZERO_IV, wrappedKey.subarray(0, AESV3_KEY_BYTES));
|
|
150
|
+
}
|
|
151
|
+
function methodFromCryptFilterName(encryptDict, entryKey) {
|
|
152
|
+
const name = require_objects.asName(require_objects.dictGet(encryptDict, entryKey)) ?? "Identity";
|
|
153
|
+
if (name === "Identity") return "identity";
|
|
154
|
+
const filters = require_objects.asDict(require_objects.dictGet(encryptDict, "CF"));
|
|
155
|
+
const filter = filters !== void 0 ? require_objects.asDict(require_objects.dictGet(filters, name)) : void 0;
|
|
156
|
+
if (filter === void 0) throw new require_diagnostics.PdfEncryptedError(`the /Encrypt dictionary names a crypt filter /${name} for /${entryKey} that its own /CF dictionary does not define`);
|
|
157
|
+
const method = require_objects.asName(require_objects.dictGet(filter, "CFM"));
|
|
158
|
+
if (method === "None") return "identity";
|
|
159
|
+
if (method === "V2") return "rc4";
|
|
160
|
+
if (method === "AESV2" || method === "AESV3") return "aes";
|
|
161
|
+
throw new require_diagnostics.PdfEncryptedError(`crypt filter /${name} uses an unsupported /CFM ${method === void 0 ? "(absent)" : `/${method}`}`);
|
|
162
|
+
}
|
|
163
|
+
function cryptFilterKeyBytes(encryptDict, declaredBits) {
|
|
164
|
+
const streamFilterName = require_objects.asName(require_objects.dictGet(encryptDict, "StmF")) ?? "Identity";
|
|
165
|
+
const filters = require_objects.asDict(require_objects.dictGet(encryptDict, "CF"));
|
|
166
|
+
const filter = filters !== void 0 ? require_objects.asDict(require_objects.dictGet(filters, streamFilterName)) : void 0;
|
|
167
|
+
if ((filter !== void 0 ? require_objects.asName(require_objects.dictGet(filter, "CFM")) : void 0) === "AESV2") return AESV2_KEY_BYTES;
|
|
168
|
+
const declared = filter !== void 0 ? require_objects.asNumber(require_objects.dictGet(filter, "Length")) : void 0;
|
|
169
|
+
if (declared === void 0) return Math.floor(declaredBits / BITS_PER_BYTE);
|
|
170
|
+
return declared >= DEFAULT_KEY_BITS ? Math.floor(declared / BITS_PER_BYTE) : declared;
|
|
171
|
+
}
|
|
172
|
+
function setUpHandler(encryptDict, fileId) {
|
|
173
|
+
const version = require_objects.asNumber(require_objects.dictGet(encryptDict, "V")) ?? 0;
|
|
174
|
+
const revision = require_objects.asNumber(require_objects.dictGet(encryptDict, "R")) ?? 0;
|
|
175
|
+
const declaredBits = require_objects.asNumber(require_objects.dictGet(encryptDict, "Length")) ?? DEFAULT_KEY_BITS;
|
|
176
|
+
const encryptMetadataEntry = require_objects.dictGet(encryptDict, "EncryptMetadata");
|
|
177
|
+
const encryptMetadata = encryptMetadataEntry?.kind === "bool" ? encryptMetadataEntry.value : true;
|
|
178
|
+
if (version === 5) {
|
|
179
|
+
if (revision !== 5 && revision !== 6) throw new require_diagnostics.PdfEncryptedError(`/V 5 encryption with an unsupported revision /R ${String(revision)}`);
|
|
180
|
+
return {
|
|
181
|
+
fileKey: computeAesV3FileKey(encryptDict, revision),
|
|
182
|
+
streamMethod: methodFromCryptFilterName(encryptDict, "StmF"),
|
|
183
|
+
stringMethod: methodFromCryptFilterName(encryptDict, "StrF"),
|
|
184
|
+
perObjectKeys: false,
|
|
185
|
+
encryptMetadata
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (version !== 1 && version !== 2 && version !== 4) throw new require_diagnostics.PdfEncryptedError(`unsupported standard security handler version /V ${String(version)}`);
|
|
189
|
+
if (revision < 2 || revision > 4) throw new require_diagnostics.PdfEncryptedError(`unsupported standard security handler revision /R ${String(revision)}`);
|
|
190
|
+
const streamMethod = version === 4 ? methodFromCryptFilterName(encryptDict, "StmF") : "rc4";
|
|
191
|
+
const stringMethod = version === 4 ? methodFromCryptFilterName(encryptDict, "StrF") : "rc4";
|
|
192
|
+
const keyBytes = version === 1 ? RC4_40_KEY_BYTES : version === 2 ? Math.floor(declaredBits / BITS_PER_BYTE) : cryptFilterKeyBytes(encryptDict, declaredBits);
|
|
193
|
+
if (keyBytes < RC4_40_KEY_BYTES || keyBytes > MAX_RC4_KEY_BYTES) throw new require_diagnostics.PdfEncryptedError(`the /Encrypt dictionary declares a ${String(keyBytes * BITS_PER_BYTE)}-bit file key, outside the 40-128 bit range /V ${String(version)} permits`);
|
|
194
|
+
const fileKey = computeLegacyFileKey(encryptDict, fileId, revision, keyBytes, encryptMetadata);
|
|
195
|
+
if (!legacyUserPasswordVerifies(encryptDict, fileId, revision, fileKey)) throw new require_diagnostics.PdfPasswordRequiredError();
|
|
196
|
+
return {
|
|
197
|
+
fileKey,
|
|
198
|
+
streamMethod,
|
|
199
|
+
stringMethod,
|
|
200
|
+
perObjectKeys: true,
|
|
201
|
+
encryptMetadata
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function objectKey(fileKey, num, gen, method) {
|
|
205
|
+
const extra = new Uint8Array(OBJECT_KEY_EXTRA_BYTES);
|
|
206
|
+
extra[0] = num & 255;
|
|
207
|
+
extra[1] = num >>> 8 & 255;
|
|
208
|
+
extra[2] = num >>> 16 & 255;
|
|
209
|
+
extra[3] = gen & 255;
|
|
210
|
+
extra[4] = gen >>> 8 & 255;
|
|
211
|
+
return require_crypto_md5.md5(require_bytes_writer.concatBytes(method === "aes" ? [
|
|
212
|
+
fileKey,
|
|
213
|
+
extra,
|
|
214
|
+
AES_OBJECT_KEY_SALT
|
|
215
|
+
] : [fileKey, extra])).subarray(0, Math.min(fileKey.length + OBJECT_KEY_EXTRA_BYTES, MD5_DIGEST_BYTES));
|
|
216
|
+
}
|
|
217
|
+
function decryptAes(key, data, sink) {
|
|
218
|
+
if (data.length <= 16) {
|
|
219
|
+
if (data.length !== 0 && data.length !== 16) sink({
|
|
220
|
+
code: "pdf/decrypt-truncated",
|
|
221
|
+
severity: "warning",
|
|
222
|
+
message: `an AES-encrypted value is ${String(data.length)} bytes, too short to carry even its own initialisation vector; treating it as empty`
|
|
223
|
+
});
|
|
224
|
+
return /* @__PURE__ */ new Uint8Array(0);
|
|
225
|
+
}
|
|
226
|
+
const body = data.subarray(16);
|
|
227
|
+
if (body.length % 16 !== 0) sink({
|
|
228
|
+
code: "pdf/decrypt-truncated",
|
|
229
|
+
severity: "warning",
|
|
230
|
+
message: `an AES-encrypted value's ciphertext is ${String(body.length)} bytes, not a whole number of 16-byte blocks; the trailing partial block is being dropped`
|
|
231
|
+
});
|
|
232
|
+
const plain = require_crypto_aes.aesCbcDecrypt(key, data.subarray(0, 16), body);
|
|
233
|
+
const padLength = plain[plain.length - 1] ?? 0;
|
|
234
|
+
if (padLength < 1 || padLength > 16 || padLength > plain.length) {
|
|
235
|
+
sink({
|
|
236
|
+
code: "pdf/decrypt-bad-padding",
|
|
237
|
+
severity: "warning",
|
|
238
|
+
message: "an AES-encrypted value did not end in valid PKCS#7 padding; keeping the decrypted bytes unstripped"
|
|
239
|
+
});
|
|
240
|
+
return plain;
|
|
241
|
+
}
|
|
242
|
+
return plain.subarray(0, plain.length - padLength);
|
|
243
|
+
}
|
|
244
|
+
function applyMethod(method, fileKey, perObjectKeys, bytes, num, gen, sink) {
|
|
245
|
+
if (method === "identity") return bytes;
|
|
246
|
+
const key = perObjectKeys ? objectKey(fileKey, num, gen, method) : fileKey;
|
|
247
|
+
return method === "rc4" ? require_crypto_rc4.rc4(key, bytes) : decryptAes(key, bytes, sink);
|
|
248
|
+
}
|
|
249
|
+
function createStandardDecryptor(encryptDict, fileId, sink) {
|
|
250
|
+
const filter = require_objects.asName(require_objects.dictGet(encryptDict, "Filter"));
|
|
251
|
+
if (filter !== "Standard") throw new require_diagnostics.PdfEncryptedError(`this PDF uses the ${filter === void 0 ? "unnamed" : `/${filter}`} security handler; only the standard security handler is supported`);
|
|
252
|
+
const setup = setUpHandler(encryptDict, fileId);
|
|
253
|
+
return {
|
|
254
|
+
decryptString(bytes, num, gen) {
|
|
255
|
+
return applyMethod(setup.stringMethod, setup.fileKey, setup.perObjectKeys, bytes, num, gen, sink);
|
|
256
|
+
},
|
|
257
|
+
decryptStream(bytes, dict, num, gen) {
|
|
258
|
+
if (!setup.encryptMetadata && require_objects.asName(require_objects.dictGet(dict, "Type")) === "Metadata") return bytes;
|
|
259
|
+
return applyMethod(setup.streamMethod, setup.fileKey, setup.perObjectKeys, bytes, num, gen, sink);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
//#endregion
|
|
264
|
+
exports.createStandardDecryptor = createStandardDecryptor;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PdfDiagnosticSink } from "./diagnostics.cjs";
|
|
2
|
+
import { PdfDict } from "./objects.cjs";
|
|
3
|
+
//#region src/encrypt.d.ts
|
|
4
|
+
type CipherMethod = 'identity' | 'rc4' | 'aes';
|
|
5
|
+
interface PdfDecryptor {
|
|
6
|
+
decryptString(bytes: Uint8Array<ArrayBuffer>, num: number, gen: number): Uint8Array<ArrayBuffer>;
|
|
7
|
+
decryptStream(bytes: Uint8Array<ArrayBuffer>, dict: PdfDict, num: number, gen: number): Uint8Array<ArrayBuffer>;
|
|
8
|
+
}
|
|
9
|
+
declare function createStandardDecryptor(encryptDict: PdfDict, fileId: Uint8Array<ArrayBuffer>, sink: PdfDiagnosticSink): PdfDecryptor;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { CipherMethod, PdfDecryptor, createStandardDecryptor };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PdfDiagnosticSink } from "./diagnostics.js";
|
|
2
|
+
import { PdfDict } from "./objects.js";
|
|
3
|
+
//#region src/encrypt.d.ts
|
|
4
|
+
type CipherMethod = 'identity' | 'rc4' | 'aes';
|
|
5
|
+
interface PdfDecryptor {
|
|
6
|
+
decryptString(bytes: Uint8Array<ArrayBuffer>, num: number, gen: number): Uint8Array<ArrayBuffer>;
|
|
7
|
+
decryptStream(bytes: Uint8Array<ArrayBuffer>, dict: PdfDict, num: number, gen: number): Uint8Array<ArrayBuffer>;
|
|
8
|
+
}
|
|
9
|
+
declare function createStandardDecryptor(encryptDict: PdfDict, fileId: Uint8Array<ArrayBuffer>, sink: PdfDiagnosticSink): PdfDecryptor;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { CipherMethod, PdfDecryptor, createStandardDecryptor };
|
package/dist/encrypt.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { concatBytes } from "./bytes/writer.js";
|
|
2
|
+
import { PdfEncryptedError, PdfPasswordRequiredError } from "./diagnostics.js";
|
|
3
|
+
import { aesCbcDecrypt, aesCbcEncrypt } from "./crypto/aes.js";
|
|
4
|
+
import { md5 } from "./crypto/md5.js";
|
|
5
|
+
import { rc4 } from "./crypto/rc4.js";
|
|
6
|
+
import { sha256, sha384, sha512 } from "./crypto/sha2.js";
|
|
7
|
+
import { asDict, asName, asNumber, dictGet } from "./objects.js";
|
|
8
|
+
//#region src/encrypt.ts
|
|
9
|
+
const PASSWORD_PADDING = new Uint8Array([
|
|
10
|
+
40,
|
|
11
|
+
191,
|
|
12
|
+
78,
|
|
13
|
+
94,
|
|
14
|
+
78,
|
|
15
|
+
117,
|
|
16
|
+
138,
|
|
17
|
+
65,
|
|
18
|
+
100,
|
|
19
|
+
0,
|
|
20
|
+
78,
|
|
21
|
+
86,
|
|
22
|
+
255,
|
|
23
|
+
250,
|
|
24
|
+
1,
|
|
25
|
+
8,
|
|
26
|
+
46,
|
|
27
|
+
46,
|
|
28
|
+
0,
|
|
29
|
+
182,
|
|
30
|
+
208,
|
|
31
|
+
104,
|
|
32
|
+
62,
|
|
33
|
+
128,
|
|
34
|
+
47,
|
|
35
|
+
12,
|
|
36
|
+
169,
|
|
37
|
+
254,
|
|
38
|
+
100,
|
|
39
|
+
83,
|
|
40
|
+
105,
|
|
41
|
+
122
|
|
42
|
+
]);
|
|
43
|
+
const AES_OBJECT_KEY_SALT = new Uint8Array([
|
|
44
|
+
115,
|
|
45
|
+
65,
|
|
46
|
+
108,
|
|
47
|
+
84
|
|
48
|
+
]);
|
|
49
|
+
const MD5_DIGEST_BYTES = 16;
|
|
50
|
+
const OBJECT_KEY_EXTRA_BYTES = 5;
|
|
51
|
+
const LEGACY_KEY_ITERATIONS = 50;
|
|
52
|
+
const ALGORITHM_5_ITERATIONS = 19;
|
|
53
|
+
const R6_MINIMUM_ROUNDS = 64;
|
|
54
|
+
const R6_ROUND_REPEATS = 64;
|
|
55
|
+
const R6_TERMINATION_MARGIN = 32;
|
|
56
|
+
const AESV3_KEY_BYTES = 32;
|
|
57
|
+
const RC4_40_KEY_BYTES = 5;
|
|
58
|
+
const AESV2_KEY_BYTES = 16;
|
|
59
|
+
const DEFAULT_KEY_BITS = 40;
|
|
60
|
+
const BITS_PER_BYTE = 8;
|
|
61
|
+
const MAX_RC4_KEY_BYTES = 16;
|
|
62
|
+
const ZERO_IV = /* @__PURE__ */ new Uint8Array(16);
|
|
63
|
+
function requireStringBytes(dict, key) {
|
|
64
|
+
const value = dictGet(dict, key);
|
|
65
|
+
if (value?.kind !== "string") throw new PdfEncryptedError(`the /Encrypt dictionary's /${key} entry is missing or is not a direct string; this file's encryption cannot be read`);
|
|
66
|
+
return value.bytes;
|
|
67
|
+
}
|
|
68
|
+
function bytesEqual(a, b, length) {
|
|
69
|
+
if (a.length < length || b.length < length) return false;
|
|
70
|
+
for (let i = 0; i < length; i++) if (a[i] !== b[i]) return false;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
function permissionsBytes(p) {
|
|
74
|
+
const unsigned = Math.trunc(p) >>> 0;
|
|
75
|
+
return new Uint8Array([
|
|
76
|
+
unsigned & 255,
|
|
77
|
+
unsigned >>> 8 & 255,
|
|
78
|
+
unsigned >>> 16 & 255,
|
|
79
|
+
unsigned >>> 24 & 255
|
|
80
|
+
]);
|
|
81
|
+
}
|
|
82
|
+
function computeLegacyFileKey(encryptDict, fileId, revision, keyBytes, encryptMetadata) {
|
|
83
|
+
const owner = requireStringBytes(encryptDict, "O");
|
|
84
|
+
const permissions = asNumber(dictGet(encryptDict, "P")) ?? 0;
|
|
85
|
+
const parts = [
|
|
86
|
+
PASSWORD_PADDING,
|
|
87
|
+
owner.subarray(0, PASSWORD_PADDING.length),
|
|
88
|
+
permissionsBytes(permissions),
|
|
89
|
+
fileId
|
|
90
|
+
];
|
|
91
|
+
if (revision >= 4 && !encryptMetadata) parts.push(new Uint8Array([
|
|
92
|
+
255,
|
|
93
|
+
255,
|
|
94
|
+
255,
|
|
95
|
+
255
|
|
96
|
+
]));
|
|
97
|
+
let digest = md5(concatBytes(parts));
|
|
98
|
+
if (revision >= 3) for (let i = 0; i < LEGACY_KEY_ITERATIONS; i++) digest = md5(digest.subarray(0, keyBytes));
|
|
99
|
+
return digest.subarray(0, keyBytes);
|
|
100
|
+
}
|
|
101
|
+
function legacyUserPasswordVerifies(encryptDict, fileId, revision, fileKey) {
|
|
102
|
+
const storedUser = requireStringBytes(encryptDict, "U");
|
|
103
|
+
if (revision === 2) return bytesEqual(rc4(fileKey, PASSWORD_PADDING), storedUser, PASSWORD_PADDING.length);
|
|
104
|
+
let value = rc4(fileKey, md5(concatBytes([PASSWORD_PADDING, fileId])));
|
|
105
|
+
for (let i = 1; i <= ALGORITHM_5_ITERATIONS; i++) {
|
|
106
|
+
const roundKey = Uint8Array.from(fileKey, (byte) => byte ^ i);
|
|
107
|
+
value = rc4(roundKey, value);
|
|
108
|
+
}
|
|
109
|
+
return bytesEqual(value, storedUser, MD5_DIGEST_BYTES);
|
|
110
|
+
}
|
|
111
|
+
function hardenedHash(password, salt, userData, revision) {
|
|
112
|
+
let k = sha256(concatBytes([
|
|
113
|
+
password,
|
|
114
|
+
salt,
|
|
115
|
+
userData
|
|
116
|
+
]));
|
|
117
|
+
if (revision === 5) return k;
|
|
118
|
+
let e = new Uint8Array([0]);
|
|
119
|
+
let round = 0;
|
|
120
|
+
while (round < R6_MINIMUM_ROUNDS || e[e.length - 1] > round - R6_TERMINATION_MARGIN) {
|
|
121
|
+
const roundInput = concatBytes([
|
|
122
|
+
password,
|
|
123
|
+
k,
|
|
124
|
+
userData
|
|
125
|
+
]);
|
|
126
|
+
const k1 = new Uint8Array(roundInput.length * R6_ROUND_REPEATS);
|
|
127
|
+
for (let i = 0; i < R6_ROUND_REPEATS; i++) k1.set(roundInput, i * roundInput.length);
|
|
128
|
+
e = aesCbcEncrypt(k.subarray(0, 16), k.subarray(16, 32), k1);
|
|
129
|
+
let sum = 0;
|
|
130
|
+
for (let i = 0; i < 16; i++) sum += e[i];
|
|
131
|
+
const selector = sum % 3;
|
|
132
|
+
k = selector === 0 ? sha256(e) : selector === 1 ? sha384(e) : sha512(e);
|
|
133
|
+
round++;
|
|
134
|
+
}
|
|
135
|
+
return k.subarray(0, AESV3_KEY_BYTES);
|
|
136
|
+
}
|
|
137
|
+
function computeAesV3FileKey(encryptDict, revision) {
|
|
138
|
+
const storedUser = requireStringBytes(encryptDict, "U");
|
|
139
|
+
const expectedLength = 48;
|
|
140
|
+
if (storedUser.length < expectedLength) throw new PdfEncryptedError(`the /Encrypt dictionary's /U entry is ${String(storedUser.length)} bytes; revision ${String(revision)} requires ${String(expectedLength)}`);
|
|
141
|
+
const empty = /* @__PURE__ */ new Uint8Array(0);
|
|
142
|
+
const validationSalt = storedUser.subarray(AESV3_KEY_BYTES, 40);
|
|
143
|
+
const keySalt = storedUser.subarray(40, expectedLength);
|
|
144
|
+
if (!bytesEqual(hardenedHash(empty, validationSalt, empty, revision), storedUser, AESV3_KEY_BYTES)) throw new PdfPasswordRequiredError();
|
|
145
|
+
const intermediateKey = hardenedHash(empty, keySalt, empty, revision);
|
|
146
|
+
const wrappedKey = requireStringBytes(encryptDict, "UE");
|
|
147
|
+
if (wrappedKey.length < AESV3_KEY_BYTES) throw new PdfEncryptedError(`the /Encrypt dictionary's /UE entry is ${String(wrappedKey.length)} bytes; revision ${String(revision)} requires ${String(AESV3_KEY_BYTES)}`);
|
|
148
|
+
return aesCbcDecrypt(intermediateKey, ZERO_IV, wrappedKey.subarray(0, AESV3_KEY_BYTES));
|
|
149
|
+
}
|
|
150
|
+
function methodFromCryptFilterName(encryptDict, entryKey) {
|
|
151
|
+
const name = asName(dictGet(encryptDict, entryKey)) ?? "Identity";
|
|
152
|
+
if (name === "Identity") return "identity";
|
|
153
|
+
const filters = asDict(dictGet(encryptDict, "CF"));
|
|
154
|
+
const filter = filters !== void 0 ? asDict(dictGet(filters, name)) : void 0;
|
|
155
|
+
if (filter === void 0) throw new PdfEncryptedError(`the /Encrypt dictionary names a crypt filter /${name} for /${entryKey} that its own /CF dictionary does not define`);
|
|
156
|
+
const method = asName(dictGet(filter, "CFM"));
|
|
157
|
+
if (method === "None") return "identity";
|
|
158
|
+
if (method === "V2") return "rc4";
|
|
159
|
+
if (method === "AESV2" || method === "AESV3") return "aes";
|
|
160
|
+
throw new PdfEncryptedError(`crypt filter /${name} uses an unsupported /CFM ${method === void 0 ? "(absent)" : `/${method}`}`);
|
|
161
|
+
}
|
|
162
|
+
function cryptFilterKeyBytes(encryptDict, declaredBits) {
|
|
163
|
+
const streamFilterName = asName(dictGet(encryptDict, "StmF")) ?? "Identity";
|
|
164
|
+
const filters = asDict(dictGet(encryptDict, "CF"));
|
|
165
|
+
const filter = filters !== void 0 ? asDict(dictGet(filters, streamFilterName)) : void 0;
|
|
166
|
+
if ((filter !== void 0 ? asName(dictGet(filter, "CFM")) : void 0) === "AESV2") return AESV2_KEY_BYTES;
|
|
167
|
+
const declared = filter !== void 0 ? asNumber(dictGet(filter, "Length")) : void 0;
|
|
168
|
+
if (declared === void 0) return Math.floor(declaredBits / BITS_PER_BYTE);
|
|
169
|
+
return declared >= DEFAULT_KEY_BITS ? Math.floor(declared / BITS_PER_BYTE) : declared;
|
|
170
|
+
}
|
|
171
|
+
function setUpHandler(encryptDict, fileId) {
|
|
172
|
+
const version = asNumber(dictGet(encryptDict, "V")) ?? 0;
|
|
173
|
+
const revision = asNumber(dictGet(encryptDict, "R")) ?? 0;
|
|
174
|
+
const declaredBits = asNumber(dictGet(encryptDict, "Length")) ?? DEFAULT_KEY_BITS;
|
|
175
|
+
const encryptMetadataEntry = dictGet(encryptDict, "EncryptMetadata");
|
|
176
|
+
const encryptMetadata = encryptMetadataEntry?.kind === "bool" ? encryptMetadataEntry.value : true;
|
|
177
|
+
if (version === 5) {
|
|
178
|
+
if (revision !== 5 && revision !== 6) throw new PdfEncryptedError(`/V 5 encryption with an unsupported revision /R ${String(revision)}`);
|
|
179
|
+
return {
|
|
180
|
+
fileKey: computeAesV3FileKey(encryptDict, revision),
|
|
181
|
+
streamMethod: methodFromCryptFilterName(encryptDict, "StmF"),
|
|
182
|
+
stringMethod: methodFromCryptFilterName(encryptDict, "StrF"),
|
|
183
|
+
perObjectKeys: false,
|
|
184
|
+
encryptMetadata
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (version !== 1 && version !== 2 && version !== 4) throw new PdfEncryptedError(`unsupported standard security handler version /V ${String(version)}`);
|
|
188
|
+
if (revision < 2 || revision > 4) throw new PdfEncryptedError(`unsupported standard security handler revision /R ${String(revision)}`);
|
|
189
|
+
const streamMethod = version === 4 ? methodFromCryptFilterName(encryptDict, "StmF") : "rc4";
|
|
190
|
+
const stringMethod = version === 4 ? methodFromCryptFilterName(encryptDict, "StrF") : "rc4";
|
|
191
|
+
const keyBytes = version === 1 ? RC4_40_KEY_BYTES : version === 2 ? Math.floor(declaredBits / BITS_PER_BYTE) : cryptFilterKeyBytes(encryptDict, declaredBits);
|
|
192
|
+
if (keyBytes < RC4_40_KEY_BYTES || keyBytes > MAX_RC4_KEY_BYTES) throw new PdfEncryptedError(`the /Encrypt dictionary declares a ${String(keyBytes * BITS_PER_BYTE)}-bit file key, outside the 40-128 bit range /V ${String(version)} permits`);
|
|
193
|
+
const fileKey = computeLegacyFileKey(encryptDict, fileId, revision, keyBytes, encryptMetadata);
|
|
194
|
+
if (!legacyUserPasswordVerifies(encryptDict, fileId, revision, fileKey)) throw new PdfPasswordRequiredError();
|
|
195
|
+
return {
|
|
196
|
+
fileKey,
|
|
197
|
+
streamMethod,
|
|
198
|
+
stringMethod,
|
|
199
|
+
perObjectKeys: true,
|
|
200
|
+
encryptMetadata
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function objectKey(fileKey, num, gen, method) {
|
|
204
|
+
const extra = new Uint8Array(OBJECT_KEY_EXTRA_BYTES);
|
|
205
|
+
extra[0] = num & 255;
|
|
206
|
+
extra[1] = num >>> 8 & 255;
|
|
207
|
+
extra[2] = num >>> 16 & 255;
|
|
208
|
+
extra[3] = gen & 255;
|
|
209
|
+
extra[4] = gen >>> 8 & 255;
|
|
210
|
+
return md5(concatBytes(method === "aes" ? [
|
|
211
|
+
fileKey,
|
|
212
|
+
extra,
|
|
213
|
+
AES_OBJECT_KEY_SALT
|
|
214
|
+
] : [fileKey, extra])).subarray(0, Math.min(fileKey.length + OBJECT_KEY_EXTRA_BYTES, MD5_DIGEST_BYTES));
|
|
215
|
+
}
|
|
216
|
+
function decryptAes(key, data, sink) {
|
|
217
|
+
if (data.length <= 16) {
|
|
218
|
+
if (data.length !== 0 && data.length !== 16) sink({
|
|
219
|
+
code: "pdf/decrypt-truncated",
|
|
220
|
+
severity: "warning",
|
|
221
|
+
message: `an AES-encrypted value is ${String(data.length)} bytes, too short to carry even its own initialisation vector; treating it as empty`
|
|
222
|
+
});
|
|
223
|
+
return /* @__PURE__ */ new Uint8Array(0);
|
|
224
|
+
}
|
|
225
|
+
const body = data.subarray(16);
|
|
226
|
+
if (body.length % 16 !== 0) sink({
|
|
227
|
+
code: "pdf/decrypt-truncated",
|
|
228
|
+
severity: "warning",
|
|
229
|
+
message: `an AES-encrypted value's ciphertext is ${String(body.length)} bytes, not a whole number of 16-byte blocks; the trailing partial block is being dropped`
|
|
230
|
+
});
|
|
231
|
+
const plain = aesCbcDecrypt(key, data.subarray(0, 16), body);
|
|
232
|
+
const padLength = plain[plain.length - 1] ?? 0;
|
|
233
|
+
if (padLength < 1 || padLength > 16 || padLength > plain.length) {
|
|
234
|
+
sink({
|
|
235
|
+
code: "pdf/decrypt-bad-padding",
|
|
236
|
+
severity: "warning",
|
|
237
|
+
message: "an AES-encrypted value did not end in valid PKCS#7 padding; keeping the decrypted bytes unstripped"
|
|
238
|
+
});
|
|
239
|
+
return plain;
|
|
240
|
+
}
|
|
241
|
+
return plain.subarray(0, plain.length - padLength);
|
|
242
|
+
}
|
|
243
|
+
function applyMethod(method, fileKey, perObjectKeys, bytes, num, gen, sink) {
|
|
244
|
+
if (method === "identity") return bytes;
|
|
245
|
+
const key = perObjectKeys ? objectKey(fileKey, num, gen, method) : fileKey;
|
|
246
|
+
return method === "rc4" ? rc4(key, bytes) : decryptAes(key, bytes, sink);
|
|
247
|
+
}
|
|
248
|
+
function createStandardDecryptor(encryptDict, fileId, sink) {
|
|
249
|
+
const filter = asName(dictGet(encryptDict, "Filter"));
|
|
250
|
+
if (filter !== "Standard") throw new PdfEncryptedError(`this PDF uses the ${filter === void 0 ? "unnamed" : `/${filter}`} security handler; only the standard security handler is supported`);
|
|
251
|
+
const setup = setUpHandler(encryptDict, fileId);
|
|
252
|
+
return {
|
|
253
|
+
decryptString(bytes, num, gen) {
|
|
254
|
+
return applyMethod(setup.stringMethod, setup.fileKey, setup.perObjectKeys, bytes, num, gen, sink);
|
|
255
|
+
},
|
|
256
|
+
decryptStream(bytes, dict, num, gen) {
|
|
257
|
+
if (!setup.encryptMetadata && asName(dictGet(dict, "Type")) === "Metadata") return bytes;
|
|
258
|
+
return applyMethod(setup.streamMethod, setup.fileKey, setup.perObjectKeys, bytes, num, gen, sink);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
263
|
+
export { createStandardDecryptor };
|
package/dist/filters.cjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_bytes_reader = require("./bytes/reader.cjs");
|
|
3
|
-
const require_bytes_flate = require("./bytes/flate.cjs");
|
|
4
3
|
const require_objects = require("./objects.cjs");
|
|
4
|
+
const require_bytes_flate = require("./bytes/flate.cjs");
|
|
5
|
+
const require_image_ccitt = require("./image/ccitt.cjs");
|
|
5
6
|
const require_predictors = require("./predictors.cjs");
|
|
6
7
|
//#region src/filters.ts
|
|
7
8
|
function decodeStream(raw, dict, sink) {
|
|
@@ -18,6 +19,7 @@ function decodeStream(raw, dict, sink) {
|
|
|
18
19
|
} else if (filter === "ASCII85Decode" || filter === "A85") bytes = ascii85Decode(bytes);
|
|
19
20
|
else if (filter === "ASCIIHexDecode" || filter === "AHx") bytes = asciiHexDecode(bytes);
|
|
20
21
|
else if (filter === "RunLengthDecode" || filter === "RL") bytes = runLengthDecode(bytes);
|
|
22
|
+
else if (filter === "CCITTFaxDecode" || filter === "CCF") bytes = ccittFaxDecode(bytes, parm, dict, sink);
|
|
21
23
|
else if (filter === "DCTDecode" || filter === "DCT") return {
|
|
22
24
|
bytes,
|
|
23
25
|
remainingFilter: "DCTDecode"
|
|
@@ -39,6 +41,22 @@ function decodeStream(raw, dict, sink) {
|
|
|
39
41
|
function applyPredictorIfPresent(data, parm, sink) {
|
|
40
42
|
return require_predictors.applyPredictor(data, require_predictors.readPredictorParams(parm), sink);
|
|
41
43
|
}
|
|
44
|
+
function ccittFaxDecode(data, parm, dict, sink) {
|
|
45
|
+
const parmGet = (key) => parm !== void 0 ? require_objects.dictGet(parm, key) : void 0;
|
|
46
|
+
const rows = require_objects.asNumber(parmGet("Rows")) ?? require_objects.asNumber(require_objects.dictGet(dict, "Height") ?? require_objects.dictGet(dict, "H"));
|
|
47
|
+
return require_image_ccitt.decodeCcittFax(data, {
|
|
48
|
+
k: require_objects.asNumber(parmGet("K")),
|
|
49
|
+
columns: require_objects.asNumber(parmGet("Columns")),
|
|
50
|
+
rows,
|
|
51
|
+
blackIs1: require_objects.asBool(parmGet("BlackIs1")),
|
|
52
|
+
encodedByteAlign: require_objects.asBool(parmGet("EncodedByteAlign")),
|
|
53
|
+
onWarning: (message) => sink({
|
|
54
|
+
code: "pdf/ccitt-fax-degraded",
|
|
55
|
+
severity: "warning",
|
|
56
|
+
message
|
|
57
|
+
})
|
|
58
|
+
}).bytes;
|
|
59
|
+
}
|
|
42
60
|
function filterNames(dict) {
|
|
43
61
|
const filterObj = require_objects.dictGet(dict, "Filter") ?? require_objects.dictGet(dict, "F");
|
|
44
62
|
if (filterObj === void 0) return [];
|