@patrickdk77/pdf-defuse 0.1.2 → 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.
- package/README.md +35 -11
- package/dist/crypto.d.ts +6 -2
- package/dist/crypto.js +47 -15
- package/dist/document.d.ts +207 -13
- package/dist/document.js +1571 -255
- package/dist/engine.js +12 -1
- package/dist/filters.js +59 -8
- package/dist/findings.js +3 -0
- package/dist/io.d.ts +1 -1
- package/dist/io.js +32 -9
- package/dist/parser.d.ts +92 -3
- package/dist/parser.js +188 -51
- package/dist/saslprep.d.ts +11 -0
- package/dist/saslprep.js +178 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.js +3 -0
- package/dist/walker.d.ts +2 -0
- package/dist/walker.js +34 -8
- package/package.json +3 -3
package/dist/engine.js
CHANGED
|
@@ -39,7 +39,8 @@ function encryptionFindings(doc, factory) {
|
|
|
39
39
|
return [];
|
|
40
40
|
const out = [];
|
|
41
41
|
if (r.status === 'ok') {
|
|
42
|
-
|
|
42
|
+
const detail = { empty: types_1.PdfDetail.EmptyPassword, user: types_1.PdfDetail.UserPassword, owner: types_1.PdfDetail.OwnerPassword, none: types_1.PdfDetail.AttachmentsOnly }[r.password];
|
|
43
|
+
out.push(factory.make(types_1.PdfCategory.Encrypted, detail));
|
|
43
44
|
const bits = r.handler.keyBits;
|
|
44
45
|
const method = r.handler.streamMethod === 'Identity' ? r.handler.stringMethod : r.handler.streamMethod;
|
|
45
46
|
if (method === 'AES256')
|
|
@@ -135,6 +136,8 @@ async function analyzeInner(run, source, options, depth, deadline, verify) {
|
|
|
135
136
|
findings.push(...encryptionFindings(doc, factory));
|
|
136
137
|
if (doc.securityResult && doc.securityResult.status !== 'ok')
|
|
137
138
|
return finish(findings, options, true, doc.headerVersion);
|
|
139
|
+
if (doc.missingHeader)
|
|
140
|
+
findings.push(factory.make(types_1.PdfCategory.Corrupted, types_1.PdfDetail.MissingHeader));
|
|
138
141
|
if (doc.headerOffset > 0)
|
|
139
142
|
findings.push(factory.make(types_1.PdfCategory.Corrupted, types_1.PdfDetail.LeadingBytes, undefined, { bytes: doc.headerOffset }));
|
|
140
143
|
if (doc.trailingBytes > 0)
|
|
@@ -180,6 +183,14 @@ async function analyzeInner(run, source, options, depth, deadline, verify) {
|
|
|
180
183
|
findings.push(factory.make(types_1.PdfCategory.Corrupted, types_1.PdfDetail.MalformedObject, 'cross-reference table', { reason: '/XRefStm entries for objects the table marks free', count: issues.xrefStmOverFree }));
|
|
181
184
|
if (issues.badObjStm.size)
|
|
182
185
|
findings.push(factory.make(types_1.PdfCategory.Corrupted, types_1.PdfDetail.MalformedObject, 'object stream', { reason: 'a header pdf.js cannot follow', count: issues.badObjStm.size }));
|
|
186
|
+
if (issues.objStmType.size)
|
|
187
|
+
findings.push(factory.make(types_1.PdfCategory.Corrupted, types_1.PdfDetail.MalformedObject, 'object stream', { reason: 'a /Type other than /ObjStm', count: issues.objStmType.size }));
|
|
188
|
+
if (issues.directRoot)
|
|
189
|
+
findings.push(factory.make(types_1.PdfCategory.Corrupted, types_1.PdfDetail.MalformedObject, 'trailer', { reason: 'a catalog written in the trailer' }));
|
|
190
|
+
if (issues.directStreams.size)
|
|
191
|
+
findings.push(factory.make(types_1.PdfCategory.Corrupted, types_1.PdfDetail.MalformedObject, undefined, { reason: 'streams written inside other objects', count: issues.directStreams.size }));
|
|
192
|
+
if (issues.objStmStreams.size)
|
|
193
|
+
findings.push(factory.make(types_1.PdfCategory.Corrupted, types_1.PdfDetail.MalformedObject, 'object stream', { reason: 'streams stored in an object stream', count: issues.objStmStreams.size }));
|
|
183
194
|
if (issues.refGen.size)
|
|
184
195
|
findings.push(factory.make(types_1.PdfCategory.Corrupted, types_1.PdfDetail.MalformedObject, undefined, { reason: 'references whose generation does not match the object', count: issues.refGen.size }));
|
|
185
196
|
if (issues.duplicateKeys.size)
|
package/dist/filters.js
CHANGED
|
@@ -484,6 +484,14 @@ function syncDecoder(name, parms) {
|
|
|
484
484
|
return [new RunLengthDecoder()];
|
|
485
485
|
case 'Crypt':
|
|
486
486
|
return [];
|
|
487
|
+
case 'BrotliDecode': {
|
|
488
|
+
// PDFium applies a predictor named here and pdf.js ignores it, so a predicted stream reads two ways and is not
|
|
489
|
+
// decoded.
|
|
490
|
+
const predictor = parms?.get('Predictor') ?? null;
|
|
491
|
+
if (predictor !== null && predictor !== 1)
|
|
492
|
+
throw new UnsupportedFilterError('A predictor on BrotliDecode, which readers apply differently');
|
|
493
|
+
return [];
|
|
494
|
+
}
|
|
487
495
|
default:
|
|
488
496
|
return undefined;
|
|
489
497
|
}
|
|
@@ -506,6 +514,11 @@ const SLICE = 4096;
|
|
|
506
514
|
* context for Flate, and a few thousand nested stages overflow the stack, so a longer chain counts as unsupported.
|
|
507
515
|
*/
|
|
508
516
|
const MAX_FILTERS = 32;
|
|
517
|
+
/**
|
|
518
|
+
* Brotli decoders in one chain. Each can take a 16 MB window from a few hundred bytes of input, and the stages of a
|
|
519
|
+
* chain all run at once.
|
|
520
|
+
*/
|
|
521
|
+
const MAX_BROTLI = 1;
|
|
509
522
|
/**
|
|
510
523
|
* Where raw deflate data starts: after the zlib header when there is one. The Adler-32 trailer is then never
|
|
511
524
|
* checked, as viewers do not check it.
|
|
@@ -541,20 +554,54 @@ async function* inflateChunks(input) {
|
|
|
541
554
|
yield r.value;
|
|
542
555
|
}
|
|
543
556
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
557
|
+
// Truncated data ends quietly with everything decoded. Corrupt data throws: Node drops the output of the write that
|
|
558
|
+
// failed, so the bytes before the error are not all there.
|
|
559
|
+
yield* through(rest(), zlib.createInflateRaw({ finishFlush: zlib.constants.Z_SYNC_FLUSH }));
|
|
560
|
+
}
|
|
561
|
+
/** Pipes `input` through a zlib transform and yields its output. */
|
|
562
|
+
async function* through(input, transform) {
|
|
563
|
+
const src = stream.Readable.from(input);
|
|
564
|
+
src.on('error', e => transform.destroy(e));
|
|
565
|
+
src.pipe(transform);
|
|
548
566
|
try {
|
|
549
|
-
|
|
550
|
-
// that failed, so the bytes before the error are not all there.
|
|
551
|
-
for await (const c of inflater)
|
|
567
|
+
for await (const c of transform)
|
|
552
568
|
yield c;
|
|
553
569
|
}
|
|
554
570
|
finally {
|
|
555
571
|
src.destroy();
|
|
556
|
-
|
|
572
|
+
transform.destroy();
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* BrotliDecode: RFC 7932 data with no header. pdf.js decodes such a stream whole or not at all, so data that is
|
|
577
|
+
* truncated, corrupt or followed by more bytes throws here. Empty data decodes to nothing, as in pdf.js.
|
|
578
|
+
*/
|
|
579
|
+
async function* brotliChunks(input) {
|
|
580
|
+
const it = input[Symbol.asyncIterator]();
|
|
581
|
+
let first;
|
|
582
|
+
while (!first?.length) {
|
|
583
|
+
const r = await it.next();
|
|
584
|
+
if (r.done)
|
|
585
|
+
return;
|
|
586
|
+
first = r.value;
|
|
587
|
+
}
|
|
588
|
+
let fed = 0;
|
|
589
|
+
async function* rest(head) {
|
|
590
|
+
fed += head.length;
|
|
591
|
+
yield head;
|
|
592
|
+
for (;;) {
|
|
593
|
+
const r = await it.next();
|
|
594
|
+
if (r.done)
|
|
595
|
+
return;
|
|
596
|
+
fed += r.value.length;
|
|
597
|
+
yield r.value;
|
|
598
|
+
}
|
|
557
599
|
}
|
|
600
|
+
const decoder = zlib.createBrotliDecompress();
|
|
601
|
+
yield* through(rest(first), decoder);
|
|
602
|
+
// Node stops reading at the end of the Brotli data and ignores what follows it.
|
|
603
|
+
if (decoder.bytesWritten < fed)
|
|
604
|
+
throw new Error('Data after the end of the Brotli stream');
|
|
558
605
|
}
|
|
559
606
|
/**
|
|
560
607
|
* Decodes a stream chunk by chunk through its whole filter chain. No stage may produce more than `limit` bytes, and
|
|
@@ -589,12 +636,16 @@ async function* decodeChunks(input, dict, limit, checkTime) {
|
|
|
589
636
|
(Array.isArray(f) ? p instanceof objects_1.PdfDict : Array.isArray(p) && p[0] instanceof objects_1.PdfDict)) {
|
|
590
637
|
throw new UnsupportedFilterError('Filter entries that readers take in different ways');
|
|
591
638
|
}
|
|
639
|
+
if (filters.filter(x => x.name === 'BrotliDecode').length > MAX_BROTLI)
|
|
640
|
+
throw new UnsupportedFilterError('More than one BrotliDecode in one chain');
|
|
592
641
|
for (const { name, parms } of filters) {
|
|
593
642
|
const decs = syncDecoder(name, parms);
|
|
594
643
|
if (!decs)
|
|
595
644
|
throw new UnsupportedFilterError(`Unsupported filter ${name}`);
|
|
596
645
|
if (name === 'FlateDecode' || name === 'Fl')
|
|
597
646
|
chain = counted(inflateChunks(chain));
|
|
647
|
+
else if (name === 'BrotliDecode')
|
|
648
|
+
chain = counted(brotliChunks(chain));
|
|
598
649
|
for (const d of decs) {
|
|
599
650
|
const upstream = chain;
|
|
600
651
|
chain = counted((async function* () {
|
package/dist/findings.js
CHANGED
|
@@ -13,6 +13,8 @@ def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.EmptyPassword, 'strip', 10,
|
|
|
13
13
|
def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.UserPassword, 'strip', 0, 'PDF decrypted with the supplied user password');
|
|
14
14
|
def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.OwnerPassword, 'strip', 0, 'PDF decrypted with the supplied owner password');
|
|
15
15
|
def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.PasswordRequired, 'reject', 0, 'PDF requires a password, and the one supplied did not open it');
|
|
16
|
+
def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.AttachmentsOnly, 'strip', 10, 'Only the attached files are encrypted, and the password supplied does not open them');
|
|
17
|
+
def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.NoKey, 'strip', 10, "Content encrypted with the attached files' key was removed, since the password supplied does not give that key");
|
|
16
18
|
def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.CertificateHandler, 'reject', 0, "PDF encrypted for specific recipients' certificates");
|
|
17
19
|
def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.UnknownHandler, 'reject', 0, 'PDF uses an unrecognized security handler');
|
|
18
20
|
def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.UnknownCryptFilter, 'reject', 0, 'PDF uses an unrecognized encryption filter');
|
|
@@ -30,6 +32,7 @@ def(types_1.PdfCategory.Corrupted, types_1.PdfDetail.MalformedObject, 'strip', 1
|
|
|
30
32
|
def(types_1.PdfCategory.Corrupted, types_1.PdfDetail.StreamLengthWrong, 'strip', 10, "A stream's declared length is missing or wrong");
|
|
31
33
|
def(types_1.PdfCategory.Corrupted, types_1.PdfDetail.LeadingBytes, 'strip', 5, 'Data before the PDF header');
|
|
32
34
|
def(types_1.PdfCategory.Corrupted, types_1.PdfDetail.TrailingBytes, 'strip', 10, 'Data after the end of the PDF');
|
|
35
|
+
def(types_1.PdfCategory.Corrupted, types_1.PdfDetail.MissingHeader, 'strip', 5, 'No PDF header at the start of the file');
|
|
33
36
|
// JAVASCRIPT
|
|
34
37
|
def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.Document, 'strip', 70, 'Document-level JavaScript runs when the PDF opens');
|
|
35
38
|
def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.OpenAction, 'strip', 70, 'JavaScript runs when the PDF opens');
|
package/dist/io.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export declare class Reader {
|
|
|
26
26
|
private readonly order;
|
|
27
27
|
constructor(source: ByteSource, size: number, blockSize?: number, maxBlocks?: number);
|
|
28
28
|
private block;
|
|
29
|
-
/** Reads up to `length` bytes at `offset`; shorter only at the end of the source. */
|
|
29
|
+
/** Reads up to `length` bytes at `offset`; shorter only at the end of the source, and empty outside it. */
|
|
30
30
|
read(offset: number, length: number): Promise<Uint8Array>;
|
|
31
31
|
/** Yields the range in chunks without caching them. */
|
|
32
32
|
chunks(offset: number, length: number, chunkSize?: number): AsyncGenerator<Uint8Array>;
|
package/dist/io.js
CHANGED
|
@@ -44,11 +44,23 @@ const crypto = __importStar(require("node:crypto"));
|
|
|
44
44
|
const fsp = __importStar(require("node:fs/promises"));
|
|
45
45
|
const os = __importStar(require("node:os"));
|
|
46
46
|
const path = __importStar(require("node:path"));
|
|
47
|
+
/**
|
|
48
|
+
* The bytes [start, end) a read covers in a source of `size` bytes, or undefined when it covers none. Offsets read
|
|
49
|
+
* from a file can be negative or fractional, and such a read gets no bytes rather than bytes from somewhere else.
|
|
50
|
+
*/
|
|
51
|
+
function span(offset, length, size) {
|
|
52
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset >= size || !(length >= 1))
|
|
53
|
+
return undefined;
|
|
54
|
+
return [offset, Math.min(size, offset + Math.floor(length))];
|
|
55
|
+
}
|
|
47
56
|
/** A source over bytes already in memory. Reads return views, not copies. */
|
|
48
57
|
function bufferSource(bytes) {
|
|
49
58
|
return {
|
|
50
59
|
size: async () => bytes.length,
|
|
51
|
-
read: async (offset, length) =>
|
|
60
|
+
read: async (offset, length) => {
|
|
61
|
+
const s = span(offset, length, bytes.length);
|
|
62
|
+
return s ? bytes.subarray(s[0], s[1]) : new Uint8Array(0);
|
|
63
|
+
},
|
|
52
64
|
};
|
|
53
65
|
}
|
|
54
66
|
/** A source over a local file, read at offsets through one file handle. */
|
|
@@ -68,8 +80,11 @@ function fileSource(filePath) {
|
|
|
68
80
|
},
|
|
69
81
|
async read(offset, length) {
|
|
70
82
|
const h = await open();
|
|
71
|
-
|
|
72
|
-
const
|
|
83
|
+
// Node reads position -1 from the file's current position, so a negative offset never reaches it.
|
|
84
|
+
const s = span(offset, length, await this.size());
|
|
85
|
+
if (!s)
|
|
86
|
+
return Buffer.alloc(0);
|
|
87
|
+
const n = s[1] - offset;
|
|
73
88
|
const buf = Buffer.allocUnsafe(n);
|
|
74
89
|
let done = 0;
|
|
75
90
|
while (done < n) {
|
|
@@ -208,11 +223,12 @@ class Reader {
|
|
|
208
223
|
}
|
|
209
224
|
return data;
|
|
210
225
|
}
|
|
211
|
-
/** Reads up to `length` bytes at `offset`; shorter only at the end of the source. */
|
|
226
|
+
/** Reads up to `length` bytes at `offset`; shorter only at the end of the source, and empty outside it. */
|
|
212
227
|
async read(offset, length) {
|
|
213
|
-
|
|
228
|
+
const s = span(offset, length, this.size);
|
|
229
|
+
if (!s)
|
|
214
230
|
return new Uint8Array(0);
|
|
215
|
-
const end =
|
|
231
|
+
const end = s[1];
|
|
216
232
|
if (end - offset > this.blockSize * 4)
|
|
217
233
|
return this.source.read(offset, end - offset);
|
|
218
234
|
const first = Math.floor(offset / this.blockSize);
|
|
@@ -234,8 +250,11 @@ class Reader {
|
|
|
234
250
|
}
|
|
235
251
|
/** Yields the range in chunks without caching them. */
|
|
236
252
|
async *chunks(offset, length, chunkSize = 262144) {
|
|
253
|
+
const s = span(offset, length, this.size);
|
|
254
|
+
if (!s)
|
|
255
|
+
return;
|
|
237
256
|
let pos = offset;
|
|
238
|
-
const end =
|
|
257
|
+
const end = s[1];
|
|
239
258
|
while (pos < end) {
|
|
240
259
|
const n = Math.min(chunkSize, end - pos);
|
|
241
260
|
const c = await this.source.read(pos, n);
|
|
@@ -292,8 +311,12 @@ class TempDir {
|
|
|
292
311
|
return size;
|
|
293
312
|
}),
|
|
294
313
|
read: (offset, length) => keep(async () => {
|
|
295
|
-
|
|
296
|
-
const
|
|
314
|
+
// A read outside the source is not passed on: a caller's source, such as a ranged S3 request, may fail on it.
|
|
315
|
+
const s = span(offset, length, size ?? Number.MAX_SAFE_INTEGER);
|
|
316
|
+
if (!s)
|
|
317
|
+
return new Uint8Array(0);
|
|
318
|
+
const b = await source.read(offset, s[1] - offset);
|
|
319
|
+
const want = size === undefined ? b.length : s[1] - offset;
|
|
297
320
|
if (b.length !== want)
|
|
298
321
|
throw Object.assign(new Error(`The source returned ${b.length} bytes at offset ${offset} where ${want} were expected`), { code: 'EIO' });
|
|
299
322
|
return b;
|
package/dist/parser.d.ts
CHANGED
|
@@ -1,10 +1,53 @@
|
|
|
1
1
|
import { type PdfObject, PdfRef } from './objects';
|
|
2
2
|
export declare class ParseError extends Error {
|
|
3
3
|
}
|
|
4
|
-
/**
|
|
4
|
+
/** The data ended inside an object. */
|
|
5
|
+
export declare class EndOfData extends ParseError {
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Thrown when the window ended before the object did. The caller retries with more bytes. `idle` says the window ended
|
|
9
|
+
* in a run of whitespace, and of comments when `comments` is set, that started at window position `from`, so the
|
|
10
|
+
* caller can skip the rest of the run in the source instead of holding it. `body` says it ended in the body of a stream
|
|
11
|
+
* written inside an object, and `line` before the end of the line after a "stream" keyword. The caller can find either
|
|
12
|
+
* end in the source.
|
|
13
|
+
*/
|
|
5
14
|
export declare class NeedMoreData extends Error {
|
|
15
|
+
readonly idle?: {
|
|
16
|
+
from: number;
|
|
17
|
+
comments: boolean;
|
|
18
|
+
} | undefined;
|
|
19
|
+
readonly body?: {
|
|
20
|
+
start: number;
|
|
21
|
+
declared?: number;
|
|
22
|
+
} | undefined;
|
|
23
|
+
readonly line?: {
|
|
24
|
+
from: number;
|
|
25
|
+
} | undefined;
|
|
26
|
+
constructor(idle?: {
|
|
27
|
+
from: number;
|
|
28
|
+
comments: boolean;
|
|
29
|
+
} | undefined, body?: {
|
|
30
|
+
start: number;
|
|
31
|
+
declared?: number;
|
|
32
|
+
} | undefined, line?: {
|
|
33
|
+
from: number;
|
|
34
|
+
} | undefined);
|
|
35
|
+
}
|
|
36
|
+
/** Thrown when a stream inside an object has a /Length the caller has not read yet. The caller reads it and retries. */
|
|
37
|
+
export declare class NeedObject extends Error {
|
|
38
|
+
readonly ref: PdfRef;
|
|
39
|
+
constructor(ref: PdfRef);
|
|
6
40
|
}
|
|
7
41
|
export declare const isWhite: (c: number) => boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Where pdf.js ends a stream body it searches for: at "endstream", or at "endsteam" or "endstrea" before a space, tab,
|
|
44
|
+
* CR or LF, which it takes too. Returns the first such place in `b` from `from` on and the keyword's length. A
|
|
45
|
+
* misspelling whose next byte `b` does not hold is left for a longer window.
|
|
46
|
+
*/
|
|
47
|
+
export declare function endstreamIn(b: Uint8Array, from: number): {
|
|
48
|
+
at: number;
|
|
49
|
+
length: number;
|
|
50
|
+
} | undefined;
|
|
8
51
|
interface ParseHooks {
|
|
9
52
|
/** Called when a name used #xx to encode a letter or digit. */
|
|
10
53
|
onEscapedName?(name: string): void;
|
|
@@ -19,6 +62,35 @@ interface ParseHooks {
|
|
|
19
62
|
/** Called every 65536 values, so a caller can stop a long parse at its deadline. */
|
|
20
63
|
onProgress?(): void;
|
|
21
64
|
}
|
|
65
|
+
/** How to read a stream written inside an object, as pdf.js reads one, and the end of the line after "stream". */
|
|
66
|
+
export interface DirectStreams {
|
|
67
|
+
/** Where window byte `pos` sits in the source. */
|
|
68
|
+
at(pos: number): number;
|
|
69
|
+
/** The value of an indirect /Length. Throws NeedObject when the caller has not read it yet. */
|
|
70
|
+
length(ref: PdfRef): number | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* A body the caller found in the source, by where it starts there: its length, or null when no "endstream" follows.
|
|
73
|
+
* The window then holds one byte for the body and goes on after "endstream".
|
|
74
|
+
*/
|
|
75
|
+
known(start: number): {
|
|
76
|
+
length: number;
|
|
77
|
+
} | null | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* The line after a "stream" keyword that the caller read in the source, by where it starts there: whether it held
|
|
80
|
+
* more than spaces. The window then goes on where the body starts.
|
|
81
|
+
*/
|
|
82
|
+
line(start: number): {
|
|
83
|
+
text: boolean;
|
|
84
|
+
} | undefined;
|
|
85
|
+
}
|
|
86
|
+
export interface ParseOptions {
|
|
87
|
+
/** Ends a dictionary or array that the end of the data cuts off, as pdf.js reads a trailer it recovers. */
|
|
88
|
+
recover?: boolean;
|
|
89
|
+
/** Reads a dictionary followed by "stream" anywhere inside an object as a stream. */
|
|
90
|
+
streams?: DirectStreams;
|
|
91
|
+
/** Reads the object itself as a stream in the same way, for an object stored in an object stream. */
|
|
92
|
+
topStream?: boolean;
|
|
93
|
+
}
|
|
22
94
|
/**
|
|
23
95
|
* Parses PDF objects from a byte window. `eof` tells the parser whether the window ends at the end of the source,
|
|
24
96
|
* so running off the end means NeedMoreData rather than a syntax error.
|
|
@@ -27,6 +99,7 @@ export declare class Parser {
|
|
|
27
99
|
readonly buf: Uint8Array;
|
|
28
100
|
readonly eof: boolean;
|
|
29
101
|
readonly hooks: ParseHooks;
|
|
102
|
+
readonly options: ParseOptions;
|
|
30
103
|
pos: number;
|
|
31
104
|
depth: number;
|
|
32
105
|
/** Estimated heap bytes of what this parser has built, with the length of every name and string. */
|
|
@@ -34,12 +107,21 @@ export declare class Parser {
|
|
|
34
107
|
/** The part of `cost` that does not grow with the length of a name or string. */
|
|
35
108
|
private overhead;
|
|
36
109
|
private values;
|
|
37
|
-
|
|
110
|
+
/** The part of `cost` that the dictionaries of streams written inside the object take. */
|
|
111
|
+
directCost: number;
|
|
112
|
+
constructor(buf: Uint8Array, pos?: number, eof?: boolean, hooks?: ParseHooks, options?: ParseOptions);
|
|
113
|
+
/** In recovery, whether nothing but whitespace and comments is left before the end of the data. */
|
|
114
|
+
private cutOff;
|
|
38
115
|
/** Rough heap costs: a slot for a number or keyword, an object for a name or reference, a buffer for a string. */
|
|
39
116
|
private charge;
|
|
40
117
|
private more;
|
|
41
118
|
peek(): number;
|
|
42
119
|
skipWhitespace(): void;
|
|
120
|
+
/**
|
|
121
|
+
* Skips whitespace and comments, and says whether anything follows them. A window that ends first asks for more,
|
|
122
|
+
* unless it ends the data.
|
|
123
|
+
*/
|
|
124
|
+
private skipToToken;
|
|
43
125
|
/** Reads a regular-character token without consuming delimiters. */
|
|
44
126
|
readToken(): string;
|
|
45
127
|
matchKeyword(word: string): boolean;
|
|
@@ -55,9 +137,16 @@ export declare class Parser {
|
|
|
55
137
|
/**
|
|
56
138
|
* Skips junk inside a container instead of losing the whole object: an unknown bare keyword, or a delimiter that
|
|
57
139
|
* cannot start an object, such as "]" in a dictionary or ">>" in an array. qpdf reads each of these as null. The
|
|
58
|
-
* container's own closing delimiter was checked before this is called.
|
|
140
|
+
* container's own closing delimiter was checked before this is called. Where a key belongs, pdf.js also skips a
|
|
141
|
+
* number, "R", "true", "false" or "null" one token at a time, and so does this.
|
|
59
142
|
*/
|
|
60
143
|
private skipBadToken;
|
|
144
|
+
/**
|
|
145
|
+
* After a dictionary inside an object, reads the stream that follows it, if any, as pdf.js does: the body ends at
|
|
146
|
+
* /Length when "endstream" follows there, and at the first "endstream" otherwise. Returns undefined when no stream
|
|
147
|
+
* follows. The body stays in the source; the stream records where it is.
|
|
148
|
+
*/
|
|
149
|
+
private directStream;
|
|
61
150
|
/**
|
|
62
151
|
* After a dictionary, checks for the "stream" keyword. Returns the position of the first body byte,
|
|
63
152
|
* or -1 when the object is not a stream.
|