@de-otio/bibcheck 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.
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/dist/cache/fs-cache.d.ts +55 -0
- package/dist/cache/fs-cache.d.ts.map +1 -0
- package/dist/cache/fs-cache.js +264 -0
- package/dist/cache/fs-cache.js.map +1 -0
- package/dist/canonical.d.ts +29 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +132 -0
- package/dist/canonical.js.map +1 -0
- package/dist/check.d.ts +140 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +646 -0
- package/dist/check.js.map +1 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +357 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +175 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +180 -0
- package/dist/config.js.map +1 -0
- package/dist/databases/crossref.d.ts +53 -0
- package/dist/databases/crossref.d.ts.map +1 -0
- package/dist/databases/crossref.js +138 -0
- package/dist/databases/crossref.js.map +1 -0
- package/dist/databases/index.d.ts +12 -0
- package/dist/databases/index.d.ts.map +1 -0
- package/dist/databases/index.js +9 -0
- package/dist/databases/index.js.map +1 -0
- package/dist/databases/openalex.d.ts +29 -0
- package/dist/databases/openalex.d.ts.map +1 -0
- package/dist/databases/openalex.js +117 -0
- package/dist/databases/openalex.js.map +1 -0
- package/dist/databases/openlibrary.d.ts +26 -0
- package/dist/databases/openlibrary.d.ts.map +1 -0
- package/dist/databases/openlibrary.js +79 -0
- package/dist/databases/openlibrary.js.map +1 -0
- package/dist/databases/worldcat.d.ts +33 -0
- package/dist/databases/worldcat.d.ts.map +1 -0
- package/dist/databases/worldcat.js +145 -0
- package/dist/databases/worldcat.js.map +1 -0
- package/dist/doctor.d.ts +44 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +386 -0
- package/dist/doctor.js.map +1 -0
- package/dist/existence.d.ts +70 -0
- package/dist/existence.d.ts.map +1 -0
- package/dist/existence.js +308 -0
- package/dist/existence.js.map +1 -0
- package/dist/http.d.ts +97 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +543 -0
- package/dist/http.js.map +1 -0
- package/dist/identifiers.d.ts +44 -0
- package/dist/identifiers.d.ts.map +1 -0
- package/dist/identifiers.js +111 -0
- package/dist/identifiers.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/linkage.d.ts +29 -0
- package/dist/linkage.d.ts.map +1 -0
- package/dist/linkage.js +73 -0
- package/dist/linkage.js.map +1 -0
- package/dist/markdown/blocks.d.ts +19 -0
- package/dist/markdown/blocks.d.ts.map +1 -0
- package/dist/markdown/blocks.js +69 -0
- package/dist/markdown/blocks.js.map +1 -0
- package/dist/markdown/citekeys.d.ts +22 -0
- package/dist/markdown/citekeys.d.ts.map +1 -0
- package/dist/markdown/citekeys.js +100 -0
- package/dist/markdown/citekeys.js.map +1 -0
- package/dist/markdown/glob.d.ts +18 -0
- package/dist/markdown/glob.d.ts.map +1 -0
- package/dist/markdown/glob.js +26 -0
- package/dist/markdown/glob.js.map +1 -0
- package/dist/markdown/prose.d.ts +19 -0
- package/dist/markdown/prose.d.ts.map +1 -0
- package/dist/markdown/prose.js +81 -0
- package/dist/markdown/prose.js.map +1 -0
- package/dist/output/json.d.ts +21 -0
- package/dist/output/json.d.ts.map +1 -0
- package/dist/output/json.js +24 -0
- package/dist/output/json.js.map +1 -0
- package/dist/output/markdown.d.ts +21 -0
- package/dist/output/markdown.d.ts.map +1 -0
- package/dist/output/markdown.js +194 -0
- package/dist/output/markdown.js.map +1 -0
- package/dist/output/sarif.d.ts +31 -0
- package/dist/output/sarif.d.ts.map +1 -0
- package/dist/output/sarif.js +322 -0
- package/dist/output/sarif.js.map +1 -0
- package/dist/output/text.d.ts +27 -0
- package/dist/output/text.d.ts.map +1 -0
- package/dist/output/text.js +212 -0
- package/dist/output/text.js.map +1 -0
- package/dist/phrases/load.d.ts +34 -0
- package/dist/phrases/load.d.ts.map +1 -0
- package/dist/phrases/load.js +148 -0
- package/dist/phrases/load.js.map +1 -0
- package/dist/phrases.d.ts +27 -0
- package/dist/phrases.d.ts.map +1 -0
- package/dist/phrases.js +116 -0
- package/dist/phrases.js.map +1 -0
- package/dist/schema/csl.d.ts +429 -0
- package/dist/schema/csl.d.ts.map +1 -0
- package/dist/schema/csl.js +101 -0
- package/dist/schema/csl.js.map +1 -0
- package/dist/schema/output.d.ts +1116 -0
- package/dist/schema/output.d.ts.map +1 -0
- package/dist/schema/output.js +419 -0
- package/dist/schema/output.js.map +1 -0
- package/dist/suppression.d.ts +106 -0
- package/dist/suppression.d.ts.map +1 -0
- package/dist/suppression.js +134 -0
- package/dist/suppression.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +14 -0
- package/dist/version.js.map +1 -0
- package/dist/worklist.d.ts +32 -0
- package/dist/worklist.d.ts.map +1 -0
- package/dist/worklist.js +211 -0
- package/dist/worklist.js.map +1 -0
- package/package.json +82 -0
package/dist/http.js
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP utility for URL verification.
|
|
3
|
+
*
|
|
4
|
+
* Provides an HttpClient backed by the native Node 20 fetch API with per-origin
|
|
5
|
+
* concurrency queuing, jittered-backoff retry on 5xx/network errors, manual
|
|
6
|
+
* redirect following with SSRF mitigation, and a higher-level headCheck utility
|
|
7
|
+
* for canonical-edition URL verification.
|
|
8
|
+
*
|
|
9
|
+
* Note: The spec calls for undici.request directly, but we use the native Node
|
|
10
|
+
* global fetch (which is backed by undici internally) because undici is not
|
|
11
|
+
* separately installed in this project. We use `redirect: 'manual'` to achieve
|
|
12
|
+
* the same manual redirect-following behaviour.
|
|
13
|
+
*/
|
|
14
|
+
import PQueue from 'p-queue';
|
|
15
|
+
import { lookup as dnsLookup } from 'node:dns/promises';
|
|
16
|
+
import { isIP } from 'node:net';
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// HttpError
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
export class HttpError extends Error {
|
|
21
|
+
name = 'HttpError';
|
|
22
|
+
status;
|
|
23
|
+
cause;
|
|
24
|
+
constructor(message, status, cause) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.status = status;
|
|
27
|
+
this.cause = cause;
|
|
28
|
+
if (typeof Error.captureStackTrace === 'function') {
|
|
29
|
+
Error.captureStackTrace(this, HttpError);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// SSRF helpers
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/**
|
|
37
|
+
* Classify a dotted-quad IPv4 (a.b.c.d, each octet already a number) as private
|
|
38
|
+
* / non-routable. Covers loopback, RFC 1918, link-local, "this host", and the
|
|
39
|
+
* CGNAT shared range.
|
|
40
|
+
*/
|
|
41
|
+
function isPrivateIpv4Octets(a, b) {
|
|
42
|
+
if (a === 0)
|
|
43
|
+
return true; // 0.0.0.0/8 ("this host")
|
|
44
|
+
if (a === 127)
|
|
45
|
+
return true; // 127.0.0.0/8 loopback
|
|
46
|
+
if (a === 10)
|
|
47
|
+
return true; // 10.0.0.0/8
|
|
48
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
49
|
+
return true; // 172.16.0.0/12
|
|
50
|
+
if (a === 192 && b === 168)
|
|
51
|
+
return true; // 192.168.0.0/16
|
|
52
|
+
if (a === 169 && b === 254)
|
|
53
|
+
return true; // 169.254.0.0/16 link-local
|
|
54
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
55
|
+
return true; // 100.64.0.0/10 CGNAT
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Parse a non-dotted IPv4 literal (decimal, octal, or hex) into a 32-bit
|
|
60
|
+
* number, e.g. "2130706433" or "0x7f000001" → 0x7f000001. Returns null when
|
|
61
|
+
* the string is not a single-integer IPv4 form. URL/WHATWG parsing already
|
|
62
|
+
* accepts these as hostnames, so we must classify them too.
|
|
63
|
+
*/
|
|
64
|
+
function parseIntegerIpv4(host) {
|
|
65
|
+
let n;
|
|
66
|
+
if (/^0x[0-9a-f]+$/i.test(host)) {
|
|
67
|
+
n = parseInt(host, 16);
|
|
68
|
+
}
|
|
69
|
+
else if (/^0[0-7]+$/.test(host)) {
|
|
70
|
+
n = parseInt(host, 8);
|
|
71
|
+
}
|
|
72
|
+
else if (/^[0-9]+$/.test(host)) {
|
|
73
|
+
n = parseInt(host, 10);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
if (!Number.isFinite(n) || n < 0 || n > 0xffffffff)
|
|
79
|
+
return null;
|
|
80
|
+
return n >>> 0;
|
|
81
|
+
}
|
|
82
|
+
export function isPrivateIp(ip) {
|
|
83
|
+
const lower = ip.toLowerCase();
|
|
84
|
+
// IPv6 loopback / ULA / link-local.
|
|
85
|
+
if (lower === '::1')
|
|
86
|
+
return true;
|
|
87
|
+
if (/^fc[0-9a-f]{2}:/.test(lower) || /^fd[0-9a-f]{2}:/.test(lower))
|
|
88
|
+
return true; // fc00::/7
|
|
89
|
+
if (/^fe[89ab][0-9a-f]:/.test(lower))
|
|
90
|
+
return true; // fe80::/10
|
|
91
|
+
// IPv4-mapped / -compatible IPv6 (e.g. ::ffff:127.0.0.1, ::ffff:7f00:1).
|
|
92
|
+
// Re-classify the embedded IPv4 portion.
|
|
93
|
+
if (lower.startsWith('::ffff:') || lower.startsWith('::')) {
|
|
94
|
+
const tail = lower.slice(lower.lastIndexOf(':') + 1);
|
|
95
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(tail)) {
|
|
96
|
+
return isPrivateIp(tail);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Dotted-quad IPv4.
|
|
100
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(lower)) {
|
|
101
|
+
const parts = lower.split('.').map(Number);
|
|
102
|
+
const [a, b] = parts;
|
|
103
|
+
/* c8 ignore next */
|
|
104
|
+
if (a === undefined || b === undefined)
|
|
105
|
+
return false; // unreachable: regex guarantees 4 octets
|
|
106
|
+
return isPrivateIpv4Octets(a, b);
|
|
107
|
+
}
|
|
108
|
+
// Single-integer IPv4 (decimal / octal / hex).
|
|
109
|
+
const n = parseIntegerIpv4(lower);
|
|
110
|
+
if (n !== null) {
|
|
111
|
+
const a = (n >>> 24) & 0xff;
|
|
112
|
+
const b = (n >>> 16) & 0xff;
|
|
113
|
+
return isPrivateIpv4Octets(a, b);
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Decide whether a configured API base URL targets a private / loopback host.
|
|
119
|
+
*
|
|
120
|
+
* The per-hop SSRF guard exists to stop *untrusted bibliography URLs* reaching
|
|
121
|
+
* internal addresses. The `[apis] *_base` endpoints, by contrast, are
|
|
122
|
+
* operator-controlled configuration; pointing them at `http://127.0.0.1:PORT`
|
|
123
|
+
* (a local stub or dev mirror) is a legitimate, explicit choice. Callers use
|
|
124
|
+
* this to opt a configured-endpoint client into `allowPrivateHosts` so the
|
|
125
|
+
* operator's deliberate localhost config is honored without weakening the guard
|
|
126
|
+
* on bibliography-derived URLs. Returns false for any unparseable / public base.
|
|
127
|
+
*/
|
|
128
|
+
export function isPrivateApiBase(baseUrl) {
|
|
129
|
+
if (baseUrl == null || baseUrl === '')
|
|
130
|
+
return false;
|
|
131
|
+
let host;
|
|
132
|
+
try {
|
|
133
|
+
host = new URL(baseUrl).hostname.toLowerCase();
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (host.startsWith('[') && host.endsWith(']'))
|
|
139
|
+
host = host.slice(1, -1);
|
|
140
|
+
if (host === 'localhost')
|
|
141
|
+
return true;
|
|
142
|
+
return isPrivateIp(host);
|
|
143
|
+
}
|
|
144
|
+
export function isHostAllowed(host, whitelist) {
|
|
145
|
+
if (whitelist.length === 0)
|
|
146
|
+
return false;
|
|
147
|
+
const h = host.toLowerCase();
|
|
148
|
+
for (const w of whitelist) {
|
|
149
|
+
const lw = w.toLowerCase();
|
|
150
|
+
if (h === lw || h.endsWith('.' + lw))
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// Internal: per-request SSRF guard
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
/**
|
|
159
|
+
* Validate a single hop's URL: enforce http/https scheme and reject any
|
|
160
|
+
* private / non-routable destination. Literal-IP hostnames (dotted-quad,
|
|
161
|
+
* IPv6, decimal/octal/hex integer, IPv4-mapped) are classified directly; a
|
|
162
|
+
* DNS name is resolved and every returned address is checked.
|
|
163
|
+
*/
|
|
164
|
+
async function assertNotPrivate(url) {
|
|
165
|
+
const parsed = new URL(url);
|
|
166
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
167
|
+
throw new HttpError(`rejected: unsupported scheme ${parsed.protocol}`);
|
|
168
|
+
}
|
|
169
|
+
// WHATWG URL strips brackets from IPv6 hostnames; isIP handles both families.
|
|
170
|
+
let hostname = parsed.hostname;
|
|
171
|
+
if (hostname.startsWith('[') && hostname.endsWith(']')) {
|
|
172
|
+
hostname = hostname.slice(1, -1);
|
|
173
|
+
}
|
|
174
|
+
// If the hostname is itself an address literal (in any form the URL parser
|
|
175
|
+
// accepts), classify it without a DNS round-trip — DNS would not be consulted
|
|
176
|
+
// for these at connect time anyway.
|
|
177
|
+
if (isIP(hostname) !== 0 || /^\d+\.\d+\.\d+\.\d+$/.test(hostname) || parseIntegerIpv4(hostname) !== null) {
|
|
178
|
+
if (isPrivateIp(hostname)) {
|
|
179
|
+
throw new HttpError('rejected: private IP');
|
|
180
|
+
}
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
let addresses;
|
|
184
|
+
try {
|
|
185
|
+
addresses = await dnsLookup(hostname, { all: true });
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
throw new HttpError(`DNS resolution failed for ${hostname}`, undefined, err);
|
|
189
|
+
}
|
|
190
|
+
for (const { address } of addresses) {
|
|
191
|
+
if (isPrivateIp(address)) {
|
|
192
|
+
throw new HttpError('rejected: private IP');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Internal: error classification
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
function isRetryableStatus(status) {
|
|
200
|
+
// 429 (Too Many Requests) and 503 (Service Unavailable) are explicitly
|
|
201
|
+
// retryable per the polite-pool etiquette; both commonly carry Retry-After.
|
|
202
|
+
// All other 5xx are retried too.
|
|
203
|
+
return status === 429 || status >= 500;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Parse a Retry-After header value into a delay in milliseconds, or null if it
|
|
207
|
+
* is absent / unparseable. Accepts delta-seconds ("120") and an HTTP-date.
|
|
208
|
+
* `now` is injectable for deterministic tests.
|
|
209
|
+
*/
|
|
210
|
+
export function parseRetryAfterMs(value, now = Date.now()) {
|
|
211
|
+
if (value === undefined)
|
|
212
|
+
return null;
|
|
213
|
+
const trimmed = value.trim();
|
|
214
|
+
if (trimmed === '')
|
|
215
|
+
return null;
|
|
216
|
+
if (/^\d+$/.test(trimmed)) {
|
|
217
|
+
return parseInt(trimmed, 10) * 1000;
|
|
218
|
+
}
|
|
219
|
+
const dateMs = Date.parse(trimmed);
|
|
220
|
+
if (Number.isNaN(dateMs))
|
|
221
|
+
return null;
|
|
222
|
+
return Math.max(0, dateMs - now);
|
|
223
|
+
}
|
|
224
|
+
function isNetworkError(err) {
|
|
225
|
+
if (!(err instanceof Error))
|
|
226
|
+
return false;
|
|
227
|
+
const code = err.code;
|
|
228
|
+
const msg = err.message;
|
|
229
|
+
return (code === 'ECONNRESET' ||
|
|
230
|
+
code === 'ECONNREFUSED' ||
|
|
231
|
+
code === 'ENOTFOUND' ||
|
|
232
|
+
code === 'ETIMEDOUT' ||
|
|
233
|
+
code === 'UND_ERR_CONNECT_TIMEOUT' ||
|
|
234
|
+
msg.includes('ECONNRESET') ||
|
|
235
|
+
msg.includes('socket hang up') ||
|
|
236
|
+
msg.includes('UND_ERR') ||
|
|
237
|
+
msg.includes('fetch failed'));
|
|
238
|
+
}
|
|
239
|
+
function isAbortError(err) {
|
|
240
|
+
if (!(err instanceof Error))
|
|
241
|
+
return false;
|
|
242
|
+
return err.name === 'AbortError' || err.code === 'ABORT_ERR';
|
|
243
|
+
}
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
// Internal: backoff / sleep
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
function jitter(baseMs) {
|
|
248
|
+
return Math.floor(Math.random() * baseMs);
|
|
249
|
+
}
|
|
250
|
+
function backoffMs(attempt, baseMs) {
|
|
251
|
+
return Math.floor(baseMs * Math.pow(1.5, attempt)) + jitter(baseMs);
|
|
252
|
+
}
|
|
253
|
+
function sleep(ms, signal) {
|
|
254
|
+
return new Promise((resolve, reject) => {
|
|
255
|
+
if (signal.aborted) {
|
|
256
|
+
reject(signal.reason);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const timer = setTimeout(resolve, ms);
|
|
260
|
+
signal.addEventListener('abort', () => {
|
|
261
|
+
clearTimeout(timer);
|
|
262
|
+
reject(signal.reason);
|
|
263
|
+
}, { once: true });
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
async function dispatchFetch(method, url, extraHeaders, userAgent, signal) {
|
|
267
|
+
const baseHeaders = {};
|
|
268
|
+
if (userAgent !== undefined)
|
|
269
|
+
baseHeaders['user-agent'] = userAgent;
|
|
270
|
+
const mergedHeaders = { ...baseHeaders, ...extraHeaders };
|
|
271
|
+
const response = await fetch(url, {
|
|
272
|
+
method,
|
|
273
|
+
headers: mergedHeaders,
|
|
274
|
+
redirect: 'manual',
|
|
275
|
+
signal,
|
|
276
|
+
});
|
|
277
|
+
const headers = {};
|
|
278
|
+
response.headers.forEach((value, key) => {
|
|
279
|
+
headers[key.toLowerCase()] = value;
|
|
280
|
+
});
|
|
281
|
+
let bodyText = '';
|
|
282
|
+
if (method === 'GET') {
|
|
283
|
+
bodyText = await response.text();
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// Drain the body for HEAD (opaque redirects have empty bodies, but be safe)
|
|
287
|
+
await response.body?.cancel().catch(() => undefined);
|
|
288
|
+
}
|
|
289
|
+
return { status: response.status, headers, bodyText };
|
|
290
|
+
}
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
// createHttpClient
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
export function createHttpClient(opts) {
|
|
295
|
+
const userAgent = opts?.userAgent;
|
|
296
|
+
const defaultTimeoutMs = opts?.defaultTimeoutMs ?? 10_000;
|
|
297
|
+
const maxRetries = opts?.maxRetries ?? 2;
|
|
298
|
+
const retryBaseMs = opts?.retryBaseMs ?? 250;
|
|
299
|
+
const perOriginConcurrency = opts?.perOriginConcurrency ?? 2;
|
|
300
|
+
const totalDeadlineMs = opts?.totalDeadlineMs ?? 30_000;
|
|
301
|
+
const allowPrivateHosts = opts?.allowPrivateHosts === true;
|
|
302
|
+
// Guard a single hop. The `allowPrivateHosts` test escape hatch bypasses the
|
|
303
|
+
// private-IP check (so the in-process loopback test server, which only listens
|
|
304
|
+
// on 127.0.0.1, is reachable across redirects). The scheme restriction is
|
|
305
|
+
// always enforced, even in test mode. Production callers leave the hatch off,
|
|
306
|
+
// so every hop — hop 0 and every redirect — is fully guarded.
|
|
307
|
+
async function guardHop(hopUrl) {
|
|
308
|
+
if (allowPrivateHosts) {
|
|
309
|
+
const proto = new URL(hopUrl).protocol;
|
|
310
|
+
if (proto !== 'http:' && proto !== 'https:') {
|
|
311
|
+
throw new HttpError(`rejected: unsupported scheme ${proto}`);
|
|
312
|
+
}
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
await assertNotPrivate(hopUrl);
|
|
316
|
+
}
|
|
317
|
+
const queues = new Map();
|
|
318
|
+
function getQueue(origin) {
|
|
319
|
+
let q = queues.get(origin);
|
|
320
|
+
if (q === undefined) {
|
|
321
|
+
q = new PQueue({ concurrency: perOriginConcurrency });
|
|
322
|
+
queues.set(origin, q);
|
|
323
|
+
}
|
|
324
|
+
return q;
|
|
325
|
+
}
|
|
326
|
+
async function withRetry(origin, fn, userSignal, timeoutMs) {
|
|
327
|
+
const q = getQueue(origin);
|
|
328
|
+
return q.add(async () => {
|
|
329
|
+
const startTime = Date.now();
|
|
330
|
+
let lastError;
|
|
331
|
+
let lastStatus;
|
|
332
|
+
// When a retryable response carries Retry-After, prefer it over the
|
|
333
|
+
// jittered backoff for the *next* sleep.
|
|
334
|
+
let retryAfterMs = null;
|
|
335
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
336
|
+
if (userSignal !== undefined && userSignal.aborted) {
|
|
337
|
+
throw userSignal.reason;
|
|
338
|
+
}
|
|
339
|
+
const elapsed = Date.now() - startTime;
|
|
340
|
+
if (attempt > 0 && elapsed > totalDeadlineMs) {
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
const signals = [AbortSignal.timeout(timeoutMs)];
|
|
344
|
+
if (userSignal !== undefined)
|
|
345
|
+
signals.push(userSignal);
|
|
346
|
+
const combined = AbortSignal.any(signals);
|
|
347
|
+
retryAfterMs = null;
|
|
348
|
+
try {
|
|
349
|
+
const result = await fn(combined);
|
|
350
|
+
if (isRetryableStatus(result.status)) {
|
|
351
|
+
lastStatus = result.status;
|
|
352
|
+
lastError = new HttpError(`HTTP ${result.status}`, result.status);
|
|
353
|
+
retryAfterMs = parseRetryAfterMs(result.headers['retry-after']);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
if (isAbortError(err)) {
|
|
361
|
+
if (userSignal !== undefined && userSignal.aborted)
|
|
362
|
+
throw err;
|
|
363
|
+
// Per-attempt timeout — retryable
|
|
364
|
+
lastError = err;
|
|
365
|
+
}
|
|
366
|
+
else if (isNetworkError(err)) {
|
|
367
|
+
lastError = err;
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
throw err;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (attempt < maxRetries) {
|
|
374
|
+
const remaining = Math.max(0, totalDeadlineMs - (Date.now() - startTime));
|
|
375
|
+
// Honor Retry-After when present; otherwise use jittered backoff.
|
|
376
|
+
// Either way the sleep is capped against the total deadline so a
|
|
377
|
+
// hostile/large Retry-After cannot stall the run past its budget.
|
|
378
|
+
const baseDelay = retryAfterMs !== null ? retryAfterMs : backoffMs(attempt, retryBaseMs);
|
|
379
|
+
const delay = Math.min(baseDelay, remaining);
|
|
380
|
+
const sleepSignals = [AbortSignal.timeout(remaining)];
|
|
381
|
+
if (userSignal !== undefined)
|
|
382
|
+
sleepSignals.push(userSignal);
|
|
383
|
+
const sleepSig = AbortSignal.any(sleepSignals);
|
|
384
|
+
try {
|
|
385
|
+
await sleep(delay, sleepSig);
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
if (userSignal !== undefined && userSignal.aborted)
|
|
389
|
+
throw userSignal.reason;
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (lastError instanceof HttpError)
|
|
395
|
+
throw lastError;
|
|
396
|
+
if (lastError !== undefined) {
|
|
397
|
+
throw new HttpError(`Request failed after ${maxRetries} retries`, lastStatus, lastError);
|
|
398
|
+
}
|
|
399
|
+
throw new HttpError(`Request failed: exceeded total deadline`, lastStatus);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
async function get(url, reqOpts) {
|
|
403
|
+
const timeoutMs = reqOpts?.timeoutMs ?? defaultTimeoutMs;
|
|
404
|
+
const origin = new URL(url).origin;
|
|
405
|
+
const result = await withRetry(origin, (signal) => dispatchFetch('GET', url, reqOpts?.headers ?? {}, userAgent, signal), reqOpts?.signal, timeoutMs);
|
|
406
|
+
const contentType = result.headers['content-type'] ?? '';
|
|
407
|
+
let body = result.bodyText;
|
|
408
|
+
if (contentType.includes('application/json')) {
|
|
409
|
+
try {
|
|
410
|
+
body = JSON.parse(result.bodyText);
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
// leave as string
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return { status: result.status, headers: result.headers, body };
|
|
417
|
+
}
|
|
418
|
+
async function head(url, headOpts) {
|
|
419
|
+
const followRedirects = headOpts?.followRedirects !== false;
|
|
420
|
+
const maxRedirects = headOpts?.maxRedirects ?? 5;
|
|
421
|
+
const timeoutMs = headOpts?.timeoutMs ?? defaultTimeoutMs;
|
|
422
|
+
const userSignal = headOpts?.signal;
|
|
423
|
+
if (!followRedirects) {
|
|
424
|
+
// SSRF guard on the (only) hop: scheme + private-IP rejection.
|
|
425
|
+
await guardHop(url);
|
|
426
|
+
const origin = new URL(url).origin;
|
|
427
|
+
const result = await withRetry(origin, (signal) => dispatchFetch('HEAD', url, headOpts?.headers ?? {}, userAgent, signal), userSignal, timeoutMs);
|
|
428
|
+
return { status: result.status, finalUrl: url, redirectChain: [] };
|
|
429
|
+
}
|
|
430
|
+
const redirectChain = [];
|
|
431
|
+
let currentUrl = url;
|
|
432
|
+
// SSRF guard on hop 0 (the initial URL) — scheme + private-IP rejection —
|
|
433
|
+
// *before* any request is dispatched.
|
|
434
|
+
await guardHop(currentUrl);
|
|
435
|
+
for (;;) {
|
|
436
|
+
const origin = new URL(currentUrl).origin;
|
|
437
|
+
const result = await withRetry(origin, (signal) => dispatchFetch('HEAD', currentUrl, headOpts?.headers ?? {}, userAgent, signal), userSignal, timeoutMs);
|
|
438
|
+
const { status, headers } = result;
|
|
439
|
+
if (status >= 300 && status < 400) {
|
|
440
|
+
const location = headers['location'];
|
|
441
|
+
if (location === undefined || location === '') {
|
|
442
|
+
return { status, finalUrl: currentUrl, redirectChain };
|
|
443
|
+
}
|
|
444
|
+
if (redirectChain.length >= maxRedirects) {
|
|
445
|
+
throw new HttpError(`too-many-redirects: exceeded ${maxRedirects} hops`);
|
|
446
|
+
}
|
|
447
|
+
redirectChain.push(currentUrl);
|
|
448
|
+
const nextUrl = new URL(location, currentUrl).href;
|
|
449
|
+
// SSRF guard on EVERY redirect hop, regardless of host equality:
|
|
450
|
+
// scheme + private-IP rejection. Redirect hops are never exempted by
|
|
451
|
+
// the test escape hatch.
|
|
452
|
+
await guardHop(nextUrl);
|
|
453
|
+
currentUrl = nextUrl;
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
// HEAD returned 4xx but not 404 — try GET+Range fallback
|
|
457
|
+
if (status >= 400 && status < 500 && status !== 404) {
|
|
458
|
+
const getHeaders = { ...(headOpts?.headers ?? {}), range: 'bytes=0-0' };
|
|
459
|
+
const origin2 = new URL(currentUrl).origin;
|
|
460
|
+
const getResult = await withRetry(origin2, (signal) => dispatchFetch('GET', currentUrl, getHeaders, userAgent, signal), userSignal, timeoutMs);
|
|
461
|
+
return { status: getResult.status, finalUrl: currentUrl, redirectChain };
|
|
462
|
+
}
|
|
463
|
+
return { status, finalUrl: currentUrl, redirectChain };
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return { get, head };
|
|
467
|
+
}
|
|
468
|
+
export async function headCheck(url, opts, signal) {
|
|
469
|
+
const cacheKey = 'headCheck:' + url;
|
|
470
|
+
if (opts.cache !== undefined) {
|
|
471
|
+
const cached = await opts.cache.get(cacheKey, signal);
|
|
472
|
+
if (cached !== null)
|
|
473
|
+
return cached;
|
|
474
|
+
}
|
|
475
|
+
let result;
|
|
476
|
+
// SSRF: enforce the trusted-host allowlist on the INPUT host BEFORE
|
|
477
|
+
// dispatching any request. A bibliography entry pointing at a metadata
|
|
478
|
+
// endpoint or an untrusted host must never be fetched at all.
|
|
479
|
+
let inputHost;
|
|
480
|
+
try {
|
|
481
|
+
inputHost = new URL(url).hostname.toLowerCase();
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
const out = { ok: false, reason: 'network-error', details: 'invalid URL' };
|
|
485
|
+
if (opts.cache !== undefined)
|
|
486
|
+
await opts.cache.set(cacheKey, out);
|
|
487
|
+
return out;
|
|
488
|
+
}
|
|
489
|
+
if (!isHostAllowed(inputHost, opts.trustedHosts)) {
|
|
490
|
+
const out = { ok: false, reason: 'wrong-host', details: inputHost };
|
|
491
|
+
if (opts.cache !== undefined)
|
|
492
|
+
await opts.cache.set(cacheKey, out);
|
|
493
|
+
return out;
|
|
494
|
+
}
|
|
495
|
+
try {
|
|
496
|
+
const response = await opts.http.head(url, { signal });
|
|
497
|
+
const host = new URL(response.finalUrl).hostname.toLowerCase();
|
|
498
|
+
if (!isHostAllowed(host, opts.trustedHosts)) {
|
|
499
|
+
result = { ok: false, reason: 'wrong-host', details: host };
|
|
500
|
+
}
|
|
501
|
+
else if (response.status >= 400) {
|
|
502
|
+
result = { ok: false, reason: 'dead-url', details: String(response.status) };
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
result = {
|
|
506
|
+
ok: true,
|
|
507
|
+
status: response.status,
|
|
508
|
+
finalUrl: response.finalUrl,
|
|
509
|
+
redirectChain: response.redirectChain,
|
|
510
|
+
host,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
catch (err) {
|
|
515
|
+
if (isAbortError(err) && !(err instanceof HttpError)) {
|
|
516
|
+
throw err;
|
|
517
|
+
}
|
|
518
|
+
if (err instanceof HttpError) {
|
|
519
|
+
const msg = err.message;
|
|
520
|
+
if (msg.includes('rejected: private IP')) {
|
|
521
|
+
result = { ok: false, reason: 'wrong-host', details: 'redirect to private IP rejected' };
|
|
522
|
+
}
|
|
523
|
+
else if (msg.includes('too-many-redirects')) {
|
|
524
|
+
result = { ok: false, reason: 'too-many-redirects', details: msg };
|
|
525
|
+
}
|
|
526
|
+
else if (isAbortError(err)) {
|
|
527
|
+
result = { ok: false, reason: 'timeout', details: msg };
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
result = { ok: false, reason: 'network-error', details: msg };
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
535
|
+
result = { ok: false, reason: 'network-error', details: msg };
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (opts.cache !== undefined) {
|
|
539
|
+
await opts.cache.set(cacheKey, result);
|
|
540
|
+
}
|
|
541
|
+
return result;
|
|
542
|
+
}
|
|
543
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bibcheck identifiers — local, offline DOI / ISBN / URL well-formedness checks.
|
|
3
|
+
*
|
|
4
|
+
* A pure functional-core module: no I/O, all input passed as arguments. It runs
|
|
5
|
+
* BEFORE any network call and catches the large class of AI-fabricated citations
|
|
6
|
+
* that carry a malformed identifier (a transposed ISBN digit, a DOI with stray
|
|
7
|
+
* punctuation, a non-URL in `url:`) — the cheapest, highest-yield hallucination
|
|
8
|
+
* signal. Emits the `IdentifiersLayer` from the output schema.
|
|
9
|
+
*
|
|
10
|
+
* No runtime dependencies: ISBN check-digit validation and normalization are
|
|
11
|
+
* hand-rolled (both are small, well-specified algorithms).
|
|
12
|
+
*/
|
|
13
|
+
import type { CslEntry } from './schema/csl.js';
|
|
14
|
+
import type { IdentifiersLayer } from './schema/output.js';
|
|
15
|
+
export interface RunIdentifiersDeps {
|
|
16
|
+
bibliography: CslEntry[];
|
|
17
|
+
}
|
|
18
|
+
export interface RunIdentifiersResult {
|
|
19
|
+
entries: Array<{
|
|
20
|
+
citekey: string;
|
|
21
|
+
identifiers: IdentifiersLayer;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
/** Validate a DOI string. */
|
|
25
|
+
export declare function validateDoi(doi: string): 'ok' | 'malformed';
|
|
26
|
+
/**
|
|
27
|
+
* Normalize an ISBN to its bare digit string (hyphens/spaces removed, upper-cased
|
|
28
|
+
* for a trailing `X`). Returns null when the input is not a 10- or 13-character
|
|
29
|
+
* ISBN shape. Exposed so callers (e.g. existence cache keys) can key on a
|
|
30
|
+
* canonical form rather than the raw, variably-hyphenated string.
|
|
31
|
+
*/
|
|
32
|
+
export declare function normalizeIsbn(raw: string): string | null;
|
|
33
|
+
/** Validate an ISBN string (10 or 13). */
|
|
34
|
+
export declare function validateIsbn(isbn: string): 'ok' | 'bad-checksum' | 'malformed';
|
|
35
|
+
/** Validate that a string is a well-formed http/https URL. */
|
|
36
|
+
export declare function validateUrl(url: string): 'ok' | 'malformed';
|
|
37
|
+
/** Compute the identifiers layer for one entry. */
|
|
38
|
+
export declare function identifiersFor(entry: CslEntry): IdentifiersLayer;
|
|
39
|
+
/**
|
|
40
|
+
* Validate every entry's identifiers. Synchronous, deterministic, no I/O.
|
|
41
|
+
* Output order matches input order.
|
|
42
|
+
*/
|
|
43
|
+
export declare function runIdentifiers(deps: RunIdentifiersDeps): RunIdentifiersResult;
|
|
44
|
+
//# sourceMappingURL=identifiers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identifiers.d.ts","sourceRoot":"","sources":["../src/identifiers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAoB,MAAM,oBAAoB,CAAC;AAM7E,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,QAAQ,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,gBAAgB,CAAA;KAAE,CAAC,CAAC;CACpE;AAiBD,6BAA6B;AAC7B,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CAE3D;AAMD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKxD;AAqBD,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,GAAG,WAAW,CAK9E;AAMD,8DAA8D;AAC9D,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CAQ3D;AAWD,mDAAmD;AACnD,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,gBAAgB,CAKhE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,oBAAoB,CAO7E"}
|