@patrickdk77/pdf-defuse 0.0.0-stage → 0.1.2
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.
- package/LICENSE +21 -0
- package/README.md +323 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +390 -0
- package/dist/crypto.d.ts +51 -0
- package/dist/crypto.js +404 -0
- package/dist/data-plugins.d.ts +28 -0
- package/dist/data-plugins.js +525 -0
- package/dist/document.d.ts +191 -0
- package/dist/document.js +1403 -0
- package/dist/engine.d.ts +16 -0
- package/dist/engine.js +392 -0
- package/dist/filters.d.ts +27 -0
- package/dist/filters.js +616 -0
- package/dist/findings.d.ts +24 -0
- package/dist/findings.js +183 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +42 -0
- package/dist/index.mjs +3 -0
- package/dist/io.d.ts +81 -0
- package/dist/io.js +414 -0
- package/dist/objects.d.ts +52 -0
- package/dist/objects.js +178 -0
- package/dist/parser.d.ts +70 -0
- package/dist/parser.js +534 -0
- package/dist/plugins.d.ts +13 -0
- package/dist/plugins.js +56 -0
- package/dist/score.d.ts +17 -0
- package/dist/score.js +63 -0
- package/dist/sniff.d.ts +15 -0
- package/dist/sniff.js +118 -0
- package/dist/types.d.ts +294 -0
- package/dist/types.js +143 -0
- package/dist/uri.d.ts +29 -0
- package/dist/uri.js +150 -0
- package/dist/walker.d.ts +302 -0
- package/dist/walker.js +2914 -0
- package/dist/writer.d.ts +6 -0
- package/dist/writer.js +112 -0
- package/package.json +86 -4
package/dist/filters.js
ADDED
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.hexVal = exports.Out = exports.UnsupportedFilterError = exports.DecompressionLimitError = void 0;
|
|
37
|
+
exports.filtersOf = filtersOf;
|
|
38
|
+
exports.decodeChunks = decodeChunks;
|
|
39
|
+
const stream = __importStar(require("node:stream"));
|
|
40
|
+
const zlib = __importStar(require("node:zlib"));
|
|
41
|
+
const objects_1 = require("./objects");
|
|
42
|
+
const writer_1 = require("./writer");
|
|
43
|
+
class DecompressionLimitError extends Error {
|
|
44
|
+
}
|
|
45
|
+
exports.DecompressionLimitError = DecompressionLimitError;
|
|
46
|
+
class UnsupportedFilterError extends Error {
|
|
47
|
+
}
|
|
48
|
+
exports.UnsupportedFilterError = UnsupportedFilterError;
|
|
49
|
+
/**
|
|
50
|
+
* Lists a stream's filters and their parameter dictionaries. A filter that is not a name, such as a reference left
|
|
51
|
+
* unresolved, gets an empty name that no decoder accepts: its bytes are still encoded, not plain.
|
|
52
|
+
*/
|
|
53
|
+
function filtersOf(dict) {
|
|
54
|
+
const f = dict.get('Filter') ?? null;
|
|
55
|
+
const p = dict.get('DecodeParms');
|
|
56
|
+
const names = (Array.isArray(f) ? f : f === null ? [] : [f]).map(x => (x instanceof objects_1.PdfName ? x.name : ''));
|
|
57
|
+
const parms = Array.isArray(p) ? p : [p];
|
|
58
|
+
return names.map((name, i) => ({ name, parms: parms[i] instanceof objects_1.PdfDict ? parms[i] : undefined }));
|
|
59
|
+
}
|
|
60
|
+
/** Growable byte buffer: one byte of memory per byte, where a JS number array costs a slot each. */
|
|
61
|
+
class Out {
|
|
62
|
+
buf;
|
|
63
|
+
len = 0;
|
|
64
|
+
constructor(size = 4096) {
|
|
65
|
+
this.buf = new Uint8Array(size);
|
|
66
|
+
}
|
|
67
|
+
push(b) {
|
|
68
|
+
if (this.len === this.buf.length) {
|
|
69
|
+
const n = new Uint8Array(this.buf.length * 2);
|
|
70
|
+
n.set(this.buf);
|
|
71
|
+
this.buf = n;
|
|
72
|
+
}
|
|
73
|
+
this.buf[this.len++] = b;
|
|
74
|
+
}
|
|
75
|
+
take() {
|
|
76
|
+
const r = this.buf.slice(0, this.len);
|
|
77
|
+
this.len = 0;
|
|
78
|
+
return r;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.Out = Out;
|
|
82
|
+
const hexVal = (c) => (c >= 0x30 && c <= 0x39 ? c - 0x30 : c >= 0x41 && c <= 0x46 ? c - 55 : c >= 0x61 && c <= 0x66 ? c - 87 : -1);
|
|
83
|
+
exports.hexVal = hexVal;
|
|
84
|
+
class AsciiHexDecoder {
|
|
85
|
+
hi = -1;
|
|
86
|
+
done = false;
|
|
87
|
+
update(chunk) {
|
|
88
|
+
const out = new Out(chunk.length / 2 + 1);
|
|
89
|
+
for (const c of chunk) {
|
|
90
|
+
if (this.done)
|
|
91
|
+
break;
|
|
92
|
+
if (c === 0x3e) {
|
|
93
|
+
this.done = true;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
const v = (0, exports.hexVal)(c);
|
|
97
|
+
if (v < 0)
|
|
98
|
+
continue;
|
|
99
|
+
if (this.hi < 0)
|
|
100
|
+
this.hi = v;
|
|
101
|
+
else {
|
|
102
|
+
out.push((this.hi << 4) | v);
|
|
103
|
+
this.hi = -1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return out.take();
|
|
107
|
+
}
|
|
108
|
+
final() {
|
|
109
|
+
return this.hi >= 0 ? Uint8Array.of(this.hi << 4) : new Uint8Array(0);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
class Ascii85Decoder {
|
|
113
|
+
group = [];
|
|
114
|
+
started = false;
|
|
115
|
+
done = false;
|
|
116
|
+
prev = -1;
|
|
117
|
+
update(chunk) {
|
|
118
|
+
const out = new Out(chunk.length);
|
|
119
|
+
for (const c of chunk) {
|
|
120
|
+
if (this.done)
|
|
121
|
+
break;
|
|
122
|
+
if (!this.started) {
|
|
123
|
+
this.started = true;
|
|
124
|
+
if (c === 0x3c) {
|
|
125
|
+
this.prev = c;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (this.prev === 0x3c) {
|
|
130
|
+
this.prev = -1;
|
|
131
|
+
if (c === 0x7e)
|
|
132
|
+
continue;
|
|
133
|
+
// '<~' is an optional prefix. A '<' without '~' after it is the digit 27, as viewers read it.
|
|
134
|
+
this.group.push(0x3c - 33);
|
|
135
|
+
}
|
|
136
|
+
if (c === 0x7e) {
|
|
137
|
+
this.done = true;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
if (c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d)
|
|
141
|
+
continue;
|
|
142
|
+
if (c === 0x7a && this.group.length === 0) {
|
|
143
|
+
out.push(0);
|
|
144
|
+
out.push(0);
|
|
145
|
+
out.push(0);
|
|
146
|
+
out.push(0);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
// pdf.js reads any other byte as a digit, NUL and form feed included, so skipping it would decode bytes pdf.js
|
|
150
|
+
// does not see. qpdf fails on most of them.
|
|
151
|
+
if (c < 0x21 || c > 0x75)
|
|
152
|
+
throw new Error(`Byte 0x${c.toString(16)} in ASCII85 data`);
|
|
153
|
+
this.group.push(c - 33);
|
|
154
|
+
if (this.group.length === 5) {
|
|
155
|
+
let v = 0;
|
|
156
|
+
for (const g of this.group)
|
|
157
|
+
v = v * 85 + g;
|
|
158
|
+
out.push((v >>> 24) & 0xff);
|
|
159
|
+
out.push((v >>> 16) & 0xff);
|
|
160
|
+
out.push((v >>> 8) & 0xff);
|
|
161
|
+
out.push(v & 0xff);
|
|
162
|
+
this.group = [];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return out.take();
|
|
166
|
+
}
|
|
167
|
+
final() {
|
|
168
|
+
if (this.group.length <= 1)
|
|
169
|
+
return new Uint8Array(0);
|
|
170
|
+
const n = this.group.length;
|
|
171
|
+
const g = [...this.group];
|
|
172
|
+
while (g.length < 5)
|
|
173
|
+
g.push(84);
|
|
174
|
+
let v = 0;
|
|
175
|
+
for (const x of g)
|
|
176
|
+
v = v * 85 + x;
|
|
177
|
+
return Uint8Array.from([(v >>> 24) & 0xff, (v >>> 16) & 0xff, (v >>> 8) & 0xff, v & 0xff].slice(0, n - 1));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
class RunLengthDecoder {
|
|
181
|
+
pending = [];
|
|
182
|
+
done = false;
|
|
183
|
+
update(chunk) {
|
|
184
|
+
const out = new Out(chunk.length * 2);
|
|
185
|
+
const data = this.pending.length ? Uint8Array.from([...this.pending, ...chunk]) : chunk;
|
|
186
|
+
this.pending = [];
|
|
187
|
+
let i = 0;
|
|
188
|
+
while (i < data.length && !this.done) {
|
|
189
|
+
const n = data[i];
|
|
190
|
+
if (n === 128) {
|
|
191
|
+
this.done = true;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
if (n < 128) {
|
|
195
|
+
if (i + 1 + n + 1 > data.length)
|
|
196
|
+
break;
|
|
197
|
+
for (let j = 0; j <= n; j++)
|
|
198
|
+
out.push(data[i + 1 + j]);
|
|
199
|
+
i += n + 2;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
if (i + 1 >= data.length)
|
|
203
|
+
break;
|
|
204
|
+
for (let j = 0; j < 257 - n; j++)
|
|
205
|
+
out.push(data[i + 1]);
|
|
206
|
+
i += 2;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (!this.done)
|
|
210
|
+
for (; i < data.length; i++)
|
|
211
|
+
this.pending.push(data[i]);
|
|
212
|
+
return out.take();
|
|
213
|
+
}
|
|
214
|
+
final() {
|
|
215
|
+
// A literal run the data cuts short keeps the bytes it has, as qpdf and pdf.js keep them.
|
|
216
|
+
const p = this.pending;
|
|
217
|
+
return p.length > 1 && p[0] < 128 ? Uint8Array.from(p.slice(1)) : new Uint8Array(0);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** LZW with fixed-size code tables: memory does not grow with the output. */
|
|
221
|
+
class LzwDecoder {
|
|
222
|
+
earlyChange;
|
|
223
|
+
prefix = new Int32Array(4096);
|
|
224
|
+
suffix = new Uint8Array(4096);
|
|
225
|
+
length = new Uint16Array(4096);
|
|
226
|
+
first = new Uint8Array(4096);
|
|
227
|
+
next = 258;
|
|
228
|
+
codeLen = 9;
|
|
229
|
+
bitBuf = 0;
|
|
230
|
+
bitCount = 0;
|
|
231
|
+
prev = -1;
|
|
232
|
+
done = false;
|
|
233
|
+
scratch = new Uint8Array(4096);
|
|
234
|
+
constructor(earlyChange) {
|
|
235
|
+
this.earlyChange = earlyChange;
|
|
236
|
+
for (let i = 0; i < 256; i++) {
|
|
237
|
+
this.prefix[i] = -1;
|
|
238
|
+
this.suffix[i] = i;
|
|
239
|
+
this.length[i] = 1;
|
|
240
|
+
this.first[i] = i;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
emit(code, out) {
|
|
244
|
+
const n = this.length[code];
|
|
245
|
+
let c = code;
|
|
246
|
+
for (let k = n - 1; k >= 0; k--) {
|
|
247
|
+
this.scratch[k] = this.suffix[c];
|
|
248
|
+
c = this.prefix[c];
|
|
249
|
+
}
|
|
250
|
+
for (let k = 0; k < n; k++)
|
|
251
|
+
out.push(this.scratch[k]);
|
|
252
|
+
}
|
|
253
|
+
update(chunk) {
|
|
254
|
+
const out = new Out(chunk.length * 4);
|
|
255
|
+
for (let i = 0; i < chunk.length && !this.done; i++) {
|
|
256
|
+
this.bitBuf = ((this.bitBuf << 8) | chunk[i]) >>> 0;
|
|
257
|
+
this.bitCount += 8;
|
|
258
|
+
while (this.bitCount >= this.codeLen && !this.done) {
|
|
259
|
+
const code = (this.bitBuf >>> (this.bitCount - this.codeLen)) & ((1 << this.codeLen) - 1);
|
|
260
|
+
this.bitCount -= this.codeLen;
|
|
261
|
+
this.bitBuf &= (1 << this.bitCount) - 1;
|
|
262
|
+
if (code === 256) {
|
|
263
|
+
this.next = 258;
|
|
264
|
+
this.codeLen = 9;
|
|
265
|
+
this.prev = -1;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (code === 257) {
|
|
269
|
+
this.done = true;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
if (this.prev < 0) {
|
|
273
|
+
if (code > 255) {
|
|
274
|
+
this.done = true;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
this.emit(code, out);
|
|
278
|
+
this.prev = code;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
let firstByte;
|
|
282
|
+
if (code < this.next) {
|
|
283
|
+
this.emit(code, out);
|
|
284
|
+
firstByte = this.first[code];
|
|
285
|
+
}
|
|
286
|
+
else if (code === this.next) {
|
|
287
|
+
firstByte = this.first[this.prev];
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
this.done = true;
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
if (this.next < 4096) {
|
|
294
|
+
const n = this.next++;
|
|
295
|
+
this.prefix[n] = this.prev;
|
|
296
|
+
this.suffix[n] = firstByte;
|
|
297
|
+
this.length[n] = this.length[this.prev] + 1;
|
|
298
|
+
this.first[n] = this.first[this.prev];
|
|
299
|
+
if (code === n)
|
|
300
|
+
this.emit(n, out);
|
|
301
|
+
}
|
|
302
|
+
this.prev = code;
|
|
303
|
+
const t = this.next + this.earlyChange;
|
|
304
|
+
this.codeLen = t >= 2048 ? 12 : t >= 1024 ? 11 : t >= 512 ? 10 : 9;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return out.take();
|
|
308
|
+
}
|
|
309
|
+
final() {
|
|
310
|
+
return new Uint8Array(0);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/** The longest predictor row accepted. A row is held whole, and real rows are far shorter. */
|
|
314
|
+
const MAX_ROW = 1 << 24;
|
|
315
|
+
/** PNG and TIFF predictors, row by row. Memory follows the bytes received, up to two rows. */
|
|
316
|
+
class PredictorDecoder {
|
|
317
|
+
predictor;
|
|
318
|
+
colors;
|
|
319
|
+
bpc;
|
|
320
|
+
samples;
|
|
321
|
+
bpp;
|
|
322
|
+
rowLen;
|
|
323
|
+
stride;
|
|
324
|
+
/** A row that spans chunks, collected here. It grows as bytes arrive, so a large declared row costs nothing until they do. */
|
|
325
|
+
cur = new Uint8Array(0);
|
|
326
|
+
fill = 0;
|
|
327
|
+
/** The last decoded row, for the PNG predictors. */
|
|
328
|
+
prev;
|
|
329
|
+
constructor(parms) {
|
|
330
|
+
// pdf.js uses whatever value it finds here, and reads /BPC before /BitsPerComponent. A value that is not a number,
|
|
331
|
+
// or a /BPC that disagrees, would decode to other bytes there, so it is refused.
|
|
332
|
+
const num = (key, fallback) => {
|
|
333
|
+
const v = parms.get(key) ?? null;
|
|
334
|
+
if (v !== null && typeof v !== 'number')
|
|
335
|
+
throw new UnsupportedFilterError(`Bad /${key}`);
|
|
336
|
+
return v ?? fallback;
|
|
337
|
+
};
|
|
338
|
+
const predictor = num('Predictor', 1);
|
|
339
|
+
const colors = num('Colors', 1);
|
|
340
|
+
const bpc = num('BitsPerComponent', 8);
|
|
341
|
+
const columns = num('Columns', 1);
|
|
342
|
+
const rowLen = Math.ceil((colors * bpc * columns) / 8);
|
|
343
|
+
// Only the values the specification allows. Others can make an empty row, which never completes.
|
|
344
|
+
const valid = (predictor === 2 || (Number.isInteger(predictor) && predictor >= 10 && predictor <= 15)) &&
|
|
345
|
+
Number.isInteger(colors) &&
|
|
346
|
+
colors >= 1 &&
|
|
347
|
+
[1, 2, 4, 8, 16].includes(bpc) &&
|
|
348
|
+
Number.isInteger(columns) &&
|
|
349
|
+
columns >= 1 &&
|
|
350
|
+
num('BPC', bpc) === bpc;
|
|
351
|
+
if (!valid || rowLen > MAX_ROW)
|
|
352
|
+
throw new UnsupportedFilterError('Bad predictor parameters');
|
|
353
|
+
this.predictor = predictor;
|
|
354
|
+
this.colors = colors;
|
|
355
|
+
this.bpc = bpc;
|
|
356
|
+
this.samples = colors * columns;
|
|
357
|
+
this.bpp = Math.ceil((colors * bpc) / 8);
|
|
358
|
+
this.rowLen = rowLen;
|
|
359
|
+
this.stride = predictor === 2 ? rowLen : rowLen + 1;
|
|
360
|
+
}
|
|
361
|
+
update(chunk) {
|
|
362
|
+
const { rowLen, stride } = this;
|
|
363
|
+
const out = new Uint8Array(Math.floor((this.fill + chunk.length) / stride) * rowLen);
|
|
364
|
+
let r = 0;
|
|
365
|
+
for (let i = 0; i < chunk.length;) {
|
|
366
|
+
let src;
|
|
367
|
+
if (this.fill === 0 && chunk.length - i >= stride) {
|
|
368
|
+
src = chunk.subarray(i, i + stride);
|
|
369
|
+
i += stride;
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
const n = Math.min(stride - this.fill, chunk.length - i);
|
|
373
|
+
if (this.cur.length < this.fill + n) {
|
|
374
|
+
const grown = new Uint8Array(Math.min(stride, Math.max(this.fill + n, 2 * this.cur.length)));
|
|
375
|
+
grown.set(this.cur.subarray(0, this.fill));
|
|
376
|
+
this.cur = grown;
|
|
377
|
+
}
|
|
378
|
+
this.cur.set(chunk.subarray(i, i + n), this.fill);
|
|
379
|
+
this.fill += n;
|
|
380
|
+
i += n;
|
|
381
|
+
if (this.fill < stride)
|
|
382
|
+
break;
|
|
383
|
+
this.fill = 0;
|
|
384
|
+
src = this.cur;
|
|
385
|
+
}
|
|
386
|
+
this.decodeRow(src, out.subarray(r * rowLen, (r + 1) * rowLen));
|
|
387
|
+
r++;
|
|
388
|
+
}
|
|
389
|
+
return out;
|
|
390
|
+
}
|
|
391
|
+
final() {
|
|
392
|
+
// A short last row decodes as if padded with zero bytes, as qpdf does. A PNG row of only its type byte holds no data.
|
|
393
|
+
if (this.fill === 0 || (this.predictor !== 2 && this.fill < 2))
|
|
394
|
+
return new Uint8Array(0);
|
|
395
|
+
const last = new Uint8Array(this.stride);
|
|
396
|
+
last.set(this.cur.subarray(0, this.fill));
|
|
397
|
+
this.fill = 0;
|
|
398
|
+
return this.update(last);
|
|
399
|
+
}
|
|
400
|
+
/** Decodes one row of `stride` input bytes into `row`. */
|
|
401
|
+
decodeRow(src, row) {
|
|
402
|
+
const { rowLen, bpp, colors, bpc } = this;
|
|
403
|
+
if (this.predictor === 2) {
|
|
404
|
+
// TIFF: each sample is the difference from the same component of the pixel before it, modulo 2^bpc.
|
|
405
|
+
row.set(src);
|
|
406
|
+
if (bpc === 8) {
|
|
407
|
+
for (let i = colors; i < rowLen; i++)
|
|
408
|
+
row[i] = (row[i] + row[i - colors]) & 0xff;
|
|
409
|
+
}
|
|
410
|
+
else if (bpc === 16) {
|
|
411
|
+
for (let i = 2 * colors; i < rowLen; i += 2) {
|
|
412
|
+
const v = ((row[i] << 8) | row[i + 1]) + ((row[i - 2 * colors] << 8) | row[i - 2 * colors + 1]);
|
|
413
|
+
row[i] = (v >> 8) & 0xff;
|
|
414
|
+
row[i + 1] = v & 0xff;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
// Samples smaller than a byte are packed high bits first.
|
|
419
|
+
const mask = (1 << bpc) - 1;
|
|
420
|
+
for (let s = colors; s < this.samples; s++) {
|
|
421
|
+
const bit = s * bpc;
|
|
422
|
+
const sh = 8 - bpc - (bit & 7);
|
|
423
|
+
const pbit = bit - colors * bpc;
|
|
424
|
+
const v = ((row[bit >> 3] >> sh) + (row[pbit >> 3] >> (8 - bpc - (pbit & 7)))) & mask;
|
|
425
|
+
row[bit >> 3] = (row[bit >> 3] & ~(mask << sh)) | (v << sh);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
this.prev ??= new Uint8Array(rowLen);
|
|
431
|
+
const prev = this.prev;
|
|
432
|
+
const type = src[0];
|
|
433
|
+
for (let i = 0; i < rowLen; i++) {
|
|
434
|
+
const a = i >= bpp ? row[i - bpp] : 0;
|
|
435
|
+
const b = prev[i];
|
|
436
|
+
const c = i >= bpp ? prev[i - bpp] : 0;
|
|
437
|
+
let v = src[i + 1];
|
|
438
|
+
if (type === 1)
|
|
439
|
+
v += a;
|
|
440
|
+
else if (type === 2)
|
|
441
|
+
v += b;
|
|
442
|
+
else if (type === 3)
|
|
443
|
+
v += (a + b) >> 1;
|
|
444
|
+
else if (type === 4) {
|
|
445
|
+
const p = a + b - c;
|
|
446
|
+
const pa = Math.abs(p - a);
|
|
447
|
+
const pb = Math.abs(p - b);
|
|
448
|
+
const pc = Math.abs(p - c);
|
|
449
|
+
v += pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
|
|
450
|
+
}
|
|
451
|
+
row[i] = v & 0xff;
|
|
452
|
+
}
|
|
453
|
+
prev.set(row);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* The synchronous decoders for one filter, in order, or undefined when the filter is not supported. Flate's inflate
|
|
458
|
+
* step is a stream and runs before them.
|
|
459
|
+
*/
|
|
460
|
+
function syncDecoder(name, parms) {
|
|
461
|
+
let decs;
|
|
462
|
+
switch (name) {
|
|
463
|
+
case 'FlateDecode':
|
|
464
|
+
case 'Fl':
|
|
465
|
+
decs = [];
|
|
466
|
+
break;
|
|
467
|
+
case 'LZWDecode':
|
|
468
|
+
case 'LZW': {
|
|
469
|
+
// pdf.js uses /EarlyChange as given, null too, where this decoder would take the default.
|
|
470
|
+
const early = parms?.get('EarlyChange');
|
|
471
|
+
if (early !== undefined && typeof early !== 'number')
|
|
472
|
+
throw new UnsupportedFilterError('Bad /EarlyChange');
|
|
473
|
+
decs = [new LzwDecoder(early ?? 1)];
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
case 'ASCIIHexDecode':
|
|
477
|
+
case 'AHx':
|
|
478
|
+
return [new AsciiHexDecoder()];
|
|
479
|
+
case 'ASCII85Decode':
|
|
480
|
+
case 'A85':
|
|
481
|
+
return [new Ascii85Decoder()];
|
|
482
|
+
case 'RunLengthDecode':
|
|
483
|
+
case 'RL':
|
|
484
|
+
return [new RunLengthDecoder()];
|
|
485
|
+
case 'Crypt':
|
|
486
|
+
return [];
|
|
487
|
+
default:
|
|
488
|
+
return undefined;
|
|
489
|
+
}
|
|
490
|
+
// Colors, BitsPerComponent and Columns mean nothing without a predictor, so they are not even read. pdf.js applies
|
|
491
|
+
// the PNG predictor for a /Predictor that is not a number, where this decoder would apply none.
|
|
492
|
+
const predictor = parms?.get('Predictor') ?? null;
|
|
493
|
+
if (predictor !== null && typeof predictor !== 'number')
|
|
494
|
+
throw new UnsupportedFilterError('Bad /Predictor');
|
|
495
|
+
if (predictor !== null && predictor > 1) {
|
|
496
|
+
if (!parms)
|
|
497
|
+
throw new Error('A /Predictor without its parameters');
|
|
498
|
+
decs.push(new PredictorDecoder(parms));
|
|
499
|
+
}
|
|
500
|
+
return decs;
|
|
501
|
+
}
|
|
502
|
+
/** Input per call to a synchronous decoder. LZW, the most expansive, turns 4 KB into at most about 10 MB. */
|
|
503
|
+
const SLICE = 4096;
|
|
504
|
+
/**
|
|
505
|
+
* The longest filter chain decoded. Real files use a handful of filters. Each stage holds its own state, a zlib
|
|
506
|
+
* context for Flate, and a few thousand nested stages overflow the stack, so a longer chain counts as unsupported.
|
|
507
|
+
*/
|
|
508
|
+
const MAX_FILTERS = 32;
|
|
509
|
+
/**
|
|
510
|
+
* Where raw deflate data starts: after the zlib header when there is one. The Adler-32 trailer is then never
|
|
511
|
+
* checked, as viewers do not check it.
|
|
512
|
+
*/
|
|
513
|
+
function deflateStart(b) {
|
|
514
|
+
if (b.length < 2 || (b[0] & 0x0f) !== 8 || ((b[0] << 8) | b[1]) % 31 !== 0)
|
|
515
|
+
return 0;
|
|
516
|
+
// A PDF has no way to supply a preset dictionary.
|
|
517
|
+
if (b[1] & 0x20)
|
|
518
|
+
throw new Error('Flate data needs a preset dictionary');
|
|
519
|
+
return 2;
|
|
520
|
+
}
|
|
521
|
+
async function* inflateChunks(input) {
|
|
522
|
+
// Two bytes decide between a zlib header and raw deflate, and an upstream stage can yield one byte at a time.
|
|
523
|
+
const it = input[Symbol.asyncIterator]();
|
|
524
|
+
let head = new Uint8Array(0);
|
|
525
|
+
while (head.length < 2) {
|
|
526
|
+
const r = await it.next();
|
|
527
|
+
if (r.done)
|
|
528
|
+
break;
|
|
529
|
+
head = head.length ? Buffer.concat([head, r.value]) : r.value;
|
|
530
|
+
}
|
|
531
|
+
if (!head.length)
|
|
532
|
+
return;
|
|
533
|
+
const first = head.subarray(deflateStart(head));
|
|
534
|
+
async function* rest() {
|
|
535
|
+
if (first.length)
|
|
536
|
+
yield first;
|
|
537
|
+
for (;;) {
|
|
538
|
+
const r = await it.next();
|
|
539
|
+
if (r.done)
|
|
540
|
+
return;
|
|
541
|
+
yield r.value;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const inflater = zlib.createInflateRaw({ finishFlush: zlib.constants.Z_SYNC_FLUSH });
|
|
545
|
+
const src = stream.Readable.from(rest());
|
|
546
|
+
src.on('error', e => inflater.destroy(e));
|
|
547
|
+
src.pipe(inflater);
|
|
548
|
+
try {
|
|
549
|
+
// Truncated data ends quietly with everything decoded. Corrupt data throws: Node drops the output of the write
|
|
550
|
+
// that failed, so the bytes before the error are not all there.
|
|
551
|
+
for await (const c of inflater)
|
|
552
|
+
yield c;
|
|
553
|
+
}
|
|
554
|
+
finally {
|
|
555
|
+
src.destroy();
|
|
556
|
+
inflater.destroy();
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Decodes a stream chunk by chunk through its whole filter chain. No stage may produce more than `limit` bytes, and
|
|
561
|
+
* `checkTime` runs between pieces of work in every stage.
|
|
562
|
+
*/
|
|
563
|
+
async function* decodeChunks(input, dict, limit, checkTime) {
|
|
564
|
+
// Each stage counts its own output, so an expanding stage is stopped while it runs, not after the next stage has
|
|
565
|
+
// taken everything it produced.
|
|
566
|
+
async function* counted(stage) {
|
|
567
|
+
let total = 0;
|
|
568
|
+
for await (const c of stage) {
|
|
569
|
+
total += c.length;
|
|
570
|
+
if (limit !== undefined && total > limit)
|
|
571
|
+
throw new DecompressionLimitError(`Decoded stream exceeds ${limit} bytes`);
|
|
572
|
+
checkTime?.();
|
|
573
|
+
yield c;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
let chain = input;
|
|
577
|
+
const filters = filtersOf(dict);
|
|
578
|
+
if (filters.length > MAX_FILTERS)
|
|
579
|
+
throw new UnsupportedFilterError(`${filters.length} filters in one chain`);
|
|
580
|
+
// pdf.js reads /F before /Filter and /DP before /DecodeParms, which qpdf ignores. pdf.js also ignores parameters
|
|
581
|
+
// shaped unlike the filters, which qpdf applies to the first filter. Readers that disagree on the chain see
|
|
582
|
+
// different bytes, so such a stream is not decoded.
|
|
583
|
+
const f = dict.get('Filter') ?? null;
|
|
584
|
+
const p = dict.get('DecodeParms') ?? null;
|
|
585
|
+
const shortF = dict.get('F');
|
|
586
|
+
const shortDp = dict.get('DP');
|
|
587
|
+
if ((shortF !== undefined && (0, writer_1.serialize)(shortF) !== (0, writer_1.serialize)(f)) ||
|
|
588
|
+
(shortDp !== undefined && (0, writer_1.serialize)(shortDp) !== (0, writer_1.serialize)(p)) ||
|
|
589
|
+
(Array.isArray(f) ? p instanceof objects_1.PdfDict : Array.isArray(p) && p[0] instanceof objects_1.PdfDict)) {
|
|
590
|
+
throw new UnsupportedFilterError('Filter entries that readers take in different ways');
|
|
591
|
+
}
|
|
592
|
+
for (const { name, parms } of filters) {
|
|
593
|
+
const decs = syncDecoder(name, parms);
|
|
594
|
+
if (!decs)
|
|
595
|
+
throw new UnsupportedFilterError(`Unsupported filter ${name}`);
|
|
596
|
+
if (name === 'FlateDecode' || name === 'Fl')
|
|
597
|
+
chain = counted(inflateChunks(chain));
|
|
598
|
+
for (const d of decs) {
|
|
599
|
+
const upstream = chain;
|
|
600
|
+
chain = counted((async function* () {
|
|
601
|
+
for await (const c of upstream) {
|
|
602
|
+
for (let i = 0; i < c.length; i += SLICE) {
|
|
603
|
+
const o = d.update(c.subarray(i, i + SLICE));
|
|
604
|
+
if (o.length)
|
|
605
|
+
yield o;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const tail = d.final();
|
|
609
|
+
if (tail.length)
|
|
610
|
+
yield tail;
|
|
611
|
+
})());
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
// An unfiltered stream's bytes are its decoded bytes.
|
|
615
|
+
yield* chain === input ? counted(input) : chain;
|
|
616
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PdfCategory as C, PdfDetail as D, type PdfActionOverride, type PdfFinding, type PdfFindingAction } from './types';
|
|
2
|
+
interface Spec {
|
|
3
|
+
action: PdfFindingAction;
|
|
4
|
+
/**
|
|
5
|
+
* May contain <key> placeholders filled from data, and <first|second> choices, which take the first alternative
|
|
6
|
+
* whose {key} values are all set and not empty, or that names none. A filled value has control and format
|
|
7
|
+
* characters replaced by "?", and one in a choice, such as a file name from the PDF, is cut to 60 characters.
|
|
8
|
+
*/
|
|
9
|
+
description: string;
|
|
10
|
+
weight: number;
|
|
11
|
+
}
|
|
12
|
+
/** The built-in spec for a kind of finding, or undefined for a kind pdf-defuse does not make, such as one from another package. */
|
|
13
|
+
export declare function findingSpec(category: string, detail: string): Spec | undefined;
|
|
14
|
+
export declare function allFindingSpecs(): Array<{
|
|
15
|
+
category: C;
|
|
16
|
+
detail: D;
|
|
17
|
+
} & Spec>;
|
|
18
|
+
export declare class FindingFactory {
|
|
19
|
+
private readonly overrides;
|
|
20
|
+
constructor(overrides?: PdfActionOverride[]);
|
|
21
|
+
actionFor(category: C, detail: D): PdfFindingAction;
|
|
22
|
+
make(category: C, detail: D, location?: string, data?: Record<string, string | number>): PdfFinding;
|
|
23
|
+
}
|
|
24
|
+
export {};
|