@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,6 @@
1
+ import { type PdfObject, PdfRef } from './objects';
2
+ export declare function serializeName(name: string): string;
3
+ /** The shortest digits that read back as `n`, written out in full because PDF has no exponent form. */
4
+ export declare function serializeNumber(n: number): string;
5
+ /** Serializes an object. `mapRef` renumbers references and may return null to drop one. */
6
+ export declare function serialize(obj: PdfObject, mapRef?: (r: PdfRef) => PdfRef | null): string;
package/dist/writer.js ADDED
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serializeName = serializeName;
4
+ exports.serializeNumber = serializeNumber;
5
+ exports.serialize = serialize;
6
+ const objects_1 = require("./objects");
7
+ const NAME_SAFE = new Uint8Array(256);
8
+ for (let c = 0x21; c <= 0x7e; c++)
9
+ NAME_SAFE[c] = 1;
10
+ for (const c of '()<>[]{}/%#')
11
+ NAME_SAFE[c.charCodeAt(0)] = 0;
12
+ const HEX = '0123456789ABCDEF';
13
+ function serializeName(name) {
14
+ let unsafe = 0;
15
+ for (let i = 0; i < name.length; i++)
16
+ if (!NAME_SAFE[name.charCodeAt(i)])
17
+ unsafe++;
18
+ if (!unsafe)
19
+ return `/${name}`;
20
+ // Filled in one buffer: appending to a string per byte costs tens of bytes of heap per byte of name.
21
+ const bytes = Buffer.from(name, 'latin1');
22
+ const out = Buffer.allocUnsafe(1 + bytes.length + 2 * unsafe);
23
+ let p = 0;
24
+ out[p++] = 0x2f;
25
+ for (const b of bytes) {
26
+ if (NAME_SAFE[b])
27
+ out[p++] = b;
28
+ else {
29
+ out[p++] = 0x23;
30
+ out[p++] = HEX.charCodeAt(b >> 4);
31
+ out[p++] = HEX.charCodeAt(b & 15);
32
+ }
33
+ }
34
+ return out.toString('latin1', 0, p);
35
+ }
36
+ /**
37
+ * Short strings as hex; long ones as escaped literals, so a large string is not doubled. A literal writes the "o" of
38
+ * every "obj" as \157, so text such as "5 0 obj" never reads as an object header to a reader that scans the file.
39
+ */
40
+ function serializeString(bytes) {
41
+ if (bytes.length <= 256)
42
+ return `<${Buffer.from(bytes).toString('hex')}>`;
43
+ let extra = 0;
44
+ for (let i = 0; i < bytes.length; i++) {
45
+ const b = bytes[i];
46
+ if (b === 0x28 || b === 0x29 || b === 0x5c || b === 0x0d)
47
+ extra++;
48
+ else if (b === 0x6f && bytes[i + 1] === 0x62 && bytes[i + 2] === 0x6a)
49
+ extra += 3;
50
+ }
51
+ const out = Buffer.allocUnsafe(bytes.length + extra + 2);
52
+ let p = 0;
53
+ out[p++] = 0x28;
54
+ for (let i = 0; i < bytes.length; i++) {
55
+ const b = bytes[i];
56
+ if (b === 0x28 || b === 0x29 || b === 0x5c) {
57
+ out[p++] = 0x5c;
58
+ out[p++] = b;
59
+ }
60
+ else if (b === 0x0d) {
61
+ out[p++] = 0x5c;
62
+ out[p++] = 0x72;
63
+ }
64
+ else if (b === 0x6f && bytes[i + 1] === 0x62 && bytes[i + 2] === 0x6a)
65
+ p += out.write('\\157', p, 'latin1');
66
+ else
67
+ out[p++] = b;
68
+ }
69
+ out[p++] = 0x29;
70
+ return out.toString('latin1', 0, p);
71
+ }
72
+ /** The shortest digits that read back as `n`, written out in full because PDF has no exponent form. */
73
+ function serializeNumber(n) {
74
+ if (!Number.isFinite(n) || n === 0)
75
+ return '0';
76
+ const s = String(n);
77
+ const e = s.indexOf('e');
78
+ if (e < 0)
79
+ return s;
80
+ const sign = n < 0 ? '-' : '';
81
+ const digits = s.slice(sign.length, e).replace('.', '');
82
+ const exp = Number(s.slice(e + 1));
83
+ return exp < 0 ? `${sign}0.${'0'.repeat(-exp - 1)}${digits}` : sign + digits + '0'.repeat(exp + 1 - digits.length);
84
+ }
85
+ /** Serializes an object. `mapRef` renumbers references and may return null to drop one. */
86
+ function serialize(obj, mapRef = r => r) {
87
+ if (obj === null)
88
+ return 'null';
89
+ if (obj === true)
90
+ return 'true';
91
+ if (obj === false)
92
+ return 'false';
93
+ if (typeof obj === 'number')
94
+ return serializeNumber(obj);
95
+ if (obj instanceof objects_1.PdfName)
96
+ return serializeName(obj.name);
97
+ if (obj instanceof objects_1.PdfString)
98
+ return serializeString(obj.bytes);
99
+ if (obj instanceof objects_1.PdfRef) {
100
+ const r = mapRef(obj);
101
+ return r ? `${r.num} ${r.gen} R` : 'null';
102
+ }
103
+ if (Array.isArray(obj))
104
+ return `[${obj.map(v => serialize(v, mapRef)).join(' ')}]`;
105
+ if (obj instanceof objects_1.PdfDict) {
106
+ let s = '<<';
107
+ for (const [k, v] of obj.map)
108
+ s += `${serializeName(k)} ${serialize(v, mapRef)}`;
109
+ return `${s}>>`;
110
+ }
111
+ throw new Error('Cannot serialize object');
112
+ }
package/package.json CHANGED
@@ -1,6 +1,88 @@
1
1
  {
2
2
  "name": "@patrickdk77/pdf-defuse",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.2",
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
+ "license": "MIT",
6
+ "author": "patrickdk77",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "sideEffects": false,
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "bin": {
20
+ "pdf-defuse": "dist/cli.js"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "engines": {
28
+ "node": ">=22.0.0"
29
+ },
30
+ "scripts": {
31
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('build-test',{recursive:true,force:true})\"",
32
+ "build": "tsc -p tsconfig.json && node scripts/write-esm-wrapper.js",
33
+ "build:test": "tsc -p tsconfig.test.json",
34
+ "lint": "biome check .",
35
+ "format": "biome format --write .",
36
+ "test": "npm run lint && npm run build && npm run build:test && mocha --timeout 120000 \"build-test/test/**/*.test.js\"",
37
+ "validate": "npm run build && node scripts/validate-corpus.js",
38
+ "prepare": "husky || true",
39
+ "prepublishOnly": "npm run clean && npm test"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/patrickdk77/pdf-defuse.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/patrickdk77/pdf-defuse/issues"
47
+ },
48
+ "homepage": "https://github.com/patrickdk77/pdf-defuse",
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "keywords": [
53
+ "pdf",
54
+ "sanitize",
55
+ "sanitise",
56
+ "scrub",
57
+ "cleaner",
58
+ "cleaning",
59
+ "disarm",
60
+ "defuse",
61
+ "defang",
62
+ "cdr",
63
+ "strip",
64
+ "security",
65
+ "inspect",
66
+ "scanner",
67
+ "detect",
68
+ "encrypted",
69
+ "decrypt",
70
+ "attachments"
71
+ ],
72
+ "devDependencies": {
73
+ "@biomejs/biome": "^2.5.15",
74
+ "@cantoo/pdf-lib": "^2.11.1",
75
+ "@types/chai": "^5.2.3",
76
+ "@types/mocha": "^10.0.10",
77
+ "@types/node": "^22.20.5",
78
+ "chai": "^6.3.0",
79
+ "husky": "^9.1.7",
80
+ "lint-staged": "^17.6.0",
81
+ "mocha": "^12.0.3",
82
+ "pdfjs-dist": "^6.4.299",
83
+ "typescript": "^7.0.2"
84
+ },
85
+ "lint-staged": {
86
+ "*.{ts,js,mjs,cjs,json,jsonc}": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true"
87
+ }
88
+ }