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,625 @@
1
+ import { PdfArray } from '../../core/objects/pdf-array'
2
+ import { PdfBoolean } from '../../core/objects/pdf-boolean'
3
+ import { PdfDictionary } from '../../core/objects/pdf-dictionary'
4
+ import { PdfHexadecimal } from '../../core/objects/pdf-hexadecimal'
5
+ import { PdfIndirectObject } from '../../core/objects/pdf-indirect-object'
6
+ import { PdfName } from '../../core/objects/pdf-name'
7
+ import { PdfNumber } from '../../core/objects/pdf-number'
8
+ import { PdfObject } from '../../core/objects/pdf-object'
9
+ import { PdfStream } from '../../core/objects/pdf-stream'
10
+ import { PdfString } from '../../core/objects/pdf-string'
11
+ import { Cipher } from '../../crypto/types'
12
+ import { ByteArray, PdfPermissions, PERMISSION_FLAGS } from '../../types'
13
+ import { assert, assertIfDefined } from '../../utils/assert'
14
+ import { stringToBytes } from '../../utils/stringToBytes'
15
+ import {
16
+ PdfEncryptionAlgorithmType,
17
+ PdfEncryptionDictionary,
18
+ PdfId,
19
+ } from '../types'
20
+
21
+ /**
22
+ * Base options for creating a security handler.
23
+ */
24
+ export type PdfSecurityHandlerOptions = {
25
+ /** Document permissions configuration. */
26
+ permissions?: number | PdfPermissions
27
+ /** Whether to encrypt document metadata. */
28
+ encryptMetadata?: boolean
29
+ }
30
+
31
+ /**
32
+ * Abstract base class for PDF security handlers.
33
+ * Security handlers manage encryption, decryption, and access permissions for PDF documents.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const handler = new PdfV5SecurityHandler({ password: 'secret' })
38
+ * await handler.write()
39
+ * const encryptedData = await handler.encrypt('stream', data, objectNumber, generationNumber)
40
+ * ```
41
+ */
42
+ export abstract class PdfSecurityHandler {
43
+ /** The encryption dictionary containing all encryption parameters. */
44
+ public dict: PdfEncryptionDictionary
45
+ /** Numeric permission flags. */
46
+ public permissions: number
47
+ /** Whether to encrypt document metadata. */
48
+ public encryptMetadata: boolean
49
+
50
+ /**
51
+ * Creates a new security handler.
52
+ *
53
+ * @param options - Configuration options for the security handler.
54
+ */
55
+ constructor(options?: PdfSecurityHandlerOptions) {
56
+ this.dict = new PdfDictionary()
57
+ this.encryptMetadata = options?.encryptMetadata ?? true
58
+ this.permissions =
59
+ typeof options?.permissions === 'number'
60
+ ? options.permissions
61
+ : this.buildPermissions(
62
+ options?.permissions ?? DEFAULT_PERMISSIONS,
63
+ )
64
+ }
65
+
66
+ /**
67
+ * Decodes the numeric permission flags into a PdfPermissions object.
68
+ *
69
+ * @returns An object with boolean flags for each permission.
70
+ */
71
+ decodePermissions(): PdfPermissions {
72
+ const perm: PdfPermissions = {}
73
+ for (const [key, bit] of Object.entries(PERMISSION_FLAGS) as [
74
+ keyof PdfPermissions,
75
+ number,
76
+ ][]) {
77
+ perm[key] = (this.permissions & bit) !== 0
78
+ }
79
+ return perm
80
+ }
81
+
82
+ /**
83
+ * Checks if the security handler is ready for encryption/decryption.
84
+ *
85
+ * @returns True if ready, false otherwise.
86
+ */
87
+ abstract isReady(): boolean
88
+
89
+ /**
90
+ * Gets the security handler filter name.
91
+ *
92
+ * @returns The filter name (e.g., 'Standard').
93
+ */
94
+ abstract getName(): string
95
+
96
+ /**
97
+ * Gets the encryption version number.
98
+ *
99
+ * @returns The version number (1-5).
100
+ */
101
+ abstract getVersion(): number
102
+
103
+ /**
104
+ * Gets the encryption revision number.
105
+ *
106
+ * @returns The revision number.
107
+ */
108
+ abstract getRevision(): number
109
+
110
+ /**
111
+ * Reads and applies encryption parameters from a dictionary.
112
+ *
113
+ * @param dictionary - The encryption dictionary from the PDF trailer.
114
+ */
115
+ abstract readEncryptionDictionary(dictionary: PdfEncryptionDictionary): void
116
+
117
+ /**
118
+ * Decrypts data of a specific type.
119
+ *
120
+ * @param type - The type of content being decrypted.
121
+ * @param data - The encrypted data.
122
+ * @param objectNumber - The PDF object number.
123
+ * @param generationNumber - The PDF generation number.
124
+ * @returns The decrypted data.
125
+ */
126
+ abstract decrypt(
127
+ type: 'string' | 'stream' | 'file',
128
+ data: ByteArray,
129
+ objectNumber?: number,
130
+ generationNumber?: number,
131
+ ): Promise<ByteArray>
132
+
133
+ /**
134
+ * Encrypts data of a specific type.
135
+ *
136
+ * @param type - The type of content being encrypted.
137
+ * @param data - The data to encrypt.
138
+ * @param objectNumber - The PDF object number.
139
+ * @param generationNumber - The PDF generation number.
140
+ * @returns The encrypted data.
141
+ */
142
+ abstract encrypt(
143
+ type: 'string' | 'stream' | 'file',
144
+ data: ByteArray,
145
+ objectNumber?: number,
146
+ generationNumber?: number,
147
+ ): Promise<ByteArray>
148
+
149
+ /**
150
+ * Computes the object-specific encryption key.
151
+ *
152
+ * @param objectNumber - The PDF object number.
153
+ * @param generationNumber - The PDF generation number.
154
+ * @param algorithm - Optional algorithm type for key derivation.
155
+ * @returns The computed object key.
156
+ */
157
+ abstract computeObjectKey(
158
+ objectNumber?: number,
159
+ generationNumber?: number,
160
+ algorithm?: PdfEncryptionAlgorithmType,
161
+ ): Promise<ByteArray>
162
+
163
+ /**
164
+ * Gets the document ID array.
165
+ *
166
+ * @returns The document ID, or undefined if not set.
167
+ */
168
+ abstract getDocumentId(): PdfId | undefined
169
+
170
+ /**
171
+ * Sets the document ID array.
172
+ *
173
+ * @param id - The document ID to set.
174
+ */
175
+ abstract setDocumentId(id: PdfId): void
176
+
177
+ /**
178
+ * Writes the encryption dictionary with computed keys.
179
+ */
180
+ abstract write(): Promise<void>
181
+
182
+ /**
183
+ * Builds the numeric permission flags from a PdfPermissions object.
184
+ *
185
+ * @param perm - The permissions to encode.
186
+ * @returns The numeric permission flags.
187
+ */
188
+ protected buildPermissions(perm: PdfPermissions): number {
189
+ let flags = 0xfffffffc // All bits set to 1 except bits 0–1, which must be 0
190
+ for (const [key, bit] of Object.entries(PERMISSION_FLAGS) as [
191
+ keyof PdfPermissions,
192
+ number,
193
+ ][]) {
194
+ if (perm[key] === false) {
195
+ // Remove this permission
196
+ flags &= ~bit
197
+ }
198
+ }
199
+ return flags | 0xfffff000 // ensure unused high bits are set
200
+ }
201
+
202
+ /**
203
+ * Recursively decrypts all strings and streams within an indirect object.
204
+ *
205
+ * @param object - The indirect object to decrypt.
206
+ */
207
+ async decryptObject(object: PdfIndirectObject): Promise<void> {
208
+ const objectNumber = object.objectNumber
209
+ const generationNumber = object.generationNumber
210
+
211
+ const decryptObject = async (obj: PdfObject): Promise<void> => {
212
+ if (obj instanceof PdfIndirectObject) {
213
+ return decryptObject(obj.content)
214
+ } else if (obj instanceof PdfString) {
215
+ const decryptedData = await this.decrypt(
216
+ 'string',
217
+ obj.raw,
218
+ objectNumber,
219
+ generationNumber,
220
+ )
221
+ obj.raw = decryptedData
222
+ } else if (obj instanceof PdfStream) {
223
+ const decryptedData = await this.decrypt(
224
+ 'stream',
225
+ obj.raw,
226
+ objectNumber,
227
+ generationNumber,
228
+ )
229
+
230
+ obj.raw = decryptedData
231
+
232
+ await decryptObject(obj.header)
233
+ } else if (obj instanceof PdfDictionary) {
234
+ for (const [key, value] of Object.entries(obj)) {
235
+ await decryptObject(value)
236
+ }
237
+ } else if (obj instanceof PdfArray) {
238
+ for (const item of obj.items) {
239
+ await decryptObject(item)
240
+ }
241
+ }
242
+ }
243
+
244
+ await decryptObject(object.content)
245
+ }
246
+
247
+ /**
248
+ * Recursively encrypts all strings and streams within an indirect object.
249
+ *
250
+ * @param object - The indirect object to encrypt.
251
+ */
252
+ async encryptObject(object: PdfIndirectObject): Promise<void> {
253
+ const objectNumber = object.objectNumber
254
+ const generationNumber = object.generationNumber
255
+
256
+ const encryptObject = async (obj: PdfObject): Promise<void> => {
257
+ if (obj instanceof PdfIndirectObject) {
258
+ return encryptObject(obj.content)
259
+ } else if (obj instanceof PdfString) {
260
+ const encryptedData = await this.encrypt(
261
+ 'string',
262
+ obj.raw,
263
+ objectNumber,
264
+ generationNumber,
265
+ )
266
+ obj.raw = encryptedData
267
+ } else if (obj instanceof PdfStream) {
268
+ const encryptedData = await this.encrypt(
269
+ 'stream',
270
+ obj.raw,
271
+ objectNumber,
272
+ generationNumber,
273
+ )
274
+ obj.raw = encryptedData
275
+ await encryptObject(obj.header)
276
+ } else if (obj instanceof PdfDictionary) {
277
+ const values = obj.values
278
+ for (const key in values) {
279
+ await encryptObject(values[key])
280
+ }
281
+ } else if (obj instanceof PdfArray) {
282
+ for (const item of obj.items) {
283
+ await encryptObject(item)
284
+ }
285
+ }
286
+ }
287
+
288
+ return await encryptObject(object.content)
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Options for creating a standard security handler with password-based encryption.
294
+ */
295
+ export type PdfStandardSecurityHandlerOptions = PdfSecurityHandlerOptions & {
296
+ /** User password for opening the document. */
297
+ password?: string | ByteArray
298
+ /** Owner password for full document access. */
299
+ ownerPassword?: string | ByteArray
300
+ /** Document identifier for key derivation. */
301
+ documentId?: PdfId | string | ByteArray
302
+ /** Pre-computed user key. */
303
+ userKey?: ByteArray
304
+ /** Pre-computed owner key. */
305
+ ownerKey?: ByteArray
306
+ }
307
+
308
+ const DEFAULT_PERMISSIONS: PdfPermissions = {
309
+ print: true,
310
+ modify: true,
311
+ copy: true,
312
+ annotate: true,
313
+ fill: true,
314
+ extract: true,
315
+ assemble: true,
316
+ printHighQuality: true,
317
+ }
318
+
319
+ /**
320
+ * Abstract base class for standard PDF security handlers.
321
+ * Implements password-based encryption as defined in the PDF specification.
322
+ *
323
+ * @example
324
+ * ```typescript
325
+ * const handler = new PdfV5SecurityHandler({
326
+ * password: 'user123',
327
+ * ownerPassword: 'admin456',
328
+ * permissions: { print: true, copy: false }
329
+ * })
330
+ * ```
331
+ */
332
+ export abstract class PdfStandardSecurityHandler extends PdfSecurityHandler {
333
+ /** Document identifier for key derivation. */
334
+ protected documentId?: PdfId
335
+ /** Computed owner key (O value). */
336
+ protected ownerKey?: ByteArray
337
+ /** Computed user key (U value). */
338
+ protected userKey?: ByteArray
339
+ /** User password for authentication. */
340
+ protected password: ByteArray
341
+ /** Owner password for full access. */
342
+ protected ownerPassword?: ByteArray
343
+ /** Derived master encryption key. */
344
+ protected masterKey?: ByteArray
345
+
346
+ /**
347
+ * Creates a new standard security handler.
348
+ *
349
+ * @param options - Configuration options including passwords and document ID.
350
+ */
351
+ constructor(options: PdfStandardSecurityHandlerOptions) {
352
+ super(options)
353
+
354
+ this.dict.set('Filter', new PdfName('Standard'))
355
+ this.password =
356
+ typeof options.password === 'string'
357
+ ? stringToBytes(options.password)
358
+ : (options.password ?? new Uint8Array())
359
+
360
+ this.ownerPassword =
361
+ typeof options.ownerPassword === 'string'
362
+ ? stringToBytes(options.ownerPassword)
363
+ : options.ownerPassword
364
+
365
+ this.documentId =
366
+ typeof options.documentId === 'string' ||
367
+ options.documentId instanceof Uint8Array
368
+ ? new PdfArray([
369
+ PdfHexadecimal.toHexadecimal(options.documentId),
370
+ PdfHexadecimal.toHexadecimal(options.documentId),
371
+ ])
372
+ : options.documentId
373
+ }
374
+
375
+ /**
376
+ * Gets the encryption key length in bits.
377
+ *
378
+ * @returns The key length in bits.
379
+ */
380
+ abstract getKeyBits(): number
381
+
382
+ /**
383
+ * Gets the security handler filter name.
384
+ *
385
+ * @returns 'Standard' for password-based encryption.
386
+ */
387
+ getName(): string {
388
+ return 'Standard'
389
+ }
390
+
391
+ /**
392
+ * Checks if the handler is ready (has document ID).
393
+ *
394
+ * @returns True if document ID is set.
395
+ */
396
+ isReady(): boolean {
397
+ return !!this.documentId
398
+ }
399
+
400
+ /**
401
+ * Sets the document ID for key derivation.
402
+ *
403
+ * @param id - The document ID array.
404
+ */
405
+ setDocumentId(id: PdfId): void {
406
+ this.documentId = id
407
+ }
408
+
409
+ /**
410
+ * Gets the document ID.
411
+ *
412
+ * @returns The document ID, or undefined if not set.
413
+ */
414
+ getDocumentId(): PdfId | undefined {
415
+ return this.documentId
416
+ }
417
+
418
+ /**
419
+ * Sets the user password.
420
+ *
421
+ * @param password - The user password string or bytes.
422
+ */
423
+ setPassword(password: string | ByteArray): void {
424
+ this.password =
425
+ typeof password === 'string' ? stringToBytes(password) : password
426
+ }
427
+
428
+ /**
429
+ * Sets the owner password.
430
+ *
431
+ * @param ownerPassword - The owner password string or bytes.
432
+ */
433
+ setOwnerPassword(ownerPassword: string | ByteArray): void {
434
+ this.ownerPassword =
435
+ typeof ownerPassword === 'string'
436
+ ? stringToBytes(ownerPassword)
437
+ : ownerPassword
438
+ }
439
+
440
+ /**
441
+ * Checks if metadata encryption is enabled.
442
+ *
443
+ * @returns True if metadata should be encrypted.
444
+ */
445
+ canEncryptMetadata(): boolean {
446
+ return this.encryptMetadata
447
+ }
448
+
449
+ /**
450
+ * Reads encryption parameters from the encryption dictionary.
451
+ *
452
+ * @param encryptionDictionary - The encryption dictionary from the PDF.
453
+ */
454
+ readEncryptionDictionary(
455
+ encryptionDictionary: PdfEncryptionDictionary,
456
+ ): void {
457
+ this.dict.copyFrom(encryptionDictionary)
458
+
459
+ const o = encryptionDictionary.get('O')
460
+ const u = encryptionDictionary.get('U')
461
+ const p = encryptionDictionary.get('P')
462
+ const ID = encryptionDictionary.get('ID')
463
+ const encryptMetadata = encryptionDictionary.get('EncryptMetadata')
464
+
465
+ assertIfDefined(
466
+ u,
467
+ u instanceof PdfHexadecimal || u instanceof PdfString,
468
+ 'U must be a hexadecimal or a string',
469
+ )
470
+ assertIfDefined(
471
+ o,
472
+ o instanceof PdfHexadecimal || o instanceof PdfString,
473
+ 'O must be a hexadecimal or a string',
474
+ )
475
+
476
+ assert(p instanceof PdfNumber, 'P must be a number')
477
+ assertIfDefined(ID, ID instanceof PdfArray, 'ID must be an array')
478
+ assertIfDefined(
479
+ encryptMetadata,
480
+ encryptMetadata instanceof PdfBoolean,
481
+ 'EncryptMetadata must be a boolean',
482
+ )
483
+
484
+ this.ownerKey = o instanceof PdfHexadecimal ? o.bytes : o.raw
485
+ this.userKey = u instanceof PdfHexadecimal ? u.bytes : u.raw
486
+ this.permissions = p.value
487
+
488
+ this.encryptMetadata = encryptMetadata?.value ?? true
489
+
490
+ if (ID) this.documentId = ID
491
+ }
492
+
493
+ /**
494
+ * Computes the user key (U value) for the encryption dictionary.
495
+ *
496
+ * @returns The computed user key.
497
+ */
498
+ protected abstract computeUserKey(): Promise<ByteArray>
499
+
500
+ /**
501
+ * Computes the owner key (O value) for the encryption dictionary.
502
+ *
503
+ * @returns The computed owner key.
504
+ */
505
+ protected abstract computeOwnerKey(): Promise<ByteArray>
506
+
507
+ /**
508
+ * Initializes the user and owner keys if not already set.
509
+ */
510
+ protected async initKeys(): Promise<void> {
511
+ this.ownerKey ||= await this.computeOwnerKey()
512
+ this.userKey ||= await this.computeUserKey()
513
+ }
514
+
515
+ /**
516
+ * Writes the encryption dictionary with all computed keys and parameters.
517
+ *
518
+ * @throws Error if required keys are not computed.
519
+ */
520
+ async write(): Promise<void> {
521
+ // Initialise the keys
522
+ await this.initKeys()
523
+
524
+ if (!this.ownerKey) {
525
+ throw new Error('Missing ownerKey')
526
+ }
527
+
528
+ if (!this.userKey) {
529
+ throw new Error('Missing userKey')
530
+ }
531
+
532
+ this.dict.set('O', new PdfString(this.ownerKey))
533
+ this.dict.set('U', new PdfString(this.userKey))
534
+ this.dict.set('P', new PdfNumber(this.permissions))
535
+ this.dict.set('V', new PdfNumber(this.getVersion()))
536
+ this.dict.set('R', new PdfNumber(this.getRevision()))
537
+ this.dict.set('Length', new PdfNumber(this.getKeyBits()))
538
+
539
+ if (this.documentId) {
540
+ this.dict.set('ID', this.documentId)
541
+ }
542
+
543
+ this.dict.set('EncryptMetadata', new PdfBoolean(this.encryptMetadata))
544
+ }
545
+
546
+ /**
547
+ * Gets a cipher for the specified object.
548
+ *
549
+ * @param objectNumber - The PDF object number.
550
+ * @param generationNumber - The PDF generation number.
551
+ * @returns A cipher instance, or null if no encryption needed.
552
+ */
553
+ protected abstract getCipher(
554
+ objectNumber?: number,
555
+ generationNumber?: number,
556
+ ): Promise<Cipher | null>
557
+
558
+ /**
559
+ * Encrypts data using the appropriate cipher.
560
+ *
561
+ * @param type - The type of content being encrypted.
562
+ * @param data - The data to encrypt.
563
+ * @param objectNumber - The PDF object number.
564
+ * @param generationNumber - The PDF generation number.
565
+ * @returns The encrypted data.
566
+ */
567
+ async encrypt(
568
+ type: 'string' | 'stream' | 'file',
569
+ data: ByteArray,
570
+ objectNumber?: number,
571
+ generationNumber?: number,
572
+ ): Promise<ByteArray> {
573
+ const cipher = await this.getCipher(objectNumber, generationNumber)
574
+
575
+ if (!cipher) {
576
+ return data
577
+ }
578
+
579
+ return await cipher.encrypt(data)
580
+ }
581
+
582
+ /**
583
+ * Decrypts data using the appropriate cipher.
584
+ *
585
+ * @param type - The type of content being decrypted.
586
+ * @param data - The encrypted data.
587
+ * @param objectNumber - The PDF object number.
588
+ * @param generationNumber - The PDF generation number.
589
+ * @returns The decrypted data.
590
+ */
591
+ async decrypt(
592
+ type: 'string' | 'stream' | 'file',
593
+ data: ByteArray,
594
+ objectNumber?: number,
595
+ generationNumber?: number,
596
+ ): Promise<ByteArray> {
597
+ const cipher = await this.getCipher(objectNumber, generationNumber)
598
+
599
+ if (!cipher) {
600
+ return data
601
+ }
602
+
603
+ return await cipher.decrypt(data)
604
+ }
605
+
606
+ /**
607
+ * Sets the master encryption key directly.
608
+ *
609
+ * @param masterKey - The master key to use.
610
+ */
611
+ setMasterKey(masterKey: ByteArray): void {
612
+ this.masterKey = masterKey
613
+ }
614
+
615
+ /**
616
+ * Recovers the user password from the owner password.
617
+ *
618
+ * @param ownerPassword - The owner password.
619
+ * @returns The recovered user password.
620
+ * @throws Error if recovery is not supported.
621
+ */
622
+ abstract recoverUserPassword(
623
+ ownerPassword?: ByteArray | string,
624
+ ): Promise<string>
625
+ }