forked from nrhirani/node-qpdf
-
-
Notifications
You must be signed in to change notification settings - Fork 15
/
encrypt.ts
140 lines (127 loc) · 5.14 KB
/
encrypt.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import execute from "./spawn.js";
import { fileExists, hyphenate } from "./utils.js";
const EncryptDefaults = {
keyLength: 256,
overwrite: true,
};
export interface EncryptOptions {
/** The location of the unencrypted pdf file */
input: string;
/**
* A number which defines the encryption algorithm to be used.
* Using a keyLengh of 40 is insecure.
* @default 256
*/
keyLength?: 40 | 128 | 256;
/** If defined, the output location of the encrypted pdf. If not defined, a Buffer will be returned. */
output?: string;
/**
* If defined, will determine if the encrypted pdf will overwrite an existing file
* @default true
*/
overwrite?: boolean | undefined;
/**
* A string containing the password with will be used to decrypt the pdf.
* Optionally, an object containing `user` and `owner` for setting different roles.
* If undefined, will encrypt a pdf without requiring a password to decrypt
*/
password?:
| string
| {
owner: string;
user: string;
};
/** Restrictions for the encrypted pdf */
restrictions?: {
/** Please see: https://qpdf.readthedocs.io/en/stable/cli.html#option-accessibility */
accessibility?: "y" | "n";
/** Please see: https://qpdf.readthedocs.io/en/stable/cli.html#option-annotate */
annotate?: "y" | "n";
/** Please see: https://qpdf.readthedocs.io/en/stable/cli.html#option-assemble */
assemble?: "y" | "n";
/** Please see: https://qpdf.readthedocs.io/en/stable/cli.html#option-cleartext-metadata */
cleartextMetadata?: boolean;
/** Please see: https://qpdf.readthedocs.io/en/stable/cli.html#option-extract */
extract?: "y" | "n";
/** Please see: https://qpdf.readthedocs.io/en/stable/cli.html#option-form */
form?: "y" | "n";
/** Please see: https://qpdf.readthedocs.io/en/stable/cli.html#option-modify */
modify?: "y" | "n" | "all" | "annotate" | "form" | "assembly" | "none";
/** Please see: https://qpdf.readthedocs.io/en/stable/cli.html#option-modify-other */
modifyOther?: "y" | "n";
/** Please see: https://qpdf.readthedocs.io/en/stable/cli.html#option-print */
print?: "y" | "n" | "full" | "low" | "none";
/** Please see: https://qpdf.readthedocs.io/en/stable/cli.html#option-use-aes */
useAes?: "y" | "n";
};
}
/**
* Encrypts a PDF file
* @param userPayload The options for encryption
* @returns The output of QPDF
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
export const encrypt = async (userPayload: EncryptOptions): Promise<Buffer> => {
// Set Defaults
const payload = { ...EncryptDefaults, ...userPayload };
// Check if the file exists
if (!payload.input) throw new Error("Please specify input file");
if (!fileExists(payload.input)) throw new Error("Input file doesn't exist");
if (payload.output && !payload.overwrite && fileExists(payload.output))
throw new Error("Output file already exists");
const callArguments = [];
// If the keyLength is 40, `--allow-weak-crypto` needs to be specified before `--encrypt`.
// This is required for qpdf 11+.
if (payload.keyLength === 40) callArguments.push("--allow-weak-crypto");
callArguments.push("--encrypt");
// Set user-password and owner-password
if (typeof payload.password === "object") {
if (
payload.password.user === undefined ||
payload.password.owner === undefined
) {
// TODO: If the keyLength is 256 AND there is no owner password, `--allow-insecure` can be used
throw new Error("Please specify both owner and user passwords");
}
callArguments.push(payload.password.user, payload.password.owner);
} else if (typeof payload.password === "string") {
// Push twice for user-password and owner-password
callArguments.push(payload.password, payload.password);
} else {
// no password specified, push two empty strings (https://stackoverflow.com/a/43736897/455124)
callArguments.push("", "");
}
// Specifying the key length
callArguments.push(payload.keyLength.toString());
// Add Restrictions for encryption
if (payload.restrictions) {
if (typeof payload.restrictions !== "object")
throw new Error("Invalid Restrictions");
for (const [restriction, value] of Object.entries(payload.restrictions)) {
// cleartextMetadata does not have a value
if (restriction === "cleartextMetadata" && value === true) {
callArguments.push(`--${hyphenate(restriction)}`);
}
if (restriction === "useAes" && payload.keyLength === 256) {
// use-aes is always on with 256 bit keyLength
} else {
callArguments.push(`--${hyphenate(restriction)}=${value as string}`);
}
}
}
// Marks end of --encrypt options, Input file path
callArguments.push("--", payload.input);
if (payload.output) {
// If the input and output locations are the same, and overwrite is true, replace the input file
if (payload.input === payload.output && payload.overwrite) {
callArguments.push("--replace-input");
} else {
callArguments.push(payload.output);
}
} else {
// Print PDF on stdout
callArguments.push("-");
}
// Execute command and return stdout for pipe
return execute(callArguments);
};