@patrickdk77/pdf-defuse 0.2.2 → 0.2.3
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 +2 -0
- package/dist/detect.d.ts +9 -0
- package/dist/detect.js +50 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -1
- package/dist/index.mjs +1 -1
- package/dist/plugins.js +4 -2
- package/dist/types.d.ts +27 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -254,6 +254,8 @@ const result = await router.process(upload, sink, { depth: 0, deadline: Date.now
|
|
|
254
254
|
|
|
255
255
|
`pdfPlugin(options)` runs every PDF it gets with those options. Built without options, it takes the options of the pdf-defuse run that found the file, which pdf-defuse passes under a symbol only it can read. A `pdfPlugin()` that another package calls with a context of its own runs with no options at all, so a master builds it with options.
|
|
256
256
|
|
|
257
|
+
`detectPdf(bytes, options)` and `detectPdfSource(source, options)` tell a router whether pdf-defuse reads a file. They return `{ mimeType, extension, offset }`, or undefined. A header in the first 1024 bytes makes the file a PDF at the header's offset. A file without one is a PDF only when `options.name` or `options.declaredType` says PDF and pdf-defuse opens it, the way pdf.js opens a file without a header. That open reads the whole file, and the answer then carries `heuristic`. A file that opens but is too broken to inspect still counts, and the inspection rejects it. `pdfPlugin` takes what detection finds. `detectPdfSource` leaves the source open, and throws with the `DEFUSE_LIMIT` code once `options.deadline` passes.
|
|
258
|
+
|
|
257
259
|
The context holds `depth` and `deadline` and nothing PDF-specific, so any package can make one. `depth` is the depth of the file itself, the same number as `file.depth`. The upload is 0 and a file inside it is 1, whatever the formats. A container gives each file it finds its own depth plus one. `pdfPlugin` runs a PDF at the depth in its context and checks `limits.nestingDepth` against it, so a PDF in a ZIP in a PDF counts as depth 2. `deadline` is the `Date.now()` value the whole upload must finish by, and a container passes it on unchanged.
|
|
258
260
|
|
|
259
261
|
A plugin returns its findings as `DefuseFinding` objects, whose `category` and `detail` are plain strings. The string enums of two packages never match each other. `PdfFinding`, with pdf-defuse's enums, fits `DefuseFinding`. pdf-defuse adds a contained file's findings to its own report with `attachment` set, and keeps the action each came with, except that `reject` becomes `strip`. `actionOverrides` apply to the findings a pdf-defuse run makes, never to those a plugin returns.
|
package/dist/detect.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ByteSource, DetectOptions, FormatDetection } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Whether pdf-defuse reads `source` as a PDF. A header in the first 1024 bytes makes it one, at the header's offset.
|
|
4
|
+
* A file without one is a PDF only when its name or declared type says PDF and it opens as one, the way pdf.js opens
|
|
5
|
+
* a file without a header. That open reads the whole file. The source stays open.
|
|
6
|
+
*/
|
|
7
|
+
export declare function detectPdfSource(source: ByteSource, options?: DetectOptions): Promise<FormatDetection | undefined>;
|
|
8
|
+
/** `detectPdfSource` over bytes in memory. */
|
|
9
|
+
export declare function detectPdf(bytes: Uint8Array, options?: DetectOptions): Promise<FormatDetection | undefined>;
|
package/dist/detect.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectPdfSource = detectPdfSource;
|
|
4
|
+
exports.detectPdf = detectPdf;
|
|
5
|
+
const document_1 = require("./document");
|
|
6
|
+
const io_1 = require("./io");
|
|
7
|
+
const sniff_1 = require("./sniff");
|
|
8
|
+
const walker_1 = require("./walker");
|
|
9
|
+
/** PDF readers look for the header in the first 1024 bytes, and so does PdfDocument when it opens a file. */
|
|
10
|
+
const HEADER_WINDOW = 1024;
|
|
11
|
+
const HEADER = Buffer.from('%PDF-', 'latin1');
|
|
12
|
+
const isIoError = (e) => typeof e?.code === 'string' && /^E[A-Z]+$/.test(e.code);
|
|
13
|
+
/**
|
|
14
|
+
* Whether pdf-defuse reads `source` as a PDF. A header in the first 1024 bytes makes it one, at the header's offset.
|
|
15
|
+
* A file without one is a PDF only when its name or declared type says PDF and it opens as one, the way pdf.js opens
|
|
16
|
+
* a file without a header. That open reads the whole file. The source stays open.
|
|
17
|
+
*/
|
|
18
|
+
async function detectPdfSource(source, options = {}) {
|
|
19
|
+
const size = await source.size();
|
|
20
|
+
const head = await source.read(0, Math.min(size, HEADER_WINDOW));
|
|
21
|
+
const at = Buffer.from(head.buffer, head.byteOffset, head.byteLength).indexOf(HEADER);
|
|
22
|
+
if (at >= 0)
|
|
23
|
+
return { mimeType: 'application/pdf', extension: 'pdf', offset: at };
|
|
24
|
+
const claimed = [options.declaredType, (0, sniff_1.typeFromName)(options.name)].some(t => t !== undefined && (0, sniff_1.family)(t) === 'application/pdf');
|
|
25
|
+
if (!claimed)
|
|
26
|
+
return undefined;
|
|
27
|
+
const temp = new io_1.TempDir(options.tempDir);
|
|
28
|
+
try {
|
|
29
|
+
const doc = await document_1.PdfDocument.open(temp.watch(source), { deadline: options.deadline, temp, memoryThreshold: options.memoryThreshold });
|
|
30
|
+
await doc.release();
|
|
31
|
+
if (temp.ioError !== undefined)
|
|
32
|
+
throw temp.ioError;
|
|
33
|
+
return { mimeType: 'application/pdf', extension: 'pdf', offset: 0, heuristic: 'no PDF header, but the name or declared type says PDF and the file opens as one' };
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
if (temp.ioError !== undefined)
|
|
37
|
+
throw temp.ioError;
|
|
38
|
+
if ((0, walker_1.stopsTree)(e) !== undefined || isIoError(e))
|
|
39
|
+
throw e;
|
|
40
|
+
// Any other failure to open, as in an inspection, means pdf-defuse does not read the file as a PDF.
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
await temp.cleanup();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** `detectPdfSource` over bytes in memory. */
|
|
48
|
+
function detectPdf(bytes, options = {}) {
|
|
49
|
+
return detectPdfSource((0, io_1.bufferSource)(bytes), options);
|
|
50
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type { DelimitedOptions, FormulaHandling } from './data-plugins';
|
|
2
2
|
export { csvPlugin, jsonPlugin, tsvPlugin } from './data-plugins';
|
|
3
|
+
export { detectPdf, detectPdfSource } from './detect';
|
|
3
4
|
export { disarmPdf, disarmPdfSource, inspectPdf, inspectPdfSource } from './engine';
|
|
4
5
|
export { allFindingSpecs } from './findings';
|
|
5
6
|
export { bufferSink, bufferSource, fileSink, fileSource, writableSink } from './io';
|
package/dist/index.js
CHANGED
|
@@ -14,11 +14,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
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;
|
|
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.detectPdfSource = exports.detectPdf = exports.tsvPlugin = exports.jsonPlugin = exports.csvPlugin = void 0;
|
|
18
18
|
var data_plugins_1 = require("./data-plugins");
|
|
19
19
|
Object.defineProperty(exports, "csvPlugin", { enumerable: true, get: function () { return data_plugins_1.csvPlugin; } });
|
|
20
20
|
Object.defineProperty(exports, "jsonPlugin", { enumerable: true, get: function () { return data_plugins_1.jsonPlugin; } });
|
|
21
21
|
Object.defineProperty(exports, "tsvPlugin", { enumerable: true, get: function () { return data_plugins_1.tsvPlugin; } });
|
|
22
|
+
var detect_1 = require("./detect");
|
|
23
|
+
Object.defineProperty(exports, "detectPdf", { enumerable: true, get: function () { return detect_1.detectPdf; } });
|
|
24
|
+
Object.defineProperty(exports, "detectPdfSource", { enumerable: true, get: function () { return detect_1.detectPdfSource; } });
|
|
22
25
|
var engine_1 = require("./engine");
|
|
23
26
|
Object.defineProperty(exports, "disarmPdf", { enumerable: true, get: function () { return engine_1.disarmPdf; } });
|
|
24
27
|
Object.defineProperty(exports, "disarmPdfSource", { enumerable: true, get: function () { return engine_1.disarmPdfSource; } });
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
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;
|
|
2
|
+
export const { csvPlugin, jsonPlugin, tsvPlugin, detectPdf, detectPdfSource, disarmPdf, disarmPdfSource, inspectPdf, inspectPdfSource, allFindingSpecs, bufferSink, bufferSource, fileSink, fileSource, writableSink, passThrough, pdfPlugin, COMBINATIONS, DEFAULT_BANDS, scoreFindings, PdfCategory, PdfDetail, PdfRisk } = cjs;
|
|
3
3
|
export default cjs;
|
package/dist/plugins.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.passThrough = passThrough;
|
|
4
4
|
exports.pdfPlugin = pdfPlugin;
|
|
5
|
+
const detect_1 = require("./detect");
|
|
5
6
|
const engine_1 = require("./engine");
|
|
6
7
|
const score_1 = require("./score");
|
|
7
8
|
const sniff_1 = require("./sniff");
|
|
@@ -36,8 +37,9 @@ function pdfPlugin(options) {
|
|
|
36
37
|
return {
|
|
37
38
|
kind: 'file',
|
|
38
39
|
name: 'pdf',
|
|
39
|
-
// It
|
|
40
|
-
|
|
40
|
+
// It takes what detectPdf finds to be a PDF: a header in the first 1024 bytes, or no header, a name or declared
|
|
41
|
+
// type that says PDF, and content that opens as one.
|
|
42
|
+
accepts: async (f) => f.sniffedType === 'application/pdf' || (await (0, detect_1.detectPdfSource)(f.source, { name: f.name, declaredType: f.declaredType })) !== undefined,
|
|
41
43
|
async process(file, sink, context) {
|
|
42
44
|
const run = options ?? context[walker_1.RUN_OPTIONS] ?? {};
|
|
43
45
|
const r = await (0, engine_1.disarmAtDepth)(file.source, sink, run, context.depth, context.deadline);
|
package/dist/types.d.ts
CHANGED
|
@@ -298,3 +298,30 @@ export interface PdfOptions {
|
|
|
298
298
|
*/
|
|
299
299
|
preserveSignatures?: boolean;
|
|
300
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* A file's format as a package of the family finds it, among the formats that package reads. The family's router asks
|
|
303
|
+
* each package and routes the file by the answers.
|
|
304
|
+
*/
|
|
305
|
+
export interface FormatDetection {
|
|
306
|
+
/** The format's MIME type, such as application/pdf. */
|
|
307
|
+
mimeType: string;
|
|
308
|
+
/** The format's usual extension, lowercase and without the dot. */
|
|
309
|
+
extension: string;
|
|
310
|
+
/** Where the format starts in the file: 0, or the offset of its data when other data comes first. */
|
|
311
|
+
offset: number;
|
|
312
|
+
/** Set when the match rests on more than the content, and says what it rests on. */
|
|
313
|
+
heuristic?: string;
|
|
314
|
+
}
|
|
315
|
+
/** What a detect function may know of a file besides its content. */
|
|
316
|
+
export interface DetectOptions {
|
|
317
|
+
/** The file's name, as the upload or the container that holds it gives it. */
|
|
318
|
+
name?: string;
|
|
319
|
+
/** The MIME type the upload or the container declares. */
|
|
320
|
+
declaredType?: string;
|
|
321
|
+
/** The Date.now() value detection must finish by, as `PluginContext.deadline`. */
|
|
322
|
+
deadline?: number;
|
|
323
|
+
/** Folder for temporary files. Defaults to `os.tmpdir()`. */
|
|
324
|
+
tempDir?: string;
|
|
325
|
+
/** Bytes held in memory before a temporary file takes over. */
|
|
326
|
+
memoryThreshold?: number;
|
|
327
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patrickdk77/pdf-defuse",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Sanitize and clean PDF files. Detects and removes JavaScript, attached files, launch actions, unsafe links and empty-password encryption, scores the risk, and writes a disarmed copy with bounded memory and no runtime dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "patrickdk77",
|