@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.
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FindingFactory = void 0;
4
+ exports.findingSpec = findingSpec;
5
+ exports.allFindingSpecs = allFindingSpecs;
6
+ const types_1 = require("./types");
7
+ const specs = new Map();
8
+ function def(c, d, action, weight, description) {
9
+ specs.set(`${c}/${d}`, { action, weight, description });
10
+ }
11
+ // ENCRYPTED
12
+ def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.EmptyPassword, 'strip', 10, 'PDF encrypted with an empty user password');
13
+ def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.UserPassword, 'strip', 0, 'PDF decrypted with the supplied user password');
14
+ def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.OwnerPassword, 'strip', 0, 'PDF decrypted with the supplied owner password');
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.CertificateHandler, 'reject', 0, "PDF encrypted for specific recipients' certificates");
17
+ def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.UnknownHandler, 'reject', 0, 'PDF uses an unrecognized security handler');
18
+ def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.UnknownCryptFilter, 'reject', 0, 'PDF uses an unrecognized encryption filter');
19
+ def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.Rc4_40, 'info', 0, 'Encryption is RC4 with a 40-bit key');
20
+ def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.Rc4_128, 'info', 0, 'Encryption is RC4 with a 128-bit key');
21
+ def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.Rc4Other, 'info', 0, 'Encryption is RC4 with a <bits>-bit key');
22
+ def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.Aes128, 'info', 0, 'Encryption is AES with a 128-bit key');
23
+ def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.Aes256, 'info', 0, 'Encryption is AES with a 256-bit key');
24
+ def(types_1.PdfCategory.Encrypted, types_1.PdfDetail.MetadataUnencrypted, 'info', 0, 'Document metadata is stored unencrypted');
25
+ // CORRUPTED
26
+ def(types_1.PdfCategory.Corrupted, types_1.PdfDetail.Unparseable, 'reject', 0, 'PDF cannot be parsed');
27
+ def(types_1.PdfCategory.Corrupted, types_1.PdfDetail.Truncated, 'reject', 0, 'PDF ends before its trailer');
28
+ def(types_1.PdfCategory.Corrupted, types_1.PdfDetail.XrefRebuilt, 'strip', 10, 'Cross-reference table missing or broken. Objects were found by scanning');
29
+ def(types_1.PdfCategory.Corrupted, types_1.PdfDetail.MalformedObject, 'strip', 10, 'An object has the wrong type for where it is used');
30
+ def(types_1.PdfCategory.Corrupted, types_1.PdfDetail.StreamLengthWrong, 'strip', 10, "A stream's declared length is missing or wrong");
31
+ def(types_1.PdfCategory.Corrupted, types_1.PdfDetail.LeadingBytes, 'strip', 5, 'Data before the PDF header');
32
+ def(types_1.PdfCategory.Corrupted, types_1.PdfDetail.TrailingBytes, 'strip', 10, 'Data after the end of the PDF');
33
+ // JAVASCRIPT
34
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.Document, 'strip', 70, 'Document-level JavaScript runs when the PDF opens');
35
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.OpenAction, 'strip', 70, 'JavaScript runs when the PDF opens');
36
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.Page, 'strip', 50, 'JavaScript runs when a page opens or closes');
37
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.Field, 'strip', 50, 'JavaScript formats, checks or calculates a form field');
38
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.Annotation, 'strip', 50, 'JavaScript runs on mouse or focus events of an annotation');
39
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.Link, 'strip', 50, 'A link runs JavaScript when clicked');
40
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.Bookmark, 'strip', 50, 'A bookmark runs JavaScript when clicked');
41
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.Chained, 'strip', 50, 'JavaScript runs after another action');
42
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.Rendition, 'strip', 50, 'A media action carries JavaScript');
43
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.Url, 'strip', 50, 'A link uses a javascript: URL');
44
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.Unattached, 'strip', 50, 'A JavaScript entry sits outside any action');
45
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.PluginPassed, 'info', 40, 'JavaScript kept unchanged by plugin <plugin>');
46
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.PluginScrubbed, 'info', 20, 'JavaScript rewritten and kept by plugin <plugin>');
47
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.PluginFailed, 'strip', 0, 'Plugin <plugin> failed, so the JavaScript was removed');
48
+ def(types_1.PdfCategory.JavaScript, types_1.PdfDetail.PluginRemoved, 'strip', 0, 'JavaScript removed by plugin <plugin>');
49
+ // ACTION
50
+ def(types_1.PdfCategory.Action, types_1.PdfDetail.Launch, 'strip', 80, 'An action runs a program or opens a file');
51
+ def(types_1.PdfCategory.Action, types_1.PdfDetail.RemoteGoto, 'strip', 60, 'A link opens another PDF file');
52
+ def(types_1.PdfCategory.Action, types_1.PdfDetail.EmbeddedGoto, 'strip', 60, 'A link opens a PDF embedded in this one');
53
+ def(types_1.PdfCategory.Action, types_1.PdfDetail.SubmitForm, 'strip', 40, 'A form sends its data to a URL');
54
+ def(types_1.PdfCategory.Action, types_1.PdfDetail.ImportData, 'strip', 40, 'A form loads data from a file');
55
+ def(types_1.PdfCategory.Action, types_1.PdfDetail.Hide, 'strip', 20, 'An action shows or hides annotations');
56
+ def(types_1.PdfCategory.Action, types_1.PdfDetail.SetLayerState, 'strip', 20, 'An action shows or hides content layers');
57
+ def(types_1.PdfCategory.Action, types_1.PdfDetail.Named, 'strip', 20, 'An action runs a viewer menu command');
58
+ def(types_1.PdfCategory.Action, types_1.PdfDetail.Triggered, 'strip', 20, 'An action fires on an event instead of a click');
59
+ def(types_1.PdfCategory.Action, types_1.PdfDetail.Unknown, 'strip', 30, 'An action of a type this package does not recognize');
60
+ // LINK
61
+ def(types_1.PdfCategory.Link, types_1.PdfDetail.Safe, 'info', 0, 'Web or email link kept');
62
+ def(types_1.PdfCategory.Link, types_1.PdfDetail.FileUrl, 'strip', 60, 'A link opens a local file');
63
+ def(types_1.PdfCategory.Link, types_1.PdfDetail.NetworkPath, 'strip', 60, 'A link opens a network share');
64
+ def(types_1.PdfCategory.Link, types_1.PdfDetail.DataUrl, 'strip', 60, 'A link carries embedded data');
65
+ def(types_1.PdfCategory.Link, types_1.PdfDetail.OtherScheme, 'strip', 30, 'A link uses a scheme other than http, https or mailto');
66
+ def(types_1.PdfCategory.Link, types_1.PdfDetail.Credentials, 'strip', 40, 'A link hides its real host behind a user name');
67
+ def(types_1.PdfCategory.Link, types_1.PdfDetail.IpHost, 'strip', 40, 'A link points at a raw IP address');
68
+ def(types_1.PdfCategory.Link, types_1.PdfDetail.LookalikeHost, 'strip', 40, "A link's host uses punycode or mixed scripts");
69
+ def(types_1.PdfCategory.Link, types_1.PdfDetail.EncodedHost, 'strip', 40, "A link's host is percent-encoded");
70
+ def(types_1.PdfCategory.Link, types_1.PdfDetail.TextMismatch, 'strip', 40, "A link's tooltip names a different site");
71
+ def(types_1.PdfCategory.Link, types_1.PdfDetail.FullPage, 'strip', 20, 'A link covers most of the page');
72
+ def(types_1.PdfCategory.Link, types_1.PdfDetail.Relative, 'strip', 30, "A relative link with no base address, which resolves against the file's own location");
73
+ // EMBEDDED_FILE
74
+ def(types_1.PdfCategory.EmbeddedFile, types_1.PdfDetail.NoPlugin, 'strip', 30, '<Attached file "{name}"|An attached file> (<{sniffed}|{type}|unknown type>) removed. No plugin accepts this type');
75
+ def(types_1.PdfCategory.EmbeddedFile, types_1.PdfDetail.TypeMismatch, 'info', 40, 'The types of <attached file "{name}"|an unnamed attached file> disagree: <its name says {nameType}|its name has no known extension>, <it is declared {declared}|it has no declared type>, and <its content looks like {sniffed}|its content is not recognized>');
76
+ def(types_1.PdfCategory.EmbeddedFile, types_1.PdfDetail.PluginPassed, 'info', 20, 'Attached file kept by plugin <plugin> with nothing removed');
77
+ def(types_1.PdfCategory.EmbeddedFile, types_1.PdfDetail.PluginScrubbed, 'info', 10, 'Attached file cleaned and kept by plugin <plugin>');
78
+ def(types_1.PdfCategory.EmbeddedFile, types_1.PdfDetail.PluginFailed, 'strip', 0, 'Plugin <plugin> failed, so the attached file was removed');
79
+ def(types_1.PdfCategory.EmbeddedFile, types_1.PdfDetail.PluginRemoved, 'strip', 0, 'Attached file removed by plugin <plugin>');
80
+ def(types_1.PdfCategory.EmbeddedFile, types_1.PdfDetail.Portfolio, 'strip', 30, 'PDF is a portfolio of files');
81
+ // MEDIA
82
+ def(types_1.PdfCategory.Media, types_1.PdfDetail.Movie, 'strip', 30, 'Embedded movie removed');
83
+ def(types_1.PdfCategory.Media, types_1.PdfDetail.Sound, 'strip', 30, 'Embedded sound removed');
84
+ def(types_1.PdfCategory.Media, types_1.PdfDetail.Screen, 'strip', 30, 'Embedded screen annotation removed');
85
+ def(types_1.PdfCategory.Media, types_1.PdfDetail.RichMedia, 'strip', 30, 'Embedded rich media removed');
86
+ def(types_1.PdfCategory.Media, types_1.PdfDetail.ThreeD, 'strip', 30, 'Embedded 3D content removed');
87
+ def(types_1.PdfCategory.Media, types_1.PdfDetail.Rendition, 'strip', 30, 'Embedded rendition removed');
88
+ def(types_1.PdfCategory.Media, types_1.PdfDetail.Slideshow, 'strip', 30, 'Embedded slideshow removed');
89
+ // ANNOTATION
90
+ def(types_1.PdfCategory.Annotation, types_1.PdfDetail.UnknownSubtype, 'strip', 30, 'An annotation of a type outside the allowlist');
91
+ // FORM
92
+ def(types_1.PdfCategory.Form, types_1.PdfDetail.Fields, 'info', 0, 'PDF has fillable form fields');
93
+ def(types_1.PdfCategory.Form, types_1.PdfDetail.Xfa, 'strip', 40, 'PDF has an XFA form, which carries its own scripts');
94
+ def(types_1.PdfCategory.Form, types_1.PdfDetail.CalculationOrder, 'strip', 20, 'Form calculation order removed with the calculation scripts it ran');
95
+ // SIGNATURE
96
+ def(types_1.PdfCategory.Signature, types_1.PdfDetail.Signed, 'info', 0, 'PDF is digitally signed. A rewrite invalidates the signature');
97
+ def(types_1.PdfCategory.Signature, types_1.PdfDetail.Certified, 'info', 0, 'PDF is certified against changes. A rewrite invalidates the certification');
98
+ def(types_1.PdfCategory.Signature, types_1.PdfDetail.UsageRights, 'info', 0, 'PDF carries Reader usage rights, which disarming removes');
99
+ // STRUCTURE
100
+ def(types_1.PdfCategory.Structure, types_1.PdfDetail.IncrementalUpdates, 'strip', 0, 'PDF has earlier revisions, which can hold deleted content');
101
+ def(types_1.PdfCategory.Structure, types_1.PdfDetail.UnreferencedObjects, 'strip', 0, 'PDF has objects nothing refers to');
102
+ def(types_1.PdfCategory.Structure, types_1.PdfDetail.VersionUpgraded, 'info', 0, 'The catalog declares a newer PDF version than the header');
103
+ def(types_1.PdfCategory.Structure, types_1.PdfDetail.ShadowedObjects, 'strip', 20, 'PDF holds object definitions its cross-reference table does not use, which other readers may pick instead');
104
+ def(types_1.PdfCategory.Structure, types_1.PdfDetail.EscapedNames, 'info', 30, 'Keywords are written with escape codes, a known way to hide them from scanners');
105
+ // CONTENT
106
+ def(types_1.PdfCategory.Content, types_1.PdfDetail.Jbig2Image, 'info', 30, 'An image uses JBIG2 compression, a past source of reader exploits');
107
+ def(types_1.PdfCategory.Content, types_1.PdfDetail.JpxImage, 'info', 10, 'An image uses JPEG 2000 compression, a past source of reader exploits');
108
+ def(types_1.PdfCategory.Content, types_1.PdfDetail.Type3Font, 'info', 10, 'A Type 3 font draws glyphs with page-description commands, a past source of reader exploits');
109
+ // METADATA
110
+ def(types_1.PdfCategory.Metadata, types_1.PdfDetail.InfoDictionary, 'info', 0, 'Document information names an author, a tool or dates');
111
+ def(types_1.PdfCategory.Metadata, types_1.PdfDetail.Xmp, 'info', 0, 'XMP metadata present');
112
+ def(types_1.PdfCategory.Metadata, types_1.PdfDetail.Stripped, 'info', 0, 'Metadata removed because the caller turned on metadata stripping');
113
+ // LIMIT
114
+ def(types_1.PdfCategory.Limit, types_1.PdfDetail.FileSize, 'reject', 0, 'PDF exceeds the file size limit');
115
+ def(types_1.PdfCategory.Limit, types_1.PdfDetail.ObjectCount, 'reject', 0, 'PDF exceeds the object count limit');
116
+ def(types_1.PdfCategory.Limit, types_1.PdfDetail.DecompressedSize, 'reject', 0, 'PDF exceeds the decompressed size limit');
117
+ def(types_1.PdfCategory.Limit, types_1.PdfDetail.NestingDepth, 'reject', 0, 'PDF exceeds the nesting depth limit');
118
+ def(types_1.PdfCategory.Limit, types_1.PdfDetail.Time, 'reject', 0, 'PDF exceeds the time limit');
119
+ // PROCESSING
120
+ def(types_1.PdfCategory.Processing, types_1.PdfDetail.MemoryFallback, 'info', 0, '<part> was too large for memory and went through a temporary file');
121
+ def(types_1.PdfCategory.Processing, types_1.PdfDetail.VerificationFailed, 'reject', 0, 'The defused output did not pass its own inspection, so nothing was written');
122
+ def(types_1.PdfCategory.Processing, types_1.PdfDetail.ContentRemoved, 'strip', 20, 'Content was removed that no other finding names');
123
+ /** 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. */
124
+ function findingSpec(category, detail) {
125
+ return specs.get(`${category}/${detail}`);
126
+ }
127
+ /** The spec of a kind pdf-defuse makes itself, which always has one. */
128
+ function ownSpec(category, detail) {
129
+ const s = findingSpec(category, detail);
130
+ if (!s)
131
+ throw new Error(`No finding spec for ${category}/${detail}`);
132
+ return s;
133
+ }
134
+ function allFindingSpecs() {
135
+ return [...specs.entries()].map(([k, s]) => {
136
+ const [category, detail] = k.split('/');
137
+ return { category, detail, ...s };
138
+ });
139
+ }
140
+ class FindingFactory {
141
+ overrides;
142
+ constructor(overrides = []) {
143
+ this.overrides = overrides;
144
+ }
145
+ actionFor(category, detail) {
146
+ const exact = this.overrides.find(o => o.category === category && o.detail === detail);
147
+ if (exact)
148
+ return exact.action;
149
+ const byCategory = this.overrides.find(o => o.category === category && o.detail === undefined);
150
+ if (byCategory)
151
+ return byCategory.action;
152
+ return ownSpec(category, detail).action;
153
+ }
154
+ make(category, detail, location, data) {
155
+ const spec = ownSpec(category, detail);
156
+ // Values can come from the PDF, so the description shows no control or direction characters from them.
157
+ const shown = (v) => String(v).replace(/[\p{Cc}\p{Cf}]/gu, '?');
158
+ const description = spec.description.replace(/<([^<>]+)>/g, (m, body) => {
159
+ if (/^[a-z]+$/.test(body))
160
+ return data?.[body] !== undefined ? shown(data[body]) : m;
161
+ for (const alt of body.split('|')) {
162
+ let missing = false;
163
+ const filled = alt.replace(/\{([a-zA-Z]+)\}/g, (_, key) => {
164
+ const v = data?.[key];
165
+ if (v === undefined || v === '')
166
+ missing = true;
167
+ const s = shown(v ?? '');
168
+ return s.length > 60 ? `${s.slice(0, 57)}...` : s;
169
+ });
170
+ if (!missing)
171
+ return filled;
172
+ }
173
+ return m;
174
+ });
175
+ const f = { category, detail, description, action: this.actionFor(category, detail) };
176
+ if (location !== undefined)
177
+ f.location = location;
178
+ if (data !== undefined && Object.keys(data).length)
179
+ f.data = data;
180
+ return f;
181
+ }
182
+ }
183
+ exports.FindingFactory = FindingFactory;
@@ -0,0 +1,8 @@
1
+ export type { DelimitedOptions, FormulaHandling } from './data-plugins';
2
+ export { csvPlugin, jsonPlugin, tsvPlugin } from './data-plugins';
3
+ export { disarmPdf, disarmPdfSource, inspectPdf, inspectPdfSource } from './engine';
4
+ export { allFindingSpecs } from './findings';
5
+ export { bufferSink, bufferSource, fileSink, fileSource, writableSink } from './io';
6
+ export { passThrough, pdfPlugin } from './plugins';
7
+ export { COMBINATIONS, DEFAULT_BANDS, scoreFindings } from './score';
8
+ export * from './types';
package/dist/index.js ADDED
@@ -0,0 +1,42 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.scoreFindings = exports.DEFAULT_BANDS = exports.COMBINATIONS = exports.pdfPlugin = exports.passThrough = exports.writableSink = exports.fileSource = exports.fileSink = exports.bufferSource = exports.bufferSink = exports.allFindingSpecs = exports.inspectPdfSource = exports.inspectPdf = exports.disarmPdfSource = exports.disarmPdf = exports.tsvPlugin = exports.jsonPlugin = exports.csvPlugin = void 0;
18
+ var data_plugins_1 = require("./data-plugins");
19
+ Object.defineProperty(exports, "csvPlugin", { enumerable: true, get: function () { return data_plugins_1.csvPlugin; } });
20
+ Object.defineProperty(exports, "jsonPlugin", { enumerable: true, get: function () { return data_plugins_1.jsonPlugin; } });
21
+ Object.defineProperty(exports, "tsvPlugin", { enumerable: true, get: function () { return data_plugins_1.tsvPlugin; } });
22
+ var engine_1 = require("./engine");
23
+ Object.defineProperty(exports, "disarmPdf", { enumerable: true, get: function () { return engine_1.disarmPdf; } });
24
+ Object.defineProperty(exports, "disarmPdfSource", { enumerable: true, get: function () { return engine_1.disarmPdfSource; } });
25
+ Object.defineProperty(exports, "inspectPdf", { enumerable: true, get: function () { return engine_1.inspectPdf; } });
26
+ Object.defineProperty(exports, "inspectPdfSource", { enumerable: true, get: function () { return engine_1.inspectPdfSource; } });
27
+ var findings_1 = require("./findings");
28
+ Object.defineProperty(exports, "allFindingSpecs", { enumerable: true, get: function () { return findings_1.allFindingSpecs; } });
29
+ var io_1 = require("./io");
30
+ Object.defineProperty(exports, "bufferSink", { enumerable: true, get: function () { return io_1.bufferSink; } });
31
+ Object.defineProperty(exports, "bufferSource", { enumerable: true, get: function () { return io_1.bufferSource; } });
32
+ Object.defineProperty(exports, "fileSink", { enumerable: true, get: function () { return io_1.fileSink; } });
33
+ Object.defineProperty(exports, "fileSource", { enumerable: true, get: function () { return io_1.fileSource; } });
34
+ Object.defineProperty(exports, "writableSink", { enumerable: true, get: function () { return io_1.writableSink; } });
35
+ var plugins_1 = require("./plugins");
36
+ Object.defineProperty(exports, "passThrough", { enumerable: true, get: function () { return plugins_1.passThrough; } });
37
+ Object.defineProperty(exports, "pdfPlugin", { enumerable: true, get: function () { return plugins_1.pdfPlugin; } });
38
+ var score_1 = require("./score");
39
+ Object.defineProperty(exports, "COMBINATIONS", { enumerable: true, get: function () { return score_1.COMBINATIONS; } });
40
+ Object.defineProperty(exports, "DEFAULT_BANDS", { enumerable: true, get: function () { return score_1.DEFAULT_BANDS; } });
41
+ Object.defineProperty(exports, "scoreFindings", { enumerable: true, get: function () { return score_1.scoreFindings; } });
42
+ __exportStar(require("./types"), exports);
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import cjs from './index.js';
2
+ export const { csvPlugin, jsonPlugin, tsvPlugin, disarmPdf, disarmPdfSource, inspectPdf, inspectPdfSource, allFindingSpecs, bufferSink, bufferSource, fileSink, fileSource, writableSink, passThrough, pdfPlugin, COMBINATIONS, DEFAULT_BANDS, scoreFindings, PdfCategory, PdfDetail, PdfRisk } = cjs;
3
+ export default cjs;
package/dist/io.d.ts ADDED
@@ -0,0 +1,81 @@
1
+ import type { Writable } from 'node:stream';
2
+ import type { ByteSink, ByteSource } from './types';
3
+ /** A source over bytes already in memory. Reads return views, not copies. */
4
+ export declare function bufferSource(bytes: Uint8Array): ByteSource;
5
+ /** A source over a local file, read at offsets through one file handle. */
6
+ export declare function fileSource(filePath: string): ByteSource;
7
+ /** Collects output in memory. */
8
+ export declare function bufferSink(): ByteSink & {
9
+ result(): Uint8Array;
10
+ size(): number;
11
+ };
12
+ /** Writes to a local file. The file is created on the first write. */
13
+ export declare function fileSink(filePath: string): ByteSink;
14
+ /**
15
+ * Wraps any Node Writable, waiting for 'drain' when it applies backpressure. The stream's first error, or its
16
+ * closing before the end, fails the pending write and every later one.
17
+ */
18
+ export declare function writableSink(stream: Writable): ByteSink;
19
+ /** Random-access reader with a small block cache, so the parser can ask for overlapping windows cheaply. */
20
+ export declare class Reader {
21
+ readonly source: ByteSource;
22
+ readonly size: number;
23
+ private readonly blockSize;
24
+ private readonly maxBlocks;
25
+ private readonly cache;
26
+ private readonly order;
27
+ constructor(source: ByteSource, size: number, blockSize?: number, maxBlocks?: number);
28
+ private block;
29
+ /** Reads up to `length` bytes at `offset`; shorter only at the end of the source. */
30
+ read(offset: number, length: number): Promise<Uint8Array>;
31
+ /** Yields the range in chunks without caching them. */
32
+ chunks(offset: number, length: number, chunkSize?: number): AsyncGenerator<Uint8Array>;
33
+ }
34
+ /** A private temporary directory, removed by cleanup(). It also keeps the run's first I/O error. */
35
+ export declare class TempDir {
36
+ private readonly base;
37
+ private dir?;
38
+ private seq;
39
+ /**
40
+ * The first error from a temporary file or a watched source. The parser recovers from a failed read as if the
41
+ * file were damaged, so the engine checks this to report an I/O error instead of a verdict on the PDF.
42
+ */
43
+ ioError?: unknown;
44
+ constructor(base?: string);
45
+ file(suffix?: string): Promise<string>;
46
+ cleanup(): Promise<void>;
47
+ /**
48
+ * Wraps a source so that its errors also land in ioError. Once the source has given its size, a read that returns
49
+ * more or fewer bytes than that size allows is an error too: the parser would take the shifted bytes for the file.
50
+ */
51
+ watch(source: ByteSource): ByteSource;
52
+ }
53
+ /** A sink that keeps small output in memory and moves to a temporary file past a threshold. */
54
+ export declare class SpillSink implements ByteSink {
55
+ private readonly temp;
56
+ private readonly threshold;
57
+ private chunks;
58
+ private size;
59
+ private filePath?;
60
+ private handle?;
61
+ private readonly md5;
62
+ private hex?;
63
+ spilled: boolean;
64
+ constructor(temp: TempDir, threshold: number);
65
+ write(chunk: Uint8Array): Promise<void>;
66
+ close(): Promise<void>;
67
+ /** Closes the sink and deletes its temporary file, once nothing will read what it holds again. */
68
+ dispose(): Promise<void>;
69
+ get length(): number;
70
+ /** The temporary file, once the sink has spilled. */
71
+ get path(): string | undefined;
72
+ digest(): string;
73
+ /** A source over what was written. Call after close(). */
74
+ source(): ByteSource;
75
+ read(chunkSize?: number): AsyncGenerator<Uint8Array>;
76
+ }
77
+ /**
78
+ * Copies a whole source, calling `checkTime` before each chunk. A read that returns more or fewer bytes than it asked
79
+ * for fails the copy, which would otherwise shift every byte after it.
80
+ */
81
+ export declare function copyToSink(source: ByteSource, sink: ByteSink, checkTime?: () => void): Promise<void>;