@bluefields/extractor 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.
@@ -0,0 +1,458 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /**
3
+ * Mozilla Readability + Turndown pipeline (Review #6).
4
+ *
5
+ * Pulls the "main content" from an HTML page (sidebar/nav/ads stripped),
6
+ * then converts to GitHub-Flavored Markdown for downstream consumption.
7
+ *
8
+ * Readability is the same library Firefox Reader View uses — battle-tested
9
+ * across millions of articles. It's not perfect (especially on
10
+ * non-article pages like pricing tables or app dashboards), but it's the
11
+ * established baseline. Site-specific overrides land in host_extractors
12
+ * (chunk M14-17).
13
+ *
14
+ * Robustness contract (perf-extract): `extractMarkdown` NEVER throws and
15
+ * NEVER builds a DOM for input that could OOM or stack-overflow the
16
+ * process. Pathological inputs (oversized bodies, binary blobs, 2500+-deep
17
+ * nesting) degrade to a bounded tag-strip and report WHY via the
18
+ * `degraded` field — see guards.ts for the measured ceilings.
19
+ */
20
+ import { Readability } from '@mozilla/readability';
21
+ import { JSDOM } from 'jsdom';
22
+ import TurndownService from 'turndown';
23
+ import { MAX_CAPTURED_TABLES, MAX_CAPTURE_TABLE_ROWS, MAX_DEGRADED_TEXT_CHARS, MAX_EXTRACT_HTML_CHARS, MAX_RECOVERY_CONTENT_CHARS, MAX_SAFE_NESTING_DEPTH, MAX_SINGLE_TABLE_CHARS, MAX_TABLE_CELLS, MAX_TABLE_COLS, MAX_TOTAL_CAPTURED_CHARS, estimateMaxNestingDepth, looksLikeBinary, stripScriptStyleBlocks, stripTagsLinear, } from './guards.js';
24
+ // v3 — adds input ceilings + typed degrade signaling + table-recovery caps
25
+ // (perf-extract). Output changes ONLY for pathological pages (oversized,
26
+ // binary, table-recovery beyond caps); bumping invalidates the extractions
27
+ // cache so any such page cached under v2 gets re-extracted under the new
28
+ // guards instead of serving a mixed-format row.
29
+ export const READABILITY_VERSION = 'readability-v3';
30
+ /**
31
+ * Extract main-content markdown from an HTML string.
32
+ *
33
+ * `url` is the source URL — used by Readability to resolve relative
34
+ * links + by Turndown for absolute-URL preservation. Pass the
35
+ * canonicalized URL (so the resolved hrefs in the markdown are stable
36
+ * across re-fetches).
37
+ *
38
+ * Never throws: binary input, oversized bodies (> MAX_EXTRACT_HTML_CHARS),
39
+ * pathologically deep markup, and any parse failure degrade to a bounded
40
+ * iterative plain-text strip instead of propagating — with the cause
41
+ * reported in `degraded`. The contract is "garbage in, best-effort text
42
+ * out" — callers can treat the result as always-present.
43
+ */
44
+ export function extractMarkdown(html, url, opts = {}) {
45
+ // Table recovery is a win on data/reference pages but a liability on news
46
+ // articles (it re-adds non-content tables as false positives). Page-type
47
+ // routing toggles this; default on.
48
+ const recoverTables = opts.recoverTables ?? true;
49
+ // Binary sniff first — cheapest guard, and tag-stripping a PNG would
50
+ // emit multi-MB garbage "text" downstream. Typed empty result instead.
51
+ if (looksLikeBinary(html)) {
52
+ return emptyResult({
53
+ reasons: ['binary_input'],
54
+ detail: 'input looks like a binary format, not HTML/text',
55
+ });
56
+ }
57
+ // Size ceiling BEFORE any DOM work. Measured: jsdom peaks at ~270 MB RSS
58
+ // per input MB (2x for tables) — see guards.ts. Oversized bodies get a
59
+ // bounded tag-strip of the prefix we would otherwise have parsed.
60
+ if (html.length > MAX_EXTRACT_HTML_CHARS) {
61
+ return degradedTextResult(html.slice(0, MAX_EXTRACT_HTML_CHARS), {
62
+ reasons: ['input_too_large'],
63
+ detail: `input ${html.length} chars > ${MAX_EXTRACT_HTML_CHARS}`,
64
+ });
65
+ }
66
+ // Pre-flight depth guard (F-2). jsdom's serializer and Readability's DOM
67
+ // walks recurse with the markup depth, so ~5000-deep nesting overflows the
68
+ // stack and a ~4000-deep page already costs ~20s just to parse. Detect that
69
+ // on the raw string and skip the DOM entirely — degrade to a tag strip.
70
+ if (estimateMaxNestingDepth(html) > MAX_SAFE_NESTING_DEPTH) {
71
+ return degradedTextResult(html, {
72
+ reasons: ['nesting_too_deep'],
73
+ detail: `estimated nesting depth > ${MAX_SAFE_NESTING_DEPTH}`,
74
+ });
75
+ }
76
+ try {
77
+ return extractViaReadability(html, url, recoverTables);
78
+ }
79
+ catch {
80
+ // Belt-and-suspenders: any unforeseen deep-recursion / parser failure
81
+ // degrades to text rather than throwing.
82
+ return degradedTextResult(html, { reasons: ['parse_failed'] });
83
+ }
84
+ }
85
+ function extractViaReadability(html, url, recoverTables) {
86
+ const dom = new JSDOM(html, { url });
87
+ const doc = dom.window.document;
88
+ // Strip UI noise (e.g. MediaWiki "[edit]" section links) before Readability:
89
+ // they are link-dense enough that Readability drops the whole heading wrapper,
90
+ // collapsing distinct sections together and losing their headings.
91
+ stripPreReadabilityNoise(doc);
92
+ // Capture significant data tables BEFORE Readability runs: it mutates the
93
+ // document and frequently strips large/link-dense tables it scores as
94
+ // clutter, so on reference/data pages the main table would otherwise be lost
95
+ // entirely (Turndown's table rule never receives it).
96
+ const capture = recoverTables
97
+ ? collectSignificantTables(doc)
98
+ : { tables: [], capped: false, cappedDetail: undefined };
99
+ // The page's own <h1> is usually the clean title; Readability's article.title
100
+ // is often derived from <title> and carries a " — Site" suffix. Capture it now
101
+ // (before Readability mutates the DOM) so we can strip that suffix.
102
+ const pageH1 = doc.querySelector('h1')?.textContent ?? null;
103
+ // keepClasses preserves `language-*` on <code> so Turndown's preCode rule can
104
+ // emit a fenced-code language hint (F-1); Readability strips all classes
105
+ // otherwise. Classes never reach the markdown output, so this is invisible
106
+ // everywhere except that one rule.
107
+ const article = new Readability(doc, { keepClasses: true }).parse();
108
+ if (!article) {
109
+ // Readability gave up — fall back to body text.
110
+ const bodyText = dom.window.document.body?.textContent?.trim() ?? '';
111
+ return {
112
+ title: null,
113
+ markdown: bodyText,
114
+ text: bodyText,
115
+ excerpt: null,
116
+ byline: null,
117
+ siteName: null,
118
+ length: bodyText.length,
119
+ };
120
+ }
121
+ const turndown = makeTurndown();
122
+ // Re-attach significant tables Readability dropped so the table rule can
123
+ // convert them — otherwise the data (often the page's whole point) is gone.
124
+ const recovery = recoverTables
125
+ ? recoverDroppedTables(article.content, capture.tables, url)
126
+ : { html: '', capped: false };
127
+ const contentHtml = recovery.html ? `${article.content}\n${recovery.html}` : article.content;
128
+ const body = turndown.turndown(contentHtml);
129
+ // Prefer the page <h1> over a suffixed article.title (see cleanTitle).
130
+ const title = cleanTitle(article.title ?? '', pageH1);
131
+ // Readability extracts the title separately and strips the source <h1> from
132
+ // article.content, so the converted body has no title heading. Restore it
133
+ // (this otherwise drops the lead heading and depresses content recall).
134
+ const markdown = prependTitleHeading(body, title);
135
+ // A tripped capture/recovery cap means table recovery was not fully
136
+ // attempted — signal it, the rest of the extraction is still first-class.
137
+ const capped = capture.capped || recovery.capped;
138
+ return {
139
+ title: title || null,
140
+ markdown,
141
+ text: article.textContent ?? null,
142
+ excerpt: article.excerpt ?? null,
143
+ byline: article.byline ?? null,
144
+ siteName: article.siteName ?? null,
145
+ length: markdown.length,
146
+ ...(capped
147
+ ? {
148
+ degraded: {
149
+ reasons: ['table_recovery_capped'],
150
+ detail: capture.cappedDetail ?? 'article content too large for recovery re-parse',
151
+ },
152
+ }
153
+ : {}),
154
+ };
155
+ }
156
+ /** Empty typed result for input we refuse to process at all (binary). */
157
+ function emptyResult(degraded) {
158
+ return {
159
+ title: null,
160
+ markdown: '',
161
+ text: '',
162
+ excerpt: null,
163
+ byline: null,
164
+ siteName: null,
165
+ length: 0,
166
+ degraded,
167
+ };
168
+ }
169
+ /**
170
+ * Iterative HTML→text fallback for input we won't risk in the DOM. Drops
171
+ * script/style bodies (indexOf scan — the old lazy regex backtracked
172
+ * quadratically on unclosed-`<script` bombs), strips tags, and decodes the
173
+ * few entities common in visible text. Never recurses, so it can't overflow
174
+ * on pathological depth. Output is capped at MAX_DEGRADED_TEXT_CHARS.
175
+ */
176
+ function degradedTextResult(html, degraded) {
177
+ let text = stripTagsLinear(stripScriptStyleBlocks(html))
178
+ .replace(/&nbsp;/gi, ' ')
179
+ .replace(/&amp;/gi, '&')
180
+ .replace(/&lt;/gi, '<')
181
+ .replace(/&gt;/gi, '>')
182
+ .replace(/&#39;|&apos;/gi, "'")
183
+ .replace(/&quot;/gi, '"')
184
+ .replace(/\s+/g, ' ')
185
+ .trim();
186
+ const reasons = [...degraded.reasons];
187
+ if (text.length > MAX_DEGRADED_TEXT_CHARS) {
188
+ text = text.slice(0, MAX_DEGRADED_TEXT_CHARS);
189
+ reasons.push('output_truncated');
190
+ }
191
+ return {
192
+ title: null,
193
+ markdown: text,
194
+ text,
195
+ excerpt: null,
196
+ byline: null,
197
+ siteName: null,
198
+ length: text.length,
199
+ degraded: { reasons, detail: degraded.detail },
200
+ };
201
+ }
202
+ /**
203
+ * Prefer the page's own `<h1>` text over Readability's `article.title` when the
204
+ * h1 is a (shorter) case-insensitive prefix of it — Readability often derives
205
+ * the title from the `<title>` tag, which carries a " — SiteName" suffix the
206
+ * page `<h1>` does not. The prefix check guards against a wrong/banner `<h1>`.
207
+ */
208
+ function cleanTitle(rawTitle, pageH1) {
209
+ const title = rawTitle.trim();
210
+ const h1 = (pageH1 ?? '').replace(/\s+/g, ' ').trim();
211
+ if (h1 && h1.length < title.length && title.toLowerCase().startsWith(h1.toLowerCase())) {
212
+ return h1;
213
+ }
214
+ return title;
215
+ }
216
+ /**
217
+ * Prepend the page title as a top-level (`#`) heading. Readability returns the
218
+ * title separately and drops the source `<h1>` from the content body, which
219
+ * leaves the markdown with no title and lowers content recall. Skips
220
+ * prepending when the body already opens with that title (some pages keep
221
+ * their `<h1>` in content), to avoid a duplicate heading.
222
+ */
223
+ function prependTitleHeading(body, rawTitle) {
224
+ const title = rawTitle.trim();
225
+ if (!title)
226
+ return body;
227
+ const firstLine = body.replace(/^\s+/, '').split('\n', 1)[0] ?? '';
228
+ const normalize = (s) => s
229
+ .replace(/^#+\s*/, '')
230
+ .trim()
231
+ .toLowerCase();
232
+ if (normalize(firstLine) === title.toLowerCase())
233
+ return body;
234
+ return `# ${title}\n\n${body}`;
235
+ }
236
+ /**
237
+ * Remove UI noise that confuses Readability's content cleaning, run before it.
238
+ * MediaWiki `[edit]` section links (`.mw-editsection`) are link-dense enough
239
+ * that Readability drops the entire heading wrapper, collapsing sections and
240
+ * losing their headings; removing them recovers the section headings AND strips
241
+ * the `[edit]` clutter from the output. No-op on non-MediaWiki pages.
242
+ */
243
+ function stripPreReadabilityNoise(doc) {
244
+ for (const el of Array.from(doc.querySelectorAll('.mw-editsection, .mw-editsection-bracket'))) {
245
+ el.remove();
246
+ }
247
+ }
248
+ // Recover a DROPPED table only when it is a SUBSTANTIAL data table. Small
249
+ // dropped tables on article pages are almost always chrome ("related",
250
+ // "most read") — recovering them adds noise and hurts article extraction;
251
+ // large dropped tables on data/reference pages ARE the content. In effect this
252
+ // is page-type routing: article pages drop only small tables (nothing recovered
253
+ // → lean Readability path), data/reference pages drop big ones (recovered).
254
+ const RECOVER_MIN_ROWS = 12;
255
+ const RECOVER_MIN_TEXT = 1200;
256
+ /** Heading-row text — a stable identity for a table, used to dedupe recoveries. */
257
+ function tableSignature(table) {
258
+ const firstRow = table.querySelector('tr');
259
+ return (firstRow?.textContent ?? '').replace(/\s+/g, ' ').trim().slice(0, 120);
260
+ }
261
+ /**
262
+ * A table worth recovering = a real data table, not a layout/nav/infobox box.
263
+ * Requires ≥ 3 rows and ≥ 2 columns, and excludes known chrome containers.
264
+ */
265
+ function isSignificantDataTable(table) {
266
+ const cls = (table.getAttribute('class') ?? '').toLowerCase();
267
+ if (/\b(navbox|vertical-navbox|sidebar|infobox|metadata|nomobile|toccolours|noprint)\b/.test(cls)) {
268
+ return false;
269
+ }
270
+ if (table.closest('.navbox, .vertical-navbox, .sidebar, .infobox, nav, header, footer')) {
271
+ return false;
272
+ }
273
+ const rows = Array.from(table.querySelectorAll('tr'));
274
+ if (rows.length < 3)
275
+ return false;
276
+ const maxCols = rows.reduce((m, r) => Math.max(m, r.querySelectorAll('th, td').length), 0);
277
+ return maxCols >= 2;
278
+ }
279
+ /**
280
+ * Snapshot significant data tables (outerHTML + signature) from a document,
281
+ * under the guards.ts ceilings: giant tables are rejected by row count
282
+ * BEFORE their outerHTML is serialized (serialization itself is the
283
+ * allocation we're guarding), and the total snapshot budget is bounded so a
284
+ * many-tables page can't duplicate the whole document into strings.
285
+ */
286
+ function collectSignificantTables(doc) {
287
+ const tables = [];
288
+ let totalChars = 0;
289
+ let capped = false;
290
+ let cappedDetail;
291
+ for (const t of Array.from(doc.querySelectorAll('table'))) {
292
+ if (!isSignificantDataTable(t))
293
+ continue;
294
+ if (tables.length >= MAX_CAPTURED_TABLES) {
295
+ capped = true;
296
+ cappedDetail = `more than ${MAX_CAPTURED_TABLES} significant tables`;
297
+ break;
298
+ }
299
+ const rows = t.querySelectorAll('tr').length;
300
+ if (rows > MAX_CAPTURE_TABLE_ROWS) {
301
+ capped = true;
302
+ cappedDetail = `table with ${rows} rows > ${MAX_CAPTURE_TABLE_ROWS}`;
303
+ continue;
304
+ }
305
+ const html = t.outerHTML;
306
+ if (html.length > MAX_SINGLE_TABLE_CHARS ||
307
+ totalChars + html.length > MAX_TOTAL_CAPTURED_CHARS) {
308
+ capped = true;
309
+ cappedDetail = 'table snapshot budget exceeded';
310
+ continue;
311
+ }
312
+ totalChars += html.length;
313
+ tables.push({
314
+ html,
315
+ signature: tableSignature(t),
316
+ rows,
317
+ textLen: (t.textContent ?? '').replace(/\s+/g, ' ').trim().length,
318
+ });
319
+ }
320
+ return { tables, capped, cappedDetail };
321
+ }
322
+ /**
323
+ * HTML of captured tables NOT already present in the Readability content
324
+ * (matched by heading-row signature, so a table Readability kept is never
325
+ * duplicated). Empty string when there is nothing to recover. Skips the
326
+ * whole re-parse (a SECOND jsdom of the article content — the expensive
327
+ * half of recovery) when the content is too large to risk.
328
+ */
329
+ function recoverDroppedTables(contentHtml, captured, url) {
330
+ if (captured.length === 0)
331
+ return { html: '', capped: false };
332
+ if (contentHtml.length > MAX_RECOVERY_CONTENT_CHARS) {
333
+ return { html: '', capped: true };
334
+ }
335
+ const presentDoc = new JSDOM(contentHtml, { url }).window.document;
336
+ const present = new Set(collectSignificantTables(presentDoc).tables.map((t) => t.signature));
337
+ const html = captured
338
+ .filter((t) => !present.has(t.signature))
339
+ .filter((t) => t.rows >= RECOVER_MIN_ROWS || t.textLen >= RECOVER_MIN_TEXT)
340
+ .map((t) => t.html)
341
+ .join('\n');
342
+ return { html, capped: false };
343
+ }
344
+ /**
345
+ * Canonicalize a raw highlighter token into a Markdown fence language. Drops
346
+ * non-languages ("default"/"text"/"plaintext"/"none") and normalizes a couple
347
+ * of aliases so a scraping customer gets a stable, conventional tag.
348
+ */
349
+ function normalizeFenceLang(raw) {
350
+ const l = (raw ?? '').trim().toLowerCase();
351
+ if (!l || l === 'default' || l === 'text' || l === 'plaintext' || l === 'none')
352
+ return '';
353
+ if (l === 'python3' || l === 'python2')
354
+ return 'python';
355
+ return l;
356
+ }
357
+ /**
358
+ * Derive a code-fence language from a <pre> node by checking, in order:
359
+ * 1. `language-xxx` / `lang-xxx` on the <code> (or <pre>) — highlight.js etc.
360
+ * 2. `highlight-source-xxx` on an ancestor — GitHub's blob wrapper.
361
+ * 3. `highlight-xxx` on an ancestor — Sphinx / Python-docs wrapper (the <pre>
362
+ * has no language class of its own; the lang lives on a grandparent div).
363
+ * Returns '' for a genuinely bare fence (no convention matched).
364
+ */
365
+ function detectFenceLang(pre) {
366
+ const code = pre.querySelector('code');
367
+ for (const el of [code, pre]) {
368
+ const m = el?.className?.match(/(?:^|\s)(?:language|lang)-([a-zA-Z0-9+#.-]+)/);
369
+ if (m)
370
+ return normalizeFenceLang(m[1]);
371
+ }
372
+ // Walk a few ancestors for the wrapper conventions. GitHub before Sphinx,
373
+ // since `highlight-source-go` also matches the looser `highlight-xxx` pattern.
374
+ let node = pre;
375
+ for (let depth = 0; node && depth < 4; depth++) {
376
+ const cls = node.className ?? '';
377
+ const gh = cls.match(/(?:^|\s)highlight-source-([a-zA-Z0-9+#.-]+)/);
378
+ if (gh)
379
+ return normalizeFenceLang(gh[1]);
380
+ const sphinx = cls.match(/(?:^|\s)highlight-([a-zA-Z0-9+#.-]+)/);
381
+ if (sphinx)
382
+ return normalizeFenceLang(sphinx[1]);
383
+ node = node.parentElement;
384
+ }
385
+ return '';
386
+ }
387
+ function makeTurndown() {
388
+ const td = new TurndownService({
389
+ headingStyle: 'atx',
390
+ codeBlockStyle: 'fenced',
391
+ bulletListMarker: '-',
392
+ emDelimiter: '_',
393
+ strongDelimiter: '**',
394
+ linkStyle: 'inlined',
395
+ });
396
+ // Preserve <pre> code blocks with their language hint. The hint lives under
397
+ // several conventions across the web; detectFenceLang covers the common ones
398
+ // so a bare fence doesn't silently drop information a scraping customer asked
399
+ // for (syntax highlighting, language routing).
400
+ td.addRule('preCode', {
401
+ filter: ['pre'],
402
+ replacement: (_content, node) => {
403
+ const pre = node;
404
+ const codeEl = pre.querySelector('code');
405
+ const lang = detectFenceLang(pre);
406
+ const code = codeEl?.textContent ?? pre.textContent ?? '';
407
+ return `\n\n\`\`\`${lang}\n${code}\n\`\`\`\n\n`;
408
+ },
409
+ });
410
+ // Convert <table> to GitHub-flavored Markdown. Turndown has no table
411
+ // support by default, so a data table would otherwise collapse into a run
412
+ // of concatenated cell text and lose all structure.
413
+ td.addRule('gfmTable', {
414
+ filter: 'table',
415
+ replacement: (_content, node) => {
416
+ const rows = Array.from(node.querySelectorAll('tr'));
417
+ if (rows.length === 0)
418
+ return '';
419
+ const cellsOf = (tr) => Array.from(tr.querySelectorAll('th, td')).map((c) => (c.textContent ?? '')
420
+ .trim()
421
+ .replace(/\s*\n\s*/g, ' ')
422
+ .replace(/\|/g, '\\|'));
423
+ let grid = rows.map(cellsOf).filter((r) => r.length > 0);
424
+ if (grid.length === 0)
425
+ return '';
426
+ // reduce, not Math.max(...spread): the spread throws RangeError past
427
+ // ~65k rows, which would degrade the whole page instead of the table.
428
+ const rawCols = grid.reduce((m, r) => Math.max(m, r.length), 0);
429
+ // Geometry ceilings (perf-extract): padding multiplies the widest row
430
+ // across ALL rows, so a single ragged 100k-cell row would otherwise
431
+ // blow the output up to hundreds of MB. Far above any legit corpus
432
+ // table — output is byte-identical for sane pages.
433
+ const cols = Math.min(rawCols, MAX_TABLE_COLS);
434
+ let clipped = rawCols > MAX_TABLE_COLS;
435
+ const maxRows = Math.max(2, Math.floor(MAX_TABLE_CELLS / Math.max(1, cols)));
436
+ if (grid.length > maxRows) {
437
+ grid = grid.slice(0, maxRows);
438
+ clipped = true;
439
+ }
440
+ const pad = (r) => {
441
+ const out = r.slice(0, cols);
442
+ while (out.length < cols)
443
+ out.push('');
444
+ return out;
445
+ };
446
+ const line = (r) => `| ${pad(r).join(' | ')} |`;
447
+ const header = line(grid[0]);
448
+ const sep = `| ${Array.from({ length: cols }, () => '---').join(' | ')} |`;
449
+ const body = grid.slice(1).map(line);
450
+ const note = clipped ? '\n\n_(table truncated: exceeds size limits)_' : '';
451
+ return `\n\n${[header, sep, ...body].join('\n')}${note}\n\n`;
452
+ },
453
+ });
454
+ // Drop noisy elements that survive Readability sometimes.
455
+ td.remove(['script', 'style', 'noscript', 'iframe']);
456
+ return td;
457
+ }
458
+ //# sourceMappingURL=readability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readability.js","sourceRoot":"","sources":["../src/readability.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Sitemap discovery + parsing (Phase B).
3
+ *
4
+ * Sources for /map URL discovery:
5
+ * 1. `robots.txt` Sitemap: directive(s)
6
+ * 2. `/sitemap.xml` at the host root
7
+ * 3. Recursive: sitemapindex → sub-sitemaps → urls
8
+ *
9
+ * Sitemap XML is constrained enough that a regex parse beats a full
10
+ * XML parser dep. Both `<urlset>` and `<sitemapindex>` use the same
11
+ * `<loc>` element; we discriminate by the wrapping element to decide
12
+ * whether each `<loc>` is a URL or a nested sitemap.
13
+ */
14
+ export interface ParsedSitemap {
15
+ /** Direct page URLs from <urlset>/<url>/<loc>. */
16
+ urls: string[];
17
+ /** Sub-sitemap URLs from <sitemapindex>/<sitemap>/<loc>. */
18
+ subsitemaps: string[];
19
+ }
20
+ /**
21
+ * Parse a sitemap XML blob. Returns separately the page URLs and the
22
+ * nested sub-sitemap URLs. A document can technically contain both
23
+ * (rare but allowed); we split based on whether the document has
24
+ * <sitemapindex> as a top-level element.
25
+ */
26
+ export declare function parseSitemapXml(xml: string): ParsedSitemap;
27
+ /**
28
+ * Pull `Sitemap:` directives out of a robots.txt body.
29
+ *
30
+ * Per https://datatracker.ietf.org/doc/html/rfc9309, Sitemap is a
31
+ * separate field with absolute-URL value. Case-insensitive.
32
+ */
33
+ export declare function parseRobotsTxtForSitemaps(robotsTxt: string): string[];
34
+ /**
35
+ * Discover URLs for a host via the standard cascade.
36
+ *
37
+ * - First fetches `/robots.txt` and extracts any Sitemap: directives.
38
+ * - If none, falls back to `/sitemap.xml` at the host root.
39
+ * - Recursively follows sitemapindex → sub-sitemaps (bounded by
40
+ * `maxSitemapsToFollow` to avoid infinite loops / sitemap bombs).
41
+ *
42
+ * Pure — the caller (`/map` route) passes in a fetchImpl so SSRF + the
43
+ * shared dispatcher live at the boundary.
44
+ */
45
+ export interface DiscoverUrlsOptions {
46
+ fetchImpl: typeof fetch;
47
+ /** Max distinct sitemap URLs to fetch (prevents bombs). Default 20. */
48
+ maxSitemapsToFollow?: number;
49
+ /** Max URLs to collect (stops early once hit). Default 10_000. */
50
+ limit?: number;
51
+ /** Include subdomains? Default false (same-host only). */
52
+ includeSubdomains?: boolean;
53
+ /** Substring filter on URLs. */
54
+ search?: string;
55
+ /** Per-fetch timeout ms. Default 10_000. */
56
+ timeoutMs?: number;
57
+ /** Optional override for the user agent. */
58
+ userAgent?: string;
59
+ /**
60
+ * SSRF pre-flight for each discovered sitemap URL. The /map route passes
61
+ * validateUrlForExternalRequest; child sitemaps come from a target's
62
+ * robots.txt `Sitemap:` directives / sitemapindex `<loc>` and are otherwise
63
+ * attacker-controlled. A rejected URL is skipped. (PENTEST_REPORT.md P5.)
64
+ */
65
+ validateUrl?: (url: string) => Promise<void>;
66
+ }
67
+ export interface DiscoverUrlsResult {
68
+ urls: string[];
69
+ /** Distinct sitemap URLs we actually fetched. */
70
+ sitemapsConsidered: string[];
71
+ /** True if we stopped because we hit the URL limit. */
72
+ truncated: boolean;
73
+ }
74
+ export declare function discoverUrls(startUrl: string, options: DiscoverUrlsOptions): Promise<DiscoverUrlsResult>;