@cj-tech-master/excelts 8.0.0 → 8.1.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.
Files changed (106) hide show
  1. package/README.md +14 -1
  2. package/README_zh.md +6 -0
  3. package/dist/browser/modules/archive/zip/stream.d.ts +4 -0
  4. package/dist/browser/modules/archive/zip/stream.js +53 -0
  5. package/dist/browser/modules/pdf/core/crypto.d.ts +65 -0
  6. package/dist/browser/modules/pdf/core/crypto.js +637 -0
  7. package/dist/browser/modules/pdf/core/encryption.d.ts +23 -20
  8. package/dist/browser/modules/pdf/core/encryption.js +88 -261
  9. package/dist/browser/modules/pdf/core/pdf-writer.d.ts +6 -4
  10. package/dist/browser/modules/pdf/core/pdf-writer.js +19 -10
  11. package/dist/browser/modules/pdf/index.d.ts +23 -2
  12. package/dist/browser/modules/pdf/index.js +21 -3
  13. package/dist/browser/modules/pdf/reader/annotation-extractor.d.ts +63 -0
  14. package/dist/browser/modules/pdf/reader/annotation-extractor.js +155 -0
  15. package/dist/browser/modules/pdf/reader/cmap-parser.d.ts +70 -0
  16. package/dist/browser/modules/pdf/reader/cmap-parser.js +321 -0
  17. package/dist/browser/modules/pdf/reader/content-interpreter.d.ts +57 -0
  18. package/dist/browser/modules/pdf/reader/content-interpreter.js +715 -0
  19. package/dist/browser/modules/pdf/reader/font-decoder.d.ts +58 -0
  20. package/dist/browser/modules/pdf/reader/font-decoder.js +1513 -0
  21. package/dist/browser/modules/pdf/reader/form-extractor.d.ts +48 -0
  22. package/dist/browser/modules/pdf/reader/form-extractor.js +355 -0
  23. package/dist/browser/modules/pdf/reader/image-extractor.d.ts +55 -0
  24. package/dist/browser/modules/pdf/reader/image-extractor.js +220 -0
  25. package/dist/browser/modules/pdf/reader/metadata-reader.d.ts +56 -0
  26. package/dist/browser/modules/pdf/reader/metadata-reader.js +275 -0
  27. package/dist/browser/modules/pdf/reader/pdf-decrypt.d.ts +26 -0
  28. package/dist/browser/modules/pdf/reader/pdf-decrypt.js +443 -0
  29. package/dist/browser/modules/pdf/reader/pdf-document.d.ts +191 -0
  30. package/dist/browser/modules/pdf/reader/pdf-document.js +818 -0
  31. package/dist/browser/modules/pdf/reader/pdf-parser.d.ts +65 -0
  32. package/dist/browser/modules/pdf/reader/pdf-parser.js +285 -0
  33. package/dist/browser/modules/pdf/reader/pdf-reader.d.ts +143 -0
  34. package/dist/browser/modules/pdf/reader/pdf-reader.js +200 -0
  35. package/dist/browser/modules/pdf/reader/pdf-tokenizer.d.ts +101 -0
  36. package/dist/browser/modules/pdf/reader/pdf-tokenizer.js +543 -0
  37. package/dist/browser/modules/pdf/reader/reader-utils.d.ts +15 -0
  38. package/dist/browser/modules/pdf/reader/reader-utils.js +27 -0
  39. package/dist/browser/modules/pdf/reader/stream-filters.d.ts +20 -0
  40. package/dist/browser/modules/pdf/reader/stream-filters.js +456 -0
  41. package/dist/browser/modules/pdf/reader/text-reconstruction.d.ts +44 -0
  42. package/dist/browser/modules/pdf/reader/text-reconstruction.js +463 -0
  43. package/dist/cjs/modules/archive/zip/stream.js +53 -0
  44. package/dist/cjs/modules/pdf/core/crypto.js +649 -0
  45. package/dist/cjs/modules/pdf/core/encryption.js +88 -263
  46. package/dist/cjs/modules/pdf/core/pdf-writer.js +19 -10
  47. package/dist/cjs/modules/pdf/index.js +23 -4
  48. package/dist/cjs/modules/pdf/reader/annotation-extractor.js +158 -0
  49. package/dist/cjs/modules/pdf/reader/cmap-parser.js +326 -0
  50. package/dist/cjs/modules/pdf/reader/content-interpreter.js +718 -0
  51. package/dist/cjs/modules/pdf/reader/font-decoder.js +1518 -0
  52. package/dist/cjs/modules/pdf/reader/form-extractor.js +358 -0
  53. package/dist/cjs/modules/pdf/reader/image-extractor.js +223 -0
  54. package/dist/cjs/modules/pdf/reader/metadata-reader.js +278 -0
  55. package/dist/cjs/modules/pdf/reader/pdf-decrypt.js +447 -0
  56. package/dist/cjs/modules/pdf/reader/pdf-document.js +822 -0
  57. package/dist/cjs/modules/pdf/reader/pdf-parser.js +301 -0
  58. package/dist/cjs/modules/pdf/reader/pdf-reader.js +203 -0
  59. package/dist/cjs/modules/pdf/reader/pdf-tokenizer.js +517 -0
  60. package/dist/cjs/modules/pdf/reader/reader-utils.js +30 -0
  61. package/dist/cjs/modules/pdf/reader/stream-filters.js +459 -0
  62. package/dist/cjs/modules/pdf/reader/text-reconstruction.js +467 -0
  63. package/dist/esm/modules/archive/zip/stream.js +53 -0
  64. package/dist/esm/modules/pdf/core/crypto.js +637 -0
  65. package/dist/esm/modules/pdf/core/encryption.js +88 -261
  66. package/dist/esm/modules/pdf/core/pdf-writer.js +19 -10
  67. package/dist/esm/modules/pdf/index.js +21 -3
  68. package/dist/esm/modules/pdf/reader/annotation-extractor.js +155 -0
  69. package/dist/esm/modules/pdf/reader/cmap-parser.js +321 -0
  70. package/dist/esm/modules/pdf/reader/content-interpreter.js +715 -0
  71. package/dist/esm/modules/pdf/reader/font-decoder.js +1513 -0
  72. package/dist/esm/modules/pdf/reader/form-extractor.js +355 -0
  73. package/dist/esm/modules/pdf/reader/image-extractor.js +220 -0
  74. package/dist/esm/modules/pdf/reader/metadata-reader.js +275 -0
  75. package/dist/esm/modules/pdf/reader/pdf-decrypt.js +443 -0
  76. package/dist/esm/modules/pdf/reader/pdf-document.js +818 -0
  77. package/dist/esm/modules/pdf/reader/pdf-parser.js +285 -0
  78. package/dist/esm/modules/pdf/reader/pdf-reader.js +200 -0
  79. package/dist/esm/modules/pdf/reader/pdf-tokenizer.js +543 -0
  80. package/dist/esm/modules/pdf/reader/reader-utils.js +27 -0
  81. package/dist/esm/modules/pdf/reader/stream-filters.js +456 -0
  82. package/dist/esm/modules/pdf/reader/text-reconstruction.js +463 -0
  83. package/dist/iife/excelts.iife.js +703 -267
  84. package/dist/iife/excelts.iife.js.map +1 -1
  85. package/dist/iife/excelts.iife.min.js +35 -35
  86. package/dist/types/modules/archive/zip/stream.d.ts +4 -0
  87. package/dist/types/modules/pdf/core/crypto.d.ts +65 -0
  88. package/dist/types/modules/pdf/core/encryption.d.ts +23 -20
  89. package/dist/types/modules/pdf/core/pdf-writer.d.ts +6 -4
  90. package/dist/types/modules/pdf/index.d.ts +23 -2
  91. package/dist/types/modules/pdf/reader/annotation-extractor.d.ts +63 -0
  92. package/dist/types/modules/pdf/reader/cmap-parser.d.ts +70 -0
  93. package/dist/types/modules/pdf/reader/content-interpreter.d.ts +57 -0
  94. package/dist/types/modules/pdf/reader/font-decoder.d.ts +58 -0
  95. package/dist/types/modules/pdf/reader/form-extractor.d.ts +48 -0
  96. package/dist/types/modules/pdf/reader/image-extractor.d.ts +55 -0
  97. package/dist/types/modules/pdf/reader/metadata-reader.d.ts +56 -0
  98. package/dist/types/modules/pdf/reader/pdf-decrypt.d.ts +26 -0
  99. package/dist/types/modules/pdf/reader/pdf-document.d.ts +191 -0
  100. package/dist/types/modules/pdf/reader/pdf-parser.d.ts +65 -0
  101. package/dist/types/modules/pdf/reader/pdf-reader.d.ts +143 -0
  102. package/dist/types/modules/pdf/reader/pdf-tokenizer.d.ts +101 -0
  103. package/dist/types/modules/pdf/reader/reader-utils.d.ts +15 -0
  104. package/dist/types/modules/pdf/reader/stream-filters.d.ts +20 -0
  105. package/dist/types/modules/pdf/reader/text-reconstruction.d.ts +44 -0
  106. package/package.json +1 -1
@@ -1,316 +1,141 @@
1
1
  "use strict";
2
2
  /**
3
- * PDF encryption support (Standard Security Handler, Revision 3).
3
+ * PDF encryption support (Standard Security Handler, V=5, R=5).
4
4
  *
5
- * Implements RC4-128 encryption compatible with PDF 1.4.
5
+ * Implements AES-256 encryption compatible with PDF 2.0 (ISO 32000-2:2020).
6
6
  * Supports:
7
7
  * - User password (required to open the document)
8
8
  * - Owner password (grants full access)
9
9
  * - Permission flags (print, copy, modify, etc.)
10
10
  *
11
- * @see PDF Reference 1.7, §3.5 - Encryption
11
+ * The file encryption key (FEK) is a random 256-bit key.
12
+ * All streams and strings are encrypted using AES-256-CBC with a random
13
+ * 16-byte IV prepended to each encrypted value.
14
+ *
15
+ * @see ISO 32000-2:2020, §7.6 — Encryption
12
16
  */
13
17
  Object.defineProperty(exports, "__esModule", { value: true });
14
18
  exports.initEncryption = initEncryption;
15
19
  exports.encryptData = encryptData;
16
- exports.rc4 = rc4;
17
- exports.md5 = md5;
18
- // =============================================================================
19
- // Constants
20
- // =============================================================================
21
- /** PDF password padding string (32 bytes) per PDF spec §3.5.2 */
22
- const PASSWORD_PADDING = new Uint8Array([
23
- 0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08,
24
- 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a
25
- ]);
20
+ const crypto_1 = require("./crypto");
26
21
  // =============================================================================
27
22
  // Public API
28
23
  // =============================================================================
29
24
  /**
30
- * Initialize encryption state from the given options.
25
+ * Initialize encryption state for AES-256 (V=5, R=5).
31
26
  */
32
27
  function initEncryption(options) {
33
- const userPwd = options.userPassword ?? "";
34
- const ownerPwd = options.ownerPassword;
28
+ const userPwd = truncatePassword(options.userPassword ?? "");
29
+ const ownerPwd = truncatePassword(options.ownerPassword);
35
30
  const perms = computePermissions(options.permissions);
36
- const fileId = generateFileId();
37
- // Step 1: Compute O value
38
- const oValue = computeOValue(ownerPwd, userPwd);
39
- // Step 2: Compute encryption key
40
- const encryptionKey = computeEncryptionKey(userPwd, oValue, perms, fileId);
31
+ // Step 1: Generate random 32-byte file encryption key
32
+ const encryptionKey = (0, crypto_1.randomBytes)(32);
33
+ // Step 2: Generate random salts
34
+ const uValidationSalt = (0, crypto_1.randomBytes)(8);
35
+ const uKeySalt = (0, crypto_1.randomBytes)(8);
36
+ const oValidationSalt = (0, crypto_1.randomBytes)(8);
37
+ const oKeySalt = (0, crypto_1.randomBytes)(8);
41
38
  // Step 3: Compute U value
42
- const uValue = computeUValue(encryptionKey, fileId);
43
- return { encryptionKey, oValue, uValue, permissions: perms, fileId };
44
- }
45
- /**
46
- * Encrypt a string or stream for a specific PDF object.
47
- * Per-object encryption key = MD5(encryptionKey + objectNumber + generation).
48
- */
49
- function encryptData(data, objectNumber, generation, encryptionKey) {
50
- // Compute per-object key: MD5(encryptionKey + objNum(3LE) + genNum(2LE))
51
- const keyInput = new Uint8Array(encryptionKey.length + 5);
52
- keyInput.set(encryptionKey);
53
- keyInput[encryptionKey.length] = objectNumber & 0xff;
54
- keyInput[encryptionKey.length + 1] = (objectNumber >> 8) & 0xff;
55
- keyInput[encryptionKey.length + 2] = (objectNumber >> 16) & 0xff;
56
- keyInput[encryptionKey.length + 3] = generation & 0xff;
57
- keyInput[encryptionKey.length + 4] = (generation >> 8) & 0xff;
58
- const objKey = md5(keyInput);
59
- // Use min(n+5, 16) bytes of the hash as the RC4 key
60
- const keyLen = Math.min(encryptionKey.length + 5, 16);
61
- const rc4Key = objKey.subarray(0, keyLen);
62
- return rc4(rc4Key, data);
39
+ // U hash = SHA-256(userPassword + uValidationSalt)
40
+ const uHash = (0, crypto_1.sha256)((0, crypto_1.concatArrays)(userPwd, uValidationSalt));
41
+ const uValue = (0, crypto_1.concatArrays)(uHash, uValidationSalt, uKeySalt);
42
+ // Step 4: Compute UE value
43
+ // UE = AES-256-CBC-encrypt(encryptionKey, SHA-256(userPassword + uKeySalt), zeroIV)
44
+ // Actually: the key for encrypting UE is SHA-256(password + key_salt),
45
+ // and we encrypt the file encryption key with it.
46
+ const ueKey = (0, crypto_1.sha256)((0, crypto_1.concatArrays)(userPwd, uKeySalt));
47
+ const zeroIv = new Uint8Array(16);
48
+ const ueValue = (0, crypto_1.aesCbcEncryptRaw)(encryptionKey, ueKey, zeroIv);
49
+ // Step 5: Compute O value
50
+ // O hash = SHA-256(ownerPassword + oValidationSalt + U(0..47))
51
+ const oHash = (0, crypto_1.sha256)((0, crypto_1.concatArrays)(ownerPwd, oValidationSalt, uValue));
52
+ const oValue = (0, crypto_1.concatArrays)(oHash, oValidationSalt, oKeySalt);
53
+ // Step 6: Compute OE value
54
+ // OE = AES-256-CBC-encrypt(encryptionKey, SHA-256(ownerPassword + oKeySalt + U(0..47)), zeroIV)
55
+ const oeKey = (0, crypto_1.sha256)((0, crypto_1.concatArrays)(ownerPwd, oKeySalt, uValue));
56
+ const oeValue = (0, crypto_1.aesCbcEncryptRaw)(encryptionKey, oeKey, zeroIv);
57
+ // Step 7: Compute Perms value
58
+ // 16-byte block: P(4 LE bytes) + 0xFF(4 bytes) + 'T' or 'F' (encryptMetadata) + 'a' 'd' 'b' + 0(3 bytes)
59
+ const permsBlock = new Uint8Array(16);
60
+ const permsView = new DataView(permsBlock.buffer);
61
+ permsView.setInt32(0, perms, true); // P value in little-endian
62
+ permsBlock[4] = 0xff;
63
+ permsBlock[5] = 0xff;
64
+ permsBlock[6] = 0xff;
65
+ permsBlock[7] = 0xff;
66
+ permsBlock[8] = 0x54; // 'T' — EncryptMetadata = true
67
+ permsBlock[9] = 0x61; // 'a'
68
+ permsBlock[10] = 0x64; // 'd'
69
+ permsBlock[11] = 0x62; // 'b'
70
+ // bytes 12-15 are zero
71
+ const permsValue = (0, crypto_1.aesEcbEncrypt)(permsBlock, encryptionKey);
72
+ // File ID (random 16 bytes, used in trailer)
73
+ const fileId = (0, crypto_1.randomBytes)(16);
74
+ return {
75
+ encryptionKey,
76
+ oValue,
77
+ uValue,
78
+ oeValue,
79
+ ueValue,
80
+ permsValue,
81
+ permissions: perms,
82
+ fileId
83
+ };
63
84
  }
64
- // =============================================================================
65
- // RC4 Cipher
66
- // =============================================================================
67
85
  /**
68
- * RC4 stream cipher implementation.
86
+ * Encrypt data for a PDF object using AES-256-CBC.
87
+ *
88
+ * For V=5/R=5, the file encryption key is used directly (no per-object key derivation).
89
+ * A random 16-byte IV is prepended to the ciphertext.
69
90
  */
70
- function rc4(key, data) {
71
- // Key Scheduling Algorithm (KSA)
72
- const s = new Uint8Array(256);
73
- for (let i = 0; i < 256; i++) {
74
- s[i] = i;
75
- }
76
- let j = 0;
77
- for (let i = 0; i < 256; i++) {
78
- j = (j + s[i] + key[i % key.length]) & 0xff;
79
- [s[i], s[j]] = [s[j], s[i]];
80
- }
81
- // Pseudo-Random Generation Algorithm (PRGA)
82
- const result = new Uint8Array(data.length);
83
- let ii = 0;
84
- let jj = 0;
85
- for (let k = 0; k < data.length; k++) {
86
- ii = (ii + 1) & 0xff;
87
- jj = (jj + s[ii]) & 0xff;
88
- [s[ii], s[jj]] = [s[jj], s[ii]];
89
- result[k] = data[k] ^ s[(s[ii] + s[jj]) & 0xff];
90
- }
91
+ function encryptData(data, _objectNumber, _generation, encryptionKey) {
92
+ const iv = (0, crypto_1.randomBytes)(16);
93
+ const ciphertext = (0, crypto_1.aesCbcEncrypt)(data, encryptionKey, iv);
94
+ // Prepend IV to ciphertext per PDF spec
95
+ const result = new Uint8Array(16 + ciphertext.length);
96
+ result.set(iv);
97
+ result.set(ciphertext, 16);
91
98
  return result;
92
99
  }
93
100
  // =============================================================================
94
- // MD5 Hash
95
- // =============================================================================
96
- /**
97
- * MD5 hash implementation (RFC 1321).
98
- * Returns 16-byte digest.
99
- */
100
- function md5(input) {
101
- // Pre-processing: padding
102
- const msgLen = input.length;
103
- const bitLen = msgLen * 8;
104
- // Pad to 64-byte boundary (56 bytes mod 64, then 8 bytes length)
105
- const padLen = ((56 - ((msgLen + 1) % 64) + 64) % 64) + 1;
106
- const padded = new Uint8Array(msgLen + padLen + 8);
107
- padded.set(input);
108
- padded[msgLen] = 0x80;
109
- // Append length in bits as 64-bit little-endian
110
- const view = new DataView(padded.buffer);
111
- view.setUint32(padded.length - 8, bitLen >>> 0, true);
112
- view.setUint32(padded.length - 4, 0, true); // high 32 bits (always 0 for our sizes)
113
- // Initialize hash values
114
- let a0 = 0x67452301;
115
- let b0 = 0xefcdab89;
116
- let c0 = 0x98badcfe;
117
- let d0 = 0x10325476;
118
- // Process each 64-byte block
119
- for (let i = 0; i < padded.length; i += 64) {
120
- const M = new Uint32Array(16);
121
- for (let j = 0; j < 16; j++) {
122
- M[j] = view.getUint32(i + j * 4, true);
123
- }
124
- let A = a0;
125
- let B = b0;
126
- let C = c0;
127
- let D = d0;
128
- for (let j = 0; j < 64; j++) {
129
- let F;
130
- let g;
131
- if (j < 16) {
132
- F = (B & C) | (~B & D);
133
- g = j;
134
- }
135
- else if (j < 32) {
136
- F = (D & B) | (~D & C);
137
- g = (5 * j + 1) % 16;
138
- }
139
- else if (j < 48) {
140
- F = B ^ C ^ D;
141
- g = (3 * j + 5) % 16;
142
- }
143
- else {
144
- F = C ^ (B | ~D);
145
- g = (7 * j) % 16;
146
- }
147
- F = (F + A + K[j] + M[g]) >>> 0;
148
- A = D;
149
- D = C;
150
- C = B;
151
- B = (B + rotl(F, S[j])) >>> 0;
152
- }
153
- a0 = (a0 + A) >>> 0;
154
- b0 = (b0 + B) >>> 0;
155
- c0 = (c0 + C) >>> 0;
156
- d0 = (d0 + D) >>> 0;
157
- }
158
- // Produce the 128-bit digest
159
- const digest = new Uint8Array(16);
160
- const dv = new DataView(digest.buffer);
161
- dv.setUint32(0, a0, true);
162
- dv.setUint32(4, b0, true);
163
- dv.setUint32(8, c0, true);
164
- dv.setUint32(12, d0, true);
165
- return digest;
166
- }
167
- function rotl(x, n) {
168
- return ((x << n) | (x >>> (32 - n))) >>> 0;
169
- }
170
- // MD5 per-round shift amounts
171
- const S = [
172
- 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14,
173
- 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6,
174
- 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
175
- ];
176
- // MD5 per-round constants (floor(2^32 × abs(sin(i+1))))
177
- const K = new Uint32Array([
178
- 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
179
- 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
180
- 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
181
- 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
182
- 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
183
- 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
184
- 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
185
- 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
186
- ]);
187
- // =============================================================================
188
- // PDF Password / Key Computation
101
+ // Internal Helpers
189
102
  // =============================================================================
190
103
  /**
191
- * Pad or truncate a password to 32 bytes using the PDF password padding.
104
+ * Truncate password to 127 bytes (UTF-8) per PDF 2.0 spec.
192
105
  */
193
- function padPassword(password) {
194
- const result = new Uint8Array(32);
106
+ function truncatePassword(password) {
195
107
  const bytes = new TextEncoder().encode(password);
196
- const len = Math.min(bytes.length, 32);
197
- result.set(bytes.subarray(0, len));
198
- result.set(PASSWORD_PADDING.subarray(0, 32 - len), len);
199
- return result;
200
- }
201
- /**
202
- * Compute the O (owner) value.
203
- * Algorithm 3 from PDF spec §3.5.2.
204
- */
205
- function computeOValue(ownerPassword, userPassword) {
206
- // Step 1: MD5 hash of padded owner password
207
- let hash = md5(padPassword(ownerPassword));
208
- // Step 2: For revision 3, hash 50 more times
209
- for (let i = 0; i < 50; i++) {
210
- hash = md5(hash);
211
- }
212
- // Use first 16 bytes as RC4 key (128-bit / key length = 16)
213
- const rc4Key = hash.subarray(0, 16);
214
- // Step 3: RC4-encrypt the padded user password
215
- let result = rc4(rc4Key, padPassword(userPassword));
216
- // Step 4: For revision 3, iterate 1-19 with modified key
217
- for (let i = 1; i <= 19; i++) {
218
- const modKey = new Uint8Array(16);
219
- for (let j = 0; j < 16; j++) {
220
- modKey[j] = rc4Key[j] ^ i;
221
- }
222
- result = rc4(modKey, result);
223
- }
224
- return result;
225
- }
226
- /**
227
- * Compute the encryption key.
228
- * Algorithm 2 from PDF spec §3.5.2.
229
- */
230
- function computeEncryptionKey(userPassword, oValue, permissions, fileId) {
231
- // Concatenate: padded password + O value + P value (4 LE bytes) + file ID
232
- const paddedPwd = padPassword(userPassword);
233
- const input = new Uint8Array(32 + 32 + 4 + fileId.length);
234
- input.set(paddedPwd);
235
- input.set(oValue, 32);
236
- const pView = new DataView(input.buffer, input.byteOffset);
237
- pView.setInt32(64, permissions, true);
238
- input.set(fileId, 68);
239
- let hash = md5(input);
240
- // For revision 3, hash 50 more times
241
- for (let i = 0; i < 50; i++) {
242
- hash = md5(hash.subarray(0, 16));
243
- }
244
- return hash.subarray(0, 16); // 128-bit key
245
- }
246
- /**
247
- * Compute the U (user) value.
248
- * Algorithm 5 from PDF spec §3.5.2 (revision 3).
249
- */
250
- function computeUValue(encryptionKey, fileId) {
251
- // Step 1: MD5 hash of padding + file ID
252
- const hashInput = new Uint8Array(32 + fileId.length);
253
- hashInput.set(PASSWORD_PADDING);
254
- hashInput.set(fileId, 32);
255
- const hash = md5(hashInput);
256
- // Step 2: RC4-encrypt with the encryption key
257
- let result = rc4(encryptionKey, hash);
258
- // Step 3: Iterate 1-19 with modified key
259
- for (let i = 1; i <= 19; i++) {
260
- const modKey = new Uint8Array(16);
261
- for (let j = 0; j < 16; j++) {
262
- modKey[j] = encryptionKey[j] ^ i;
263
- }
264
- result = rc4(modKey, result);
265
- }
266
- // Pad to 32 bytes with arbitrary padding
267
- const uValue = new Uint8Array(32);
268
- uValue.set(result);
269
- return uValue;
108
+ return bytes.length > 127 ? bytes.subarray(0, 127) : bytes;
270
109
  }
271
110
  /**
272
111
  * Compute the permissions integer (P value) from permission flags.
273
112
  */
274
113
  function computePermissions(perms) {
275
- // Start with all bits set that are "reserved" and must be 1
276
- // Bits 1-2, 7-8 must be 0; bits 13-32 must be 1 (per spec)
277
- let p = 0xfffff000 | 0b11000000; // bits 7-8 = reserved 1, high bits = 1
114
+ // Start with all reserved bits set to 1
115
+ let p = 0xfffff000 | 0b11000000;
278
116
  if (perms?.print) {
279
- p |= 1 << 2; // bit 3
117
+ p |= 1 << 2;
280
118
  }
281
119
  if (perms?.modify) {
282
- p |= 1 << 3; // bit 4
120
+ p |= 1 << 3;
283
121
  }
284
122
  if (perms?.copy) {
285
- p |= 1 << 4; // bit 5
123
+ p |= 1 << 4;
286
124
  }
287
125
  if (perms?.annotate) {
288
- p |= 1 << 5; // bit 6
126
+ p |= 1 << 5;
289
127
  }
290
128
  if (perms?.fillForms) {
291
- p |= 1 << 8; // bit 9
129
+ p |= 1 << 8;
292
130
  }
293
131
  if (perms?.accessibility) {
294
- p |= 1 << 9; // bit 10
132
+ p |= 1 << 9;
295
133
  }
296
134
  if (perms?.assemble) {
297
- p |= 1 << 10; // bit 11
135
+ p |= 1 << 10;
298
136
  }
299
137
  if (perms?.printHighQuality) {
300
- p |= 1 << 11; // bit 12
138
+ p |= 1 << 11;
301
139
  }
302
- // Convert to signed 32-bit
303
140
  return p | 0;
304
141
  }
305
- /**
306
- * Generate a random file identifier (16 bytes).
307
- */
308
- function generateFileId() {
309
- // Use MD5 of current timestamp + random for determinism in tests
310
- const seed = new Uint8Array(16);
311
- const now = Date.now();
312
- const view = new DataView(seed.buffer);
313
- view.setFloat64(0, now, true);
314
- view.setFloat64(8, Math.random() * 1e15, true);
315
- return md5(seed);
316
- }
@@ -2,14 +2,16 @@
2
2
  /**
3
3
  * PDF file writer.
4
4
  *
5
- * Assembles a complete PDF document from indirect objects.
5
+ * Assembles a complete PDF 2.0 document from indirect objects.
6
6
  * Handles the four sections of a PDF file:
7
- * 1. Header (%PDF-1.4)
7
+ * 1. Header (%PDF-2.0)
8
8
  * 2. Body (indirect objects)
9
9
  * 3. Cross-reference table
10
10
  * 4. Trailer (with document catalog reference)
11
11
  *
12
- * @see PDF Reference 1.7, Chapter 3.4 - File Structure
12
+ * Encryption uses AES-256 (V=5, R=5) per ISO 32000-2:2020.
13
+ *
14
+ * @see ISO 32000-2:2020, Chapter 7.5 — File Structure
13
15
  */
14
16
  Object.defineProperty(exports, "__esModule", { value: true });
15
17
  exports.PdfWriter = void 0;
@@ -22,7 +24,7 @@ const encryption_1 = require("./encryption");
22
24
  // PDF Writer
23
25
  // =============================================================================
24
26
  /**
25
- * Constructs a valid PDF 1.4 file from a set of indirect objects.
27
+ * Constructs a valid PDF 2.0 file from a set of indirect objects.
26
28
  *
27
29
  * Usage:
28
30
  * 1. Allocate object numbers with allocObject()
@@ -171,7 +173,7 @@ class PdfWriter {
171
173
  let byteOffset = 0;
172
174
  // --- Header ---
173
175
  // Include a comment with high bytes to signal binary content per PDF spec §3.4.1
174
- const headerStr = "%PDF-1.4\n";
176
+ const headerStr = "%PDF-2.0\n";
175
177
  const headerStrBytes = encoder.encode(headerStr);
176
178
  chunks.push(headerStrBytes);
177
179
  byteOffset += headerStrBytes.length;
@@ -223,16 +225,23 @@ class PdfWriter {
223
225
  chunks.push(objFooter);
224
226
  byteOffset += objFooter.length;
225
227
  }
226
- // --- Encrypt dictionary (must be added before xref) ---
228
+ // --- Encrypt dictionary (V=5, R=5, AES-256) ---
227
229
  if (this.encryption) {
228
230
  const encDict = new pdf_object_1.PdfDict()
229
231
  .set("Filter", "/Standard")
230
- .set("V", "2")
231
- .set("R", "3")
232
- .set("Length", "128")
232
+ .set("V", "5")
233
+ .set("R", "5")
234
+ .set("Length", "256")
233
235
  .set("P", String(this.encryption.permissions))
234
236
  .set("O", (0, pdf_object_1.pdfHexString)(this.encryption.oValue))
235
- .set("U", (0, pdf_object_1.pdfHexString)(this.encryption.uValue));
237
+ .set("U", (0, pdf_object_1.pdfHexString)(this.encryption.uValue))
238
+ .set("OE", (0, pdf_object_1.pdfHexString)(this.encryption.oeValue))
239
+ .set("UE", (0, pdf_object_1.pdfHexString)(this.encryption.ueValue))
240
+ .set("Perms", (0, pdf_object_1.pdfHexString)(this.encryption.permsValue))
241
+ .set("EncryptMetadata", "true")
242
+ .set("CF", "<< /StdCF << /Type /CryptFilter /CFM /AESV3 /AuthEvent /DocOpen /Length 32 >> >>")
243
+ .set("StmF", "/StdCF")
244
+ .set("StrF", "/StdCF");
236
245
  const encContent = encDict.toString();
237
246
  const encObj = {
238
247
  objectNumber: encryptObjNum,
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * PDF module for excelts.
4
4
  *
5
- * A full-featured, zero-dependency PDF engine.
5
+ * A full-featured, zero-dependency PDF engine for both writing and reading.
6
6
  *
7
- * @example Standalone:
7
+ * @example Standalone PDF generation:
8
8
  * ```typescript
9
9
  * import { pdf } from "excelts/pdf";
10
10
  *
@@ -26,12 +26,25 @@
26
26
  * const bytes = excelToPdf(workbook);
27
27
  * ```
28
28
  *
29
+ * @example Read PDF — extract text, images, and metadata:
30
+ * ```typescript
31
+ * import { readPdf } from "excelts/pdf";
32
+ *
33
+ * const result = readPdf(pdfBytes);
34
+ * console.log(result.text); // All text
35
+ * console.log(result.pages[0].text); // Page 1 text
36
+ * console.log(result.pages[0].images); // Page 1 images
37
+ * console.log(result.pages[0].annotations); // Page 1 annotations
38
+ * console.log(result.metadata.title); // Document title
39
+ * console.log(result.formFields); // Form fields
40
+ * ```
41
+ *
29
42
  * @module pdf
30
43
  */
31
44
  Object.defineProperty(exports, "__esModule", { value: true });
32
- exports.isPdfError = exports.PdfStructureError = exports.PdfFontError = exports.PdfRenderError = exports.PdfError = exports.PageSizes = exports.excelToPdf = exports.pdf = void 0;
45
+ exports.isPdfError = exports.PdfStructureError = exports.PdfFontError = exports.PdfRenderError = exports.PdfError = exports.PageSizes = exports.readPdf = exports.excelToPdf = exports.pdf = void 0;
33
46
  // =============================================================================
34
- // Public API
47
+ // Public API — Writing
35
48
  // =============================================================================
36
49
  /** Standalone PDF generation — accepts plain arrays, sheet objects, or workbooks. */
37
50
  var pdf_1 = require("./pdf");
@@ -39,6 +52,12 @@ Object.defineProperty(exports, "pdf", { enumerable: true, get: function () { ret
39
52
  /** Excel-to-PDF conversion — accepts an Excel Workbook instance. */
40
53
  var excel_bridge_1 = require("./excel-bridge");
41
54
  Object.defineProperty(exports, "excelToPdf", { enumerable: true, get: function () { return excel_bridge_1.excelToPdf; } });
55
+ // =============================================================================
56
+ // Public API — Reading
57
+ // =============================================================================
58
+ /** Read a PDF file and extract text, images, and metadata. */
59
+ var pdf_reader_1 = require("./reader/pdf-reader");
60
+ Object.defineProperty(exports, "readPdf", { enumerable: true, get: function () { return pdf_reader_1.readPdf; } });
42
61
  var types_1 = require("./types");
43
62
  Object.defineProperty(exports, "PageSizes", { enumerable: true, get: function () { return types_1.PageSizes; } });
44
63
  // =============================================================================