@normativepdf/recover 0.1.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.
@@ -0,0 +1,383 @@
1
+ /**
2
+ * 相互参照チェーンの歩きと、その**回復方針**。
3
+ *
4
+ * normativepdf は §7.5 を条文どおりに読み、回復方針を持たない —— `readXrefSectionAt` の
5
+ * doc コメントが「自分で歩く消費者のために公開している。第 1 消費者は pdf-verify-mcp の
6
+ * revision diff」と名指ししているとおり、ここがその方針の置き場である。
7
+ *
8
+ * **なぜ 1 か所なのか**(2026-08-28 の実測。ADR-0010 が器を決めた):
9
+ * この方針は revision diff だけのものではない。文書を開く入口(`pdf-parser.ts`)も
10
+ * 同じ判断を要る —— コーパス 2,947 検体のうち 17 件は、入口の節や `startxref` が
11
+ * 条文に反していて `parsePdf` が受け取らない。方針が 2 か所にあると、
12
+ * 「この文書をどこまで読んだか」の答えがツールによって割れる。
13
+ *
14
+ * 方針は 4 つ:
15
+ * 1. 最後の `startxref` が読めなければ、古い入口を順に試す(試したことは申告する)
16
+ * 2. `/Prev` が正の整数でなければ、そこで打ち切る —— 「きれいに終わった」とは言わない
17
+ * 3. 巡回と `MAX_REVISIONS` で止める
18
+ * 4. 線形化(Annex F)の 2 節は 1 リビジョンに畳む
19
+ *
20
+ * ここに判定は書かない。どこまで読めたかを返すだけで、それをどう読むかは呼び出し側が決める。
21
+ */
22
+ import { ByteCursor, dictGet, parseIndirectObject, parseObject, readXrefSectionAt, TokenReader, } from 'normativepdf';
23
+ const CONTEXT = 'xref-walk';
24
+ /** Guard against a malformed `/Prev` cycle. */
25
+ const MAX_REVISIONS = 200;
26
+ /** How far into the file the linearisation dictionary is looked for. */
27
+ const LINEARIZED_HEADER_SCAN = 1024;
28
+ export const LATIN1 = new TextDecoder('latin1');
29
+ /* ------------------------------------------------------------------ *
30
+ * byte helpers
31
+ * ------------------------------------------------------------------ */
32
+ export function isWhitespace(byte) {
33
+ return (byte === 0x20 ||
34
+ byte === 0x0a ||
35
+ byte === 0x0d ||
36
+ byte === 0x09 ||
37
+ byte === 0x0c ||
38
+ byte === 0x00);
39
+ }
40
+ export function isDelimiter(byte) {
41
+ return (byte === 0x28 || // (
42
+ byte === 0x29 || // )
43
+ byte === 0x3c || // <
44
+ byte === 0x3e || // >
45
+ byte === 0x5b || // [
46
+ byte === 0x5d || // ]
47
+ byte === 0x7b || // {
48
+ byte === 0x7d || // }
49
+ byte === 0x2f || // /
50
+ byte === 0x25 // %
51
+ );
52
+ }
53
+ /** Zero-copy Buffer view, so keyword scans use the native search. */
54
+ function asBuffer(bytes) {
55
+ return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
56
+ }
57
+ export function indexOfBytes(hay, needle, from) {
58
+ return asBuffer(hay).indexOf(needle, Math.max(0, from), 'latin1');
59
+ }
60
+ export function skipWhitespace(bytes, index) {
61
+ let i = index;
62
+ while (i < bytes.length) {
63
+ if (isWhitespace(bytes[i])) {
64
+ i += 1;
65
+ continue;
66
+ }
67
+ // A comment runs to the end of the line and counts as whitespace.
68
+ if (bytes[i] === 0x25) {
69
+ while (i < bytes.length && bytes[i] !== 0x0a && bytes[i] !== 0x0d)
70
+ i += 1;
71
+ continue;
72
+ }
73
+ break;
74
+ }
75
+ return i;
76
+ }
77
+ /** Read the next whitespace/delimiter-terminated token. */
78
+ export function readToken(bytes, index) {
79
+ const start = skipWhitespace(bytes, index);
80
+ if (start >= bytes.length)
81
+ return null;
82
+ let end = start;
83
+ if (isDelimiter(bytes[end])) {
84
+ // Names and dictionary markers are handled by their own readers.
85
+ return { token: String.fromCharCode(bytes[end]), next: end + 1 };
86
+ }
87
+ while (end < bytes.length && !isWhitespace(bytes[end]) && !isDelimiter(bytes[end]))
88
+ end += 1;
89
+ return { token: LATIN1.decode(bytes.subarray(start, end)), next: end };
90
+ }
91
+ /* ------------------------------------------------------------------ *
92
+ * cross-reference sections — read by normativepdf, recovered here
93
+ * ------------------------------------------------------------------ */
94
+ /**
95
+ * §7.5.2 — "byte offsets shall be calculated from the PERCENT SIGN" of the
96
+ * `%PDF-` header, which need not be at byte 0. A file with no header at all is
97
+ * not dismissed here (that is the validator's verdict, not this module's): the
98
+ * origin falls back to 0 so the chain can still be described.
99
+ */
100
+ export function findOrigin(bytes) {
101
+ return Math.max(0, indexOfBytes(bytes, '%PDF-', 0));
102
+ }
103
+ async function readSection(bytes, origin, offset, onDebug) {
104
+ if (offset <= 0 || origin + offset >= bytes.length)
105
+ return null;
106
+ let section;
107
+ try {
108
+ section = await readXrefSectionAt(bytes, offset, origin);
109
+ }
110
+ catch (error) {
111
+ onDebug?.(CONTEXT, `cross-reference section at ${offset} is unreadable: ${String(error)}`);
112
+ return null;
113
+ }
114
+ return {
115
+ revision: {
116
+ offset: origin + section.offset,
117
+ kind: section.kind,
118
+ entries: new Map(section.entries),
119
+ selfObjectNumber: section.selfObjectNumber ?? null,
120
+ trailer: section.trailer,
121
+ },
122
+ prev: readPrev(section),
123
+ };
124
+ }
125
+ function readPrev(section) {
126
+ const prev = dictGet(section.trailer, 'Prev');
127
+ if (prev === undefined)
128
+ return { kind: 'end' };
129
+ if (prev.kind === 'integer' && prev.value > 0)
130
+ return { kind: 'at', offset: prev.value };
131
+ // `/Prev 0` と「整数ですらない `/Prev`」は別の事実として運ぶ(§7.5.5 Table 15)。
132
+ if (prev.kind === 'integer')
133
+ return { kind: 'zero' };
134
+ return { kind: 'malformed' };
135
+ }
136
+ /** Every `startxref` value in the file, in the order they appear. */
137
+ function collectStartxrefTargets(bytes) {
138
+ const targets = [];
139
+ let from = 0;
140
+ for (;;) {
141
+ const at = indexOfBytes(bytes, 'startxref', from);
142
+ if (at < 0)
143
+ break;
144
+ const token = readToken(bytes, at + 'startxref'.length);
145
+ if (token) {
146
+ const value = Number.parseInt(token.token, 10);
147
+ if (!Number.isNaN(value))
148
+ targets.push(value);
149
+ }
150
+ from = at + 'startxref'.length;
151
+ }
152
+ return targets;
153
+ }
154
+ /**
155
+ * Walk `startxref` → `/Prev` → … and return the revisions oldest first.
156
+ *
157
+ * Reading each section is normativepdf's job; everything below is the recovery
158
+ * policy that library deliberately leaves to its consumer.
159
+ */
160
+ export async function walkXrefChain(bytes, origin = findOrigin(bytes), options = {}) {
161
+ const targets = collectStartxrefTargets(bytes);
162
+ if (targets.length === 0)
163
+ return null;
164
+ // Normally the last `startxref` is the entry point. When it does not point
165
+ // at a parseable section the file still has to be described rather than
166
+ // dismissed, so an older entry point is tried — and the fact is reported,
167
+ // because it means the trailing bytes are NOT represented in the diff.
168
+ // The probe's result is kept: re-reading the entry section would decode the
169
+ // same (possibly Flate + predictor) cross-reference stream twice.
170
+ let entry = null;
171
+ let entrySection = null;
172
+ let newestSectionUnreadable = false;
173
+ for (let i = targets.length - 1; i >= 0; i -= 1) {
174
+ const probe = targets[i] > 0 ? await readSection(bytes, origin, targets[i], options.onDebug) : null;
175
+ if (probe) {
176
+ entry = targets[i];
177
+ entrySection = probe;
178
+ newestSectionUnreadable = i !== targets.length - 1;
179
+ break;
180
+ }
181
+ }
182
+ if (entry === null)
183
+ return null;
184
+ let next = entry;
185
+ let pending = entrySection;
186
+ const sections = [];
187
+ const visited = new Set();
188
+ let stop = { kind: 'complete' };
189
+ while (next !== null && next > 0) {
190
+ if (visited.has(next)) {
191
+ stop = { kind: 'cyclic', offset: next };
192
+ break;
193
+ }
194
+ visited.add(next);
195
+ const read = pending ?? (await readSection(bytes, origin, next, options.onDebug));
196
+ pending = null;
197
+ if (!read) {
198
+ stop = { kind: 'unreadable', offset: next, reason: 'no cross-reference section here' };
199
+ break;
200
+ }
201
+ sections.push(read.revision);
202
+ if (sections.length >= MAX_REVISIONS) {
203
+ stop = {
204
+ kind: 'unreadable',
205
+ offset: next,
206
+ reason: `stopped at ${MAX_REVISIONS} revisions`,
207
+ };
208
+ break;
209
+ }
210
+ if (read.prev.kind === 'end') {
211
+ next = null;
212
+ }
213
+ else if (read.prev.kind === 'at') {
214
+ next = read.prev.offset;
215
+ }
216
+ else {
217
+ // A `/Prev` that is present but not a direct positive integer means the
218
+ // chain does not end here — it just cannot be followed. Reporting that as
219
+ // a clean end would let `assessDocMdp` treat an unwalkable tail as "no
220
+ // older revisions", which is the mistake this whole module exists to
221
+ // avoid. [[revision-diff-lies-linearized-and-full-save]]
222
+ stop =
223
+ read.prev.kind === 'zero'
224
+ ? { kind: 'prev-zero', offset: 0 }
225
+ : { kind: 'malformed', offset: 0 };
226
+ next = null;
227
+ }
228
+ }
229
+ let continuedPastStop = false;
230
+ if (options.continuePastStop && stop.kind !== 'complete') {
231
+ // まだ訪ねていない `startxref` の値を、新しいものから順に試す。
232
+ // ここで足した節は `/Prev` が繋いだものではないので、そう申告する。
233
+ for (let i = targets.length - 1; i >= 0; i -= 1) {
234
+ const target = targets[i];
235
+ if (target <= 0 || visited.has(target))
236
+ continue;
237
+ const read = await readSection(bytes, origin, target, options.onDebug);
238
+ if (!read)
239
+ continue;
240
+ visited.add(target);
241
+ sections.push(read.revision);
242
+ continuedPastStop = true;
243
+ let follow = read.prev.kind === 'at' ? read.prev.offset : null;
244
+ while (follow !== null && follow > 0 && !visited.has(follow)) {
245
+ visited.add(follow);
246
+ const more = await readSection(bytes, origin, follow, options.onDebug);
247
+ if (!more)
248
+ break;
249
+ sections.push(more.revision);
250
+ follow = more.prev.kind === 'at' ? more.prev.offset : null;
251
+ }
252
+ }
253
+ }
254
+ const truncated = stop.kind !== 'complete';
255
+ if (sections.length === 0)
256
+ return null;
257
+ const ordered = sections.reverse();
258
+ // A linearised file (ISO 32000-2 Annex F) carries TWO cross-reference
259
+ // sections for a single save: the first-page section near the top of the
260
+ // file, whose /Prev points at the main section at the bottom. Walking the
261
+ // chain naively turns one save into two "revisions" and reports every object
262
+ // as added. The giveaway is that the newer section sits at a LOWER offset.
263
+ const linearized = ordered.length >= 2 &&
264
+ ordered[ordered.length - 1].offset < ordered[ordered.length - 2].offset &&
265
+ /\/Linearized\b/.test(LATIN1.decode(bytes.subarray(origin, origin + LINEARIZED_HEADER_SCAN)));
266
+ if (linearized) {
267
+ const firstPage = ordered.pop();
268
+ const main = ordered[ordered.length - 1];
269
+ if (firstPage && main) {
270
+ for (const [key, entry] of firstPage.entries)
271
+ main.entries.set(key, entry);
272
+ }
273
+ }
274
+ return {
275
+ origin,
276
+ sections: ordered,
277
+ stop,
278
+ truncated,
279
+ newestSectionUnreadable,
280
+ linearized,
281
+ continuedPastStop,
282
+ };
283
+ }
284
+ /* ------------------------------------------------------------------ *
285
+ * 相互参照節が 1 つも読めない文書 —— オブジェクトを数え上げて組み直す
286
+ * ------------------------------------------------------------------ */
287
+ /**
288
+ * ファイルの中の `N G obj` を数え上げて、相互参照表を組み直す。
289
+ *
290
+ * 🔴 **これは推測である。** ファイルが持っていない表をこちらが作るのだから、
291
+ * 使った文書ではそう申告しなければならない(`DocumentScope.reconstructed`)。
292
+ * それでも refuse より良いのは、監査に持ち込まれる文書こそ壊れているからで、
293
+ * 「読めません」だけを返すと**中に何が書いてあるかを誰も見ないまま終わる**。
294
+ *
295
+ * 呼ぶのは `walkXrefChain` が 1 節も読めなかったときだけ。
296
+ * 後ろにある定義が前の定義を上書きする(増分更新は後ろが新しい・§7.5.6)。
297
+ */
298
+ export function reconstructXref(bytes, origin) {
299
+ const entries = new Map();
300
+ for (let i = origin; i + 3 <= bytes.length; i += 1) {
301
+ if (bytes[i] !== 0x6f || bytes[i + 1] !== 0x62 || bytes[i + 2] !== 0x6a)
302
+ continue; // "obj"
303
+ if (i + 3 < bytes.length && !isWhitespace(bytes[i + 3]) && !isDelimiter(bytes[i + 3]))
304
+ continue;
305
+ // 直前を遡って `N G` を読む
306
+ let j = i - 1;
307
+ while (j >= origin && isWhitespace(bytes[j]))
308
+ j -= 1;
309
+ const genEnd = j + 1;
310
+ while (j >= origin && bytes[j] >= 0x30 && bytes[j] <= 0x39)
311
+ j -= 1;
312
+ const genStart = j + 1;
313
+ if (genStart === genEnd)
314
+ continue;
315
+ while (j >= origin && isWhitespace(bytes[j]))
316
+ j -= 1;
317
+ const numEnd = j + 1;
318
+ while (j >= origin && bytes[j] >= 0x30 && bytes[j] <= 0x39)
319
+ j -= 1;
320
+ const numStart = j + 1;
321
+ if (numStart === numEnd)
322
+ continue;
323
+ const objectNumber = Number.parseInt(LATIN1.decode(bytes.subarray(numStart, numEnd)), 10);
324
+ const generation = Number.parseInt(LATIN1.decode(bytes.subarray(genStart, genEnd)), 10);
325
+ if (!Number.isFinite(objectNumber) || !Number.isFinite(generation))
326
+ continue;
327
+ entries.set(objectNumber, { type: 'in-use', offset: numStart - origin, generation });
328
+ }
329
+ if (entries.size === 0)
330
+ return null;
331
+ // trailer は後ろにあるものが新しい。読めたものを新しい順に重ねる。
332
+ const merged = new Map();
333
+ for (const at of [...findAll(bytes, 'trailer', origin)].reverse()) {
334
+ try {
335
+ const reader = new TokenReader(new ByteCursor(bytes, at + 'trailer'.length));
336
+ const dict = parseObject(reader);
337
+ if (dict.kind !== 'dict')
338
+ continue;
339
+ for (const [key, value] of dict.entries)
340
+ if (!merged.has(key))
341
+ merged.set(key, value);
342
+ }
343
+ catch {
344
+ // 読めない trailer は飛ばす —— ここは推測の上の推測にしない
345
+ }
346
+ }
347
+ if (!merged.has('Root')) {
348
+ // trailer が無い(相互参照ストリームだけの文書)なら、/Type /Catalog を探す
349
+ for (const [objectNumber, entry] of entries) {
350
+ if (entry.type !== 'in-use')
351
+ continue;
352
+ try {
353
+ const reader = new TokenReader(new ByteCursor(bytes, origin + entry.offset));
354
+ const { object } = parseIndirectObject(reader);
355
+ if (object.kind !== 'dict')
356
+ continue;
357
+ const type = dictGet(object, 'Type');
358
+ if (type?.kind === 'name' && type.value === 'Catalog') {
359
+ merged.set('Root', { kind: 'ref', objectNumber, generationNumber: entry.generation });
360
+ break;
361
+ }
362
+ }
363
+ catch {
364
+ // 読めないオブジェクトは飛ばす
365
+ }
366
+ }
367
+ }
368
+ if (!merged.has('Root'))
369
+ return null;
370
+ return { entries, trailer: { kind: 'dict', entries: merged } };
371
+ }
372
+ /** バイト列に現れる `needle` の位置を全部返す。 */
373
+ function* findAll(bytes, needle, from) {
374
+ let at = from;
375
+ for (;;) {
376
+ at = indexOfBytes(bytes, needle, at);
377
+ if (at < 0)
378
+ return;
379
+ yield at;
380
+ at += needle.length;
381
+ }
382
+ }
383
+ //# sourceMappingURL=xref-walk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xref-walk.js","sourceRoot":"","sources":["../src/xref-walk.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@normativepdf/recover",
3
+ "version": "0.1.0",
4
+ "description": "Recovery policy for reading damaged PDF cross-reference chains. Walks startxref -> /Prev, rebuilds the table when no section can be read, and declares how far it got. It reads; it does not judge",
5
+ "keywords": [
6
+ "pdf",
7
+ "iso-32000",
8
+ "xref",
9
+ "recovery",
10
+ "cos",
11
+ "typescript"
12
+ ],
13
+ "author": "shuji-bonji",
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/shuji-bonji/recover.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/shuji-bonji/recover/issues"
21
+ },
22
+ "homepage": "https://github.com/shuji-bonji/recover#readme",
23
+ "type": "module",
24
+ "sideEffects": false,
25
+ "main": "dist/index.js",
26
+ "types": "dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.ja.md"
37
+ ],
38
+ "scripts": {
39
+ "check:engines": "node scripts/check-engines.mjs",
40
+ "build": "tsc",
41
+ "dev": "tsc --watch",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "typecheck": "tsc --noEmit",
45
+ "typecheck:tests": "tsc --noEmit -p tsconfig.test.json",
46
+ "lint": "biome lint ./src ./tests",
47
+ "lint:fix": "biome lint --fix ./src ./tests",
48
+ "format": "biome format ./src ./tests",
49
+ "format:fix": "biome format --write ./src ./tests",
50
+ "check": "biome check ./src ./tests",
51
+ "check:fix": "biome check --fix ./src ./tests",
52
+ "prepublishOnly": "npm run build"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public",
56
+ "provenance": true
57
+ },
58
+ "dependencies": {
59
+ "normativepdf": "0.9.0"
60
+ },
61
+ "devDependencies": {
62
+ "@biomejs/biome": "2.5.4",
63
+ "@types/node": "^22.0.0",
64
+ "typescript": "^7.0.2",
65
+ "vitest": "^3.2.0"
66
+ },
67
+ "engines": {
68
+ "node": ">=20.0.0"
69
+ }
70
+ }