@tenphi/akno-core 0.2.0 → 0.4.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.
Files changed (80) hide show
  1. package/README.md +4 -2
  2. package/config/default.jsonc +38 -15
  3. package/dist/config/load.d.ts +5 -2
  4. package/dist/config/load.d.ts.map +1 -1
  5. package/dist/config/load.js +43 -12
  6. package/dist/config/load.js.map +1 -1
  7. package/dist/config/schema.d.ts +27 -3
  8. package/dist/config/schema.d.ts.map +1 -1
  9. package/dist/config/schema.js +39 -4
  10. package/dist/config/schema.js.map +1 -1
  11. package/dist/config/write-setup.d.ts +2 -0
  12. package/dist/config/write-setup.d.ts.map +1 -1
  13. package/dist/config/write-setup.js +7 -3
  14. package/dist/config/write-setup.js.map +1 -1
  15. package/dist/doctor.d.ts +15 -5
  16. package/dist/doctor.d.ts.map +1 -1
  17. package/dist/doctor.js +18 -0
  18. package/dist/doctor.js.map +1 -1
  19. package/dist/index/indexer.d.ts +12 -6
  20. package/dist/index/indexer.d.ts.map +1 -1
  21. package/dist/index/indexer.js +41 -24
  22. package/dist/index/indexer.js.map +1 -1
  23. package/dist/ingest/extract.d.ts +14 -4
  24. package/dist/ingest/extract.d.ts.map +1 -1
  25. package/dist/ingest/extract.js +142 -26
  26. package/dist/ingest/extract.js.map +1 -1
  27. package/dist/ingest/extractor-contract.d.ts +63 -0
  28. package/dist/ingest/extractor-contract.d.ts.map +1 -0
  29. package/dist/ingest/extractor-contract.js +26 -0
  30. package/dist/ingest/extractor-contract.js.map +1 -0
  31. package/dist/ingest/fetch.d.ts +16 -13
  32. package/dist/ingest/fetch.d.ts.map +1 -1
  33. package/dist/ingest/fetch.js +176 -52
  34. package/dist/ingest/fetch.js.map +1 -1
  35. package/dist/ingest/linux-extractor.d.ts +32 -0
  36. package/dist/ingest/linux-extractor.d.ts.map +1 -0
  37. package/dist/ingest/linux-extractor.js +344 -0
  38. package/dist/ingest/linux-extractor.js.map +1 -0
  39. package/dist/kb/frontmatter.d.ts +15 -0
  40. package/dist/kb/frontmatter.d.ts.map +1 -1
  41. package/dist/kb/frontmatter.js +105 -9
  42. package/dist/kb/frontmatter.js.map +1 -1
  43. package/dist/maintenance/adopt.d.ts.map +1 -1
  44. package/dist/maintenance/adopt.js +2 -1
  45. package/dist/maintenance/adopt.js.map +1 -1
  46. package/dist/maintenance/curate.js +3 -3
  47. package/dist/maintenance/curate.js.map +1 -1
  48. package/dist/maintenance/dream.d.ts.map +1 -1
  49. package/dist/maintenance/dream.js +27 -17
  50. package/dist/maintenance/dream.js.map +1 -1
  51. package/dist/open.d.ts +1 -1
  52. package/dist/open.d.ts.map +1 -1
  53. package/dist/ops/ingest.d.ts.map +1 -1
  54. package/dist/ops/ingest.js +11 -5
  55. package/dist/ops/ingest.js.map +1 -1
  56. package/dist/ops/remember.d.ts.map +1 -1
  57. package/dist/ops/remember.js +2 -1
  58. package/dist/ops/remember.js.map +1 -1
  59. package/dist/ops/retain.js +2 -2
  60. package/dist/ops/retain.js.map +1 -1
  61. package/dist/ops/undo.d.ts +1 -1
  62. package/dist/ops/undo.js +1 -1
  63. package/dist/ops/write.d.ts.map +1 -1
  64. package/dist/ops/write.js +11 -7
  65. package/dist/ops/write.js.map +1 -1
  66. package/dist/store/db.d.ts.map +1 -1
  67. package/dist/store/db.js +4 -1
  68. package/dist/store/db.js.map +1 -1
  69. package/dist/store/migrations.d.ts +7 -6
  70. package/dist/store/migrations.d.ts.map +1 -1
  71. package/dist/store/migrations.js +16 -8
  72. package/dist/store/migrations.js.map +1 -1
  73. package/dist/watch/watcher.d.ts +3 -3
  74. package/dist/watch/watcher.js +5 -5
  75. package/dist/watch/watcher.js.map +1 -1
  76. package/dist/write/journal.d.ts +4 -8
  77. package/dist/write/journal.d.ts.map +1 -1
  78. package/dist/write/journal.js +215 -55
  79. package/dist/write/journal.js.map +1 -1
  80. package/package.json +4 -3
@@ -0,0 +1,26 @@
1
+ export function selectDocumentExtractorBackend(platform, backends) {
2
+ if (platform === 'darwin')
3
+ return backends.macos;
4
+ if (platform === 'linux')
5
+ return backends.linux;
6
+ return unsupportedBackend(platform);
7
+ }
8
+ function unsupportedBackend(platform) {
9
+ return {
10
+ name: 'unsupported',
11
+ extract: async () => ({
12
+ text: '',
13
+ pages: null,
14
+ ocr: false,
15
+ confidence: null,
16
+ provenance: { backend: 'unsupported', tool: null },
17
+ text_from: 'none',
18
+ degradation: {
19
+ kind: 'unsupported-platform',
20
+ message: `document extraction is not supported on ${platform}`,
21
+ platform,
22
+ },
23
+ }),
24
+ };
25
+ }
26
+ //# sourceMappingURL=extractor-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor-contract.js","sourceRoot":"","sources":["../../src/ingest/extractor-contract.ts"],"names":[],"mappings":"AAyFA,MAAM,UAAU,8BAA8B,CAC5C,QAAgB,EAChB,QAA2B;IAE3B,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC,KAAK,CAAC;IACjD,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,QAAQ,CAAC,KAAK,CAAC;IAChD,OAAO,kBAAkB,CAAC,QAAQ,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,kBAAkB,CAAC,QAAgB;IAC1C,OAAO;QACL,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;YACpB,IAAI,EAAE,EAAE;YACR,KAAK,EAAE,IAAI;YACX,GAAG,EAAE,KAAK;YACV,UAAU,EAAE,IAAI;YAChB,UAAU,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE;YAClD,SAAS,EAAE,MAAM;YACjB,WAAW,EAAE;gBACX,IAAI,EAAE,sBAAsB;gBAC5B,OAAO,EAAE,2CAA2C,QAAQ,EAAE;gBAC9D,QAAQ;aACT;SACF,CAAC;KACH,CAAC;AACJ,CAAC"}
@@ -1,19 +1,10 @@
1
1
  /**
2
2
  * `ingest` pulls documents from a file, a folder, or a URL. This is the URL.
3
3
  *
4
- * Downloading is the one thing `ingest` does that reaches outside the machine, so the
5
- * limits are deliberate rather than defensive-by-habit:
6
- *
7
- * - **http and https only.** `file://` would make `ingest({url})` a way to read any
8
- * path on the machine through an interface that looks like it fetches the web, and
9
- * `data:` would smuggle bytes past the size cap.
10
- * - **Redirects are followed** by `fetch`, which caps the chain itself; a redirect is
11
- * normal and an infinite one is already handled below us.
12
- * - **The size cap applies to what arrives, not to what the server claims.** A
13
- * `Content-Length` is a promise, and the stream is what actually lands on disk.
14
- * - **The filename comes from the response, then the URL, then the content type.** A
15
- * URL path is often `/download` or `/`, which says nothing — and a name adding nothing is
16
- * exactly what naming-from-content replaces.
4
+ * A URL is an instruction to make a network connection, so validation is performed on
5
+ * resolved destinations rather than on spelling alone. Every DNS answer must be public,
6
+ * the request is pinned to one of those validated answers, and every redirect starts the
7
+ * same process again. That closes both redirect-to-private and DNS-rebinding paths.
17
8
  */
18
9
  export interface Fetched {
19
10
  /** A temp file. The caller ingests it and is responsible for cleaning up. */
@@ -24,12 +15,24 @@ export interface Fetched {
24
15
  bytes: number;
25
16
  finalUrl: string;
26
17
  }
18
+ interface ResolvedAddress {
19
+ address: string;
20
+ family: 4 | 6;
21
+ }
27
22
  export interface FetchOptions {
28
23
  url: string;
29
24
  maxBytes: number;
30
25
  timeoutMs?: number;
26
+ /** Exact origins explicitly trusted to reach non-public destinations. */
27
+ trustedOrigins?: string[];
28
+ /** Test seam; production uses the operating system resolver. */
29
+ resolve?: (hostname: string) => Promise<ResolvedAddress[]>;
30
+ maxRedirects?: number;
31
31
  }
32
32
  export declare function fetchDocument(options: FetchOptions): Promise<Fetched>;
33
33
  /** The temp directory a fetch created. Removed once the bytes have been stored. */
34
34
  export declare function cleanupFetch(fetched: Fetched): Promise<void>;
35
+ /** Exposed for deterministic policy tests without making a network request. */
36
+ export declare function assertSafeUrlDestination(raw: string, options?: Pick<FetchOptions, 'trustedOrigins' | 'resolve'>): Promise<void>;
37
+ export {};
35
38
  //# sourceMappingURL=fetch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/ingest/fetch.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,OAAO;IACtB,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CA4D3E;AAED,mFAAmF;AACnF,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAElE"}
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/ingest/fetch.ts"],"names":[],"mappings":"AASA;;;;;;;GAOG;AAEH,MAAM,WAAW,OAAO;IACtB,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,eAAe;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,gEAAgE;IAChE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAKD,wBAAsB,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAkE3E;AAED,mFAAmF;AACnF,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAElE;AAED,+EAA+E;AAC/E,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,IAAI,CAAC,YAAY,EAAE,gBAAgB,GAAG,SAAS,CAAM,GAC7D,OAAO,CAAC,IAAI,CAAC,CAIf"}
@@ -1,88 +1,222 @@
1
+ import dns from 'node:dns/promises';
2
+ import http, {} from 'node:http';
3
+ import https from 'node:https';
4
+ import { BlockList, isIP } from 'node:net';
1
5
  import fsp from 'node:fs/promises';
2
6
  import os from 'node:os';
3
7
  import path from 'node:path';
4
8
  import { AknoError } from '@tenphi/akno-protocol';
9
+ const DENIED_DESTINATIONS = deniedDestinations();
10
+ const REDIRECTS = new Set([301, 302, 303, 307, 308]);
5
11
  export async function fetchDocument(options) {
6
- const url = parseUrl(options.url);
12
+ const timeoutMs = options.timeoutMs ?? 60_000;
13
+ const trusted = new Set((options.trustedOrigins ?? []).map(normalizeTrustedOrigin));
14
+ const resolve = options.resolve ?? resolveHost;
15
+ let url = parseUrl(options.url);
16
+ let redirects = 0;
7
17
  let response;
8
- try {
9
- response = await fetch(url, {
10
- redirect: 'follow',
11
- signal: AbortSignal.timeout(options.timeoutMs ?? 60_000),
12
- headers: { accept: '*/*' },
13
- });
14
- }
15
- catch (err) {
16
- const timedOut = err instanceof Error && (err.name === 'TimeoutError' || err.name === 'AbortError');
17
- throw new AknoError('unavailable', timedOut
18
- ? `fetching ${url.href} timed out`
19
- : `could not fetch ${url.href}: ${err instanceof Error ? err.message : String(err)}`);
20
- }
21
- if (!response.ok) {
22
- throw new AknoError('unavailable', `${url.href} returned ${response.status} ${response.statusText}`);
18
+ while (true) {
19
+ const addresses = await permittedAddresses(url, trusted, resolve);
20
+ response = await request(url, addresses, timeoutMs);
21
+ const status = response.statusCode ?? 0;
22
+ if (!REDIRECTS.has(status))
23
+ break;
24
+ const location = header(response.headers, 'location');
25
+ response.resume();
26
+ if (!location)
27
+ throw new AknoError('unavailable', 'URL redirect returned no location');
28
+ if (redirects++ >= (options.maxRedirects ?? 5)) {
29
+ throw new AknoError('unavailable', 'URL redirect limit exceeded');
30
+ }
31
+ url = parseUrl(new URL(location, url).href);
23
32
  }
24
- if (!response.body) {
25
- throw new AknoError('unavailable', `${url.href} returned no body`);
33
+ const status = response.statusCode ?? 0;
34
+ if (status < 200 || status >= 300) {
35
+ response.resume();
36
+ throw new AknoError('unavailable', `URL returned ${status}${response.statusMessage ? ` ${response.statusMessage}` : ''}`);
26
37
  }
27
- const contentType = (response.headers.get('content-type') ?? '').split(';')[0].trim() || null;
28
- const originalName = filenameFor(response, url, contentType);
38
+ const contentType = (header(response.headers, 'content-type') ?? '').split(';')[0].trim() || null;
39
+ const originalName = filenameFor(response.headers, url, contentType);
29
40
  const directory = await fsp.mkdtemp(path.join(os.tmpdir(), 'akno-fetch-'));
30
41
  const target = path.join(directory, originalName);
31
- const handle = await fsp.open(target, 'w');
32
42
  let written = 0;
43
+ let handle = null;
33
44
  try {
34
- for await (const chunk of response.body) {
35
- written += chunk.byteLength;
36
- // Enforced against the bytes that actually arrive: `Content-Length` is a claim,
37
- // and a server willing to lie about the size is exactly the one to guard against.
45
+ handle = await fsp.open(target, 'w');
46
+ for await (const chunk of response) {
47
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
48
+ written += bytes.byteLength;
49
+ // Enforced against the bytes that actually arrive: Content-Length is only a claim.
38
50
  if (written > options.maxBytes) {
39
- await handle.close();
40
- await fsp.rm(directory, { recursive: true, force: true });
41
- throw new AknoError('invalid', `${url.href} is larger than the configured limit of ${Math.round(options.maxBytes / 1_048_576)} MB`);
51
+ throw new AknoError('invalid', `download is larger than the configured limit of ${Math.round(options.maxBytes / 1_048_576)} MB`);
42
52
  }
43
- await handle.write(chunk);
53
+ await handle.write(bytes);
44
54
  }
45
55
  }
56
+ catch (error) {
57
+ await fsp.rm(directory, { recursive: true, force: true });
58
+ throw networkFailure(error);
59
+ }
46
60
  finally {
47
- await handle.close().catch(() => { });
61
+ if (handle)
62
+ await handle.close().catch(() => { });
48
63
  }
49
64
  if (written === 0) {
50
65
  await fsp.rm(directory, { recursive: true, force: true });
51
- throw new AknoError('unavailable', `${url.href} returned an empty body`);
66
+ throw new AknoError('unavailable', 'URL returned an empty body');
52
67
  }
53
- return { path: target, originalName, contentType, bytes: written, finalUrl: response.url || url.href };
68
+ return { path: target, originalName, contentType, bytes: written, finalUrl: url.href };
54
69
  }
55
70
  /** The temp directory a fetch created. Removed once the bytes have been stored. */
56
71
  export async function cleanupFetch(fetched) {
57
72
  await fsp.rm(path.dirname(fetched.path), { recursive: true, force: true });
58
73
  }
74
+ /** Exposed for deterministic policy tests without making a network request. */
75
+ export async function assertSafeUrlDestination(raw, options = {}) {
76
+ const url = parseUrl(raw);
77
+ const trusted = new Set((options.trustedOrigins ?? []).map(normalizeTrustedOrigin));
78
+ await permittedAddresses(url, trusted, options.resolve ?? resolveHost);
79
+ }
80
+ async function permittedAddresses(url, trusted, resolve) {
81
+ const hostname = normalizeHost(url.hostname);
82
+ let addresses;
83
+ try {
84
+ addresses = isIP(hostname)
85
+ ? [{ address: hostname, family: isIP(hostname) }]
86
+ : await resolve(hostname);
87
+ }
88
+ catch {
89
+ throw new AknoError('unavailable', 'could not resolve URL host');
90
+ }
91
+ if (addresses.length === 0)
92
+ throw new AknoError('unavailable', 'URL host resolved to no addresses');
93
+ if (!trusted.has(url.origin) && addresses.some((entry) => !isPublicAddress(entry))) {
94
+ throw new AknoError('forbidden', 'URL destination is not allowed by network policy', {
95
+ reason: 'network_destination_denied',
96
+ });
97
+ }
98
+ return addresses;
99
+ }
100
+ async function resolveHost(hostname) {
101
+ const answers = await dns.lookup(hostname, { all: true, verbatim: true });
102
+ return answers.map((answer) => ({ address: answer.address, family: answer.family }));
103
+ }
104
+ function isPublicAddress(entry) {
105
+ return !DENIED_DESTINATIONS.check(entry.address, entry.family === 4 ? 'ipv4' : 'ipv6');
106
+ }
107
+ function deniedDestinations() {
108
+ const list = new BlockList();
109
+ for (const [network, prefix] of [
110
+ ['0.0.0.0', 8],
111
+ ['10.0.0.0', 8],
112
+ ['100.64.0.0', 10],
113
+ ['127.0.0.0', 8],
114
+ ['169.254.0.0', 16],
115
+ ['172.16.0.0', 12],
116
+ ['192.0.0.0', 24],
117
+ ['192.0.2.0', 24],
118
+ ['192.168.0.0', 16],
119
+ ['192.88.99.0', 24],
120
+ ['198.18.0.0', 15],
121
+ ['198.51.100.0', 24],
122
+ ['203.0.113.0', 24],
123
+ ['224.0.0.0', 4],
124
+ ['240.0.0.0', 4],
125
+ ]) {
126
+ list.addSubnet(network, prefix, 'ipv4');
127
+ }
128
+ for (const [network, prefix] of [
129
+ ['::', 128],
130
+ ['::1', 128],
131
+ ['::', 96],
132
+ ['::ffff:0:0', 96],
133
+ ['::ffff:0:0:0', 96],
134
+ ['64:ff9b::', 96],
135
+ ['64:ff9b:1::', 48],
136
+ ['100::', 64],
137
+ ['100:0:0:1::', 64],
138
+ ['fc00::', 7],
139
+ ['fe80::', 10],
140
+ ['fec0::', 10],
141
+ ['ff00::', 8],
142
+ ['2001::', 23],
143
+ ['2001:db8::', 32],
144
+ ['2002::', 16],
145
+ ['3fff::', 20],
146
+ ['5f00::', 16],
147
+ ]) {
148
+ list.addSubnet(network, prefix, 'ipv6');
149
+ }
150
+ return list;
151
+ }
152
+ function request(url, addresses, timeoutMs) {
153
+ const transport = url.protocol === 'https:' ? https : http;
154
+ const lookup = (_hostname, lookupOptions, callback) => {
155
+ if (lookupOptions.all) {
156
+ callback(null, addresses);
157
+ return;
158
+ }
159
+ const selected = addresses[0];
160
+ callback(null, selected.address, selected.family);
161
+ };
162
+ return new Promise((resolve, reject) => {
163
+ const req = transport.get(url, {
164
+ signal: AbortSignal.timeout(timeoutMs),
165
+ headers: { accept: '*/*' },
166
+ lookup,
167
+ }, resolve);
168
+ req.on('error', (error) => reject(networkFailure(error)));
169
+ });
170
+ }
59
171
  function parseUrl(raw) {
60
172
  let url;
61
173
  try {
62
174
  url = new URL(raw);
63
175
  }
64
176
  catch {
65
- throw new AknoError('invalid', `not a usable URL: ${raw}`);
177
+ throw new AknoError('invalid', 'not a usable URL');
66
178
  }
67
179
  if (url.protocol !== 'http:' && url.protocol !== 'https:') {
68
180
  throw new AknoError('invalid', `only http and https URLs are fetched, not ${url.protocol} — pass a local path instead`);
69
181
  }
182
+ if (url.username || url.password) {
183
+ throw new AknoError('invalid', 'credentials are not allowed in an ingest URL');
184
+ }
70
185
  return url;
71
186
  }
72
- /**
73
- * `Content-Disposition`, then the URL's last path segment, then an extension guessed
74
- * from the content type. Every one of these can be useless, which is fine: the page is named
75
- * from the content anyway, and a useless name is what triggers the rename.
76
- */
77
- function filenameFor(response, url, contentType) {
78
- const disposition = response.headers.get('content-disposition');
187
+ function normalizeHost(value) {
188
+ return value
189
+ .replace(/^\[|\]$/g, '')
190
+ .replace(/\.$/, '')
191
+ .toLowerCase();
192
+ }
193
+ function normalizeTrustedOrigin(value) {
194
+ const url = parseUrl(value);
195
+ if (url.pathname !== '/' || url.search || url.hash) {
196
+ throw new AknoError('invalid', 'a trusted URL origin may contain only scheme, host, and port');
197
+ }
198
+ return url.origin;
199
+ }
200
+ function networkFailure(error) {
201
+ if (error instanceof AknoError)
202
+ return error;
203
+ const name = error instanceof Error ? error.name : '';
204
+ return new AknoError('unavailable', name === 'AbortError' || name === 'TimeoutError' ? 'fetching URL timed out' : 'could not fetch URL');
205
+ }
206
+ function header(headers, name) {
207
+ const value = headers[name];
208
+ if (Array.isArray(value))
209
+ return value[0] ?? null;
210
+ return value ?? null;
211
+ }
212
+ /** Content-Disposition, then URL path, then an extension guessed from content type. */
213
+ function filenameFor(headers, url, contentType) {
214
+ const disposition = header(headers, 'content-disposition');
79
215
  const candidates = [];
80
216
  if (disposition) {
81
217
  const star = /filename\*=(?:UTF-8'')?([^;]+)/i.exec(disposition);
82
218
  const plain = /filename="?([^";]+)"?/i.exec(disposition);
83
219
  const raw = star?.[1] ?? plain?.[1];
84
- // `path.basename` is what stops `../../../etc/passwd` being a path: a server chooses
85
- // this header, so it is untrusted input that becomes a filename.
86
220
  if (raw)
87
221
  candidates.push(sanitize(path.basename(safeDecode(raw.trim()))));
88
222
  }
@@ -91,18 +225,10 @@ function filenameFor(response, url, contentType) {
91
225
  for (const candidate of candidates) {
92
226
  if (candidate.length === 0 || candidate === '.' || candidate === '..')
93
227
  continue;
94
- // The extractor dispatches on the extension, so a name without one is unusable even
95
- // though it reads fine. Servers send `filename="report"` often enough that guessing
96
- // from the content type is the difference between working and skipping.
97
228
  return path.extname(candidate) ? candidate : `${candidate}${extension}`;
98
229
  }
99
230
  return `download${extension}`;
100
231
  }
101
- /**
102
- * A server chooses these bytes, so they are untrusted input on their way to becoming a
103
- * filename: separators and control characters are dropped. Spaces survive, because a
104
- * filename with spaces is ordinary and the rename handles presentation anyway.
105
- */
106
232
  function sanitize(name) {
107
233
  return [...name]
108
234
  .filter((char) => char !== '/' && char !== '\\' && char.codePointAt(0) >= 0x20)
@@ -135,8 +261,6 @@ function extensionFor(contentType) {
135
261
  'application/msword': '.doc',
136
262
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx',
137
263
  };
138
- // No extension at all is honest: the extractor dispatches on it and will say it has
139
- // none, which is better than claiming `.bin` and looking like a decision.
140
264
  return contentType ? (table[contentType] ?? '') : '';
141
265
  }
142
266
  //# sourceMappingURL=fetch.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/ingest/fetch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/ingest/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,mBAAmB,CAAC;AACpC,OAAO,IAAI,EAAE,EAAkD,MAAM,WAAW,CAAC;AACjF,OAAO,KAAK,MAAM,YAAY,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,IAAI,EAAuB,MAAM,UAAU,CAAC;AAChE,OAAO,GAAG,MAAM,kBAAkB,CAAC;AACnC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAqClD,MAAM,mBAAmB,GAAG,kBAAkB,EAAE,CAAC;AACjD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAqB;IACvD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;IAC9C,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACpF,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,WAAW,CAAC;IAC/C,IAAI,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,QAAyB,CAAC;IAE9B,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAClE,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,MAAM;QAElC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QACtD,QAAQ,CAAC,MAAM,EAAE,CAAC;QAClB,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,SAAS,CAAC,aAAa,EAAE,mCAAmC,CAAC,CAAC;QACvF,IAAI,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,CAAC,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,SAAS,CAAC,aAAa,EAAE,6BAA6B,CAAC,CAAC;QACpE,CAAC;QACD,GAAG,GAAG,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,IAAI,CAAC,CAAC;IACxC,IAAI,MAAM,GAAG,GAAG,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QAClC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAClB,MAAM,IAAI,SAAS,CACjB,aAAa,EACb,gBAAgB,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACtF,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;IACnG,MAAM,YAAY,GAAG,WAAW,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;IACrE,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAElD,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,MAAM,GAAgD,IAAI,CAAC;IAC/D,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACrC,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAmB,CAAC,CAAC;YAChF,OAAO,IAAI,KAAK,CAAC,UAAU,CAAC;YAC5B,mFAAmF;YACnF,IAAI,OAAO,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;gBAC/B,MAAM,IAAI,SAAS,CACjB,SAAS,EACT,mDAAmD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,KAAK,CACjG,CAAC;YACJ,CAAC;YACD,MAAM,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,MAAM,cAAc,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;YAAS,CAAC;QACT,IAAI,MAAM;YAAE,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;QAClB,MAAM,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,MAAM,IAAI,SAAS,CAAC,aAAa,EAAE,4BAA4B,CAAC,CAAC;IACnE,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC;AACzF,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAgB;IACjD,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AAC7E,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,GAAW,EACX,UAA4D,EAAE;IAE9D,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACpF,MAAM,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,WAAW,CAAC,CAAC;AACzE,CAAC;AAED,KAAK,UAAU,kBAAkB,CAC/B,GAAQ,EACR,OAAoB,EACpB,OAAyD;IAEzD,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7C,IAAI,SAA4B,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC;YACxB,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAU,EAAE,CAAC;YAC1D,CAAC,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,SAAS,CAAC,aAAa,EAAE,4BAA4B,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,aAAa,EAAE,mCAAmC,CAAC,CAAC;IAEpG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QACnF,MAAM,IAAI,SAAS,CAAC,WAAW,EAAE,kDAAkD,EAAE;YACnF,MAAM,EAAE,4BAA4B;SACrC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,QAAgB;IACzC,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1E,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAe,EAAE,CAAC,CAAC,CAAC;AAChG,CAAC;AAED,SAAS,eAAe,CAAC,KAAsB;IAC7C,OAAO,CAAC,mBAAmB,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AACzF,CAAC;AAED,SAAS,kBAAkB;IACzB,MAAM,IAAI,GAAG,IAAI,SAAS,EAAE,CAAC;IAC7B,KAAK,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI;QAC9B,CAAC,SAAS,EAAE,CAAC,CAAC;QACd,CAAC,UAAU,EAAE,CAAC,CAAC;QACf,CAAC,YAAY,EAAE,EAAE,CAAC;QAClB,CAAC,WAAW,EAAE,CAAC,CAAC;QAChB,CAAC,aAAa,EAAE,EAAE,CAAC;QACnB,CAAC,YAAY,EAAE,EAAE,CAAC;QAClB,CAAC,WAAW,EAAE,EAAE,CAAC;QACjB,CAAC,WAAW,EAAE,EAAE,CAAC;QACjB,CAAC,aAAa,EAAE,EAAE,CAAC;QACnB,CAAC,aAAa,EAAE,EAAE,CAAC;QACnB,CAAC,YAAY,EAAE,EAAE,CAAC;QAClB,CAAC,cAAc,EAAE,EAAE,CAAC;QACpB,CAAC,aAAa,EAAE,EAAE,CAAC;QACnB,CAAC,WAAW,EAAE,CAAC,CAAC;QAChB,CAAC,WAAW,EAAE,CAAC,CAAC;KACR,EAAE,CAAC;QACX,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IACD,KAAK,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI;QAC9B,CAAC,IAAI,EAAE,GAAG,CAAC;QACX,CAAC,KAAK,EAAE,GAAG,CAAC;QACZ,CAAC,IAAI,EAAE,EAAE,CAAC;QACV,CAAC,YAAY,EAAE,EAAE,CAAC;QAClB,CAAC,cAAc,EAAE,EAAE,CAAC;QACpB,CAAC,WAAW,EAAE,EAAE,CAAC;QACjB,CAAC,aAAa,EAAE,EAAE,CAAC;QACnB,CAAC,OAAO,EAAE,EAAE,CAAC;QACb,CAAC,aAAa,EAAE,EAAE,CAAC;QACnB,CAAC,QAAQ,EAAE,CAAC,CAAC;QACb,CAAC,QAAQ,EAAE,EAAE,CAAC;QACd,CAAC,QAAQ,EAAE,EAAE,CAAC;QACd,CAAC,QAAQ,EAAE,CAAC,CAAC;QACb,CAAC,QAAQ,EAAE,EAAE,CAAC;QACd,CAAC,YAAY,EAAE,EAAE,CAAC;QAClB,CAAC,QAAQ,EAAE,EAAE,CAAC;QACd,CAAC,QAAQ,EAAE,EAAE,CAAC;QACd,CAAC,QAAQ,EAAE,EAAE,CAAC;KACN,EAAE,CAAC;QACX,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,OAAO,CAAC,GAAQ,EAAE,SAA4B,EAAE,SAAiB;IACxE,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3D,MAAM,MAAM,GAAmB,CAAC,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,EAAE;QACpE,IAAI,aAAa,CAAC,GAAG,EAAE,CAAC;YACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YAC1B,OAAO;QACT,CAAC;QACD,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAE,CAAC;QAC/B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC,CAAC;IACF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,CACvB,GAAG,EACH;YACE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;YACtC,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;YAC1B,MAAM;SACP,EACD,OAAO,CACR,CAAC;QACF,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,SAAS,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1D,MAAM,IAAI,SAAS,CACjB,SAAS,EACT,6CAA6C,GAAG,CAAC,QAAQ,8BAA8B,CACxF,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QACjC,MAAM,IAAI,SAAS,CAAC,SAAS,EAAE,8CAA8C,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,aAAa,CAAC,KAAa;IAClC,OAAO,KAAK;SACT,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;SAClB,WAAW,EAAE,CAAC;AACnB,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAa;IAC3C,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,GAAG,CAAC,QAAQ,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QACnD,MAAM,IAAI,SAAS,CAAC,SAAS,EAAE,8DAA8D,CAAC,CAAC;IACjG,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,CAAC;AACpB,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,IAAI,KAAK,YAAY,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7C,MAAM,IAAI,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,OAAO,IAAI,SAAS,CAClB,aAAa,EACb,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,cAAc,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,qBAAqB,CACpG,CAAC;AACJ,CAAC;AAED,SAAS,MAAM,CAAC,OAA4B,EAAE,IAAY;IACxD,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAClD,OAAO,KAAK,IAAI,IAAI,CAAC;AACvB,CAAC;AAED,uFAAuF;AACvF,SAAS,WAAW,CAAC,OAA4B,EAAE,GAAQ,EAAE,WAA0B;IACrF,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAC;IAC3D,MAAM,UAAU,GAAa,EAAE,CAAC;IAEhC,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,IAAI,GAAG,iCAAiC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,wBAAwB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACzD,MAAM,GAAG,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,GAAG;YAAE,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5E,CAAC;IACD,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAEnE,MAAM,SAAS,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;IAC5C,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,IAAI,SAAS,KAAK,GAAG,IAAI,SAAS,KAAK,IAAI;YAAE,SAAS;QAChF,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,SAAS,EAAE,CAAC;IAC1E,CAAC;IACD,OAAO,WAAW,SAAS,EAAE,CAAC;AAChC,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,OAAO,CAAC,GAAG,IAAI,CAAC;SACb,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC,CAAE,IAAI,IAAI,CAAC;SAC/E,IAAI,CAAC,EAAE,CAAC;SACR,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,IAAI,CAAC;QACH,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,WAA0B;IAC9C,MAAM,KAAK,GAA2B;QACpC,iBAAiB,EAAE,MAAM;QACzB,YAAY,EAAE,MAAM;QACpB,eAAe,EAAE,KAAK;QACtB,WAAW,EAAE,OAAO;QACpB,UAAU,EAAE,MAAM;QAClB,kBAAkB,EAAE,OAAO;QAC3B,WAAW,EAAE,MAAM;QACnB,YAAY,EAAE,MAAM;QACpB,YAAY,EAAE,OAAO;QACrB,YAAY,EAAE,OAAO;QACrB,WAAW,EAAE,MAAM;QACnB,YAAY,EAAE,OAAO;QACrB,iBAAiB,EAAE,MAAM;QACzB,oBAAoB,EAAE,MAAM;QAC5B,yEAAyE,EAAE,OAAO;KACnF,CAAC;IACF,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACvD,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { DocumentExtractorBackend } from './extractor-contract.ts';
2
+ type LinuxExecuteFile = (command: string, args: string[], options: {
3
+ maxBuffer: number;
4
+ timeout: number;
5
+ killSignal: NodeJS.Signals;
6
+ }) => Promise<{
7
+ stdout: string;
8
+ stderr: string;
9
+ }>;
10
+ export interface LinuxCommandRunner {
11
+ available(command: string): Promise<boolean>;
12
+ makeTempDir(): Promise<string>;
13
+ readText(file: string): Promise<string>;
14
+ removeTempDir(directory: string): Promise<void>;
15
+ run(command: string, args: string[]): Promise<{
16
+ stdout: string;
17
+ }>;
18
+ }
19
+ export declare function createNodeLinuxCommandRunner(execute?: LinuxExecuteFile): LinuxCommandRunner;
20
+ export interface LinuxExtractionCapabilities {
21
+ backend: 'linux-native';
22
+ libreoffice: boolean;
23
+ pdfinfo: boolean;
24
+ pdftotext: boolean;
25
+ pdftoppm: boolean;
26
+ tesseract: boolean;
27
+ note: string | null;
28
+ }
29
+ export declare function linuxExtractionCapabilities(commands: LinuxCommandRunner): Promise<LinuxExtractionCapabilities>;
30
+ export declare function createLinuxDocumentExtractor(commands: LinuxCommandRunner): DocumentExtractorBackend;
31
+ export {};
32
+ //# sourceMappingURL=linux-extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linux-extractor.d.ts","sourceRoot":"","sources":["../../src/ingest/linux-extractor.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAEV,wBAAwB,EAEzB,MAAM,yBAAyB,CAAC;AAOjC,KAAK,gBAAgB,GAAG,CACtB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC,OAAO,CAAA;CAAE,KACxE,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAOjD,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnE;AAED,wBAAgB,4BAA4B,CAAC,OAAO,GAAE,gBAA8B,GAAG,kBAAkB,CAexG;AAkBD,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,cAAc,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAAC,2BAA2B,CAAC,CAgBtC;AAED,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,kBAAkB,GAAG,wBAAwB,CAcnG"}