pdf-lite 1.0.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 (206) hide show
  1. package/.commitlintrc.cjs +25 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  4. package/.github/workflows/docs.yaml +93 -0
  5. package/.github/workflows/prepare-release.yaml +79 -0
  6. package/.github/workflows/release.yaml +80 -0
  7. package/.github/workflows/test.yaml +35 -0
  8. package/.husky/commit-msg +1 -0
  9. package/.husky/pre-commit +1 -0
  10. package/.prettierignore +4 -0
  11. package/.prettierrc +4 -0
  12. package/CONTRIBUTING.md +109 -0
  13. package/EXAMPLES.md +1515 -0
  14. package/LICENSE +21 -0
  15. package/README.md +285 -0
  16. package/examples/001-create-pdf.ts +112 -0
  17. package/examples/002-create-encrypted-pdf.ts +121 -0
  18. package/examples/003-sign-pdf.ts +347 -0
  19. package/examples/004-incremental-update.ts +206 -0
  20. package/examples/005-modify-acroform.ts +374 -0
  21. package/examples/006-tokeniser-example.ts +131 -0
  22. package/examples/007-decoder-example.ts +197 -0
  23. package/package.json +72 -0
  24. package/packages/pdf-lite/README.md +3 -0
  25. package/packages/pdf-lite/package.json +68 -0
  26. package/packages/pdf-lite/scripts/create-encryption-tests.sh +41 -0
  27. package/packages/pdf-lite/scripts/gen-signing-keys.sh +290 -0
  28. package/packages/pdf-lite/scripts/generate-all-signing-keys.sh +70 -0
  29. package/packages/pdf-lite/src/core/decoder.ts +454 -0
  30. package/packages/pdf-lite/src/core/generators.ts +128 -0
  31. package/packages/pdf-lite/src/core/incremental-parser.ts +221 -0
  32. package/packages/pdf-lite/src/core/index.ts +2 -0
  33. package/packages/pdf-lite/src/core/objects/pdf-array.ts +54 -0
  34. package/packages/pdf-lite/src/core/objects/pdf-boolean.ts +19 -0
  35. package/packages/pdf-lite/src/core/objects/pdf-comment.ts +50 -0
  36. package/packages/pdf-lite/src/core/objects/pdf-date.ts +74 -0
  37. package/packages/pdf-lite/src/core/objects/pdf-dictionary.ts +171 -0
  38. package/packages/pdf-lite/src/core/objects/pdf-hexadecimal.ts +54 -0
  39. package/packages/pdf-lite/src/core/objects/pdf-indirect-object.ts +137 -0
  40. package/packages/pdf-lite/src/core/objects/pdf-name.ts +19 -0
  41. package/packages/pdf-lite/src/core/objects/pdf-null.ts +15 -0
  42. package/packages/pdf-lite/src/core/objects/pdf-number.ts +98 -0
  43. package/packages/pdf-lite/src/core/objects/pdf-object-reference.ts +30 -0
  44. package/packages/pdf-lite/src/core/objects/pdf-object.ts +107 -0
  45. package/packages/pdf-lite/src/core/objects/pdf-start-xref.ts +39 -0
  46. package/packages/pdf-lite/src/core/objects/pdf-stream.ts +687 -0
  47. package/packages/pdf-lite/src/core/objects/pdf-string.ts +38 -0
  48. package/packages/pdf-lite/src/core/objects/pdf-trailer.ts +57 -0
  49. package/packages/pdf-lite/src/core/objects/pdf-xref-table.ts +264 -0
  50. package/packages/pdf-lite/src/core/parser.ts +22 -0
  51. package/packages/pdf-lite/src/core/ref.ts +102 -0
  52. package/packages/pdf-lite/src/core/serializer.ts +68 -0
  53. package/packages/pdf-lite/src/core/streams/object-stream.ts +20 -0
  54. package/packages/pdf-lite/src/core/tokeniser.ts +687 -0
  55. package/packages/pdf-lite/src/core/tokens/boolean-token.ts +20 -0
  56. package/packages/pdf-lite/src/core/tokens/byte-offset-token.ts +20 -0
  57. package/packages/pdf-lite/src/core/tokens/comment-token.ts +32 -0
  58. package/packages/pdf-lite/src/core/tokens/end-array-token.ts +10 -0
  59. package/packages/pdf-lite/src/core/tokens/end-dictionary-token.ts +10 -0
  60. package/packages/pdf-lite/src/core/tokens/end-object-token.ts +10 -0
  61. package/packages/pdf-lite/src/core/tokens/end-stream-token.ts +11 -0
  62. package/packages/pdf-lite/src/core/tokens/hexadecimal-token.ts +22 -0
  63. package/packages/pdf-lite/src/core/tokens/name-token.ts +19 -0
  64. package/packages/pdf-lite/src/core/tokens/null-token.ts +9 -0
  65. package/packages/pdf-lite/src/core/tokens/number-token.ts +164 -0
  66. package/packages/pdf-lite/src/core/tokens/object-reference-token.ts +24 -0
  67. package/packages/pdf-lite/src/core/tokens/start-array-token.ts +10 -0
  68. package/packages/pdf-lite/src/core/tokens/start-dictionary-token.ts +10 -0
  69. package/packages/pdf-lite/src/core/tokens/start-object-token.ts +28 -0
  70. package/packages/pdf-lite/src/core/tokens/start-stream-token.ts +52 -0
  71. package/packages/pdf-lite/src/core/tokens/start-xref-token.ts +10 -0
  72. package/packages/pdf-lite/src/core/tokens/stream-chunk-token.ts +8 -0
  73. package/packages/pdf-lite/src/core/tokens/string-token.ts +17 -0
  74. package/packages/pdf-lite/src/core/tokens/token.ts +43 -0
  75. package/packages/pdf-lite/src/core/tokens/trailer-token.ts +12 -0
  76. package/packages/pdf-lite/src/core/tokens/whitespace-token.ts +43 -0
  77. package/packages/pdf-lite/src/core/tokens/xref-table-entry-token.ts +65 -0
  78. package/packages/pdf-lite/src/core/tokens/xref-table-section-start-token.ts +31 -0
  79. package/packages/pdf-lite/src/core/tokens/xref-table-start-token.ts +13 -0
  80. package/packages/pdf-lite/src/crypto/ciphers/aes128.ts +63 -0
  81. package/packages/pdf-lite/src/crypto/ciphers/aes256.ts +50 -0
  82. package/packages/pdf-lite/src/crypto/ciphers/rc4.ts +82 -0
  83. package/packages/pdf-lite/src/crypto/constants.ts +10 -0
  84. package/packages/pdf-lite/src/crypto/key-derivation/key-derivation-aes256.ts +213 -0
  85. package/packages/pdf-lite/src/crypto/key-derivation/key-derivation.ts +122 -0
  86. package/packages/pdf-lite/src/crypto/key-gen/key-gen-aes256.ts +79 -0
  87. package/packages/pdf-lite/src/crypto/key-gen/key-gen-rc4-128.ts +190 -0
  88. package/packages/pdf-lite/src/crypto/key-gen/key-gen-rc4-40.ts +129 -0
  89. package/packages/pdf-lite/src/crypto/types.ts +6 -0
  90. package/packages/pdf-lite/src/crypto/utils.ts +81 -0
  91. package/packages/pdf-lite/src/filters/ascii85.ts +128 -0
  92. package/packages/pdf-lite/src/filters/asciihex.ts +55 -0
  93. package/packages/pdf-lite/src/filters/flate.ts +39 -0
  94. package/packages/pdf-lite/src/filters/lzw.ts +144 -0
  95. package/packages/pdf-lite/src/filters/pass-through.ts +37 -0
  96. package/packages/pdf-lite/src/filters/runlength.ts +92 -0
  97. package/packages/pdf-lite/src/filters/types.ts +21 -0
  98. package/packages/pdf-lite/src/index.ts +4 -0
  99. package/packages/pdf-lite/src/pdf/errors.ts +5 -0
  100. package/packages/pdf-lite/src/pdf/index.ts +4 -0
  101. package/packages/pdf-lite/src/pdf/pdf-document.ts +924 -0
  102. package/packages/pdf-lite/src/pdf/pdf-reader.ts +57 -0
  103. package/packages/pdf-lite/src/pdf/pdf-revision.ts +234 -0
  104. package/packages/pdf-lite/src/pdf/pdf-xref-lookup.ts +527 -0
  105. package/packages/pdf-lite/src/security/crypt-filters/aesv2.ts +58 -0
  106. package/packages/pdf-lite/src/security/crypt-filters/aesv3.ts +56 -0
  107. package/packages/pdf-lite/src/security/crypt-filters/base.ts +140 -0
  108. package/packages/pdf-lite/src/security/crypt-filters/identity.ts +40 -0
  109. package/packages/pdf-lite/src/security/crypt-filters/v2.ts +59 -0
  110. package/packages/pdf-lite/src/security/handlers/base.ts +625 -0
  111. package/packages/pdf-lite/src/security/handlers/pubSec.ts +413 -0
  112. package/packages/pdf-lite/src/security/handlers/utils.ts +304 -0
  113. package/packages/pdf-lite/src/security/handlers/v1.ts +225 -0
  114. package/packages/pdf-lite/src/security/handlers/v2.ts +128 -0
  115. package/packages/pdf-lite/src/security/handlers/v4.ts +379 -0
  116. package/packages/pdf-lite/src/security/handlers/v5.ts +298 -0
  117. package/packages/pdf-lite/src/security/types.ts +158 -0
  118. package/packages/pdf-lite/src/signing/document-security-store.ts +224 -0
  119. package/packages/pdf-lite/src/signing/index.ts +3 -0
  120. package/packages/pdf-lite/src/signing/signatures/adbe-pkcs7-detached.ts +154 -0
  121. package/packages/pdf-lite/src/signing/signatures/adbe-pkcs7-sha1.ts +161 -0
  122. package/packages/pdf-lite/src/signing/signatures/adbe-x509-rsa-sha1.ts +106 -0
  123. package/packages/pdf-lite/src/signing/signatures/base.ts +229 -0
  124. package/packages/pdf-lite/src/signing/signatures/etsi-cades-detached.ts +229 -0
  125. package/packages/pdf-lite/src/signing/signatures/etsi-rfc3161.ts +92 -0
  126. package/packages/pdf-lite/src/signing/signatures/index.ts +6 -0
  127. package/packages/pdf-lite/src/signing/signer.ts +120 -0
  128. package/packages/pdf-lite/src/signing/types.ts +86 -0
  129. package/packages/pdf-lite/src/signing/utils.ts +71 -0
  130. package/packages/pdf-lite/src/types.ts +44 -0
  131. package/packages/pdf-lite/src/utils/IterableReadableStream.ts +30 -0
  132. package/packages/pdf-lite/src/utils/algos.ts +446 -0
  133. package/packages/pdf-lite/src/utils/assert.ts +42 -0
  134. package/packages/pdf-lite/src/utils/bytesToHex.ts +18 -0
  135. package/packages/pdf-lite/src/utils/bytesToHexBytes.ts +27 -0
  136. package/packages/pdf-lite/src/utils/bytesToString.ts +17 -0
  137. package/packages/pdf-lite/src/utils/concatUint8Arrays.ts +26 -0
  138. package/packages/pdf-lite/src/utils/escapeString.ts +49 -0
  139. package/packages/pdf-lite/src/utils/hexBytesToBytes.ts +22 -0
  140. package/packages/pdf-lite/src/utils/hexBytesToString.ts +21 -0
  141. package/packages/pdf-lite/src/utils/hexToBytes.ts +18 -0
  142. package/packages/pdf-lite/src/utils/padBytes.ts +25 -0
  143. package/packages/pdf-lite/src/utils/predictors.ts +332 -0
  144. package/packages/pdf-lite/src/utils/replaceInBuffer.ts +56 -0
  145. package/packages/pdf-lite/src/utils/stringToBytes.ts +22 -0
  146. package/packages/pdf-lite/src/utils/stringToHexBytes.ts +23 -0
  147. package/packages/pdf-lite/src/utils/unescapeString.ts +123 -0
  148. package/packages/pdf-lite/test/acceptance/__snapshots__/versions.node.test.ts.snap +60766 -0
  149. package/packages/pdf-lite/test/acceptance/fixtures/1.3/basic.pdf +0 -0
  150. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-aes-128.pdf +0 -0
  151. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-aes-256.pdf +0 -0
  152. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-rc4-128.pdf +0 -0
  153. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic-rc4-40.pdf +0 -0
  154. package/packages/pdf-lite/test/acceptance/fixtures/1.4/basic.pdf +0 -0
  155. package/packages/pdf-lite/test/acceptance/fixtures/1.5/basic.pdf +0 -0
  156. package/packages/pdf-lite/test/acceptance/fixtures/1.6/basic.pdf +0 -0
  157. package/packages/pdf-lite/test/acceptance/fixtures/1.7/basic.pdf +0 -0
  158. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-aes-128.pdf +43 -0
  159. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-aes-256.pdf +43 -0
  160. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-rc4-128.pdf +43 -0
  161. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic-rc4-40.pdf +44 -0
  162. package/packages/pdf-lite/test/acceptance/fixtures/2.0/basic.pdf +79 -0
  163. package/packages/pdf-lite/test/acceptance/versions.node.test.ts +41 -0
  164. package/packages/pdf-lite/test/unit/__snapshots__/decoder.node.test.ts.snap +86947 -0
  165. package/packages/pdf-lite/test/unit/__snapshots__/tokeniser.node.test.ts.snap +131829 -0
  166. package/packages/pdf-lite/test/unit/ciphers.test.ts +61 -0
  167. package/packages/pdf-lite/test/unit/decoder.node.test.ts +21 -0
  168. package/packages/pdf-lite/test/unit/decoder.test.ts +567 -0
  169. package/packages/pdf-lite/test/unit/filters.test.ts +67 -0
  170. package/packages/pdf-lite/test/unit/fixtures/basic.pdf +0 -0
  171. package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-aes-128.pdf +0 -0
  172. package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-aes-256.pdf +0 -0
  173. package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-rc4-128.pdf +0 -0
  174. package/packages/pdf-lite/test/unit/fixtures/encrypted_v1/basic-rc4-40.pdf +43 -0
  175. package/packages/pdf-lite/test/unit/fixtures/protectedAdobeLivecycle.pdf +0 -0
  176. package/packages/pdf-lite/test/unit/fixtures/rsa-2048/index.ts +187 -0
  177. package/packages/pdf-lite/test/unit/fixtures/template.pdf +0 -0
  178. package/packages/pdf-lite/test/unit/incremental-update.test.ts +0 -0
  179. package/packages/pdf-lite/test/unit/objects.test.ts +0 -0
  180. package/packages/pdf-lite/test/unit/pdf-document-signing.test.ts +0 -0
  181. package/packages/pdf-lite/test/unit/pdf-revision.test.ts +195 -0
  182. package/packages/pdf-lite/test/unit/pdf.browser.test.ts +0 -0
  183. package/packages/pdf-lite/test/unit/predictors.test.ts +226 -0
  184. package/packages/pdf-lite/test/unit/ref.test.ts +158 -0
  185. package/packages/pdf-lite/test/unit/security-handlers.test.ts +645 -0
  186. package/packages/pdf-lite/test/unit/serializer.test.ts +81 -0
  187. package/packages/pdf-lite/test/unit/signature-objects.test.ts +814 -0
  188. package/packages/pdf-lite/test/unit/string-escaping.test.ts +84 -0
  189. package/packages/pdf-lite/test/unit/tokeniser.node.test.ts +38 -0
  190. package/packages/pdf-lite/test/unit/tokeniser.test.ts +1213 -0
  191. package/packages/pdf-lite/test/unit/utils.test.ts +248 -0
  192. package/packages/pdf-lite/test/unit/xref-lookup.test.ts +72 -0
  193. package/packages/pdf-lite/tsconfig.json +4 -0
  194. package/packages/pdf-lite/tsconfig.prod.json +8 -0
  195. package/packages/pdf-lite/typedoc.json +14 -0
  196. package/packages/pdf-lite/vitest.config.ts +43 -0
  197. package/pnpm-workspace.yaml +2 -0
  198. package/renovate.json +34 -0
  199. package/scripts/build-examples.ts +30 -0
  200. package/scripts/bump-version.sh +56 -0
  201. package/scripts/gen-html-docs.sh +21 -0
  202. package/scripts/gen-md-docs.sh +15 -0
  203. package/scripts/prepare-release.sh +33 -0
  204. package/tsconfig.json +22 -0
  205. package/tsconfig.prod.json +12 -0
  206. package/typedoc.json +34 -0
@@ -0,0 +1,446 @@
1
+ import { deflate, Inflate } from 'pako'
2
+ import { bytesToHex } from './bytesToHex.js'
3
+ import { AlgorithmIdentifier } from 'pki-lite/algorithms/AlgorithmIdentifier'
4
+ import 'pki-lite-crypto-extended'
5
+ import { ByteArray } from '../types.js'
6
+
7
+ /**
8
+ * Computes the SHA-1 hash of the input data.
9
+ *
10
+ * @param input - The data to hash.
11
+ * @returns A promise that resolves to the SHA-1 hash as a byte array.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const hash = await sha1(new Uint8Array([1, 2, 3]))
16
+ * ```
17
+ */
18
+ export async function sha1(input: ByteArray): Promise<ByteArray> {
19
+ return AlgorithmIdentifier.digestAlgorithm('SHA-1').digest(input)
20
+ }
21
+
22
+ /**
23
+ * Computes the SHA-256 hash of the input data.
24
+ *
25
+ * @param input - The data to hash.
26
+ * @returns A promise that resolves to the SHA-256 hash as a byte array.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const hash = await sha256(new Uint8Array([1, 2, 3]))
31
+ * ```
32
+ */
33
+ export async function sha256(input: ByteArray): Promise<ByteArray> {
34
+ return AlgorithmIdentifier.digestAlgorithm('SHA-256').digest(input)
35
+ }
36
+
37
+ /**
38
+ * Computes the SHA-384 hash of the input data.
39
+ *
40
+ * @param input - The data to hash.
41
+ * @returns A promise that resolves to the SHA-384 hash as a byte array.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const hash = await sha384(new Uint8Array([1, 2, 3]))
46
+ * ```
47
+ */
48
+ export async function sha384(input: ByteArray): Promise<ByteArray> {
49
+ return AlgorithmIdentifier.digestAlgorithm('SHA-384').digest(input)
50
+ }
51
+
52
+ /**
53
+ * Computes the SHA-512 hash of the input data.
54
+ *
55
+ * @param input - The data to hash.
56
+ * @returns A promise that resolves to the SHA-512 hash as a byte array.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const hash = await sha512(new Uint8Array([1, 2, 3]))
61
+ * ```
62
+ */
63
+ export async function sha512(input: ByteArray): Promise<ByteArray> {
64
+ return AlgorithmIdentifier.digestAlgorithm('SHA-512').digest(input)
65
+ }
66
+
67
+ /**
68
+ * Computes the MD5 hash of the input data.
69
+ *
70
+ * @param input - The data to hash.
71
+ * @returns A promise that resolves to the MD5 hash as a byte array.
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * const hash = await md5(new Uint8Array([1, 2, 3]))
76
+ * ```
77
+ */
78
+ export async function md5(input: ByteArray): Promise<ByteArray> {
79
+ return AlgorithmIdentifier.digestAlgorithm('MD5').digest(input)
80
+ }
81
+
82
+ /**
83
+ * Computes a cryptographic hash of the input data using the specified algorithm.
84
+ *
85
+ * @param input - The data to hash.
86
+ * @param algorithm - The hash algorithm to use. Defaults to 'SHA-256'.
87
+ * @returns A promise that resolves to the hash as a byte array.
88
+ * @throws Error if an unsupported hash algorithm is specified.
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const hash = await hash(data, 'SHA-256')
93
+ * ```
94
+ */
95
+ export async function hash(
96
+ input: ByteArray,
97
+ algorithm: 'SHA-256' | 'SHA-384' | 'SHA-512' | 'MD5' | 'SHA-1' = 'SHA-256',
98
+ ): Promise<ByteArray> {
99
+ switch (algorithm) {
100
+ case 'SHA-256':
101
+ return sha256(input)
102
+ case 'SHA-384':
103
+ return sha384(input)
104
+ case 'SHA-512':
105
+ return sha512(input)
106
+ case 'MD5':
107
+ return md5(input)
108
+ case 'SHA-1':
109
+ return sha1(input)
110
+ default:
111
+ throw new Error(`Unsupported hash algorithm: ${algorithm}`)
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Generates cryptographically secure random bytes.
117
+ *
118
+ * @param length - The number of random bytes to generate.
119
+ * @returns A byte array containing random bytes.
120
+ * @throws Error if length is not a positive integer.
121
+ *
122
+ * @example
123
+ * ```typescript
124
+ * const randomBytes = getRandomBytes(16) // 16 random bytes
125
+ * ```
126
+ */
127
+ export function getRandomBytes(length: number): ByteArray {
128
+ if (length <= 0) throw new Error('Length must be a positive integer')
129
+ return AlgorithmIdentifier.randomBytes(length)
130
+ }
131
+
132
+ /**
133
+ * Encrypts data using AES-128-CBC mode.
134
+ *
135
+ * @param key - The 16-byte encryption key.
136
+ * @param data - The data to encrypt.
137
+ * @param iv - The 16-byte initialization vector. Defaults to zero IV.
138
+ * @returns A promise that resolves to the encrypted data.
139
+ * @throws Error if the key is not exactly 16 bytes.
140
+ *
141
+ * @example
142
+ * ```typescript
143
+ * const encrypted = await aes128cbcEncrypt(key, plaintext, iv)
144
+ * ```
145
+ */
146
+ export async function aes128cbcEncrypt(
147
+ key: ByteArray,
148
+ data: ByteArray,
149
+ iv: ByteArray = new Uint8Array(16), // Zero IV, as per PDF spec
150
+ ): Promise<ByteArray> {
151
+ if (key.length !== 16) {
152
+ throw new Error(
153
+ `AES-128 key must be exactly 16 bytes, got ${key.length}`,
154
+ )
155
+ }
156
+
157
+ return AlgorithmIdentifier.contentEncryptionAlgorithm({
158
+ type: 'AES_128_CBC',
159
+ params: {
160
+ nonce: iv,
161
+ },
162
+ }).encrypt(data, key)
163
+ }
164
+
165
+ /**
166
+ * Decrypts data using AES-128-CBC mode.
167
+ *
168
+ * @param key - The 16-byte decryption key.
169
+ * @param encrypted - The encrypted data to decrypt.
170
+ * @param iv - The 16-byte initialization vector. Defaults to zero IV.
171
+ * @returns A promise that resolves to the decrypted data.
172
+ * @throws Error if the key is not exactly 16 bytes or encrypted data is too short.
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * const decrypted = await aes128cbcDecrypt(key, ciphertext, iv)
177
+ * ```
178
+ */
179
+ export async function aes128cbcDecrypt(
180
+ key: ByteArray,
181
+ encrypted: ByteArray,
182
+ iv: ByteArray = new Uint8Array(16), // Zero IV, as per PDF spec
183
+ ): Promise<ByteArray> {
184
+ if (key.length !== 16) {
185
+ throw new Error(
186
+ `AES-128 key must be exactly 16 bytes, got ${key.length}`,
187
+ )
188
+ }
189
+ if (encrypted.length < 16) {
190
+ throw new Error('Encrypted stream too short — no IV found')
191
+ }
192
+
193
+ return AlgorithmIdentifier.contentEncryptionAlgorithm({
194
+ type: 'AES_128_CBC',
195
+ params: {
196
+ nonce: iv,
197
+ },
198
+ }).decrypt(encrypted, key)
199
+ }
200
+
201
+ /**
202
+ * Encrypts data using AES-256-CBC mode.
203
+ *
204
+ * @param fileKey - The 32-byte encryption key.
205
+ * @param block - The data to encrypt.
206
+ * @param iv - The 16-byte initialization vector. Defaults to zero IV.
207
+ * @returns A promise that resolves to the encrypted data.
208
+ *
209
+ * @example
210
+ * ```typescript
211
+ * const encrypted = await aes256cbcEncrypt(key, plaintext, iv)
212
+ * ```
213
+ */
214
+ export async function aes256cbcEncrypt(
215
+ fileKey: ByteArray,
216
+ block: ByteArray,
217
+ iv: ByteArray = new Uint8Array(16), // Zero IV, as per PDF spec
218
+ ): Promise<ByteArray> {
219
+ return AlgorithmIdentifier.contentEncryptionAlgorithm({
220
+ type: 'AES_256_CBC',
221
+ params: {
222
+ nonce: iv,
223
+ },
224
+ }).encrypt(block, fileKey)
225
+ }
226
+
227
+ /**
228
+ * Decrypts data using AES-256-CBC mode.
229
+ *
230
+ * @param fileKey - The 32-byte decryption key.
231
+ * @param ciphertext - The encrypted data to decrypt.
232
+ * @param iv - The 16-byte initialization vector. Defaults to zero IV.
233
+ * @returns A promise that resolves to the decrypted data.
234
+ *
235
+ * @example
236
+ * ```typescript
237
+ * const decrypted = await aes256cbcDecrypt(key, ciphertext, iv)
238
+ * ```
239
+ */
240
+ export async function aes256cbcDecrypt(
241
+ fileKey: ByteArray,
242
+ ciphertext: ByteArray,
243
+ iv: ByteArray = new Uint8Array(16), // Zero IV, as per PDF spec
244
+ ): Promise<ByteArray> {
245
+ return AlgorithmIdentifier.contentEncryptionAlgorithm({
246
+ type: 'AES_256_CBC',
247
+ params: {
248
+ nonce: iv,
249
+ },
250
+ }).decrypt(ciphertext, fileKey)
251
+ }
252
+
253
+ /**
254
+ * Encrypts data using AES-256-ECB mode.
255
+ *
256
+ * @param fileKey - The 32-byte encryption key.
257
+ * @param data - The data to encrypt.
258
+ * @returns A promise that resolves to the encrypted data.
259
+ * @throws Error if the key is not exactly 32 bytes.
260
+ *
261
+ * @example
262
+ * ```typescript
263
+ * const encrypted = await aes256ecbEncrypt(key, plaintext)
264
+ * ```
265
+ */
266
+ export async function aes256ecbEncrypt(
267
+ fileKey: ByteArray,
268
+ data: ByteArray,
269
+ ): Promise<ByteArray> {
270
+ if (fileKey.length !== 32) {
271
+ throw new Error(
272
+ `AES-256 key must be exactly 32 bytes, got ${fileKey.length}`,
273
+ )
274
+ }
275
+
276
+ return AlgorithmIdentifier.contentEncryptionAlgorithm({
277
+ type: 'AES_256_ECB',
278
+ params: {},
279
+ }).encrypt(data, fileKey)
280
+ }
281
+
282
+ /**
283
+ * Decrypts data using AES-256-ECB mode.
284
+ *
285
+ * @param fileKey - The 32-byte decryption key.
286
+ * @param encrypted - The encrypted data to decrypt.
287
+ * @returns A promise that resolves to the decrypted data.
288
+ * @throws Error if the key is not exactly 32 bytes.
289
+ *
290
+ * @example
291
+ * ```typescript
292
+ * const decrypted = await aes256ecbDecrypt(key, ciphertext)
293
+ * ```
294
+ */
295
+ export async function aes256ecbDecrypt(
296
+ fileKey: ByteArray,
297
+ encrypted: ByteArray,
298
+ ): Promise<ByteArray> {
299
+ if (fileKey.length !== 32) {
300
+ throw new Error(
301
+ `AES-256 key must be exactly 32 bytes, got ${fileKey.length}`,
302
+ )
303
+ }
304
+
305
+ return AlgorithmIdentifier.contentEncryptionAlgorithm({
306
+ type: 'AES_256_ECB',
307
+ params: {},
308
+ }).decrypt(encrypted, fileKey)
309
+ }
310
+
311
+ /**
312
+ * Encrypts data using AES-128-CBC mode without PKCS#7 padding.
313
+ *
314
+ * @param key - The 16-byte encryption key.
315
+ * @param data - The data to encrypt. Must be a multiple of 16 bytes.
316
+ * @param iv - The 16-byte initialization vector.
317
+ * @returns A promise that resolves to the encrypted data.
318
+ *
319
+ * @example
320
+ * ```typescript
321
+ * const encrypted = await aes128CbcNoPaddingEncrypt(key, data, iv)
322
+ * ```
323
+ */
324
+ export async function aes128CbcNoPaddingEncrypt(
325
+ key: ByteArray,
326
+ data: ByteArray,
327
+ iv: ByteArray,
328
+ ): Promise<ByteArray> {
329
+ return AlgorithmIdentifier.contentEncryptionAlgorithm({
330
+ type: 'AES_128_CBC',
331
+ params: {
332
+ nonce: iv,
333
+ disablePadding: true,
334
+ },
335
+ }).encrypt(data, key)
336
+ }
337
+
338
+ /**
339
+ * Encrypts data using AES-256-CBC mode without PKCS#7 padding.
340
+ *
341
+ * @param key - The 32-byte encryption key.
342
+ * @param data - The data to encrypt. Must be a multiple of 16 bytes.
343
+ * @param iv - The 16-byte initialization vector.
344
+ * @returns A promise that resolves to the encrypted data.
345
+ *
346
+ * @example
347
+ * ```typescript
348
+ * const encrypted = await aes256CbcNoPaddingEncrypt(key, data, iv)
349
+ * ```
350
+ */
351
+ export async function aes256CbcNoPaddingEncrypt(
352
+ key: ByteArray,
353
+ data: ByteArray,
354
+ iv: ByteArray,
355
+ ): Promise<ByteArray> {
356
+ return AlgorithmIdentifier.contentEncryptionAlgorithm({
357
+ type: 'AES_256_CBC',
358
+ params: {
359
+ nonce: iv,
360
+ disablePadding: true,
361
+ },
362
+ }).encrypt(data, key)
363
+ }
364
+
365
+ /**
366
+ * Decrypts data using AES-256-CBC mode without PKCS#7 padding.
367
+ *
368
+ * @param key - The 32-byte decryption key.
369
+ * @param ciphertext - The encrypted data to decrypt.
370
+ * @param iv - The 16-byte initialization vector. Defaults to zero IV.
371
+ * @returns A promise that resolves to the decrypted data.
372
+ *
373
+ * @example
374
+ * ```typescript
375
+ * const decrypted = await aes256CbcNoPaddingDecrypt(key, ciphertext, iv)
376
+ * ```
377
+ */
378
+ export async function aes256CbcNoPaddingDecrypt(
379
+ key: ByteArray,
380
+ ciphertext: ByteArray,
381
+ iv: ByteArray = new Uint8Array(16),
382
+ ): Promise<ByteArray> {
383
+ return AlgorithmIdentifier.contentEncryptionAlgorithm({
384
+ type: 'AES_256_CBC',
385
+ params: {
386
+ nonce: iv,
387
+ disablePadding: true,
388
+ },
389
+ }).decrypt(ciphertext, key)
390
+ }
391
+
392
+ /**
393
+ * Compresses data using the DEFLATE algorithm.
394
+ *
395
+ * @param data - The data to compress.
396
+ * @returns The compressed data as a byte array.
397
+ *
398
+ * @example
399
+ * ```typescript
400
+ * const compressed = deflateData(rawData)
401
+ * ```
402
+ */
403
+ export function deflateData(data: ByteArray): ByteArray {
404
+ const output = deflate(data)
405
+ if (!output) {
406
+ return new Uint8Array(0)
407
+ }
408
+ return output
409
+ }
410
+
411
+ /**
412
+ * Decompresses data using the INFLATE algorithm (reverses DEFLATE).
413
+ *
414
+ * @param data - The compressed data to decompress.
415
+ * @returns The decompressed data as a byte array.
416
+ * @throws Error if inflation fails due to invalid or corrupted data.
417
+ *
418
+ * @example
419
+ * ```typescript
420
+ * const decompressed = inflateData(compressedData)
421
+ * ```
422
+ */
423
+ export function inflateData(data: ByteArray): ByteArray {
424
+ if (data.length === 0) {
425
+ return new Uint8Array(0) // Return empty array for empty input
426
+ }
427
+
428
+ const isWhitespace = (byte: number): boolean => {
429
+ return byte === 0x20 || byte === 0x09 || byte === 0x0a || byte === 0x0d
430
+ }
431
+
432
+ const inflate = new Inflate()
433
+ inflate.push(data, true)
434
+
435
+ if (inflate.err || !inflate.result) {
436
+ if (isWhitespace(data[data.length - 1])) {
437
+ data = data.slice(0, -1) // Remove trailing whitespace]
438
+ return inflateData(data) // Retry inflation after removing whitespace
439
+ }
440
+ throw new Error(
441
+ `Inflate error: ${inflate.err} - ${inflate.msg}. Data hex: ${bytesToHex(data)}`,
442
+ ) // Log the hex representation of the data for debugging
443
+ }
444
+
445
+ return inflate.result as ByteArray
446
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Asserts that a value is truthy, throwing an error if it is not.
3
+ *
4
+ * @param value - The value to check.
5
+ * @param message - Optional error message to throw if the assertion fails.
6
+ * @throws Error if the value is falsy.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * assert(user !== null, 'User must be defined')
11
+ * ```
12
+ */
13
+ export function assert(value: unknown, message?: string): asserts value {
14
+ if (!value) {
15
+ throw new Error(message)
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Conditionally asserts a condition only when a value is defined.
21
+ * Does nothing if the value is undefined or null.
22
+ *
23
+ * @param value - The value to check for definedness.
24
+ * @param condition - The condition to assert if value is defined.
25
+ * @param message - Optional error message to throw if the assertion fails.
26
+ * @throws Error if value is defined and condition is falsy.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * assertIfDefined(user, user?.age >= 18, 'User must be an adult')
31
+ * ```
32
+ */
33
+ export function assertIfDefined(
34
+ value: unknown,
35
+ condition: unknown,
36
+ message?: string,
37
+ ): asserts condition {
38
+ if (value === undefined || value === null) {
39
+ return
40
+ }
41
+ assert(condition, message)
42
+ }
@@ -0,0 +1,18 @@
1
+ import { ByteArray } from '../types'
2
+ import { bytesToHexBytes } from './bytesToHexBytes'
3
+ import { bytesToString } from './bytesToString'
4
+
5
+ /**
6
+ * Converts a byte array to a hexadecimal string.
7
+ *
8
+ * @param bytes - The byte array to convert.
9
+ * @returns A hexadecimal string representation of the bytes.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * bytesToHex(new Uint8Array([255, 0, 127])) // Returns 'FF007F'
14
+ * ```
15
+ */
16
+ export function bytesToHex(bytes: ByteArray): string {
17
+ return bytesToString(bytesToHexBytes(bytes))
18
+ }
@@ -0,0 +1,27 @@
1
+ import { ByteArray } from '../types'
2
+
3
+ const hexChars = '0123456789ABCDEF'
4
+
5
+ /**
6
+ * Converts a byte array to a byte array containing hexadecimal character codes.
7
+ * Each byte becomes two bytes representing its hex digits.
8
+ *
9
+ * @param bytes - The byte array to convert.
10
+ * @returns A byte array with hexadecimal character codes.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * bytesToHexBytes(new Uint8Array([255])) // Returns bytes for 'FF'
15
+ * ```
16
+ */
17
+ export function bytesToHexBytes(bytes: ByteArray): ByteArray {
18
+ const result = new Uint8Array(bytes.length * 2)
19
+
20
+ for (let i = 0; i < bytes.length; i++) {
21
+ const byte = bytes[i]
22
+ result[i * 2] = hexChars.charCodeAt((byte >> 4) & 0x0f)
23
+ result[i * 2 + 1] = hexChars.charCodeAt(byte & 0x0f)
24
+ }
25
+
26
+ return result
27
+ }
@@ -0,0 +1,17 @@
1
+ import { ByteArray } from '../types'
2
+
3
+ /**
4
+ * Converts a byte array to a string using UTF-8 decoding.
5
+ *
6
+ * @param bytes - The byte array to decode.
7
+ * @returns The decoded string.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * bytesToString(new Uint8Array([72, 101, 108, 108, 111])) // Returns 'Hello'
12
+ * ```
13
+ */
14
+ export function bytesToString(bytes: ByteArray): string {
15
+ const decoder = new TextDecoder()
16
+ return decoder.decode(bytes)
17
+ }
@@ -0,0 +1,26 @@
1
+ import { ByteArray } from '../types'
2
+
3
+ /**
4
+ * Concatenates multiple Uint8Array instances into a single ByteArray.
5
+ *
6
+ * @param arrays - The arrays to concatenate.
7
+ * @returns A new ByteArray containing all input arrays concatenated in order.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const result = concatUint8Arrays(
12
+ * new Uint8Array([1, 2]),
13
+ * new Uint8Array([3, 4])
14
+ * ) // Returns Uint8Array([1, 2, 3, 4])
15
+ * ```
16
+ */
17
+ export function concatUint8Arrays(...arrays: Uint8Array[]): ByteArray {
18
+ const totalLength = arrays.reduce((acc, arr) => acc + arr.length, 0)
19
+ const result = new Uint8Array(totalLength)
20
+ let offset = 0
21
+ for (const arr of arrays) {
22
+ result.set(arr, offset)
23
+ offset += arr.length
24
+ }
25
+ return result
26
+ }
@@ -0,0 +1,49 @@
1
+ import { ByteArray } from '../types'
2
+ import { stringToBytes } from './stringToBytes'
3
+
4
+ /**
5
+ * Escapes special characters in a PDF string according to PDF specification.
6
+ * Escapes parentheses, backslashes, line feeds, and carriage returns.
7
+ *
8
+ * @param bytes - The byte array or string to escape.
9
+ * @returns A new byte array with escaped characters.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * escapeString('Hello (World)') // Escapes the parentheses
14
+ * ```
15
+ */
16
+ export function escapeString(bytes: ByteArray | string): ByteArray {
17
+ if (typeof bytes === 'string') {
18
+ bytes = stringToBytes(bytes)
19
+ }
20
+
21
+ const result: number[] = []
22
+ const BACKSLASH = 0x5c
23
+
24
+ for (const b of bytes) {
25
+ switch (b) {
26
+ case 0x28:
27
+ result.push(BACKSLASH, 0x28)
28
+ break // (
29
+ case 0x29:
30
+ result.push(BACKSLASH, 0x29)
31
+ break // )
32
+ case BACKSLASH:
33
+ result.push(BACKSLASH, BACKSLASH)
34
+ break // \
35
+ case 0x0a:
36
+ result.push(BACKSLASH, 0x6e)
37
+ break // LF
38
+ case 0x0d:
39
+ result.push(BACKSLASH, 0x72)
40
+ break // CR
41
+ default:
42
+ result.push(b)
43
+
44
+ break
45
+ }
46
+ }
47
+
48
+ return new Uint8Array(result)
49
+ }
@@ -0,0 +1,22 @@
1
+ import { ByteArray } from '../types'
2
+
3
+ /**
4
+ * Converts a byte array containing hexadecimal ASCII characters to raw bytes.
5
+ * Each pair of hex character bytes is converted to a single byte value.
6
+ *
7
+ * @param hex - The byte array containing hex character codes.
8
+ * @returns A byte array with decoded values.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * // 'FF' as bytes (0x46, 0x46) becomes 0xFF
13
+ * hexBytesToBytes(new Uint8Array([70, 70])) // Returns Uint8Array([255])
14
+ * ```
15
+ */
16
+ export function hexBytesToBytes(hex: ByteArray): ByteArray {
17
+ const bytes = new Uint8Array(hex.length / 2)
18
+ for (let i = 0; i < hex.length; i += 2) {
19
+ bytes[i / 2] = parseInt(String.fromCharCode(hex[i], hex[i + 1]), 16)
20
+ }
21
+ return bytes
22
+ }
@@ -0,0 +1,21 @@
1
+ import { ByteArray } from '../types'
2
+
3
+ /**
4
+ * Converts a byte array to a lowercase hexadecimal string.
5
+ *
6
+ * @param bytes - The byte array to convert.
7
+ * @returns A lowercase hexadecimal string representation.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * hexBytesToString(new Uint8Array([255, 0])) // Returns 'ff00'
12
+ * ```
13
+ */
14
+ export function hexBytesToString(bytes: ByteArray): string {
15
+ let hex = ''
16
+ for (let i = 0; i < bytes.length; i++) {
17
+ const byte = bytes[i].toString(16)
18
+ hex += byte.length === 1 ? '0' + byte : byte
19
+ }
20
+ return hex
21
+ }
@@ -0,0 +1,18 @@
1
+ import { ByteArray } from '../types'
2
+ import { hexBytesToBytes } from './hexBytesToBytes'
3
+ import { stringToBytes } from './stringToBytes'
4
+
5
+ /**
6
+ * Converts a hexadecimal string to a byte array.
7
+ *
8
+ * @param hex - The hexadecimal string to convert.
9
+ * @returns A byte array containing the decoded values.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * hexToBytes('FF00') // Returns Uint8Array([255, 0])
14
+ * ```
15
+ */
16
+ export function hexToBytes(hex: string): ByteArray {
17
+ return hexBytesToBytes(stringToBytes(hex))
18
+ }