upload-safety 0.0.0-stage → 0.2.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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -2
  3. package/dist/check-upload.d.ts +23 -0
  4. package/dist/check-upload.d.ts.map +1 -0
  5. package/dist/check-upload.js +22 -0
  6. package/dist/check-upload.js.map +1 -0
  7. package/dist/excel-macro-sheets.d.ts +12 -0
  8. package/dist/excel-macro-sheets.d.ts.map +1 -0
  9. package/dist/excel-macro-sheets.js +51 -0
  10. package/dist/excel-macro-sheets.js.map +1 -0
  11. package/dist/heif-metadata.d.ts +14 -0
  12. package/dist/heif-metadata.d.ts.map +1 -0
  13. package/dist/heif-metadata.js +216 -0
  14. package/dist/heif-metadata.js.map +1 -0
  15. package/dist/index.d.ts +7 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +7 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/ole-streams.d.ts +15 -0
  20. package/dist/ole-streams.d.ts.map +1 -0
  21. package/dist/ole-streams.js +192 -0
  22. package/dist/ole-streams.js.map +1 -0
  23. package/dist/pdf-active-content.d.ts +3 -0
  24. package/dist/pdf-active-content.d.ts.map +1 -0
  25. package/dist/pdf-active-content.js +130 -0
  26. package/dist/pdf-active-content.js.map +1 -0
  27. package/dist/pdf-encryption.d.ts +70 -0
  28. package/dist/pdf-encryption.d.ts.map +1 -0
  29. package/dist/pdf-encryption.js +576 -0
  30. package/dist/pdf-encryption.js.map +1 -0
  31. package/dist/photo-metadata.d.ts +14 -0
  32. package/dist/photo-metadata.d.ts.map +1 -0
  33. package/dist/photo-metadata.js +307 -0
  34. package/dist/photo-metadata.js.map +1 -0
  35. package/dist/powerpoint-vba.d.ts +14 -0
  36. package/dist/powerpoint-vba.d.ts.map +1 -0
  37. package/dist/powerpoint-vba.js +122 -0
  38. package/dist/powerpoint-vba.js.map +1 -0
  39. package/dist/text-content.d.ts +7 -0
  40. package/dist/text-content.d.ts.map +1 -0
  41. package/dist/text-content.js +83 -0
  42. package/dist/text-content.js.map +1 -0
  43. package/dist/upload-allowlist.d.ts +48 -0
  44. package/dist/upload-allowlist.d.ts.map +1 -0
  45. package/dist/upload-allowlist.js +560 -0
  46. package/dist/upload-allowlist.js.map +1 -0
  47. package/dist/xml-markup.d.ts +12 -0
  48. package/dist/xml-markup.d.ts.map +1 -0
  49. package/dist/xml-markup.js +83 -0
  50. package/dist/xml-markup.js.map +1 -0
  51. package/dist/zip-entries.d.ts +34 -0
  52. package/dist/zip-entries.d.ts.map +1 -0
  53. package/dist/zip-entries.js +222 -0
  54. package/dist/zip-entries.js.map +1 -0
  55. package/package.json +62 -5
  56. package/src/check-upload.ts +50 -0
  57. package/src/excel-macro-sheets.ts +52 -0
  58. package/src/heif-metadata.ts +232 -0
  59. package/src/index.ts +6 -0
  60. package/src/ole-streams.ts +226 -0
  61. package/src/pdf-active-content.ts +138 -0
  62. package/src/pdf-encryption.ts +629 -0
  63. package/src/photo-metadata.ts +325 -0
  64. package/src/powerpoint-vba.ts +136 -0
  65. package/src/text-content.ts +85 -0
  66. package/src/upload-allowlist.ts +657 -0
  67. package/src/xml-markup.ts +85 -0
  68. package/src/zip-entries.ts +255 -0
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Reads the directory and streams of an OLE compound file (the container of
3
+ * legacy .doc/.xls/.ppt files) by following its sector chains.
4
+ * Returns null for anything malformed, oversized or outside versions 3 and 4.
5
+ */
6
+ /** Upper bounds that keep a crafted file from costing more than a real one. */
7
+ export const MAX_OLE_STREAM_BYTES = 64 * 1024 * 1024;
8
+ const MAX_OLE_SECTORS = 1 << 20;
9
+ const SIGNATURE = Buffer.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]);
10
+ const HEADER_SIZE = 512;
11
+ const HEADER_FAT_SECTORS = 109;
12
+ const BYTE_ORDER = 0xfffe;
13
+ const MINI_SECTOR_SHIFT = 6;
14
+ const MINI_SECTOR_SIZE = 1 << MINI_SECTOR_SHIFT;
15
+ const MINI_STREAM_CUTOFF = 4096;
16
+ const END_OF_CHAIN = 0xfffffffe;
17
+ const FREE_SECTOR = 0xffffffff;
18
+ const NO_ENTRY = 0xffffffff;
19
+ const ENTRY_SIZE = 128;
20
+ const STORAGE = 1;
21
+ const STREAM = 2;
22
+ const ROOT = 5;
23
+ /**
24
+ * The first `count` sectors of a chain, or all up to its end without a count; null when one
25
+ * is out of range or repeats. Some writers run a chain on past its stream, as Office tolerates.
26
+ */
27
+ function followChain(next, start, sectorCount, count) {
28
+ const visited = new Uint8Array(sectorCount);
29
+ const sectors = [];
30
+ let sector = start;
31
+ while (sector !== END_OF_CHAIN && sectors.length !== count) {
32
+ if (sector >= sectorCount || visited[sector]) {
33
+ return null;
34
+ }
35
+ visited[sector] = 1;
36
+ sectors.push(sector);
37
+ sector = next(sector);
38
+ }
39
+ return count === undefined || sectors.length === count ? sectors : null;
40
+ }
41
+ /** Reads a stream of `size` bytes, or a chain to its end without one, through an allocation table. */
42
+ function chainReader(table, sector, sectorCount, sectorSize) {
43
+ const next = (index) => (index * 4 + 4 <= table.length ? table.readUInt32LE(index * 4) : FREE_SECTOR);
44
+ return (start, size) => {
45
+ if ((size ?? 0) > MAX_OLE_STREAM_BYTES) {
46
+ return null;
47
+ }
48
+ const count = size === undefined ? undefined : Math.ceil(size / sectorSize);
49
+ const sectors = count === 0 ? [] : followChain(next, start, sectorCount, count);
50
+ const data = sectors && Buffer.concat(sectors.map(sector));
51
+ return data && data.length >= (size ?? 0) ? data.subarray(0, size) : null;
52
+ };
53
+ }
54
+ /** The directory's entries, null for unused ones, or null when an entry is malformed. */
55
+ function readEntries(directory, version) {
56
+ if (!directory) {
57
+ return null;
58
+ }
59
+ const entries = [];
60
+ for (let offset = 0; offset + ENTRY_SIZE <= directory.length; offset += ENTRY_SIZE) {
61
+ const nameLength = directory.readUInt16LE(offset + 0x40);
62
+ const type = directory[offset + 0x42];
63
+ // Version 3 writers may leave garbage in the upper half of the size.
64
+ const size = directory.readUInt32LE(offset + 0x78) + (version === 3 ? 0 : directory.readUInt32LE(offset + 0x7c) * 2 ** 32);
65
+ if (type !== STORAGE && type !== STREAM && type !== ROOT) {
66
+ entries.push(null);
67
+ continue;
68
+ }
69
+ if (nameLength < 2 || nameLength > 64 || nameLength % 2) {
70
+ return null;
71
+ }
72
+ entries.push({
73
+ name: directory.toString('utf16le', offset, offset + nameLength - 2),
74
+ type,
75
+ left: directory.readUInt32LE(offset + 0x44),
76
+ right: directory.readUInt32LE(offset + 0x48),
77
+ child: directory.readUInt32LE(offset + 0x4c),
78
+ start: directory.readUInt32LE(offset + 0x74),
79
+ size,
80
+ });
81
+ }
82
+ return entries[0]?.type === ROOT ? entries : null;
83
+ }
84
+ /**
85
+ * The entries right below the root, walked through the sibling tree, or null for a broken
86
+ * tree or two of the same name, of which a check could read another than Office.
87
+ */
88
+ function readRootChildren(entries, root) {
89
+ const children = [];
90
+ const names = new Set();
91
+ const visited = new Set();
92
+ const pending = [root.child];
93
+ while (pending.length) {
94
+ const index = pending.pop();
95
+ if (index === NO_ENTRY) {
96
+ continue;
97
+ }
98
+ const entry = entries[index];
99
+ if (!entry || entry.type === ROOT || visited.has(index) || names.has(entry.name.toLowerCase())) {
100
+ return null;
101
+ }
102
+ visited.add(index);
103
+ names.add(entry.name.toLowerCase());
104
+ children.push(entry);
105
+ pending.push(entry.left, entry.right);
106
+ }
107
+ return children;
108
+ }
109
+ export function readOleDirectory(content) {
110
+ if (content.length < HEADER_SIZE || !content.subarray(0, SIGNATURE.length).equals(SIGNATURE) ||
111
+ content.readUInt16LE(0x1c) !== BYTE_ORDER || content.readUInt16LE(0x20) !== MINI_SECTOR_SHIFT ||
112
+ content.readUInt32LE(0x38) !== MINI_STREAM_CUTOFF) {
113
+ return null;
114
+ }
115
+ const version = content.readUInt16LE(0x1a);
116
+ const sectorShift = content.readUInt16LE(0x1e);
117
+ if (!(version === 3 && sectorShift === 9) && !(version === 4 && sectorShift === 12)) {
118
+ return null;
119
+ }
120
+ const sectorSize = 1 << sectorShift;
121
+ // Version 4 pads the header to a full sector; sector 0 follows it.
122
+ const sectorCount = Math.ceil((content.length - sectorSize) / sectorSize);
123
+ if (sectorCount <= 0 || sectorCount > MAX_OLE_SECTORS) {
124
+ return null;
125
+ }
126
+ const sector = (index) => content.subarray((index + 1) * sectorSize, (index + 2) * sectorSize);
127
+ // The FAT's sectors are listed in the header, then in the DIFAT chain.
128
+ const fatSectorCount = content.readUInt32LE(0x2c);
129
+ const difatSectorCount = content.readUInt32LE(0x48);
130
+ if (fatSectorCount > sectorCount || difatSectorCount > sectorCount) {
131
+ return null;
132
+ }
133
+ const fatSectors = [];
134
+ for (let index = 0; index < Math.min(fatSectorCount, HEADER_FAT_SECTORS); index++) {
135
+ fatSectors.push(content.readUInt32LE(0x4c + index * 4));
136
+ }
137
+ // Writers end the DIFAT chain with either marker, so its length comes from the header.
138
+ const difatSeen = new Set();
139
+ let difat = content.readUInt32LE(0x44);
140
+ for (let count = 0; count < difatSectorCount; count++) {
141
+ if (difat >= sectorCount || difatSeen.has(difat)) {
142
+ return null;
143
+ }
144
+ difatSeen.add(difat);
145
+ const entries = sector(difat);
146
+ for (let index = 0; index < sectorSize / 4 - 1 && fatSectors.length < fatSectorCount; index++) {
147
+ fatSectors.push(entries.readUInt32LE(index * 4));
148
+ }
149
+ difat = entries.readUInt32LE(sectorSize - 4);
150
+ }
151
+ if (fatSectors.length !== fatSectorCount || fatSectors.some((index) => index >= sectorCount)) {
152
+ return null;
153
+ }
154
+ const fat = Buffer.concat(fatSectors.map(sector));
155
+ const readChain = chainReader(fat, sector, sectorCount, sectorSize);
156
+ const directory = readEntries(readChain(content.readUInt32LE(0x30)), version);
157
+ const root = directory?.[0];
158
+ if (!directory || !root) {
159
+ return null;
160
+ }
161
+ const children = readRootChildren(directory, root);
162
+ if (!children) {
163
+ return null;
164
+ }
165
+ // Streams under the cutoff live in 64-byte sectors of the root's mini stream.
166
+ let readMiniChain;
167
+ const readSmallStream = (start, size) => {
168
+ if (!readMiniChain) {
169
+ const miniFatSectors = content.readUInt32LE(0x40);
170
+ const miniFat = miniFatSectors <= sectorCount ? readChain(content.readUInt32LE(0x3c), miniFatSectors * sectorSize) : null;
171
+ const miniStream = readChain(root.start, root.size);
172
+ readMiniChain = miniFat && miniStream ?
173
+ chainReader(miniFat, (index) => miniStream.subarray(index * MINI_SECTOR_SIZE, (index + 1) * MINI_SECTOR_SIZE), Math.ceil(miniStream.length / MINI_SECTOR_SIZE), MINI_SECTOR_SIZE) :
174
+ () => null;
175
+ }
176
+ return readMiniChain(start, size);
177
+ };
178
+ return {
179
+ names: directory.flatMap((entry) => (entry ? [entry.name] : [])),
180
+ readStream(name) {
181
+ const entry = children.find((child) => child.type === STREAM && child.name.toLowerCase() === name.toLowerCase());
182
+ if (!entry) {
183
+ return null;
184
+ }
185
+ if (entry.size === 0) {
186
+ return Buffer.alloc(0);
187
+ }
188
+ return entry.size < MINI_STREAM_CUTOFF ? readSmallStream(entry.start, entry.size) : readChain(entry.start, entry.size);
189
+ },
190
+ };
191
+ }
192
+ //# sourceMappingURL=ole-streams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ole-streams.js","sourceRoot":"","sources":["../src/ole-streams.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** The verdict on a PDF: encrypted behind a password, holding active content, unreadable or plain. */
2
+ export declare function inspectPdf(content: Buffer): 'valid' | 'macros' | 'encrypted' | 'mismatch';
3
+ //# sourceMappingURL=pdf-active-content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-active-content.d.ts","sourceRoot":"","sources":["../src/pdf-active-content.ts"],"names":[],"mappings":"AA2HA,sGAAsG;AACtG,wBAAgB,UAAU,CAAE,OAAO,EAAE,MAAM,GAAI,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,CAa3F"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Finds what a PDF reader runs, submits or opens on its own: JavaScript, launch, form submit and
3
+ * import actions, links to other files, multimedia, XFA forms and embedded files. Their names are
4
+ * searched in the raw file and in every Flate-compressed object stream, where a writer may pack the
5
+ * dictionaries that hold them.
6
+ */
7
+ import { inflateSync } from 'node:zlib';
8
+ import { findEnclosingDictionary, findNames, hasEncryptName, isEncryptedPdf } from './pdf-encryption.js';
9
+ // A name alone also turns up in strings, such as a link to `.../Web/JavaScript/`, so it only counts where
10
+ // a reader takes it for one: an action type after `/S`, a subtype or type after its key, or a key before
11
+ // its value. Only whitespace and comments may come between. Short keys such as `/JS` or `/EF` would turn
12
+ // up by chance in megabytes of compressed data, and need one of these names to do anything.
13
+ const SEPARATOR = '(?:[\\0\\t\\n\\f\\r ]|%[^\\r\\n]*[\\r\\n])*';
14
+ const ACTIVE_NAME = new RegExp([
15
+ `${nameSource(['S'])}${SEPARATOR}${nameSource(['JavaScript', 'Launch', 'SubmitForm', 'ImportData', 'GoToR', 'GoToE', 'Rendition'])}`,
16
+ `${nameSource(['Subtype', 'Type'])}${SEPARATOR}${nameSource(['RichMedia', 'FileAttachment', 'EmbeddedFile'])}`,
17
+ `${nameSource(['JavaScript', 'XFA', 'EmbeddedFiles'])}${SEPARATOR}[0-9<[(]`,
18
+ ].join('|'));
19
+ // Readers need `/First` to find the objects in an object stream; outline items have the key too.
20
+ const FIRST_NAME = new RegExp(nameSource(['First']), 'g');
21
+ /** Bounds far above real files that keep a crafted one cheap; past them the PDF counts as unreadable. */
22
+ const MAX_FIRST_NAMES = 50_000;
23
+ const MAX_PARSED_BYTES = 8 * 1024 * 1024;
24
+ const MAX_INFLATED_BYTES = 64 * 1024 * 1024;
25
+ const STREAM_KEYWORD = 'stream';
26
+ const CR = 0x0d;
27
+ const LF = 0x0a;
28
+ const PDF_WHITESPACE = new Set([0x00, 0x09, LF, 0x0c, CR, 0x20]);
29
+ /** One of `names` up to its delimiter, with any byte spelled as a `#xx` escape (`/Java#53cript` too). */
30
+ function nameSource(names) {
31
+ const spelled = names.map((name) => Array.from(name, (character) => {
32
+ const [high, low] = character.charCodeAt(0).toString(16);
33
+ return `(?:${character}|#${high}[${low.toLowerCase()}${low.toUpperCase()}])`;
34
+ }).join(''));
35
+ return `/(?:${spelled.join('|')})(?![^\\0\\t\\n\\f\\r ()<>[\\]{}/%])`;
36
+ }
37
+ // Whole, not in windows as `/Encrypt` is searched: whitespace and comments between the names have no bound.
38
+ function hasActiveName(content) {
39
+ return ACTIVE_NAME.test(content.toString('latin1'));
40
+ }
41
+ /** Whether a stream's only filter is Flate without a predictor, null when it has none at all. */
42
+ function isPlainFlate(dictionary) {
43
+ const filter = dictionary.entries.get('Filter');
44
+ if (!filter) {
45
+ return null;
46
+ }
47
+ const names = filter.kind === 'array' ? filter.items : [filter];
48
+ if (names.length !== 1 || names[0].kind !== 'name' || names[0].value !== 'FlateDecode') {
49
+ return false;
50
+ }
51
+ const parameters = dictionary.entries.get('DecodeParms');
52
+ const single = parameters?.kind === 'array' && parameters.items.length === 1 ? parameters.items[0] : parameters;
53
+ if (!single || single.kind === 'null') {
54
+ return true;
55
+ }
56
+ const predictor = single.kind === 'dict' ? single.entries.get('Predictor') : undefined;
57
+ return single.kind === 'dict' && (!predictor || (predictor.kind === 'number' && predictor.value === 1));
58
+ }
59
+ /** Where a stream's data starts when `stream` follows the dictionary ending at `end`, -1 otherwise. */
60
+ function findStreamData(content, end) {
61
+ let position = end;
62
+ while (PDF_WHITESPACE.has(content[position])) {
63
+ position++;
64
+ }
65
+ if (content.toString('latin1', position, position + STREAM_KEYWORD.length) !== STREAM_KEYWORD) {
66
+ return -1;
67
+ }
68
+ position += STREAM_KEYWORD.length;
69
+ if (content[position] === CR) {
70
+ position++;
71
+ }
72
+ return content[position] === LF ? position + 1 : position;
73
+ }
74
+ /**
75
+ * Whether an object stream holds an active name, or undefined when one does not inflate, uses another
76
+ * filter or there are more than the bounds allow. Readers find object streams through the xref, which
77
+ * this check does not parse, so every dictionary with `/First` that a stream follows counts as one.
78
+ */
79
+ function objectStreamsHaveActiveName(content) {
80
+ const positions = findNames(content, FIRST_NAME, MAX_FIRST_NAMES);
81
+ if (positions.length > MAX_FIRST_NAMES) {
82
+ return undefined;
83
+ }
84
+ const budget = { bytes: MAX_PARSED_BYTES };
85
+ let inflated = 0;
86
+ for (const position of positions) {
87
+ const found = findEnclosingDictionary(content, 'First', position, budget);
88
+ if (!found) {
89
+ return undefined;
90
+ }
91
+ const dataStart = findStreamData(content, found.end);
92
+ const flate = dataStart < 0 ? null : isPlainFlate(found.dictionary);
93
+ if (flate === null) {
94
+ // An outline item, or a stream left uncompressed, which the raw search already covered.
95
+ continue;
96
+ }
97
+ if (!flate) {
98
+ return undefined;
99
+ }
100
+ let data;
101
+ try {
102
+ // zlib stops at the end of the compressed data and ignores what follows.
103
+ data = inflateSync(content.subarray(dataStart), { maxOutputLength: MAX_INFLATED_BYTES - inflated });
104
+ }
105
+ catch {
106
+ return undefined;
107
+ }
108
+ inflated += data.length;
109
+ if (hasActiveName(data)) {
110
+ return true;
111
+ }
112
+ }
113
+ return false;
114
+ }
115
+ /** The verdict on a PDF: encrypted behind a password, holding active content, unreadable or plain. */
116
+ export function inspectPdf(content) {
117
+ if (isEncryptedPdf(content)) {
118
+ return 'encrypted';
119
+ }
120
+ if (hasActiveName(content)) {
121
+ return 'macros';
122
+ }
123
+ const active = objectStreamsHaveActiveName(content);
124
+ if (active === undefined) {
125
+ // Object streams of a PDF that opens without a password are encrypted all the same.
126
+ return hasEncryptName(content) ? 'encrypted' : 'mismatch';
127
+ }
128
+ return active ? 'macros' : 'valid';
129
+ }
130
+ //# sourceMappingURL=pdf-active-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-active-content.js","sourceRoot":"","sources":["../src/pdf-active-content.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ export declare const MAX_R6_ROUNDS = 288;
2
+ /** Where each match of a global `pattern` starts, in file order and up to one past `limit`. */
3
+ export declare function findNames(content: Buffer, pattern: RegExp, limit: number): number[];
4
+ export declare function hasEncryptName(content: Buffer): boolean;
5
+ type PdfValue = {
6
+ kind: 'number';
7
+ value: number;
8
+ } | {
9
+ kind: 'boolean';
10
+ value: boolean;
11
+ } | {
12
+ kind: 'null';
13
+ } | {
14
+ kind: 'name';
15
+ value: string;
16
+ } | {
17
+ kind: 'string';
18
+ value: Buffer;
19
+ } | {
20
+ kind: 'ref';
21
+ object: number;
22
+ generation: number;
23
+ } | {
24
+ kind: 'array';
25
+ items: PdfValue[];
26
+ } | PdfDictionary;
27
+ export type PdfDictionary = {
28
+ kind: 'dict';
29
+ entries: Map<string, PdfValue>;
30
+ /** Where each key's `/` sits, to tell which dictionary an `/Encrypt` name belongs to. */
31
+ keyOffsets: Map<string, number>;
32
+ };
33
+ /** Reads the few PDF objects this check needs within a fixed window; anything odd throws `Unreadable`. */
34
+ export declare class PdfReader {
35
+ position: number;
36
+ private readonly content;
37
+ private readonly end;
38
+ constructor(content: Buffer, start: number);
39
+ /** The value at the current position, or `undefined` when it cannot be read. */
40
+ tryRead(): PdfValue | undefined;
41
+ private peek;
42
+ private skipSpace;
43
+ private readRegular;
44
+ private readValue;
45
+ /** `N G R` once the object number `N` was read; leaves the position alone otherwise. */
46
+ private readReference;
47
+ private readName;
48
+ private readDictionary;
49
+ private readHexString;
50
+ private readLiteralString;
51
+ private skipLineFeed;
52
+ private readEscape;
53
+ }
54
+ /**
55
+ * The innermost dictionary whose own `key` starts at `keyOffset`, trying each earlier `<<`, and where it ends.
56
+ */
57
+ export declare function findEnclosingDictionary(content: Buffer, key: string, keyOffset: number, budget: {
58
+ bytes: number;
59
+ }): {
60
+ dictionary: PdfDictionary;
61
+ end: number;
62
+ } | undefined;
63
+ /** Algorithm 2.B for a user password (no `U` vector), with the number of rounds it took. */
64
+ export declare function hashR6(password: Buffer, salt: Buffer): {
65
+ hash: Buffer;
66
+ rounds: number;
67
+ };
68
+ export declare function isEncryptedPdf(content: Buffer): boolean;
69
+ export {};
70
+ //# sourceMappingURL=pdf-encryption.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-encryption.d.ts","sourceRoot":"","sources":["../src/pdf-encryption.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,aAAa,MAAM,CAAC;AAiCjC,+FAA+F;AAC/F,wBAAgB,SAAS,CAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAI,MAAM,EAAE,CAWrF;AAED,wBAAgB,cAAc,CAAE,OAAO,EAAE,MAAM,GAAI,OAAO,CAEzD;AAED,KAAK,QAAQ,GACV;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,QAAQ,EAAE,CAAA;CAAE,GACpC,aAAa,CAAC;AAEjB,MAAM,MAAM,aAAa,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC/B,yFAAyF;IACzF,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC,CAAC;AAIF,0GAA0G;AAC1G,qBAAa,SAAS;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEhB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAM3C,gFAAgF;IAChF,OAAO,IAAI,QAAQ,GAAG,SAAS;IAW/B,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,SAAS;IAejB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,SAAS;IAsCjB,wFAAwF;IACxF,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,QAAQ;IAgBhB,OAAO,CAAC,cAAc;IAuBtB,OAAO,CAAC,aAAa;IAsBrB,OAAO,CAAC,iBAAiB;IAuBzB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,UAAU;CAqBlB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAI;IAAE,UAAU,EAAE,aAAa,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAgB5K;AAwND,4FAA4F;AAC5F,wBAAgB,MAAM,CAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAkBzF;AAwDD,wBAAgB,cAAc,CAAE,OAAO,EAAE,MAAM,GAAI,OAAO,CAKzD"}