pi-unsloth-webtools 0.3.1 → 0.4.1

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/web-access.ts CHANGED
@@ -6,7 +6,13 @@ const MAX_CACHEABLE_DOMAIN_LEN = 253;
6
6
  const SITE_FILTER_LIMIT = 8;
7
7
  const DOTTED_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)+$/;
8
8
  const PORT_RE = /^[0-9]{1,5}$/;
9
+ export const MAX_SIGNAL_TIMEOUT_MS = 2 ** 31 - 1;
9
10
 
11
+ function throwInvalidDomain(value: unknown): never {
12
+ throw new Error(`Invalid website domain: ${String(value)}`);
13
+ }
14
+
15
+ const INVALID_HOST_REASON = "Blocked: the URL has an invalid hostname or port.";
10
16
  export interface WebsitePolicy {
11
17
  allowedDomains: string[];
12
18
  blockedDomains: string[];
@@ -19,18 +25,19 @@ export function normalizeDomain(value: unknown): string {
19
25
  [...domain].some((char) => char.charCodeAt(0) < 32) ||
20
26
  ["\\", "/", "@", "?", "#"].some((char) => domain.includes(char))
21
27
  ) {
22
- throw new Error(`Invalid website domain: ${String(value)}`);
28
+ throwInvalidDomain(value);
23
29
  }
24
- const bracketed = domain.startsWith("[") && domain.endsWith("]");
25
- if (domain.startsWith("[") !== domain.endsWith("]")) {
26
- throw new Error(`Invalid website domain: ${String(value)}`);
30
+ const startsBracket = domain.startsWith("[");
31
+ const endsBracket = domain.endsWith("]");
32
+ if (startsBracket !== endsBracket) {
33
+ throwInvalidDomain(value);
27
34
  }
28
- const stripped = (bracketed ? domain.slice(1, -1) : domain).replace(/\.+$/, "");
35
+ const stripped = (startsBracket ? domain.slice(1, -1) : domain).replace(/\.+$/, "");
29
36
  if (/^[0-9a-fA-F:.]+$/.test(stripped) && stripped.includes(":")) {
30
37
  try {
31
38
  return compressIpv6(stripped);
32
39
  } catch {
33
- throw new Error(`Invalid website domain: ${String(value)}`);
40
+ throwInvalidDomain(value);
34
41
  }
35
42
  }
36
43
  if (stripped.includes(":")) {
@@ -52,13 +59,13 @@ export function normalizeDomain(value: unknown): string {
52
59
  try {
53
60
  asciiDomain = domainToASCII(stripped).toLowerCase();
54
61
  } catch {
55
- throw new Error(`Invalid website domain: ${String(value)}`);
62
+ throwInvalidDomain(value);
56
63
  }
57
64
  if (
58
65
  asciiDomain.length > 253 ||
59
66
  !asciiDomain.split(".").every((label) => DOMAIN_LABEL_RE.test(label))
60
67
  ) {
61
- throw new Error(`Invalid website domain: ${String(value)}`);
68
+ throwInvalidDomain(value);
62
69
  }
63
70
  return asciiDomain;
64
71
  }
@@ -194,7 +201,7 @@ export function checkUrlAccess(
194
201
  try {
195
202
  parsed = new URL(candidate);
196
203
  } catch {
197
- return [false, "Blocked: the URL has an invalid hostname or port.", ""];
204
+ return [false, INVALID_HOST_REASON, ""];
198
205
  }
199
206
  const scheme = parsed.protocol.replace(/:$/, "").toLowerCase();
200
207
  if (scheme !== "http" && scheme !== "https") {
@@ -204,20 +211,16 @@ export function checkUrlAccess(
204
211
  return [false, "Blocked: URLs with credentials or encoded hostnames are not allowed.", ""];
205
212
  }
206
213
  if (!parsed.hostname) {
207
- return [false, "Blocked: the URL has an invalid hostname or port.", ""];
214
+ return [false, INVALID_HOST_REASON, ""];
208
215
  }
209
- try {
210
- if (parsed.port && !(PORT_RE.test(parsed.port) && Number(parsed.port) >= 1 && Number(parsed.port) <= 65535)) {
211
- return [false, "Blocked: the URL has an invalid hostname or port.", ""];
212
- }
213
- } catch {
214
- return [false, "Blocked: the URL has an invalid hostname or port.", ""];
216
+ if (parsed.port && !(PORT_RE.test(parsed.port) && Number(parsed.port) >= 1 && Number(parsed.port) <= 65535)) {
217
+ return [false, INVALID_HOST_REASON, ""];
215
218
  }
216
219
  let hostname: string;
217
220
  try {
218
221
  hostname = normalizeDomain(parsed.hostname);
219
222
  } catch {
220
- return [false, "Blocked: the URL has an invalid hostname or port.", ""];
223
+ return [false, INVALID_HOST_REASON, ""];
221
224
  }
222
225
  if (!policyAllows(hostname, policy)) {
223
226
  return [false, `Blocked: the website access policy disallows ${hostname}.`, hostname];
@@ -323,7 +326,7 @@ const GITHUB_NON_OWNER_SEGMENTS = new Set([
323
326
 
324
327
  const GITHUB_NAME_RE = /^[A-Za-z0-9_.\-]{1,100}$/;
325
328
 
326
- function githubRepoOwnerRepo(url: string): [string, string] | null {
329
+ function parseGithubRepo(url: string): { owner: string; repo: string; rest: string[] } | null {
327
330
  let parsed: URL;
328
331
  try {
329
332
  parsed = new URL(url);
@@ -333,22 +336,40 @@ function githubRepoOwnerRepo(url: string): [string, string] | null {
333
336
  const host = (parsed.hostname ?? "").toLowerCase();
334
337
  if (host !== "github.com" && host !== "www.github.com") return null;
335
338
  const parts = parsed.pathname.split("/").filter((part) => part.length > 0);
336
- if (parts.length !== 2) return null;
337
- const [owner, repo] = parts;
339
+ if (parts.length < 2) return null;
340
+ const owner = parts[0];
341
+ const repoRaw = parts[1];
338
342
  if (GITHUB_NON_OWNER_SEGMENTS.has(owner.toLowerCase())) return null;
339
- const cleanRepo = repo.endsWith(".git") ? repo.slice(0, -4) : repo;
340
- if (!GITHUB_NAME_RE.test(owner) || !GITHUB_NAME_RE.test(cleanRepo)) return null;
341
- return [owner, cleanRepo];
343
+ const repo = repoRaw.endsWith(".git") ? repoRaw.slice(0, -4) : repoRaw;
344
+ if (!GITHUB_NAME_RE.test(owner) || !GITHUB_NAME_RE.test(repo)) return null;
345
+ return { owner, repo, rest: parts.slice(2) };
346
+ }
347
+ function githubRepoRoot(url: string): { owner: string; repo: string; rest: string[] } | null {
348
+ const parsed = parseGithubRepo(url);
349
+ if (!parsed || parsed.rest.length !== 0) return null;
350
+ return parsed;
342
351
  }
343
352
 
344
353
  export function githubRepoReadmeApiUrl(url: string): string | null {
345
- const pair = githubRepoOwnerRepo(url);
346
- return pair ? `https://api.github.com/repos/${pair[0]}/${pair[1]}/readme` : null;
354
+ const parsed = githubRepoRoot(url);
355
+ if (!parsed) return null;
356
+ return `https://api.github.com/repos/${parsed.owner}/${parsed.repo}/readme`;
347
357
  }
348
358
 
349
359
  export function githubRepoRawReadmeUrl(url: string): string | null {
350
- const pair = githubRepoOwnerRepo(url);
351
- return pair ? `https://raw.githubusercontent.com/${pair[0]}/${pair[1]}/HEAD/README.md` : null;
360
+ const parsed = githubRepoRoot(url);
361
+ if (!parsed) return null;
362
+ return `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/HEAD/README.md`;
363
+ }
364
+ export function githubRawContentUrl(url: string): string | null {
365
+ const parsed = parseGithubRepo(url);
366
+ if (!parsed || parsed.rest.length < 2) return null;
367
+ const [kind, ref] = parsed.rest;
368
+ if (kind !== "blob" && kind !== "raw") return null;
369
+ if (!ref) return null;
370
+ const filePath = parsed.rest.slice(2).join("/");
371
+ if (!filePath) return null;
372
+ return `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/${ref}/${filePath}`;
352
373
  }
353
374
 
354
375
  function ipv4Octets(ip: string): number[] | null {