pdf-parser.js 5.2.27 → 5.2.29
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/{afm-widths-BYiX2L9D.cjs → afm-widths-BHD2HZIL.cjs} +553 -642
- package/dist/{afm-widths-blUDDjlI.js → afm-widths-B_IGbGky.js} +553 -642
- package/dist/afm-widths.cjs +1 -1
- package/dist/afm-widths.js +1 -1
- package/dist/annotations.cjs +5 -3
- package/dist/annotations.js +5 -3
- package/dist/builtin-encoding.cjs +17 -6
- package/dist/builtin-encoding.js +17 -6
- package/dist/bytes/crc32.cjs +5 -3
- package/dist/bytes/crc32.js +5 -3
- package/dist/bytes/flate.cjs +0 -1
- package/dist/bytes/flate.js +0 -1
- package/dist/cff-bounds.cjs +42 -24
- package/dist/cff-bounds.js +42 -24
- package/dist/cff.cjs +13 -6
- package/dist/cff.js +13 -6
- package/dist/cmap-table.cjs +54 -26
- package/dist/cmap-table.js +54 -26
- package/dist/cmap.cjs +5 -3
- package/dist/cmap.js +5 -3
- package/dist/content-write.cjs +3 -2
- package/dist/content-write.js +3 -2
- package/dist/crypto/aes.cjs +76 -55
- package/dist/crypto/aes.js +76 -55
- package/dist/crypto/md5.cjs +345 -149
- package/dist/crypto/md5.js +345 -149
- package/dist/crypto/rc4.cjs +5 -4
- package/dist/crypto/rc4.js +5 -4
- package/dist/crypto/sha2.cjs +92 -43
- package/dist/crypto/sha2.js +92 -43
- package/dist/embedded-font.cjs +4 -2
- package/dist/embedded-font.js +4 -2
- package/dist/encoding.cjs +1 -1
- package/dist/encoding.js +1 -1
- package/dist/encrypt-write.cjs +25 -21
- package/dist/encrypt-write.js +25 -21
- package/dist/encrypt.cjs +43 -64
- package/dist/encrypt.js +43 -64
- package/dist/filters.cjs +40 -20
- package/dist/filters.js +40 -20
- package/dist/font-face.cjs +2 -1
- package/dist/font-face.js +2 -1
- package/dist/font-read.cjs +12 -6
- package/dist/font-read.js +12 -6
- package/dist/font-tables.cjs +55 -28
- package/dist/font-tables.js +55 -28
- package/dist/gdef-table.cjs +6 -3
- package/dist/gdef-table.js +6 -3
- package/dist/glyf-contours.cjs +3 -1
- package/dist/glyf-contours.js +3 -1
- package/dist/glyf.cjs +22 -11
- package/dist/glyf.js +22 -11
- package/dist/gpos-table.cjs +37 -19
- package/dist/gpos-table.js +37 -19
- package/dist/gsub-table.cjs +28 -17
- package/dist/gsub-table.js +28 -17
- package/dist/hmtx-table.cjs +5 -3
- package/dist/hmtx-table.js +5 -3
- package/dist/image/ccitt-encode.cjs +8 -5
- package/dist/image/ccitt-encode.js +8 -5
- package/dist/image/ccitt.cjs +801 -209
- package/dist/image/ccitt.js +801 -209
- package/dist/image/jbig2-arith.cjs +340 -306
- package/dist/image/jbig2-arith.js +340 -306
- package/dist/image/jbig2-bitmap.cjs +8 -5
- package/dist/image/jbig2-bitmap.js +8 -5
- package/dist/image/jbig2-generic.cjs +240 -69
- package/dist/image/jbig2-generic.js +240 -69
- package/dist/image/jbig2-text.cjs +4 -2
- package/dist/image/jbig2-text.js +4 -2
- package/dist/image/jbig2.cjs +69 -31
- package/dist/image/jbig2.js +69 -31
- package/dist/image/jp2-boxes.cjs +52 -27
- package/dist/image/jp2-boxes.js +52 -27
- package/dist/image/jpeg-info.cjs +29 -24
- package/dist/image/jpeg-info.js +29 -24
- package/dist/image/jpeg2000-codestream.cjs +44 -24
- package/dist/image/jpeg2000-codestream.js +44 -24
- package/dist/image/jpeg2000-dwt.cjs +2 -1
- package/dist/image/jpeg2000-dwt.js +2 -1
- package/dist/image/jpeg2000-t1.cjs +21 -11
- package/dist/image/jpeg2000-t1.js +21 -11
- package/dist/image/jpeg2000-t2.cjs +12 -5
- package/dist/image/jpeg2000-t2.js +12 -5
- package/dist/image/jpeg2000-tagtree.cjs +8 -4
- package/dist/image/jpeg2000-tagtree.js +8 -4
- package/dist/image/jpeg2000.cjs +11 -6
- package/dist/image/jpeg2000.js +11 -6
- package/dist/image/png-decode.cjs +62 -39
- package/dist/image/png-decode.d.cts +3 -2
- package/dist/image/png-decode.d.ts +3 -2
- package/dist/image/png-decode.js +62 -39
- package/dist/image/png-filter.cjs +23 -15
- package/dist/image/png-filter.js +23 -15
- package/dist/images-read.cjs +41 -36
- package/dist/images-read.js +41 -36
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/interpret.cjs +39 -24
- package/dist/interpret.js +39 -24
- package/dist/lexer.cjs +86 -51
- package/dist/lexer.js +86 -51
- package/dist/math-content-write.cjs +9 -5
- package/dist/math-content-write.js +9 -5
- package/dist/math-font-write.cjs +2 -1
- package/dist/math-font-write.js +2 -1
- package/dist/math-font.cjs +16 -7
- package/dist/math-font.js +16 -7
- package/dist/math-table.cjs +25 -13
- package/dist/math-table.js +25 -13
- package/dist/matrix.cjs +3 -2
- package/dist/matrix.js +3 -2
- package/dist/measure.cjs +1 -1
- package/dist/measure.js +1 -1
- package/dist/navigation.cjs +10 -5
- package/dist/navigation.js +10 -5
- package/dist/ot-layout-common.cjs +4 -2
- package/dist/ot-layout-common.js +4 -2
- package/dist/parse.cjs +7 -5
- package/dist/parse.js +7 -5
- package/dist/pdf-text.cjs +5 -2
- package/dist/pdf-text.js +5 -2
- package/dist/predictors.cjs +14 -9
- package/dist/predictors.js +14 -9
- package/dist/raster.cjs +30 -19
- package/dist/raster.js +30 -19
- package/dist/read.cjs +17 -8
- package/dist/read.js +17 -8
- package/dist/serialize.cjs +6 -2
- package/dist/serialize.js +6 -2
- package/dist/sfnt-subset.cjs +25 -13
- package/dist/sfnt-subset.js +25 -13
- package/dist/sfnt.cjs +20 -10
- package/dist/sfnt.js +20 -10
- package/dist/text-group.cjs +0 -1
- package/dist/text-group.js +0 -1
- package/dist/tounicode.cjs +4 -2
- package/dist/tounicode.js +4 -2
- package/dist/winansi.cjs +1 -1
- package/dist/winansi.js +1 -1
- package/dist/write.cjs +20 -12
- package/dist/write.js +20 -12
- package/dist/xref.cjs +5 -2
- package/dist/xref.js +5 -2
- package/package.json +2 -2
package/dist/encrypt-write.js
CHANGED
|
@@ -14,6 +14,8 @@ var PdfEncryptionError = class extends Error {
|
|
|
14
14
|
};
|
|
15
15
|
const RC4_128_KEY_BYTES = 16;
|
|
16
16
|
const R6_PASSWORD_MAX_BYTES = 127;
|
|
17
|
+
const BITS_PER_BYTE = 8;
|
|
18
|
+
const MAX_PERMISSION_BITS = 32;
|
|
17
19
|
const RESERVED_BIT_7_AND_8 = 192;
|
|
18
20
|
const DEPRECATED_ALWAYS_ONE_BIT_10 = 512;
|
|
19
21
|
const SCHEME_SPECS = {
|
|
@@ -42,11 +44,12 @@ const SCHEME_SPECS = {
|
|
|
42
44
|
method: "aes"
|
|
43
45
|
}
|
|
44
46
|
};
|
|
47
|
+
const MAX_PRINTABLE_ASCII = 127;
|
|
45
48
|
function legacyPasswordBytes(password) {
|
|
46
49
|
const bytes = new Uint8Array(password.length);
|
|
47
50
|
for (let i = 0; i < password.length; i++) {
|
|
48
51
|
const code = password.charCodeAt(i);
|
|
49
|
-
if (code >
|
|
52
|
+
if (code > MAX_PRINTABLE_ASCII) throw new PdfEncryptionError(`password contains character code ${String(code)}, outside the printable-ASCII range this writer supports for the rc4-40/rc4-128/aes-128 schemes; use a plain-ASCII password, or the aes-256 scheme, whose revision-6 passwords are Unicode`);
|
|
50
53
|
bytes[i] = code;
|
|
51
54
|
}
|
|
52
55
|
return bytes;
|
|
@@ -65,15 +68,16 @@ function permissionsToP(permissions) {
|
|
|
65
68
|
p |= DEPRECATED_ALWAYS_ONE_BIT_10;
|
|
66
69
|
if (permissions?.assemble ?? true) p |= 1024;
|
|
67
70
|
if (permissions?.printHighRes ?? true) p |= 2048;
|
|
68
|
-
for (let bit = 13; bit <=
|
|
71
|
+
for (let bit = 13; bit <= MAX_PERMISSION_BITS; bit++) p |= 1 << bit - 1;
|
|
69
72
|
return p;
|
|
70
73
|
}
|
|
74
|
+
const ITERATED_REVISION_MIN = 3;
|
|
71
75
|
function computeLegacyOwnerValue(ownerPassword, userPassword, revision, keyBytes) {
|
|
72
76
|
let digest = md5(padOrTruncatePassword(ownerPassword));
|
|
73
|
-
if (revision >=
|
|
77
|
+
if (revision >= ITERATED_REVISION_MIN) for (let i = 0; i < 50; i++) digest = md5(digest);
|
|
74
78
|
const ownerKey = digest.subarray(0, keyBytes);
|
|
75
79
|
let value = rc4(ownerKey, padOrTruncatePassword(userPassword));
|
|
76
|
-
if (revision >=
|
|
80
|
+
if (revision >= ITERATED_REVISION_MIN) for (let i = 1; i <= 19; i++) {
|
|
77
81
|
const roundKey = Uint8Array.from(ownerKey, (byte) => byte ^ i);
|
|
78
82
|
value = rc4(roundKey, value);
|
|
79
83
|
}
|
|
@@ -124,9 +128,9 @@ function buildLegacyEncryptor(spec, options, fileId) {
|
|
|
124
128
|
["O", pdfHexString(owner)],
|
|
125
129
|
["U", pdfHexString(user)],
|
|
126
130
|
["P", pdfNum(p)],
|
|
127
|
-
["Length", pdfNum(spec.keyBytes *
|
|
131
|
+
["Length", pdfNum(spec.keyBytes * BITS_PER_BYTE)]
|
|
128
132
|
]);
|
|
129
|
-
if (spec.v ===
|
|
133
|
+
if (spec.v === SCHEME_SPECS["aes-128"].v) {
|
|
130
134
|
entries.set("CF", pdfDict({ StdCF: pdfDict({
|
|
131
135
|
AuthEvent: pdfName("DocOpen"),
|
|
132
136
|
CFM: pdfName("AESV2"),
|
|
@@ -165,21 +169,21 @@ function buildAes256Encryptor(spec, options) {
|
|
|
165
169
|
]);
|
|
166
170
|
const ownerIntermediateKey = hardenedHash(ownerPassword, ownerKeySalt, u, spec.r);
|
|
167
171
|
const oe = aesCbcEncrypt(ownerIntermediateKey, ZERO_IV, fileKey);
|
|
172
|
+
const PERMS_P_BYTES = 4;
|
|
173
|
+
const PERMS_SIGN_EXTENSION_BYTES = new Uint8Array(PERMS_P_BYTES).fill(255);
|
|
174
|
+
const PERMS_METADATA_FLAG_OFFSET = 8;
|
|
175
|
+
const ENCRYPT_METADATA_TRUE_ASCII = 84;
|
|
176
|
+
const ENCRYPT_METADATA_FALSE_ASCII = 70;
|
|
177
|
+
const ADB_MARKER_OFFSET = 9;
|
|
178
|
+
const ADB_MARKER_BYTES = Uint8Array.from("adb", (c) => c.charCodeAt(0));
|
|
179
|
+
const PERMS_RANDOM_BYTES = 4;
|
|
180
|
+
const PERMS_RANDOM_OFFSET = ADB_MARKER_OFFSET + ADB_MARKER_BYTES.length;
|
|
168
181
|
const permsBlock = /* @__PURE__ */ new Uint8Array(16);
|
|
169
182
|
permsBlock.set(permissionsBytes(p), 0);
|
|
170
|
-
permsBlock.set(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
255
|
|
175
|
-
], 4);
|
|
176
|
-
permsBlock[8] = encryptMetadata ? 84 : 70;
|
|
177
|
-
permsBlock.set([
|
|
178
|
-
97,
|
|
179
|
-
100,
|
|
180
|
-
98
|
|
181
|
-
], 9);
|
|
182
|
-
permsBlock.set(randomBytes(4), 12);
|
|
183
|
+
permsBlock.set(PERMS_SIGN_EXTENSION_BYTES, PERMS_P_BYTES);
|
|
184
|
+
permsBlock[PERMS_METADATA_FLAG_OFFSET] = encryptMetadata ? ENCRYPT_METADATA_TRUE_ASCII : ENCRYPT_METADATA_FALSE_ASCII;
|
|
185
|
+
permsBlock.set(ADB_MARKER_BYTES, ADB_MARKER_OFFSET);
|
|
186
|
+
permsBlock.set(randomBytes(PERMS_RANDOM_BYTES), PERMS_RANDOM_OFFSET);
|
|
183
187
|
const perms = aesCbcEncrypt(fileKey, ZERO_IV, permsBlock);
|
|
184
188
|
const entries = /* @__PURE__ */ new Map([
|
|
185
189
|
["Filter", pdfName("Standard")],
|
|
@@ -191,7 +195,7 @@ function buildAes256Encryptor(spec, options) {
|
|
|
191
195
|
["UE", pdfHexString(ue)],
|
|
192
196
|
["P", pdfNum(p)],
|
|
193
197
|
["Perms", pdfHexString(perms)],
|
|
194
|
-
["Length", pdfNum(spec.keyBytes *
|
|
198
|
+
["Length", pdfNum(spec.keyBytes * BITS_PER_BYTE)],
|
|
195
199
|
["CF", pdfDict({ StdCF: pdfDict({
|
|
196
200
|
AuthEvent: pdfName("DocOpen"),
|
|
197
201
|
CFM: pdfName("AESV3"),
|
|
@@ -206,7 +210,7 @@ function buildAes256Encryptor(spec, options) {
|
|
|
206
210
|
function createStandardEncryptor(options, fileId) {
|
|
207
211
|
const scheme = options.scheme ?? "aes-256";
|
|
208
212
|
const spec = SCHEME_SPECS[scheme];
|
|
209
|
-
return spec.v ===
|
|
213
|
+
return spec.v === SCHEME_SPECS["aes-256"].v ? buildAes256Encryptor(spec, options) : buildLegacyEncryptor(spec, options, fileId);
|
|
210
214
|
}
|
|
211
215
|
function encryptIndirectObject(value, num, gen, encryptor) {
|
|
212
216
|
if (value.kind === "string") return {
|
package/dist/encrypt.cjs
CHANGED
|
@@ -7,46 +7,18 @@ const require_crypto_md5 = require("./crypto/md5.cjs");
|
|
|
7
7
|
const require_crypto_rc4 = require("./crypto/rc4.cjs");
|
|
8
8
|
const require_crypto_sha2 = require("./crypto/sha2.cjs");
|
|
9
9
|
//#region src/encrypt.ts
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
]);
|
|
10
|
+
const HEX_RADIX = 16;
|
|
11
|
+
const HEX_CHARS_PER_BYTE = 2;
|
|
12
|
+
function bytesFromHex(hex) {
|
|
13
|
+
const bytes = new Uint8Array(hex.length / HEX_CHARS_PER_BYTE);
|
|
14
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
15
|
+
const start = i * HEX_CHARS_PER_BYTE;
|
|
16
|
+
bytes[i] = parseInt(hex.substring(start, start + HEX_CHARS_PER_BYTE), HEX_RADIX);
|
|
17
|
+
}
|
|
18
|
+
return bytes;
|
|
19
|
+
}
|
|
20
|
+
const PASSWORD_PADDING = bytesFromHex("28bf4e5e4e758a4164004e56fffa01082e2e00b6d0683e802f0ca9fe6453697a");
|
|
21
|
+
const AES_OBJECT_KEY_SALT = new TextEncoder().encode("sAlT");
|
|
50
22
|
const MD5_DIGEST_BYTES = 16;
|
|
51
23
|
const OBJECT_KEY_EXTRA_BYTES = 5;
|
|
52
24
|
const LEGACY_KEY_ITERATIONS = 50;
|
|
@@ -60,6 +32,9 @@ const RC4_40_KEY_BYTES = 5;
|
|
|
60
32
|
const AESV2_KEY_BYTES = 16;
|
|
61
33
|
const DEFAULT_KEY_BITS = 40;
|
|
62
34
|
const BITS_PER_BYTE = 8;
|
|
35
|
+
const BYTE_MASK = 255;
|
|
36
|
+
const UINT32_BYTE_2_SHIFT = 16;
|
|
37
|
+
const UINT32_BYTE_3_SHIFT = 24;
|
|
63
38
|
const MAX_RC4_KEY_BYTES = 16;
|
|
64
39
|
const ZERO_IV = /* @__PURE__ */ new Uint8Array(16);
|
|
65
40
|
function requireStringBytes(dict, key) {
|
|
@@ -74,10 +49,10 @@ function bytesEqual(a, b, length) {
|
|
|
74
49
|
function permissionsBytes(p) {
|
|
75
50
|
const unsigned = Math.trunc(p) >>> 0;
|
|
76
51
|
return new Uint8Array([
|
|
77
|
-
unsigned &
|
|
78
|
-
unsigned >>>
|
|
79
|
-
unsigned >>>
|
|
80
|
-
unsigned >>>
|
|
52
|
+
unsigned & BYTE_MASK,
|
|
53
|
+
unsigned >>> BITS_PER_BYTE & BYTE_MASK,
|
|
54
|
+
unsigned >>> UINT32_BYTE_2_SHIFT & BYTE_MASK,
|
|
55
|
+
unsigned >>> UINT32_BYTE_3_SHIFT & BYTE_MASK
|
|
81
56
|
]);
|
|
82
57
|
}
|
|
83
58
|
function padOrTruncatePassword(password) {
|
|
@@ -85,6 +60,9 @@ function padOrTruncatePassword(password) {
|
|
|
85
60
|
padded.set(password.subarray(0, PASSWORD_PADDING.length));
|
|
86
61
|
return padded;
|
|
87
62
|
}
|
|
63
|
+
const METADATA_FLAG_MIN_REVISION = 4;
|
|
64
|
+
const METADATA_NOT_ENCRYPTED_FLAG_BYTES = 4;
|
|
65
|
+
const ITERATED_REVISION_MIN = 3;
|
|
88
66
|
function computeLegacyFileKeyFromPaddedPassword(paddedPassword, owner, permissions, fileId, revision, keyBytes, encryptMetadata) {
|
|
89
67
|
const parts = [
|
|
90
68
|
paddedPassword,
|
|
@@ -92,14 +70,9 @@ function computeLegacyFileKeyFromPaddedPassword(paddedPassword, owner, permissio
|
|
|
92
70
|
permissionsBytes(permissions),
|
|
93
71
|
fileId
|
|
94
72
|
];
|
|
95
|
-
if (revision >=
|
|
96
|
-
255,
|
|
97
|
-
255,
|
|
98
|
-
255,
|
|
99
|
-
255
|
|
100
|
-
]));
|
|
73
|
+
if (revision >= METADATA_FLAG_MIN_REVISION && !encryptMetadata) parts.push(new Uint8Array(METADATA_NOT_ENCRYPTED_FLAG_BYTES).fill(BYTE_MASK));
|
|
101
74
|
let digest = require_crypto_md5.md5(require_bytes_writer.concatBytes(parts));
|
|
102
|
-
if (revision >=
|
|
75
|
+
if (revision >= ITERATED_REVISION_MIN) for (let i = 0; i < 50; i++) digest = require_crypto_md5.md5(digest.subarray(0, keyBytes));
|
|
103
76
|
return digest.subarray(0, keyBytes);
|
|
104
77
|
}
|
|
105
78
|
function computeLegacyFileKey(encryptDict, fileId, revision, keyBytes, encryptMetadata) {
|
|
@@ -120,13 +93,16 @@ function legacyUserPasswordVerifies(encryptDict, fileId, revision, fileKey) {
|
|
|
120
93
|
const storedUser = requireStringBytes(encryptDict, "U");
|
|
121
94
|
return bytesEqual(legacyUserValueCore(fileKey, fileId, revision), storedUser, revision === 2 ? PASSWORD_PADDING.length : 16);
|
|
122
95
|
}
|
|
96
|
+
const HARDENED_HASH_SHA256_ONLY_REVISION = 5;
|
|
97
|
+
const STANDARD_AESV3_REVISION = 6;
|
|
98
|
+
const HASH_SELECTOR_MODULUS = 3;
|
|
123
99
|
function hardenedHash(password, salt, userData, revision) {
|
|
124
100
|
let k = require_crypto_sha2.sha256(require_bytes_writer.concatBytes([
|
|
125
101
|
password,
|
|
126
102
|
salt,
|
|
127
103
|
userData
|
|
128
104
|
]));
|
|
129
|
-
if (revision ===
|
|
105
|
+
if (revision === HARDENED_HASH_SHA256_ONLY_REVISION) return k;
|
|
130
106
|
let e;
|
|
131
107
|
let round = 0;
|
|
132
108
|
do {
|
|
@@ -140,7 +116,7 @@ function hardenedHash(password, salt, userData, revision) {
|
|
|
140
116
|
e = require_crypto_aes.aesCbcEncrypt(k.subarray(0, 16), k.subarray(16, 32), k1);
|
|
141
117
|
let sum = 0;
|
|
142
118
|
for (let i = 0; i < 16; i++) sum += e[i];
|
|
143
|
-
const selector = sum %
|
|
119
|
+
const selector = sum % HASH_SELECTOR_MODULUS;
|
|
144
120
|
k = selector === 0 ? require_crypto_sha2.sha256(e) : selector === 1 ? require_crypto_sha2.sha384(e) : require_crypto_sha2.sha512(e);
|
|
145
121
|
round++;
|
|
146
122
|
} while (round < R6_MINIMUM_ROUNDS || e[e.length - 1] > round - R6_TERMINATION_MARGIN);
|
|
@@ -180,14 +156,17 @@ function cryptFilterKeyBytes(encryptDict, declaredBits) {
|
|
|
180
156
|
if (declared === void 0) return Math.floor(declaredBits / BITS_PER_BYTE);
|
|
181
157
|
return declared >= DEFAULT_KEY_BITS ? Math.floor(declared / BITS_PER_BYTE) : declared;
|
|
182
158
|
}
|
|
159
|
+
const V5_HANDLER_VERSION = 5;
|
|
160
|
+
const V4_HANDLER_VERSION = 4;
|
|
161
|
+
const LEGACY_MAX_REVISION = 4;
|
|
183
162
|
function setUpHandler(encryptDict, fileId) {
|
|
184
163
|
const version = require_objects.asNumber(require_objects.dictGet(encryptDict, "V")) ?? 0;
|
|
185
164
|
const revision = require_objects.asNumber(require_objects.dictGet(encryptDict, "R")) ?? 0;
|
|
186
165
|
const declaredBits = require_objects.asNumber(require_objects.dictGet(encryptDict, "Length")) ?? DEFAULT_KEY_BITS;
|
|
187
166
|
const encryptMetadataEntry = require_objects.dictGet(encryptDict, "EncryptMetadata");
|
|
188
167
|
const encryptMetadata = encryptMetadataEntry?.kind === "bool" ? encryptMetadataEntry.value : true;
|
|
189
|
-
if (version ===
|
|
190
|
-
if (revision !==
|
|
168
|
+
if (version === V5_HANDLER_VERSION) {
|
|
169
|
+
if (revision !== HARDENED_HASH_SHA256_ONLY_REVISION && revision !== STANDARD_AESV3_REVISION) throw new require_diagnostics.PdfEncryptedError(`/V 5 encryption with an unsupported revision /R ${String(revision)}`);
|
|
191
170
|
return {
|
|
192
171
|
fileKey: computeAesV3FileKey(encryptDict, revision),
|
|
193
172
|
streamMethod: methodFromCryptFilterName(encryptDict, "StmF"),
|
|
@@ -196,10 +175,10 @@ function setUpHandler(encryptDict, fileId) {
|
|
|
196
175
|
encryptMetadata
|
|
197
176
|
};
|
|
198
177
|
}
|
|
199
|
-
if (version !== 1 && version !== 2 && version !==
|
|
200
|
-
if (revision < 2 || revision >
|
|
201
|
-
const streamMethod = version ===
|
|
202
|
-
const stringMethod = version ===
|
|
178
|
+
if (version !== 1 && version !== 2 && version !== V4_HANDLER_VERSION) throw new require_diagnostics.PdfEncryptedError(`unsupported standard security handler version /V ${String(version)}`);
|
|
179
|
+
if (revision < 2 || revision > LEGACY_MAX_REVISION) throw new require_diagnostics.PdfEncryptedError(`unsupported standard security handler revision /R ${String(revision)}`);
|
|
180
|
+
const streamMethod = version === V4_HANDLER_VERSION ? methodFromCryptFilterName(encryptDict, "StmF") : "rc4";
|
|
181
|
+
const stringMethod = version === V4_HANDLER_VERSION ? methodFromCryptFilterName(encryptDict, "StrF") : "rc4";
|
|
203
182
|
const keyBytes = version === 1 ? 5 : version === 2 ? Math.floor(declaredBits / BITS_PER_BYTE) : cryptFilterKeyBytes(encryptDict, declaredBits);
|
|
204
183
|
if (keyBytes < 5 || 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`);
|
|
205
184
|
const fileKey = computeLegacyFileKey(encryptDict, fileId, revision, keyBytes, encryptMetadata);
|
|
@@ -214,11 +193,11 @@ function setUpHandler(encryptDict, fileId) {
|
|
|
214
193
|
}
|
|
215
194
|
function objectKey(fileKey, num, gen, method) {
|
|
216
195
|
const extra = new Uint8Array(OBJECT_KEY_EXTRA_BYTES);
|
|
217
|
-
extra[0] = num &
|
|
218
|
-
extra[1] = num >>>
|
|
219
|
-
extra[2] = num >>>
|
|
220
|
-
extra[3] = gen &
|
|
221
|
-
extra[4] = gen >>>
|
|
196
|
+
extra[0] = num & BYTE_MASK;
|
|
197
|
+
extra[1] = num >>> BITS_PER_BYTE & BYTE_MASK;
|
|
198
|
+
extra[2] = num >>> UINT32_BYTE_2_SHIFT & BYTE_MASK;
|
|
199
|
+
extra[3] = gen & BYTE_MASK;
|
|
200
|
+
extra[4] = gen >>> BITS_PER_BYTE & BYTE_MASK;
|
|
222
201
|
return require_crypto_md5.md5(require_bytes_writer.concatBytes(method === "aes" ? [
|
|
223
202
|
fileKey,
|
|
224
203
|
extra,
|
package/dist/encrypt.js
CHANGED
|
@@ -6,46 +6,18 @@ import { md5 } from "./crypto/md5.js";
|
|
|
6
6
|
import { rc4 } from "./crypto/rc4.js";
|
|
7
7
|
import { sha256, sha384, sha512 } from "./crypto/sha2.js";
|
|
8
8
|
//#region src/encrypt.ts
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
]);
|
|
9
|
+
const HEX_RADIX = 16;
|
|
10
|
+
const HEX_CHARS_PER_BYTE = 2;
|
|
11
|
+
function bytesFromHex(hex) {
|
|
12
|
+
const bytes = new Uint8Array(hex.length / HEX_CHARS_PER_BYTE);
|
|
13
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
14
|
+
const start = i * HEX_CHARS_PER_BYTE;
|
|
15
|
+
bytes[i] = parseInt(hex.substring(start, start + HEX_CHARS_PER_BYTE), HEX_RADIX);
|
|
16
|
+
}
|
|
17
|
+
return bytes;
|
|
18
|
+
}
|
|
19
|
+
const PASSWORD_PADDING = bytesFromHex("28bf4e5e4e758a4164004e56fffa01082e2e00b6d0683e802f0ca9fe6453697a");
|
|
20
|
+
const AES_OBJECT_KEY_SALT = new TextEncoder().encode("sAlT");
|
|
49
21
|
const MD5_DIGEST_BYTES = 16;
|
|
50
22
|
const OBJECT_KEY_EXTRA_BYTES = 5;
|
|
51
23
|
const LEGACY_KEY_ITERATIONS = 50;
|
|
@@ -59,6 +31,9 @@ const RC4_40_KEY_BYTES = 5;
|
|
|
59
31
|
const AESV2_KEY_BYTES = 16;
|
|
60
32
|
const DEFAULT_KEY_BITS = 40;
|
|
61
33
|
const BITS_PER_BYTE = 8;
|
|
34
|
+
const BYTE_MASK = 255;
|
|
35
|
+
const UINT32_BYTE_2_SHIFT = 16;
|
|
36
|
+
const UINT32_BYTE_3_SHIFT = 24;
|
|
62
37
|
const MAX_RC4_KEY_BYTES = 16;
|
|
63
38
|
const ZERO_IV = /* @__PURE__ */ new Uint8Array(16);
|
|
64
39
|
function requireStringBytes(dict, key) {
|
|
@@ -73,10 +48,10 @@ function bytesEqual(a, b, length) {
|
|
|
73
48
|
function permissionsBytes(p) {
|
|
74
49
|
const unsigned = Math.trunc(p) >>> 0;
|
|
75
50
|
return new Uint8Array([
|
|
76
|
-
unsigned &
|
|
77
|
-
unsigned >>>
|
|
78
|
-
unsigned >>>
|
|
79
|
-
unsigned >>>
|
|
51
|
+
unsigned & BYTE_MASK,
|
|
52
|
+
unsigned >>> BITS_PER_BYTE & BYTE_MASK,
|
|
53
|
+
unsigned >>> UINT32_BYTE_2_SHIFT & BYTE_MASK,
|
|
54
|
+
unsigned >>> UINT32_BYTE_3_SHIFT & BYTE_MASK
|
|
80
55
|
]);
|
|
81
56
|
}
|
|
82
57
|
function padOrTruncatePassword(password) {
|
|
@@ -84,6 +59,9 @@ function padOrTruncatePassword(password) {
|
|
|
84
59
|
padded.set(password.subarray(0, PASSWORD_PADDING.length));
|
|
85
60
|
return padded;
|
|
86
61
|
}
|
|
62
|
+
const METADATA_FLAG_MIN_REVISION = 4;
|
|
63
|
+
const METADATA_NOT_ENCRYPTED_FLAG_BYTES = 4;
|
|
64
|
+
const ITERATED_REVISION_MIN = 3;
|
|
87
65
|
function computeLegacyFileKeyFromPaddedPassword(paddedPassword, owner, permissions, fileId, revision, keyBytes, encryptMetadata) {
|
|
88
66
|
const parts = [
|
|
89
67
|
paddedPassword,
|
|
@@ -91,14 +69,9 @@ function computeLegacyFileKeyFromPaddedPassword(paddedPassword, owner, permissio
|
|
|
91
69
|
permissionsBytes(permissions),
|
|
92
70
|
fileId
|
|
93
71
|
];
|
|
94
|
-
if (revision >=
|
|
95
|
-
255,
|
|
96
|
-
255,
|
|
97
|
-
255,
|
|
98
|
-
255
|
|
99
|
-
]));
|
|
72
|
+
if (revision >= METADATA_FLAG_MIN_REVISION && !encryptMetadata) parts.push(new Uint8Array(METADATA_NOT_ENCRYPTED_FLAG_BYTES).fill(BYTE_MASK));
|
|
100
73
|
let digest = md5(concatBytes(parts));
|
|
101
|
-
if (revision >=
|
|
74
|
+
if (revision >= ITERATED_REVISION_MIN) for (let i = 0; i < 50; i++) digest = md5(digest.subarray(0, keyBytes));
|
|
102
75
|
return digest.subarray(0, keyBytes);
|
|
103
76
|
}
|
|
104
77
|
function computeLegacyFileKey(encryptDict, fileId, revision, keyBytes, encryptMetadata) {
|
|
@@ -119,13 +92,16 @@ function legacyUserPasswordVerifies(encryptDict, fileId, revision, fileKey) {
|
|
|
119
92
|
const storedUser = requireStringBytes(encryptDict, "U");
|
|
120
93
|
return bytesEqual(legacyUserValueCore(fileKey, fileId, revision), storedUser, revision === 2 ? PASSWORD_PADDING.length : 16);
|
|
121
94
|
}
|
|
95
|
+
const HARDENED_HASH_SHA256_ONLY_REVISION = 5;
|
|
96
|
+
const STANDARD_AESV3_REVISION = 6;
|
|
97
|
+
const HASH_SELECTOR_MODULUS = 3;
|
|
122
98
|
function hardenedHash(password, salt, userData, revision) {
|
|
123
99
|
let k = sha256(concatBytes([
|
|
124
100
|
password,
|
|
125
101
|
salt,
|
|
126
102
|
userData
|
|
127
103
|
]));
|
|
128
|
-
if (revision ===
|
|
104
|
+
if (revision === HARDENED_HASH_SHA256_ONLY_REVISION) return k;
|
|
129
105
|
let e;
|
|
130
106
|
let round = 0;
|
|
131
107
|
do {
|
|
@@ -139,7 +115,7 @@ function hardenedHash(password, salt, userData, revision) {
|
|
|
139
115
|
e = aesCbcEncrypt(k.subarray(0, 16), k.subarray(16, 32), k1);
|
|
140
116
|
let sum = 0;
|
|
141
117
|
for (let i = 0; i < 16; i++) sum += e[i];
|
|
142
|
-
const selector = sum %
|
|
118
|
+
const selector = sum % HASH_SELECTOR_MODULUS;
|
|
143
119
|
k = selector === 0 ? sha256(e) : selector === 1 ? sha384(e) : sha512(e);
|
|
144
120
|
round++;
|
|
145
121
|
} while (round < R6_MINIMUM_ROUNDS || e[e.length - 1] > round - R6_TERMINATION_MARGIN);
|
|
@@ -179,14 +155,17 @@ function cryptFilterKeyBytes(encryptDict, declaredBits) {
|
|
|
179
155
|
if (declared === void 0) return Math.floor(declaredBits / BITS_PER_BYTE);
|
|
180
156
|
return declared >= DEFAULT_KEY_BITS ? Math.floor(declared / BITS_PER_BYTE) : declared;
|
|
181
157
|
}
|
|
158
|
+
const V5_HANDLER_VERSION = 5;
|
|
159
|
+
const V4_HANDLER_VERSION = 4;
|
|
160
|
+
const LEGACY_MAX_REVISION = 4;
|
|
182
161
|
function setUpHandler(encryptDict, fileId) {
|
|
183
162
|
const version = asNumber(dictGet(encryptDict, "V")) ?? 0;
|
|
184
163
|
const revision = asNumber(dictGet(encryptDict, "R")) ?? 0;
|
|
185
164
|
const declaredBits = asNumber(dictGet(encryptDict, "Length")) ?? DEFAULT_KEY_BITS;
|
|
186
165
|
const encryptMetadataEntry = dictGet(encryptDict, "EncryptMetadata");
|
|
187
166
|
const encryptMetadata = encryptMetadataEntry?.kind === "bool" ? encryptMetadataEntry.value : true;
|
|
188
|
-
if (version ===
|
|
189
|
-
if (revision !==
|
|
167
|
+
if (version === V5_HANDLER_VERSION) {
|
|
168
|
+
if (revision !== HARDENED_HASH_SHA256_ONLY_REVISION && revision !== STANDARD_AESV3_REVISION) throw new PdfEncryptedError(`/V 5 encryption with an unsupported revision /R ${String(revision)}`);
|
|
190
169
|
return {
|
|
191
170
|
fileKey: computeAesV3FileKey(encryptDict, revision),
|
|
192
171
|
streamMethod: methodFromCryptFilterName(encryptDict, "StmF"),
|
|
@@ -195,10 +174,10 @@ function setUpHandler(encryptDict, fileId) {
|
|
|
195
174
|
encryptMetadata
|
|
196
175
|
};
|
|
197
176
|
}
|
|
198
|
-
if (version !== 1 && version !== 2 && version !==
|
|
199
|
-
if (revision < 2 || revision >
|
|
200
|
-
const streamMethod = version ===
|
|
201
|
-
const stringMethod = version ===
|
|
177
|
+
if (version !== 1 && version !== 2 && version !== V4_HANDLER_VERSION) throw new PdfEncryptedError(`unsupported standard security handler version /V ${String(version)}`);
|
|
178
|
+
if (revision < 2 || revision > LEGACY_MAX_REVISION) throw new PdfEncryptedError(`unsupported standard security handler revision /R ${String(revision)}`);
|
|
179
|
+
const streamMethod = version === V4_HANDLER_VERSION ? methodFromCryptFilterName(encryptDict, "StmF") : "rc4";
|
|
180
|
+
const stringMethod = version === V4_HANDLER_VERSION ? methodFromCryptFilterName(encryptDict, "StrF") : "rc4";
|
|
202
181
|
const keyBytes = version === 1 ? 5 : version === 2 ? Math.floor(declaredBits / BITS_PER_BYTE) : cryptFilterKeyBytes(encryptDict, declaredBits);
|
|
203
182
|
if (keyBytes < 5 || 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`);
|
|
204
183
|
const fileKey = computeLegacyFileKey(encryptDict, fileId, revision, keyBytes, encryptMetadata);
|
|
@@ -213,11 +192,11 @@ function setUpHandler(encryptDict, fileId) {
|
|
|
213
192
|
}
|
|
214
193
|
function objectKey(fileKey, num, gen, method) {
|
|
215
194
|
const extra = new Uint8Array(OBJECT_KEY_EXTRA_BYTES);
|
|
216
|
-
extra[0] = num &
|
|
217
|
-
extra[1] = num >>>
|
|
218
|
-
extra[2] = num >>>
|
|
219
|
-
extra[3] = gen &
|
|
220
|
-
extra[4] = gen >>>
|
|
195
|
+
extra[0] = num & BYTE_MASK;
|
|
196
|
+
extra[1] = num >>> BITS_PER_BYTE & BYTE_MASK;
|
|
197
|
+
extra[2] = num >>> UINT32_BYTE_2_SHIFT & BYTE_MASK;
|
|
198
|
+
extra[3] = gen & BYTE_MASK;
|
|
199
|
+
extra[4] = gen >>> BITS_PER_BYTE & BYTE_MASK;
|
|
221
200
|
return md5(concatBytes(method === "aes" ? [
|
|
222
201
|
fileKey,
|
|
223
202
|
extra,
|
package/dist/filters.cjs
CHANGED
|
@@ -6,6 +6,8 @@ const require_image_ccitt = require("./image/ccitt.cjs");
|
|
|
6
6
|
const require_image_jbig2 = require("./image/jbig2.cjs");
|
|
7
7
|
const require_predictors = require("./predictors.cjs");
|
|
8
8
|
//#region src/filters.ts
|
|
9
|
+
const BITS_PER_BYTE = 8;
|
|
10
|
+
const BYTE_MASK = 255;
|
|
9
11
|
function decodeStream(raw, dict, sink, resolve) {
|
|
10
12
|
const filters = filterNames(dict);
|
|
11
13
|
const parms = decodeParmsList(dict, filters.length);
|
|
@@ -103,7 +105,7 @@ function jbig2Decode(data, parm, dict, sink, resolve) {
|
|
|
103
105
|
}
|
|
104
106
|
});
|
|
105
107
|
return {
|
|
106
|
-
bytes: Uint8Array.from(image.bytes, (byte) => byte ^
|
|
108
|
+
bytes: Uint8Array.from(image.bytes, (byte) => byte ^ BYTE_MASK),
|
|
107
109
|
globals
|
|
108
110
|
};
|
|
109
111
|
} catch (error) {
|
|
@@ -146,6 +148,9 @@ function decodeParmsList(dict, count) {
|
|
|
146
148
|
const LZW_CLEAR_TABLE = 256;
|
|
147
149
|
const LZW_EOD = 257;
|
|
148
150
|
const LZW_INITIAL_CODE_WIDTH = 9;
|
|
151
|
+
const LZW_CODE_WIDTH_10 = 10;
|
|
152
|
+
const LZW_CODE_WIDTH_11 = 11;
|
|
153
|
+
const LZW_CODE_WIDTH_12 = 12;
|
|
149
154
|
const LZW_FIRST_NEW_CODE = 258;
|
|
150
155
|
function initialLzwDictionary() {
|
|
151
156
|
return Array.from({ length: 256 }, (_, i) => new Uint8Array([i]));
|
|
@@ -169,9 +174,9 @@ function lzwDecode(data, earlyChange, sink) {
|
|
|
169
174
|
const readCode = () => {
|
|
170
175
|
while (bitCount < codeWidth) {
|
|
171
176
|
if (pos >= data.length) return;
|
|
172
|
-
bitBuffer = bitBuffer <<
|
|
177
|
+
bitBuffer = bitBuffer << BITS_PER_BYTE | data[pos];
|
|
173
178
|
pos++;
|
|
174
|
-
bitCount +=
|
|
179
|
+
bitCount += BITS_PER_BYTE;
|
|
175
180
|
}
|
|
176
181
|
const value = bitBuffer >>> bitCount - codeWidth & (1 << codeWidth) - 1;
|
|
177
182
|
bitCount -= codeWidth;
|
|
@@ -203,9 +208,9 @@ function lzwDecode(data, earlyChange, sink) {
|
|
|
203
208
|
if (prevEntry !== void 0) {
|
|
204
209
|
dict[nextCode] = concatTwo(prevEntry, new Uint8Array([entry[0] ?? 0]));
|
|
205
210
|
nextCode++;
|
|
206
|
-
if (nextCode + bias ===
|
|
207
|
-
else if (nextCode + bias ===
|
|
208
|
-
else if (nextCode + bias ===
|
|
211
|
+
if (nextCode + bias === 2 ** LZW_INITIAL_CODE_WIDTH) codeWidth = LZW_CODE_WIDTH_10;
|
|
212
|
+
else if (nextCode + bias === 2 ** LZW_CODE_WIDTH_10) codeWidth = LZW_CODE_WIDTH_11;
|
|
213
|
+
else if (nextCode + bias === 2 ** LZW_CODE_WIDTH_11) codeWidth = LZW_CODE_WIDTH_12;
|
|
209
214
|
}
|
|
210
215
|
prevEntry = entry;
|
|
211
216
|
}
|
|
@@ -217,21 +222,26 @@ const ASCII85_END_MARKER = 126;
|
|
|
217
222
|
const ASCII85_MIN_DIGIT = 33;
|
|
218
223
|
const ASCII85_MAX_DIGIT = 117;
|
|
219
224
|
const ASCII85_MAX_DIGIT_VALUE = 84;
|
|
225
|
+
const ASCII85_RADIX = 85;
|
|
226
|
+
const ASCII85_GROUP_DIGITS = 5;
|
|
227
|
+
const ASCII85_OPTIONAL_PREFIX_LT = 60;
|
|
228
|
+
const ASCII85_BYTE_3_SHIFT = 24;
|
|
229
|
+
const ASCII85_BYTE_2_SHIFT = 16;
|
|
220
230
|
function ascii85GroupBytes(digits, byteCount) {
|
|
221
231
|
let value = 0;
|
|
222
|
-
for (const digit of digits) value = value *
|
|
232
|
+
for (const digit of digits) value = value * ASCII85_RADIX + digit;
|
|
223
233
|
return [
|
|
224
|
-
value >>>
|
|
225
|
-
value >>>
|
|
226
|
-
value >>>
|
|
227
|
-
value &
|
|
234
|
+
value >>> ASCII85_BYTE_3_SHIFT & BYTE_MASK,
|
|
235
|
+
value >>> ASCII85_BYTE_2_SHIFT & BYTE_MASK,
|
|
236
|
+
value >>> BITS_PER_BYTE & BYTE_MASK,
|
|
237
|
+
value & BYTE_MASK
|
|
228
238
|
].slice(0, byteCount);
|
|
229
239
|
}
|
|
230
240
|
function ascii85Decode(data) {
|
|
231
241
|
const out = [];
|
|
232
242
|
let tuple = [];
|
|
233
243
|
let i = 0;
|
|
234
|
-
if (data.length >= 2 && data[0] ===
|
|
244
|
+
if (data.length >= 2 && data[0] === ASCII85_OPTIONAL_PREFIX_LT && data[1] === ASCII85_END_MARKER) i = 2;
|
|
235
245
|
for (; i < data.length; i++) {
|
|
236
246
|
const byte = data[i];
|
|
237
247
|
if (byte === ASCII85_END_MARKER) break;
|
|
@@ -242,36 +252,46 @@ function ascii85Decode(data) {
|
|
|
242
252
|
}
|
|
243
253
|
if (byte < ASCII85_MIN_DIGIT || byte > ASCII85_MAX_DIGIT) continue;
|
|
244
254
|
tuple.push(byte - ASCII85_MIN_DIGIT);
|
|
245
|
-
if (tuple.length ===
|
|
255
|
+
if (tuple.length === ASCII85_GROUP_DIGITS) {
|
|
246
256
|
out.push(...ascii85GroupBytes(tuple, ASCII85_GROUP_BYTES));
|
|
247
257
|
tuple = [];
|
|
248
258
|
}
|
|
249
259
|
}
|
|
250
260
|
if (tuple.length > 1) {
|
|
251
261
|
const padded = tuple.slice();
|
|
252
|
-
while (padded.length <
|
|
262
|
+
while (padded.length < ASCII85_GROUP_DIGITS) padded.push(ASCII85_MAX_DIGIT_VALUE);
|
|
253
263
|
out.push(...ascii85GroupBytes(padded, tuple.length - 1));
|
|
254
264
|
}
|
|
255
265
|
return Uint8Array.from(out);
|
|
256
266
|
}
|
|
267
|
+
const ASCII_DIGIT_ZERO = 48;
|
|
268
|
+
const ASCII_DIGIT_NINE = 57;
|
|
269
|
+
const ASCII_UPPER_A = 65;
|
|
270
|
+
const ASCII_UPPER_F = 70;
|
|
271
|
+
const ASCII_LOWER_A = 97;
|
|
272
|
+
const ASCII_LOWER_F = 102;
|
|
273
|
+
const HEX_LETTER_DIGIT_OFFSET = 10;
|
|
274
|
+
const ASCII_HEX_TERMINATOR = 62;
|
|
275
|
+
const HEX_NIBBLE_BITS = 4;
|
|
257
276
|
function hexDigitValue(byte) {
|
|
258
|
-
if (byte >=
|
|
259
|
-
if (byte >=
|
|
260
|
-
if (byte >=
|
|
277
|
+
if (byte >= ASCII_DIGIT_ZERO && byte <= ASCII_DIGIT_NINE) return byte - ASCII_DIGIT_ZERO;
|
|
278
|
+
if (byte >= ASCII_UPPER_A && byte <= ASCII_UPPER_F) return byte - ASCII_UPPER_A + HEX_LETTER_DIGIT_OFFSET;
|
|
279
|
+
if (byte >= ASCII_LOWER_A && byte <= ASCII_LOWER_F) return byte - ASCII_LOWER_A + HEX_LETTER_DIGIT_OFFSET;
|
|
261
280
|
}
|
|
262
281
|
function asciiHexDecode(data) {
|
|
263
282
|
const digits = [];
|
|
264
283
|
for (const byte of data) {
|
|
265
|
-
if (byte ===
|
|
284
|
+
if (byte === ASCII_HEX_TERMINATOR) break;
|
|
266
285
|
const value = hexDigitValue(byte);
|
|
267
286
|
if (value !== void 0) digits.push(value);
|
|
268
287
|
}
|
|
269
288
|
if (digits.length % 2 === 1) digits.push(0);
|
|
270
289
|
const out = new Uint8Array(digits.length / 2);
|
|
271
|
-
for (let i = 0; i < out.length; i++) out[i] = digits[i * 2] <<
|
|
290
|
+
for (let i = 0; i < out.length; i++) out[i] = digits[i * 2] << HEX_NIBBLE_BITS | digits[i * 2 + 1];
|
|
272
291
|
return out;
|
|
273
292
|
}
|
|
274
293
|
const RUN_LENGTH_EOD = 128;
|
|
294
|
+
const RUN_LENGTH_REPEAT_COUNT_BASE = 257;
|
|
275
295
|
function runLengthDecode(data) {
|
|
276
296
|
const out = [];
|
|
277
297
|
let i = 0;
|
|
@@ -283,7 +303,7 @@ function runLengthDecode(data) {
|
|
|
283
303
|
const count = length + 1;
|
|
284
304
|
for (let j = 0; j < count && i < data.length; j++, i++) out.push(data[i]);
|
|
285
305
|
} else {
|
|
286
|
-
const count =
|
|
306
|
+
const count = RUN_LENGTH_REPEAT_COUNT_BASE - length;
|
|
287
307
|
const byte = data[i] ?? 0;
|
|
288
308
|
i++;
|
|
289
309
|
for (let j = 0; j < count; j++) out.push(byte);
|