lifecycleion 0.0.9 → 0.0.11

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 (66) hide show
  1. package/README.md +47 -36
  2. package/dist/lib/domain-utils/domain-utils.cjs +1154 -0
  3. package/dist/lib/domain-utils/domain-utils.cjs.map +1 -0
  4. package/dist/lib/domain-utils/domain-utils.d.cts +210 -0
  5. package/dist/lib/domain-utils/domain-utils.d.ts +210 -0
  6. package/dist/lib/domain-utils/domain-utils.js +1112 -0
  7. package/dist/lib/domain-utils/domain-utils.js.map +1 -0
  8. package/dist/lib/event-emitter.cjs.map +1 -1
  9. package/dist/lib/event-emitter.js.map +1 -1
  10. package/dist/lib/http-client/index.cjs +5254 -0
  11. package/dist/lib/http-client/index.cjs.map +1 -0
  12. package/dist/lib/http-client/index.d.cts +372 -0
  13. package/dist/lib/http-client/index.d.ts +372 -0
  14. package/dist/lib/http-client/index.js +5207 -0
  15. package/dist/lib/http-client/index.js.map +1 -0
  16. package/dist/lib/http-client-mock/index.cjs +525 -0
  17. package/dist/lib/http-client-mock/index.cjs.map +1 -0
  18. package/dist/lib/http-client-mock/index.d.cts +129 -0
  19. package/dist/lib/http-client-mock/index.d.ts +129 -0
  20. package/dist/lib/http-client-mock/index.js +488 -0
  21. package/dist/lib/http-client-mock/index.js.map +1 -0
  22. package/dist/lib/http-client-node/index.cjs +1112 -0
  23. package/dist/lib/http-client-node/index.cjs.map +1 -0
  24. package/dist/lib/http-client-node/index.d.cts +43 -0
  25. package/dist/lib/http-client-node/index.d.ts +43 -0
  26. package/dist/lib/http-client-node/index.js +1075 -0
  27. package/dist/lib/http-client-node/index.js.map +1 -0
  28. package/dist/lib/http-client-xhr/index.cjs +323 -0
  29. package/dist/lib/http-client-xhr/index.cjs.map +1 -0
  30. package/dist/lib/http-client-xhr/index.d.cts +23 -0
  31. package/dist/lib/http-client-xhr/index.d.ts +23 -0
  32. package/dist/lib/http-client-xhr/index.js +286 -0
  33. package/dist/lib/http-client-xhr/index.js.map +1 -0
  34. package/dist/lib/lifecycle-manager/index.cjs.map +1 -1
  35. package/dist/lib/lifecycle-manager/index.js.map +1 -1
  36. package/dist/lib/logger/index.cjs +6 -5
  37. package/dist/lib/logger/index.cjs.map +1 -1
  38. package/dist/lib/logger/index.d.cts +3 -3
  39. package/dist/lib/logger/index.d.ts +3 -3
  40. package/dist/lib/logger/index.js +6 -5
  41. package/dist/lib/logger/index.js.map +1 -1
  42. package/dist/lib/lru-cache/index.cjs +1141 -0
  43. package/dist/lib/lru-cache/index.cjs.map +1 -0
  44. package/dist/lib/lru-cache/index.d.cts +100 -0
  45. package/dist/lib/lru-cache/index.d.ts +100 -0
  46. package/dist/lib/lru-cache/index.js +1104 -0
  47. package/dist/lib/lru-cache/index.js.map +1 -0
  48. package/dist/lib/process-signal-manager.cjs.map +1 -1
  49. package/dist/lib/process-signal-manager.js.map +1 -1
  50. package/dist/lib/promise-protected-resolver.cjs.map +1 -1
  51. package/dist/lib/promise-protected-resolver.js.map +1 -1
  52. package/dist/lib/retry-utils/index.cjs.map +1 -1
  53. package/dist/lib/retry-utils/index.d.cts +3 -23
  54. package/dist/lib/retry-utils/index.d.ts +3 -23
  55. package/dist/lib/retry-utils/index.js.map +1 -1
  56. package/dist/lib/safe-handle-callback.cjs.map +1 -1
  57. package/dist/lib/safe-handle-callback.d.cts +2 -2
  58. package/dist/lib/safe-handle-callback.d.ts +2 -2
  59. package/dist/lib/safe-handle-callback.js.map +1 -1
  60. package/dist/lib/single-event-observer.cjs.map +1 -1
  61. package/dist/lib/single-event-observer.js.map +1 -1
  62. package/dist/types-CUPvmYQ8.d.cts +868 -0
  63. package/dist/types-D_MywcG0.d.cts +23 -0
  64. package/dist/types-D_MywcG0.d.ts +23 -0
  65. package/dist/types-Hw2PUTIT.d.ts +868 -0
  66. package/package.json +45 -3
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/domain-utils/domain-utils.ts","../../../src/lib/domain-utils/helpers.ts"],"sourcesContent":["import { getDomain, getSubdomain, getPublicSuffix } from 'tldts';\nimport {\n isAllWildcards,\n hasPartialLabelWildcard,\n checkDNSLength,\n normalizeDomain,\n isIPv6,\n toAsciiDots,\n canonicalizeBracketedIPv6Content,\n matchesMultiLabelPattern,\n extractFixedTailAfterLastWildcard,\n isIPAddress,\n normalizeWildcardPattern,\n INTERNAL_PSEUDO_TLDS,\n INVALID_DOMAIN_CHARS,\n MAX_LABELS,\n} from './helpers';\n\nexport function safeParseURL(input: string): URL | null {\n try {\n return new URL(input);\n } catch {\n return null;\n }\n}\n\nfunction hasValidWildcardOriginHost(url: URL): boolean {\n return normalizeDomain(url.hostname) !== '';\n}\n\nfunction extractAuthority(input: string, schemeIdx: number): string {\n const afterScheme = input.slice(schemeIdx + 3);\n const cut = Math.min(\n ...[\n afterScheme.indexOf('/'),\n afterScheme.indexOf('?'),\n afterScheme.indexOf('#'),\n ].filter((i) => i !== -1),\n );\n\n return cut === Infinity ? afterScheme : afterScheme.slice(0, cut);\n}\n\nfunction hasDanglingPortInAuthority(input: string): boolean {\n const schemeIdx = input.indexOf('://');\n if (schemeIdx === -1) {\n return false;\n }\n\n const authority = extractAuthority(input, schemeIdx);\n const at = authority.lastIndexOf('@');\n const hostPort = at === -1 ? authority : authority.slice(at + 1);\n\n return hostPort.endsWith(':');\n}\n\n/**\n * Normalize a bare origin for consistent comparison.\n * Returns the canonical origin form with a normalized hostname,\n * lowercase scheme, no trailing slash, and default ports removed\n * (80 for http, 443 for https).\n */\nexport function normalizeOrigin(origin: string): string {\n // Preserve literal \"null\" origin exactly; treat all other invalids as empty sentinel\n if (origin === 'null') {\n return 'null';\n }\n\n // Normalize Unicode dots before URL parsing for browser compatibility\n // Chrome allows URLs like https://127。0。0。1\n const normalizedOrigin = toAsciiDots(origin);\n if (hasDanglingPortInAuthority(normalizedOrigin)) {\n return '';\n }\n\n const url = safeParseURL(normalizedOrigin);\n if (url) {\n // Only normalize bare origins. Allow a single trailing slash so callers\n // can pass values like \"https://example.com/\" without broadening real paths.\n if (\n url.username ||\n url.password ||\n (url.pathname && url.pathname !== '/') ||\n url.search ||\n url.hash\n ) {\n return '';\n }\n\n // Normalize hostname with punycode\n const normalizedHostname = normalizeDomain(url.hostname);\n\n // If hostname normalization fails (pathological IDN), return original origin\n // to avoid emitting values like \"https://\" with an empty host.\n if (normalizedHostname === '') {\n return '';\n }\n\n // Preserve brackets for IPv6 hosts; avoid double-bracketing if already present\n let host: string;\n // Extract the raw bracketed host (if present) from the authority portion only\n // to prevent matching brackets in path/query/fragment portions of full URLs.\n const schemeSep = normalizedOrigin.indexOf('://');\n const authority = extractAuthority(normalizedOrigin, schemeSep);\n const bracketMatch = authority.match(/\\[([^\\]]+)\\]/);\n const rawBracketContent = bracketMatch ? bracketMatch[1] : null;\n\n // Decode only for IPv6 detection, not for output\n const hostnameForIpv6Check = (\n rawBracketContent ? rawBracketContent : normalizedHostname\n )\n .replace(/%25/g, '%')\n .toLowerCase();\n\n if (isIPv6(hostnameForIpv6Check)) {\n // Canonicalize bracket content using shared helper (do not decode %25)\n const raw = rawBracketContent\n ? rawBracketContent\n : normalizedHostname.replace(/^\\[|\\]$/g, '');\n\n const canon = canonicalizeBracketedIPv6Content(raw);\n\n host = `[${canon}]`;\n } else {\n host = normalizedHostname;\n }\n\n // Normalize default ports for http/https\n let port = '';\n const protocolLower = url.protocol.toLowerCase();\n const defaultPort =\n protocolLower === 'https:'\n ? '443'\n : protocolLower === 'http:'\n ? '80'\n : '';\n\n if (url.port) {\n // Remove default ports for known protocols\n port = url.port === defaultPort ? '' : `:${url.port}`;\n } else {\n // Fallback: some URL implementations with exotic hosts might not populate url.port\n // even if an explicit port exists in the original string. Detect and normalize manually.\n // Handle potential userinfo (user:pass@) prefix for future compatibility\n\n // Try IPv6 bracketed format first\n let portMatch = authority.match(/^(?:[^@]*@)?\\[[^\\]]+\\]:(\\d+)$/);\n\n if (portMatch) {\n const explicit = portMatch[1];\n port = explicit === defaultPort ? '' : `:${explicit}`;\n } else {\n // Fallback for non-IPv6 authorities: detect :port after host\n portMatch = authority.match(/^(?:[^@]*@)?([^:]+):(\\d+)$/);\n if (portMatch) {\n const explicit = portMatch[2];\n port = explicit === defaultPort ? '' : `:${explicit}`;\n }\n }\n }\n\n // Explicitly use lowercase protocol for consistency\n return `${protocolLower}//${host}${port}`;\n }\n\n // If URL parsing fails, return empty sentinel (handles invalid URLs).\n // Literal \"null\" is handled above.\n return '';\n}\n\n/**\n * Smart wildcard matching for domains (apex must be explicit)\n *\n * Special case: a single \"*\" matches any host (domains and IPs).\n * For non-global patterns, apex domains must be listed explicitly.\n *\n * Pattern matching rules:\n * - \"*.example.com\" matches DIRECT subdomains only:\n * - \"api.example.com\" ✅ (direct subdomain)\n * - \"app.api.example.com\" ❌ (nested subdomain - use ** for this)\n * - \"**.example.com\" matches ALL subdomains (including nested):\n * - \"api.example.com\" ✅ (direct subdomain)\n * - \"app.api.example.com\" ✅ (nested subdomain)\n * - \"v2.app.api.example.com\" ✅ (deep nesting)\n * - \"*.*.example.com\" matches exactly TWO subdomain levels:\n * - \"a.b.example.com\" ✅ (two levels)\n * - \"api.example.com\" ❌ (one level)\n * - \"x.y.z.example.com\" ❌ (three levels)\n */\nexport function matchesWildcardDomain(\n domain: string,\n pattern: string,\n): boolean {\n const normalizedDomain = normalizeDomain(domain);\n\n if (normalizedDomain === '') {\n return false; // invalid domain cannot match\n }\n\n // Normalize pattern preserving wildcard labels and trailing dot handling\n const normalizedPattern = normalizeWildcardPattern(pattern);\n if (!normalizedPattern) {\n return false; // invalid pattern\n }\n\n // Check if pattern contains wildcards\n if (!normalizedPattern.includes('*')) {\n return false;\n }\n\n // Allow single \"*\" as global wildcard - matches both domains and IP addresses\n if (normalizedPattern === '*') {\n return true;\n }\n\n // Do not wildcard-match IP addresses with non-global patterns; only exact IP matches are supported elsewhere\n if (isIPAddress(normalizedDomain)) {\n return false;\n }\n\n // Reject other all-wildcards patterns (e.g., \"*.*\", \"**.*\")\n if (isAllWildcards(normalizedPattern)) {\n return false;\n }\n\n // PSL/IP tail guard: ensure the fixed tail is neither a PSL, a pseudo-TLD, nor an IP.\n // This prevents patterns like \"*.com\" or \"**.co.uk\" from matching\n\n const labels = normalizedPattern.split('.');\n const { fixedTail: fixedTailLabels } =\n extractFixedTailAfterLastWildcard(labels);\n if (fixedTailLabels.length === 0) {\n return false; // require a concrete tail\n }\n\n const tail = fixedTailLabels.join('.');\n\n if (isIPAddress(tail)) {\n return false; // no wildcarding around IPs\n }\n\n const ps = getPublicSuffix(tail);\n\n if (INTERNAL_PSEUDO_TLDS.has(tail) || (ps && ps === tail)) {\n return false; // no wildcarding around suffix-like tails\n }\n\n // \"**.\" requires at least one label before the remainder, so a domain that\n // exactly equals the remainder can never match (e.g., \"**.example.com\" ≠ \"example.com\").\n if (normalizedPattern.startsWith('**.')) {\n if (normalizedDomain === normalizeDomain(normalizedPattern.slice(3))) {\n return false;\n }\n }\n\n return matchesMultiLabelPattern(normalizedDomain, normalizedPattern);\n}\n\n/**\n * Smart origin wildcard matching for CORS with URL parsing\n * Supports protocol-specific wildcards and domain wildcards:\n * - * - matches any valid HTTP(S) origin (global wildcard)\n * - https://* or http://* - matches any domain with specific protocol\n * - *.example.com - matches direct subdomains with any protocol (ignores port)\n * - **.example.com - matches all subdomains including nested with any protocol\n * - https://*.example.com or http://*.example.com - matches direct subdomains with specific protocol\n * - https://**.example.com or http://**.example.com - matches all subdomains including nested with specific protocol\n *\n * Protocol support:\n * - For CORS, only http/https are supported; non-HTTP(S) origins never match\n * - Invalid or non-HTTP(S) schemes are rejected early for security\n *\n * Special cases:\n * - \"null\" origins: Cannot be matched by wildcard patterns, only by exact string inclusion in arrays\n * (Security note: sandboxed/file/data contexts can emit literal \"null\". Treat as lower trust; do not\n * allow via \"*\" or host wildcards. Include the literal \"null\" explicitly if you want to allow it.)\n * - Apex domains (example.com) must be listed explicitly, wildcards ignore port numbers\n * - Invalid URLs that fail parsing are treated as literal strings (no wildcard matching)\n */\nexport function matchesWildcardOrigin(\n origin: string,\n pattern: string,\n): boolean {\n // Normalize Unicode dots before URL parsing for consistency\n const normalizedOrigin = toAsciiDots(origin);\n const normalizedPattern = toAsciiDots(pattern);\n\n if (hasDanglingPortInAuthority(normalizedOrigin)) {\n return false;\n }\n\n // Parse once and reuse\n const originURL = safeParseURL(normalizedOrigin);\n\n // For CORS, only http/https are relevant; reject other schemes early when parsed.\n if (originURL) {\n const scheme = originURL.protocol.toLowerCase();\n if (scheme !== 'http:' && scheme !== 'https:') {\n return false;\n }\n\n if (\n originURL.username ||\n originURL.password ||\n (originURL.pathname && originURL.pathname !== '/') ||\n originURL.search ||\n originURL.hash\n ) {\n return false;\n }\n }\n\n // Global wildcard: single \"*\" matches any valid HTTP(S) origin\n if (normalizedPattern === '*') {\n return originURL !== null && hasValidWildcardOriginHost(originURL);\n }\n\n // Protocol-only wildcards: require valid URL parsing for security\n const patternLower = normalizedPattern.toLowerCase();\n\n if (patternLower === 'https://*' || patternLower === 'http://*') {\n if (!originURL) {\n return false; // must be a valid URL\n }\n\n const want = patternLower === 'https://*' ? 'https:' : 'http:';\n return (\n originURL.protocol.toLowerCase() === want &&\n hasValidWildcardOriginHost(originURL)\n );\n }\n\n // Remaining logic requires a parsed URL\n if (!originURL) {\n return false;\n }\n\n const normalizedHostname = normalizeDomain(originURL.hostname);\n\n if (normalizedHostname === '') {\n return false;\n }\n\n const originProtocol = originURL.protocol.slice(0, -1).toLowerCase(); // Remove trailing \":\" and lowercase\n\n // Handle protocol-specific domain wildcards: https://*.example.com\n if (normalizedPattern.includes('://')) {\n const [patternProtocol, ...rest] = normalizedPattern.split('://');\n const domainPattern = rest.join('://');\n\n // Reject non-domain characters in the domain pattern portion\n if (INVALID_DOMAIN_CHARS.test(domainPattern)) {\n return false;\n }\n\n // Protocol must match exactly\n if (originProtocol !== patternProtocol.toLowerCase()) {\n return false;\n }\n\n // Fast reject: domain pattern must contain at least one wildcard and not be all-wildcards\n if (!domainPattern.includes('*') || isAllWildcards(domainPattern)) {\n return false;\n }\n\n // Check domain pattern using direct domain matching\n return matchesWildcardDomain(normalizedHostname, domainPattern);\n }\n\n // Handle domain wildcard patterns (including multi-label patterns)\n if (normalizedPattern.includes('*')) {\n // Fast reject for invalid all-wildcards patterns (e.g., \"*.*\", \"**.*\")\n // Note: single \"*\" is handled above as global wildcard\n if (normalizedPattern !== '*' && isAllWildcards(normalizedPattern)) {\n return false;\n }\n\n return matchesWildcardDomain(normalizedHostname, normalizedPattern);\n }\n\n return false;\n}\n\n/**\n * Check if a domain matches any pattern in a list\n * Supports exact matches, wildcards, and normalization\n *\n * Validation:\n * - Origin-style patterns (e.g., \"https://*.example.com\") are NOT allowed in domain lists.\n * If any entry contains \"://\", an error will be thrown to surface misconfiguration early.\n * - Empty or whitespace-only entries are ignored.\n * Use `matchesOriginList` for origin-style patterns.\n */\nexport function matchesDomainList(\n domain: string,\n allowedDomains: string[],\n): boolean {\n const normalizedDomain = normalizeDomain(domain);\n\n // Early exit: invalid input cannot match any allowed domain\n if (normalizedDomain === '') {\n return false;\n }\n\n // Trim and filter out empty entries first\n const cleaned = allowedDomains\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n\n // Validate: throw if any origin-style patterns are present\n const ORIGIN_LIKE = /^[a-z][a-z0-9+\\-.]*:\\/\\//i;\n const originLike = cleaned.filter((s) => ORIGIN_LIKE.test(s));\n\n if (originLike.length > 0) {\n throw new Error(\n `matchesDomainList: origin-style patterns are not allowed in domain lists: ${originLike.join(', ')}`,\n );\n }\n\n for (const allowed of cleaned) {\n if (allowed.includes('*')) {\n if (matchesWildcardDomain(domain, allowed)) {\n return true;\n }\n continue;\n }\n\n const normalizedAllowed = normalizeDomain(allowed);\n if (\n isAllowedExactHostname(normalizedAllowed) &&\n normalizedDomain === normalizedAllowed\n ) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction isAllowedExactHostname(normalizedHostname: string): boolean {\n if (!normalizedHostname) {\n return false;\n }\n\n // The literal string \"null\" is origin-only and must never match in domain context.\n // Guard explicitly rather than relying on PSL classification of unknown single-label TLDs.\n if (normalizedHostname === 'null') {\n return false;\n }\n\n if (\n isIPAddress(normalizedHostname) ||\n INTERNAL_PSEUDO_TLDS.has(normalizedHostname)\n ) {\n return true;\n }\n\n const publicSuffix = getPublicSuffix(normalizedHostname);\n return !(publicSuffix && publicSuffix === normalizedHostname);\n}\n\n/**\n * Validate a configuration entry for either domain or origin contexts.\n * Non-throwing: returns { valid, info? } where info can carry non-fatal hints.\n *\n * - Domain context: accepts exact domains and domain wildcard patterns.\n * - Origin context: accepts\n * - exact origins,\n * - protocol-only wildcards like \"https://*\",\n * - protocol + domain wildcard like \"https://*.example.com\",\n * - bare domains (treated like domain context).\n *\n * Common rules:\n * - Only full-label wildcards are allowed (\"*\" or \"**\"); partial label wildcards are invalid.\n * - All-wildcards domain patterns (e.g., \"*.*\") are invalid. The global \"*\" may be allowed\n * in origin context when explicitly enabled via options.\n * - Wildcards cannot target IP tails.\n * - PSL tail guard also rejects pseudo-TLD suffix wildcards like `*.localhost`.\n */\nexport type WildcardKind = 'none' | 'global' | 'protocol' | 'subdomain';\n\nexport type ValidationResult = {\n valid: boolean;\n info?: string;\n wildcardKind: WildcardKind;\n};\n\nfunction isValidPortString(port: string): boolean {\n if (!/^\\d+$/.test(port)) {\n return false;\n }\n\n const portNumber = Number(port);\n return Number.isInteger(portNumber) && portNumber >= 0 && portNumber <= 65535;\n}\n\nexport function validateConfigEntry(\n entry: string,\n context: 'domain' | 'origin',\n options?: { allowGlobalWildcard?: boolean; allowProtocolWildcard?: boolean },\n): ValidationResult {\n const raw = (entry ?? '').trim();\n const SCHEME_RE = /^[a-z][a-z0-9+\\-.]*$/i;\n if (!raw) {\n return { valid: false, info: 'empty entry', wildcardKind: 'none' };\n }\n\n // Normalize options with secure defaults\n const opts = {\n allowGlobalWildcard: false,\n allowProtocolWildcard: true,\n ...(options ?? {}),\n } as Required<NonNullable<typeof options>> & {\n allowGlobalWildcard: boolean;\n allowProtocolWildcard: boolean;\n };\n\n // Helper: validate non-wildcard labels (punycode + DNS limits)\n function validateConcreteLabels(pattern: string): boolean {\n const labels = pattern.split('.');\n const concrete: string[] = [];\n\n for (const lbl of labels) {\n if (lbl === '*' || lbl === '**') {\n continue;\n }\n\n if (lbl.length > 63) {\n return false;\n }\n\n const nd = normalizeDomain(lbl);\n\n if (nd === '') {\n return false;\n }\n\n concrete.push(nd);\n }\n\n if (concrete.length > 0) {\n if (!checkDNSLength(concrete.join('.'))) {\n return false;\n }\n }\n\n return true;\n }\n\n // Helper: PSL tail guard and IP-tail rejection for wildcard patterns\n function wildcardTailIsInvalid(pattern: string): boolean {\n const normalized = normalizeWildcardPattern(pattern);\n\n const labels = normalized.split('.');\n\n // Extract the fixed tail after the last wildcard\n const { fixedTail: fixedTailLabels } =\n extractFixedTailAfterLastWildcard(labels);\n if (fixedTailLabels.length === 0) {\n return true; // require a concrete tail\n }\n\n const tail = fixedTailLabels.join('.');\n if (isIPAddress(tail)) {\n return true; // no wildcarding around IPs\n }\n const ps = getPublicSuffix(tail);\n if (INTERNAL_PSEUDO_TLDS.has(tail) || (ps && ps === tail)) {\n return true;\n }\n return false;\n }\n\n // Helper: domain-wildcard structural checks (no URL chars, full labels, etc.)\n function validateDomainWildcard(pattern: string): ValidationResult {\n // Normalize Unicode dots and trim\n const trimmed = pattern\n .trim()\n .normalize('NFC')\n .replace(/[.。。]/g, '.'); // normalize Unicode dot variants to ASCII\n\n if (INVALID_DOMAIN_CHARS.test(trimmed)) {\n return {\n valid: false,\n info: 'invalid characters in domain pattern',\n wildcardKind: 'none',\n };\n }\n\n if (hasPartialLabelWildcard(trimmed)) {\n return {\n valid: false,\n info: 'partial-label wildcards are not allowed',\n wildcardKind: 'none',\n };\n }\n\n const normalized = normalizeWildcardPattern(trimmed);\n\n if (!normalized) {\n return {\n valid: false,\n info: 'invalid domain labels',\n wildcardKind: 'none',\n };\n }\n\n if (normalized.split('.').length > MAX_LABELS) {\n return {\n valid: false,\n info: 'wildcard pattern exceeds label limit',\n wildcardKind: 'none',\n };\n }\n\n if (isAllWildcards(normalized)) {\n return {\n valid: false,\n info: 'all-wildcards pattern is not allowed',\n wildcardKind: 'none',\n };\n }\n\n if (!validateConcreteLabels(normalized)) {\n return {\n valid: false,\n info: 'invalid domain labels',\n wildcardKind: 'none',\n };\n }\n\n if (wildcardTailIsInvalid(normalized)) {\n return {\n valid: false,\n info: 'wildcard tail targets public suffix or IP (disallowed)',\n wildcardKind: 'none',\n };\n }\n\n return { valid: true, wildcardKind: 'subdomain' };\n }\n\n // Helper: exact domain check (no protocols). Reject apex public suffixes.\n function validateExactDomain(s: string): ValidationResult {\n // The literal string \"null\" is origin-only; reject it explicitly\n // rather than relying on PSL classification of unknown single-label TLDs.\n if (s.toLowerCase() === 'null') {\n return {\n valid: false,\n info: '\"null\" is not a valid domain entry',\n wildcardKind: 'none',\n };\n }\n\n // Check if it's an IP address first - if so, allow it (consistent with matchesDomainList)\n // Normalize Unicode dots for consistent IP detection\n const sDots = toAsciiDots(s);\n if (isIPAddress(sDots)) {\n return { valid: true, wildcardKind: 'none' };\n }\n\n // For non-IP addresses, reject URL-like characters\n if (INVALID_DOMAIN_CHARS.test(s)) {\n return {\n valid: false,\n info: 'invalid characters in domain',\n wildcardKind: 'none',\n };\n }\n\n const nd = normalizeDomain(s);\n\n if (nd === '') {\n return { valid: false, info: 'invalid domain', wildcardKind: 'none' };\n }\n\n const ps = getPublicSuffix(nd);\n\n if (ps && ps === nd && !INTERNAL_PSEUDO_TLDS.has(nd)) {\n return {\n valid: false,\n info: 'entry equals a public suffix (not registrable)',\n wildcardKind: 'none',\n };\n }\n return { valid: true, wildcardKind: 'none' };\n }\n\n // Domain context path\n if (context === 'domain') {\n // Reject any origin-style entries (with protocols) upfront\n if (/^[a-z][a-z0-9+\\-.]*:\\/\\//i.test(raw)) {\n return {\n valid: false,\n info: 'protocols are not allowed in domain context',\n wildcardKind: 'none',\n };\n }\n\n // Special-case: global wildcard in domain context (config-time validation)\n if (raw === '*') {\n return opts.allowGlobalWildcard\n ? { valid: true, wildcardKind: 'global' }\n : {\n valid: false,\n info: \"global wildcard '*' not allowed in this context\",\n wildcardKind: 'none',\n };\n }\n\n if (raw.includes('*')) {\n return validateDomainWildcard(raw);\n }\n return validateExactDomain(raw);\n }\n\n // Origin context\n // Special-case: literal \"null\" origin is allowed by exact inclusion\n if (raw === 'null') {\n return { valid: true, wildcardKind: 'none' };\n }\n\n // Special-case: global wildcard in origin context (config-time validation)\n if (raw === '*') {\n return opts.allowGlobalWildcard\n ? { valid: true, wildcardKind: 'global' }\n : {\n valid: false,\n info: \"global wildcard '*' not allowed in this context\",\n wildcardKind: 'none',\n };\n }\n\n const schemeIdx = raw.indexOf('://');\n if (schemeIdx === -1) {\n // Bare domain/or domain pattern allowed in origin lists; reuse domain rules\n if (raw.includes('*')) {\n return validateDomainWildcard(raw);\n }\n return validateExactDomain(raw);\n }\n\n const scheme = raw.slice(0, schemeIdx).toLowerCase();\n const rest = raw.slice(schemeIdx + 3);\n\n if (!SCHEME_RE.test(scheme)) {\n return {\n valid: false,\n info: 'invalid scheme in origin',\n wildcardKind: 'none',\n };\n }\n\n let normalizedRest = rest;\n\n // Disallow query/fragment in origin entries. Allow a single trailing slash\n // for exact origins so copied values like \"https://example.com/\" validate\n // the same way the runtime matchers normalize them.\n if (normalizedRest.includes('#') || normalizedRest.includes('?')) {\n return {\n valid: false,\n info: 'origin must not contain path, query, or fragment',\n wildcardKind: 'none',\n };\n }\n\n const slashIdx = normalizedRest.indexOf('/');\n if (slashIdx !== -1) {\n const authority = normalizedRest.slice(0, slashIdx);\n const suffix = normalizedRest.slice(slashIdx);\n\n if (suffix !== '/' || authority.includes('*')) {\n return {\n valid: false,\n info: 'origin must not contain path, query, or fragment',\n wildcardKind: 'none',\n };\n }\n\n normalizedRest = authority;\n }\n\n if (!normalizedRest) {\n return {\n valid: false,\n info: 'missing host in origin',\n wildcardKind: 'none',\n };\n }\n\n // Reject userinfo in origin entries for security and clarity\n if (normalizedRest.includes('@')) {\n return {\n valid: false,\n info: 'origin must not include userinfo',\n wildcardKind: 'none',\n };\n }\n\n // Protocol-only wildcard: scheme://*\n if (normalizedRest === '*') {\n if (scheme !== 'http' && scheme !== 'https') {\n return {\n valid: false,\n info: 'wildcard origins require http or https scheme',\n wildcardKind: 'none',\n };\n }\n\n if (!opts.allowProtocolWildcard) {\n return {\n valid: false,\n info: 'protocol wildcard not allowed',\n wildcardKind: 'none',\n };\n }\n\n const info =\n scheme === 'http' || scheme === 'https'\n ? undefined\n : 'non-http(s) scheme; CORS may not match';\n return { valid: true, info, wildcardKind: 'protocol' };\n }\n\n // Extract host (and optional port) while respecting IPv6 brackets\n let host = normalizedRest;\n let hasPort = false;\n\n if (normalizedRest.startsWith('[')) {\n const end = normalizedRest.indexOf(']');\n if (end === -1) {\n return {\n valid: false,\n info: 'unclosed IPv6 bracket',\n wildcardKind: 'none',\n };\n }\n host = normalizedRest.slice(0, end + 1);\n const after = normalizedRest.slice(end + 1);\n if (after.startsWith(':')) {\n const port = after.slice(1);\n\n if (!isValidPortString(port)) {\n return {\n valid: false,\n info: 'invalid port in origin',\n wildcardKind: 'none',\n };\n }\n\n // port present -> allowed for exact origins, but reject with wildcard hosts below\n // leave host as bracketed literal\n hasPort = true;\n } else if (after.length > 0) {\n return {\n valid: false,\n info: 'unexpected characters after IPv6 host',\n wildcardKind: 'none',\n };\n }\n } else {\n // strip port if present\n const colon = normalizedRest.indexOf(':');\n if (colon !== -1) {\n host = normalizedRest.slice(0, colon);\n const port = normalizedRest.slice(colon + 1);\n\n if (!isValidPortString(port)) {\n return {\n valid: false,\n info: 'invalid port in origin',\n wildcardKind: 'none',\n };\n }\n\n // optional port part is fine for exact origins\n hasPort = true;\n }\n }\n\n // If wildcard present in origin authority, treat as protocol+domain wildcard\n if (host.includes('*')) {\n if (scheme !== 'http' && scheme !== 'https') {\n return {\n valid: false,\n info: 'wildcard origins require http or https scheme',\n wildcardKind: 'none',\n };\n }\n\n // Forbid ports/brackets with wildcard hosts\n if (host.includes('[') || host.includes(']')) {\n return {\n valid: false,\n info: 'wildcard host cannot be an IP literal',\n wildcardKind: 'none',\n };\n }\n\n if (hasPort) {\n return {\n valid: false,\n info: 'ports are not allowed in wildcard origins',\n wildcardKind: 'none',\n };\n }\n\n // Validate as domain wildcard\n const verdict = validateDomainWildcard(host);\n if (!verdict.valid) {\n return verdict;\n }\n\n const info =\n scheme === 'http' || scheme === 'https'\n ? undefined\n : 'non-http(s) scheme; CORS may not match';\n return { valid: true, info, wildcardKind: 'subdomain' };\n }\n\n // Exact origin: allow any scheme; validate host as domain or IP\n if (host.startsWith('[')) {\n const bracketContent = host.slice(1, -1);\n\n if (!isIPv6(bracketContent)) {\n return {\n valid: false,\n info: 'invalid IPv6 literal in origin',\n wildcardKind: 'none',\n };\n }\n\n const info =\n scheme === 'http' || scheme === 'https'\n ? undefined\n : 'non-http(s) scheme; CORS may not match';\n\n return { valid: true, info, wildcardKind: 'none' };\n }\n\n const hostDots = toAsciiDots(host);\n if (isIPAddress(hostDots)) {\n const info =\n scheme === 'http' || scheme === 'https'\n ? undefined\n : 'non-http(s) scheme; CORS may not match';\n return { valid: true, info, wildcardKind: 'none' };\n }\n\n // Domain host\n const nd = normalizeDomain(host);\n\n if (nd === '') {\n return {\n valid: false,\n info: 'invalid domain in origin',\n wildcardKind: 'none',\n };\n }\n const ps = getPublicSuffix(nd);\n if (ps && ps === nd && !INTERNAL_PSEUDO_TLDS.has(nd)) {\n return {\n valid: false,\n info: 'origin host equals a public suffix (not registrable)',\n wildcardKind: 'none',\n };\n }\n const info =\n scheme === 'http' || scheme === 'https'\n ? undefined\n : 'non-http(s) scheme; CORS may not match';\n return { valid: true, info, wildcardKind: 'none' };\n}\n\n/**\n * Parse an exact origin for list matching.\n * Rejects userinfo, non-empty paths, queries, and fragments so malformed inputs\n * are not silently normalized into broader origins.\n */\nfunction parseExactOriginForMatching(entry: string): {\n normalizedOrigin: string;\n normalizedHostname: string;\n} | null {\n if (entry === 'null') {\n return { normalizedOrigin: 'null', normalizedHostname: '' };\n }\n\n const normalized = toAsciiDots(entry);\n const schemeIdx = normalized.indexOf('://');\n\n if (schemeIdx !== -1) {\n const authority = extractAuthority(normalized, schemeIdx);\n const at = authority.lastIndexOf('@');\n const hostPort = at === -1 ? authority : authority.slice(at + 1);\n\n if (hostPort.endsWith(':')) {\n return null;\n }\n }\n\n const url = safeParseURL(normalized);\n if (!url) {\n return null;\n }\n\n if (url.username || url.password) {\n return null;\n }\n\n if (url.pathname && url.pathname !== '/') {\n return null;\n }\n\n if (url.search || url.hash) {\n return null;\n }\n\n const normalizedOrigin = normalizeOrigin(entry);\n if (normalizedOrigin === '') {\n return null;\n }\n\n return {\n normalizedOrigin,\n normalizedHostname: normalizeDomain(url.hostname),\n };\n}\n\nfunction isCredentialsSafeWildcardOriginPattern(pattern: string): boolean {\n const trimmed = pattern\n .trim()\n .normalize('NFC')\n .replace(/[.。。]/g, '.');\n\n function isValidCredentialWildcardHost(hostPattern: string): boolean {\n if (isAllWildcards(hostPattern)) {\n return false;\n }\n\n if (INVALID_DOMAIN_CHARS.test(hostPattern)) {\n return false;\n }\n\n if (hasPartialLabelWildcard(hostPattern)) {\n return false;\n }\n\n const labels = hostPattern.split('.');\n const concrete: string[] = [];\n\n for (const lbl of labels) {\n if (lbl === '*' || lbl === '**') {\n continue;\n }\n\n if (lbl.length > 63) {\n return false;\n }\n\n const nd = normalizeDomain(lbl);\n if (nd === '') {\n return false;\n }\n\n concrete.push(nd);\n }\n\n if (concrete.length > 0 && !checkDNSLength(concrete.join('.'))) {\n return false;\n }\n\n const normalized = normalizeWildcardPattern(hostPattern);\n const { fixedTail } = extractFixedTailAfterLastWildcard(\n (normalized || hostPattern).split('.'),\n );\n if (!normalized || fixedTail.length === 0) {\n return false;\n }\n\n const tail = fixedTail.join('.');\n if (isIPAddress(tail)) {\n return false;\n }\n\n const ps = getPublicSuffix(tail);\n return !INTERNAL_PSEUDO_TLDS.has(tail) && !(ps && ps === tail);\n }\n\n if (!trimmed.includes('*')) {\n return false;\n }\n\n const schemeIdx = trimmed.indexOf('://');\n if (schemeIdx === -1) {\n return isValidCredentialWildcardHost(trimmed);\n }\n\n const scheme = trimmed.slice(0, schemeIdx).toLowerCase();\n const host = trimmed.slice(schemeIdx + 3);\n\n if ((scheme !== 'http' && scheme !== 'https') || host === '*') {\n return false;\n }\n\n return isValidCredentialWildcardHost(host);\n}\n\n/**\n * Helper function to check origin list with wildcard support.\n * Supports exact matches, wildcard matches, and normalization.\n *\n * Exact origins may use non-HTTP(S) schemes and are compared exactly.\n * Wildcard matching remains HTTP(S)-only.\n * Blank allowlist entries are ignored after trimming.\n * Special case: single \"*\" matches any valid HTTP(S) origin.\n *\n * @param origin - The origin to check (undefined for requests without Origin header)\n * @param allowedOrigins - Array of allowed origin patterns\n * @param opts - Options for handling edge cases\n * @param opts.treatNoOriginAsAllowed - If true, allows requests without Origin header when \"*\" is in the allowed list\n */\nexport function matchesOriginList(\n origin: string | undefined,\n allowedOrigins: string[],\n opts: { treatNoOriginAsAllowed?: boolean } = {},\n): boolean {\n const cleaned = allowedOrigins.map((s) => s.trim()).filter(Boolean);\n\n if (!origin) {\n // Only allow requests without Origin header if explicitly opted in AND \"*\" is in the list\n return !!opts.treatNoOriginAsAllowed && cleaned.includes('*');\n }\n\n const parsedOrigin = parseExactOriginForMatching(origin);\n if (!parsedOrigin) {\n return false;\n }\n\n return cleaned.some((allowed) => {\n // Global wildcard: single \"*\" matches any origin - delegate to matchesWildcardOrigin for proper validation\n if (allowed === '*') {\n return matchesWildcardOrigin(origin, '*');\n }\n\n if (allowed.includes('*')) {\n // Avoid double-normalizing/parsing; wildcard matcher handles parsing + normalization itself\n // We pass the raw origin/pattern here (vs normalized in the non-wildcard path) because\n // the wildcard matcher needs to parse the origin as a URL for protocol/host extraction\n return matchesWildcardOrigin(origin, allowed);\n }\n\n if (allowed === 'null') {\n return parsedOrigin.normalizedOrigin === 'null';\n }\n\n if (!allowed.includes('://')) {\n const normalizedAllowedDomain = normalizeDomain(allowed);\n\n return (\n isAllowedExactHostname(normalizedAllowedDomain) &&\n parsedOrigin.normalizedHostname !== '' &&\n parsedOrigin.normalizedHostname === normalizedAllowedDomain\n );\n }\n\n const parsedAllowed = parseExactOriginForMatching(allowed);\n if (!parsedAllowed) {\n return false;\n }\n\n if (!isAllowedExactHostname(parsedAllowed.normalizedHostname)) {\n return false;\n }\n\n return parsedOrigin.normalizedOrigin === parsedAllowed.normalizedOrigin;\n });\n}\n\n/**\n * Helper function to check if origin matches any pattern in a list (credentials-safe).\n *\n * Exact origins may use non-HTTP(S) schemes and are compared exactly.\n * When `allowWildcardSubdomains` is enabled, only host subdomain wildcard\n * patterns are honored. Global \"*\" and protocol-only wildcards such as\n * \"https://*\" are intentionally not honored in credentials mode.\n * Blank allowlist entries are ignored after trimming.\n */\nexport function matchesCORSCredentialsList(\n origin: string | undefined,\n allowedOrigins: string[],\n options: { allowWildcardSubdomains?: boolean } = {},\n): boolean {\n if (!origin) {\n return false;\n }\n\n const parsedOrigin = parseExactOriginForMatching(origin);\n if (!parsedOrigin) {\n return false;\n }\n\n const cleaned = allowedOrigins.map((s) => s.trim()).filter(Boolean);\n\n const allowWildcard = !!options.allowWildcardSubdomains;\n\n for (const allowed of cleaned) {\n // Optional wildcard support for credentials lists (subdomain patterns only)\n if (allowWildcard && allowed.includes('*')) {\n if (\n isCredentialsSafeWildcardOriginPattern(allowed) &&\n matchesWildcardOrigin(origin, allowed)\n ) {\n return true;\n }\n continue;\n }\n\n if (allowed === 'null') {\n if (parsedOrigin.normalizedOrigin === 'null') {\n return true;\n }\n\n continue;\n }\n\n if (!allowed.includes('://')) {\n const normalizedAllowedDomain = normalizeDomain(allowed);\n\n if (\n isAllowedExactHostname(normalizedAllowedDomain) &&\n parsedOrigin.normalizedHostname !== '' &&\n parsedOrigin.normalizedHostname === normalizedAllowedDomain\n ) {\n return true;\n }\n\n continue;\n }\n\n const parsedAllowed = parseExactOriginForMatching(allowed);\n if (!parsedAllowed) {\n continue;\n }\n\n if (!isAllowedExactHostname(parsedAllowed.normalizedHostname)) {\n continue;\n }\n\n if (parsedOrigin.normalizedOrigin === parsedAllowed.normalizedOrigin) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Result of parsing a Host header\n */\nexport interface ParsedHost {\n /** Domain/hostname with brackets stripped (e.g., \"[::1]\" → \"::1\") */\n domain: string;\n /** Port number as string, or empty string if no port specified */\n port: string;\n}\n\n/**\n * Parse Host header into domain and port components\n * Supports IPv6 brackets and handles port extraction with strict validation\n *\n * This function is commonly used to parse the HTTP Host header,\n * which may contain:\n * - Regular hostnames: \"example.com\" or \"example.com:8080\"\n * - IPv6 addresses: \"[::1]\" or \"[::1]:8080\"\n * - IPv4 addresses: \"127.0.0.1\" or \"127.0.0.1:8080\"\n *\n * The returned domain has brackets stripped for normalization\n * (e.g., \"[::1]\" → \"::1\"), while port is returned separately.\n *\n * **Strict validation:** For bracketed IPv6 addresses, after the closing bracket `]`,\n * only the following are valid:\n * - Nothing (end of string): `[::1]` → valid\n * - Port with colon: `[::1]:8080` → valid\n * - Any other characters: `[::1]garbage`, `[::1][::2]` → returns empty (malformed)\n *\n * @param host - Host header value (hostname[:port] or [ipv6][:port])\n * @returns Object with domain (without brackets) and port (empty string if no port).\n * Returns `{ domain: '', port: '' }` for malformed input.\n *\n * @example\n * parseHostHeader('example.com:8080')\n * // => { domain: 'example.com', port: '8080' }\n *\n * parseHostHeader('[::1]:8080')\n * // => { domain: '::1', port: '8080' }\n *\n * parseHostHeader('[2001:db8::1]')\n * // => { domain: '2001:db8::1', port: '' }\n *\n * parseHostHeader('localhost')\n * // => { domain: 'localhost', port: '' }\n *\n * parseHostHeader('[::1][::2]') // malformed\n * // => { domain: '', port: '' }\n */\nexport function parseHostHeader(host: string): ParsedHost {\n const trimmedHost = host.trim();\n\n if (!trimmedHost) {\n return { domain: '', port: '' };\n }\n\n function parsePortOrFail(port: string): ParsedHost | null {\n if (!isValidPortString(port)) {\n return { domain: '', port: '' };\n }\n\n return null;\n }\n\n // Handle IPv6 brackets\n if (trimmedHost.startsWith('[')) {\n const end = trimmedHost.indexOf(']');\n\n if (end !== -1) {\n const domain = trimmedHost.slice(1, end); // Remove brackets for normalization\n const rest = trimmedHost.slice(end + 1);\n\n if (!isIPv6(domain)) {\n return { domain: '', port: '' };\n }\n\n // Strict validation: after closing bracket, only allow empty or :port\n if (rest === '') {\n return { domain, port: '' };\n }\n\n if (rest.startsWith(':')) {\n const invalid = parsePortOrFail(rest.slice(1));\n if (invalid) {\n return invalid;\n }\n\n return { domain, port: rest.slice(1) };\n }\n\n // Malformed: has junk after closing bracket (e.g., \"[::1]garbage\" or \"[::1][::2]\")\n return { domain: '', port: '' };\n }\n\n // Malformed bracket - missing closing bracket\n return { domain: '', port: '' };\n }\n\n // Regular hostname:port parsing\n const idx = trimmedHost.indexOf(':');\n\n if (idx === -1) {\n return { domain: trimmedHost, port: '' };\n }\n\n if (idx === 0) {\n return { domain: '', port: '' };\n }\n\n const invalid = parsePortOrFail(trimmedHost.slice(idx + 1));\n if (invalid) {\n return invalid;\n }\n\n return {\n domain: trimmedHost.slice(0, idx),\n port: trimmedHost.slice(idx + 1),\n };\n}\n\n/**\n * Helper function to check if domain is apex (no subdomain)\n * Uses tldts to properly handle multi-part TLDs like .co.uk\n */\nexport function isApexDomain(domain: string): boolean {\n const normalizedDomain = normalizeDomain(domain);\n\n if (!normalizedDomain || isIPAddress(normalizedDomain)) {\n return false;\n }\n\n // Handle pseudo-TLD domains before tldts, which doesn't know about them.\n const labels = normalizedDomain.split('.');\n const lastLabel = labels[labels.length - 1];\n if (INTERNAL_PSEUDO_TLDS.has(lastLabel)) {\n if (normalizedDomain === lastLabel) {\n return true; // bare pseudo-TLD hostname (e.g. localhost, local) → apex\n }\n if (lastLabel === 'localhost') {\n return false; // localhost is a hostname, not a TLD; sub.localhost is not apex\n }\n return labels.length === 2; // foo.local → apex; bar.foo.local → not\n }\n\n // Use tldts to properly detect apex domains vs subdomains\n // This correctly handles multi-part TLDs like .co.uk, .com.au, etc.\n const parsedDomain = getDomain(normalizedDomain);\n const subdomain = getSubdomain(normalizedDomain);\n\n // Guard against null returns from tldts for invalid hosts\n if (!parsedDomain) {\n return false;\n }\n\n // Domain is apex if it matches the parsed domain and has no subdomain\n return parsedDomain === normalizedDomain && !subdomain;\n}\n\nexport {\n normalizeDomain,\n isIPAddress,\n isIPv4,\n isIPv6,\n checkDNSLength,\n canonicalizeBracketedIPv6Content,\n} from './helpers';\n\nexport { getDomain, getSubdomain } from 'tldts';\n","import { toASCII } from 'tr46';\n\n// Defense-in-depth: cap label processing to avoid pathological patterns\nexport const MAX_LABELS = 32;\n// Extra safety: cap recursive matching steps to avoid exponential blow-ups\nconst STEP_LIMIT = 10_000;\n\n// Invalid domain characters: ports, paths, fragments, brackets, userinfo, backslashes\nexport const INVALID_DOMAIN_CHARS = /[/?#:[\\]@\\\\]/;\n\n// Internal / special-use TLDs that we explicitly treat as non-PSL for wildcard-tail checks.\n// Keep this list explicit—do not guess.\n// Currently: 'localhost', 'local', 'test' (IANA special-use), and 'internal' (common in k8s/corporate).\n// If you want to allow other names (e.g., 'lan'), add them here deliberately.\nexport const INTERNAL_PSEUDO_TLDS = Object.freeze(\n new Set<string>(['localhost', 'local', 'test', 'internal']),\n);\n\n// Helper functions for wildcard pattern validation\nexport function isAllWildcards(s: string): boolean {\n return s.split('.').every((l) => l === '*' || l === '**');\n}\n\nexport function hasPartialLabelWildcard(s: string): boolean {\n return s.split('.').some((l) => l.includes('*') && l !== '*' && l !== '**');\n}\n\n/**\n * Check DNS length constraints for hostnames (non-throwing):\n * - each label <= 63 octets\n * - total FQDN <= 255 octets\n * - max 127 labels (theoretical DNS limit)\n * Assumes ASCII input (post-TR46 processing).\n */\nexport function checkDNSLength(host: string): boolean {\n const labels = host.split('.');\n\n // Label count cap for domains (127 is theoretical DNS limit)\n if (labels.length === 0 || labels.length > 127) {\n return false;\n }\n\n let total = 0;\n let i = 0;\n\n for (const lbl of labels) {\n const isLast = i++ === labels.length - 1;\n\n if (lbl.length === 0) {\n // Allow only a *trailing* empty label (for FQDN with a dot)\n if (!isLast) {\n return false;\n }\n continue;\n }\n\n if (lbl.length > 63) {\n return false;\n }\n\n total += lbl.length + 1; // account for dot\n }\n\n return total > 0 ? total - 1 <= 255 : false;\n}\n\n// IPv6 regex pattern hoisted to module scope for performance\nconst IPV6_BASE_REGEX =\n /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;\n\n/**\n * Check if a string is an IPv4 address\n */\nexport function isIPv4(str: string): boolean {\n const ipv4Regex =\n /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;\n return ipv4Regex.test(str);\n}\n\n/**\n * Check if a string is an IPv6 address\n */\nexport function isIPv6(str: string): boolean {\n // Zone identifiers are intentionally rejected to keep behavior portable across\n // Node, Bun, and browser-facing URL handling.\n const cleaned = str.replace(/^\\[|\\]$/g, '');\n if (cleaned.includes('%')) {\n return false;\n }\n\n return IPV6_BASE_REGEX.test(cleaned);\n}\n\n/**\n * Check if a string is an IP address (IPv4 or IPv6)\n */\nexport function isIPAddress(str: string): boolean {\n return isIPv4(str) || isIPv6(str);\n}\n\nfunction canonicalizeIPAddressLiteral(host: string): string | null {\n const isIPAddressLike =\n host.includes('.') ||\n host.includes(':') ||\n (host.startsWith('[') && host.endsWith(']'));\n\n if (!isIPAddressLike) {\n return null;\n }\n\n if (isIPv6(host)) {\n return canonicalizeBracketedIPv6Content(host.replace(/^\\[|\\]$/g, ''));\n }\n\n try {\n const url = new URL(`http://${host}/`);\n const canonicalHostname = url.hostname.toLowerCase();\n\n if (isIPv4(canonicalHostname)) {\n return canonicalHostname;\n }\n\n if (isIPv6(canonicalHostname)) {\n return canonicalHostname.replace(/^\\[|\\]$/g, '');\n }\n } catch {\n // Fall through to regular domain normalization.\n }\n\n return null;\n}\n\n/**\n * Canonicalize IPv6 literal content for deterministic origin comparison.\n * Uses the platform URL parser so the result matches WHATWG URL origin semantics.\n */\nexport function canonicalizeBracketedIPv6Content(content: string): string {\n try {\n const url = new URL(`http://[${content}]/`);\n return url.hostname.replace(/^\\[|\\]$/g, '');\n } catch {\n // Callers normally validate before canonicalizing; keep this helper\n // non-throwing as a defensive fallback.\n return content.toLowerCase();\n }\n}\n\n/**\n * Extract the fixed tail (non-wildcard labels) after the last wildcard in a pattern.\n * Returns the labels that come after the rightmost wildcard in the pattern.\n *\n * @param patternLabels - Array of pattern labels (e.g., [\"*\", \"api\", \"example\", \"com\"])\n * @returns Object with fixedTailStart index and fixedTail labels array\n */\nexport function extractFixedTailAfterLastWildcard(patternLabels: string[]): {\n fixedTailStart: number;\n fixedTail: string[];\n} {\n // Find the rightmost wildcard\n let lastWildcardIdx = -1;\n for (let i = patternLabels.length - 1; i >= 0; i--) {\n const lbl = patternLabels[i];\n if (lbl === '*' || lbl === '**') {\n lastWildcardIdx = i;\n break;\n }\n }\n\n const fixedTailStart = lastWildcardIdx + 1;\n const fixedTail = patternLabels.slice(fixedTailStart);\n\n return { fixedTailStart, fixedTail };\n}\n\n/**\n * Internal recursive helper for wildcard label matching\n */\nfunction matchesWildcardLabelsInternal(\n domainLabels: string[],\n patternLabels: string[],\n domainIndex: number,\n patternIndex: number,\n counter: { count: number },\n): boolean {\n if (++counter.count > STEP_LIMIT) {\n return false;\n }\n\n while (patternIndex < patternLabels.length) {\n const patternLabel = patternLabels[patternIndex];\n\n if (patternLabel === '**') {\n const isLeftmost = patternIndex === 0;\n\n // ** at index 0 means \"1+ labels\" while interior ** is \"0+\"\n // If leftmost, require at least one domain label\n if (isLeftmost) {\n for (let i = domainIndex + 1; i <= domainLabels.length; i++) {\n if (\n matchesWildcardLabelsInternal(\n domainLabels,\n patternLabels,\n i,\n patternIndex + 1,\n counter,\n )\n ) {\n return true;\n }\n }\n return false;\n }\n\n // Interior **: zero-or-more\n if (\n matchesWildcardLabelsInternal(\n domainLabels,\n patternLabels,\n domainIndex,\n patternIndex + 1,\n counter,\n )\n ) {\n return true;\n }\n\n // Try matching one or more labels\n for (let i = domainIndex + 1; i <= domainLabels.length; i++) {\n if (\n matchesWildcardLabelsInternal(\n domainLabels,\n patternLabels,\n i,\n patternIndex + 1,\n counter,\n )\n ) {\n return true;\n }\n }\n return false;\n } else if (patternLabel === '*') {\n // * matches exactly one label\n if (domainIndex >= domainLabels.length) {\n return false; // Not enough domain labels\n }\n domainIndex++;\n patternIndex++;\n } else {\n // Exact label match\n if (\n domainIndex >= domainLabels.length ||\n domainLabels[domainIndex] !== patternLabel\n ) {\n return false;\n }\n domainIndex++;\n patternIndex++;\n }\n }\n\n // All pattern labels matched, check if all domain labels are consumed\n return domainIndex === domainLabels.length;\n}\n\n/**\n * Match domain labels against wildcard pattern labels\n */\nexport function matchesWildcardLabels(\n domainLabels: string[],\n patternLabels: string[],\n): boolean {\n const counter = { count: 0 };\n return matchesWildcardLabelsInternal(\n domainLabels,\n patternLabels,\n 0,\n 0,\n counter,\n );\n}\n\n/**\n * Helper function for label-wise wildcard matching\n * Supports patterns like *.example.com, **.example.com, *.*.example.com, etc.\n */\nexport function matchesMultiLabelPattern(\n domain: string,\n pattern: string,\n): boolean {\n const domainLabels = domain.split('.');\n const patternLabels = pattern.split('.');\n\n // Guard against pathological label counts\n if (domainLabels.length > MAX_LABELS || patternLabels.length > MAX_LABELS) {\n return false;\n }\n\n // Pattern must have at least one non-wildcard label (the base domain)\n if (\n patternLabels.length === 0 ||\n patternLabels.every((label) => label === '*' || label === '**')\n ) {\n return false;\n }\n\n // Extract the fixed tail after the last wildcard\n const { fixedTailStart, fixedTail } =\n extractFixedTailAfterLastWildcard(patternLabels);\n\n // Domain must be at least as long as the fixed tail\n if (domainLabels.length < fixedTail.length) {\n return false;\n }\n\n // Match fixed tail exactly (right-aligned)\n for (let i = 0; i < fixedTail.length; i++) {\n const domainLabel =\n domainLabels[domainLabels.length - fixedTail.length + i];\n const patternLabel = fixedTail[i];\n if (patternLabel !== domainLabel) {\n return false;\n }\n }\n\n // Now match the left side (which may include wildcards and fixed labels)\n const remainingDomainLabels = domainLabels.slice(\n 0,\n domainLabels.length - fixedTail.length,\n );\n const leftPatternLabels = patternLabels.slice(0, fixedTailStart);\n\n if (leftPatternLabels.length === 0) {\n // No left pattern, so only the fixed tail is required\n return remainingDomainLabels.length === 0;\n }\n\n return matchesWildcardLabels(remainingDomainLabels, leftPatternLabels);\n}\n\n/**\n * Normalize Unicode dot variants to ASCII dots for consistent IP and domain handling\n * @param s - String that may contain Unicode dot variants\n * @returns String with Unicode dots normalized to ASCII dots\n */\nexport function toAsciiDots(s: string): string {\n return s.replace(/[.。。]/g, '.'); // fullwidth/japanese/halfwidth\n}\n\n/**\n * Normalize a domain name for consistent comparison\n * Handles trim, lowercase, a single trailing-dot FQDN form, NFC normalization,\n * and punycode conversion for IDN safety. Returns the canonical host form\n * without a trailing dot. Repeated trailing dots are rejected as invalid.\n * IP literals are canonicalized to a stable WHATWG URL-compatible form.\n */\nexport function normalizeDomain(domain: string): string {\n let trimmed = domain.trim();\n\n // Normalize Unicode dots BEFORE checking IP for consistent behavior\n trimmed = toAsciiDots(trimmed);\n\n // Allow a single trailing dot for FQDNs, but reject repeated trailing dots\n if (/\\.\\.+$/.test(trimmed)) {\n return '';\n }\n\n if (trimmed.endsWith('.')) {\n trimmed = trimmed.slice(0, -1);\n }\n\n // Canonicalize IP literals up front so exact host checks line up with WHATWG URL parsing.\n const canonicalIPAddress = canonicalizeIPAddressLiteral(trimmed);\n if (canonicalIPAddress !== null) {\n return canonicalIPAddress;\n }\n\n // Apply NFC normalization for Unicode domains\n const normalized = trimmed.normalize('NFC').toLowerCase();\n\n try {\n // Use TR46/IDNA processing for robust Unicode domain handling that mirrors browser behavior\n const ascii = toASCII(normalized, {\n useSTD3ASCIIRules: true,\n checkHyphens: true,\n checkBidi: true,\n checkJoiners: true,\n transitionalProcessing: false, // matches modern browser behavior (non-transitional)\n verifyDNSLength: false, // we already do our own length checks\n });\n if (!ascii) {\n throw new Error('TR46 processing failed');\n }\n // Enforce DNS length constraints post-TR46\n return checkDNSLength(ascii) ? ascii : ''; // return sentinel on invalid DNS lengths\n } catch {\n // On TR46 failure, return sentinel empty-string to signal invalid hostname\n return '';\n }\n}\n\n/**\n * Normalize a wildcard domain pattern by preserving wildcard labels\n * and punycode only non-wildcard labels. Also trims and removes\n * a trailing dot if present.\n */\nexport function normalizeWildcardPattern(pattern: string): string {\n let trimmed = pattern\n .trim()\n .normalize('NFC')\n .replace(/[.。。]/g, '.'); // normalize Unicode dot variants to ASCII\n\n // Refuse non-domain characters (ports, paths, fragments, brackets, userinfo, backslashes)\n if (INVALID_DOMAIN_CHARS.test(trimmed)) {\n return ''; // sentinel for invalid pattern\n }\n\n if (trimmed.endsWith('.')) {\n trimmed = trimmed.slice(0, -1);\n }\n\n const labels = trimmed.split('.');\n\n // Reject empty labels post-split early (e.g., *..example.com)\n // This avoids double dots slipping to punycode\n for (const lbl of labels) {\n if (lbl.length === 0) {\n return ''; // sentinel for invalid pattern (no empty labels)\n }\n }\n\n const normalizedLabels = [];\n for (const lbl of labels) {\n if (lbl === '*' || lbl === '**') {\n normalizedLabels.push(lbl);\n continue;\n }\n\n // Pre-punycode check for obviously invalid labels\n if (lbl.length > 63) {\n return ''; // sentinel for invalid pattern\n }\n\n const nd = normalizeDomain(lbl);\n\n if (nd === '') {\n // Invalid label after normalization\n return ''; // sentinel for invalid pattern\n }\n\n normalizedLabels.push(nd);\n }\n\n // Extract concrete (non-wildcard) labels and validate final ASCII length\n const concreteLabels = normalizedLabels.filter(\n (lbl) => lbl !== '*' && lbl !== '**',\n );\n if (concreteLabels.length > 0) {\n const concretePattern = concreteLabels.join('.');\n // Validate the ASCII length of the concrete parts to prevent pathological long IDNs\n if (!checkDNSLength(concretePattern)) {\n return ''; // sentinel for invalid pattern\n }\n }\n\n return normalizedLabels.join('.');\n}\n"],"mappings":"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","names":["import_tldts","nd","ps","info","invalid"]}
@@ -0,0 +1,210 @@
1
+ export { getDomain, getSubdomain } from 'tldts';
2
+
3
+ /**
4
+ * Check DNS length constraints for hostnames (non-throwing):
5
+ * - each label <= 63 octets
6
+ * - total FQDN <= 255 octets
7
+ * - max 127 labels (theoretical DNS limit)
8
+ * Assumes ASCII input (post-TR46 processing).
9
+ */
10
+ declare function checkDNSLength(host: string): boolean;
11
+ /**
12
+ * Check if a string is an IPv4 address
13
+ */
14
+ declare function isIPv4(str: string): boolean;
15
+ /**
16
+ * Check if a string is an IPv6 address
17
+ */
18
+ declare function isIPv6(str: string): boolean;
19
+ /**
20
+ * Check if a string is an IP address (IPv4 or IPv6)
21
+ */
22
+ declare function isIPAddress(str: string): boolean;
23
+ /**
24
+ * Canonicalize IPv6 literal content for deterministic origin comparison.
25
+ * Uses the platform URL parser so the result matches WHATWG URL origin semantics.
26
+ */
27
+ declare function canonicalizeBracketedIPv6Content(content: string): string;
28
+ /**
29
+ * Normalize a domain name for consistent comparison
30
+ * Handles trim, lowercase, a single trailing-dot FQDN form, NFC normalization,
31
+ * and punycode conversion for IDN safety. Returns the canonical host form
32
+ * without a trailing dot. Repeated trailing dots are rejected as invalid.
33
+ * IP literals are canonicalized to a stable WHATWG URL-compatible form.
34
+ */
35
+ declare function normalizeDomain(domain: string): string;
36
+
37
+ declare function safeParseURL(input: string): URL | null;
38
+ /**
39
+ * Normalize a bare origin for consistent comparison.
40
+ * Returns the canonical origin form with a normalized hostname,
41
+ * lowercase scheme, no trailing slash, and default ports removed
42
+ * (80 for http, 443 for https).
43
+ */
44
+ declare function normalizeOrigin(origin: string): string;
45
+ /**
46
+ * Smart wildcard matching for domains (apex must be explicit)
47
+ *
48
+ * Special case: a single "*" matches any host (domains and IPs).
49
+ * For non-global patterns, apex domains must be listed explicitly.
50
+ *
51
+ * Pattern matching rules:
52
+ * - "*.example.com" matches DIRECT subdomains only:
53
+ * - "api.example.com" ✅ (direct subdomain)
54
+ * - "app.api.example.com" ❌ (nested subdomain - use ** for this)
55
+ * - "**.example.com" matches ALL subdomains (including nested):
56
+ * - "api.example.com" ✅ (direct subdomain)
57
+ * - "app.api.example.com" ✅ (nested subdomain)
58
+ * - "v2.app.api.example.com" ✅ (deep nesting)
59
+ * - "*.*.example.com" matches exactly TWO subdomain levels:
60
+ * - "a.b.example.com" ✅ (two levels)
61
+ * - "api.example.com" ❌ (one level)
62
+ * - "x.y.z.example.com" ❌ (three levels)
63
+ */
64
+ declare function matchesWildcardDomain(domain: string, pattern: string): boolean;
65
+ /**
66
+ * Smart origin wildcard matching for CORS with URL parsing
67
+ * Supports protocol-specific wildcards and domain wildcards:
68
+ * - * - matches any valid HTTP(S) origin (global wildcard)
69
+ * - https://* or http://* - matches any domain with specific protocol
70
+ * - *.example.com - matches direct subdomains with any protocol (ignores port)
71
+ * - **.example.com - matches all subdomains including nested with any protocol
72
+ * - https://*.example.com or http://*.example.com - matches direct subdomains with specific protocol
73
+ * - https://**.example.com or http://**.example.com - matches all subdomains including nested with specific protocol
74
+ *
75
+ * Protocol support:
76
+ * - For CORS, only http/https are supported; non-HTTP(S) origins never match
77
+ * - Invalid or non-HTTP(S) schemes are rejected early for security
78
+ *
79
+ * Special cases:
80
+ * - "null" origins: Cannot be matched by wildcard patterns, only by exact string inclusion in arrays
81
+ * (Security note: sandboxed/file/data contexts can emit literal "null". Treat as lower trust; do not
82
+ * allow via "*" or host wildcards. Include the literal "null" explicitly if you want to allow it.)
83
+ * - Apex domains (example.com) must be listed explicitly, wildcards ignore port numbers
84
+ * - Invalid URLs that fail parsing are treated as literal strings (no wildcard matching)
85
+ */
86
+ declare function matchesWildcardOrigin(origin: string, pattern: string): boolean;
87
+ /**
88
+ * Check if a domain matches any pattern in a list
89
+ * Supports exact matches, wildcards, and normalization
90
+ *
91
+ * Validation:
92
+ * - Origin-style patterns (e.g., "https://*.example.com") are NOT allowed in domain lists.
93
+ * If any entry contains "://", an error will be thrown to surface misconfiguration early.
94
+ * - Empty or whitespace-only entries are ignored.
95
+ * Use `matchesOriginList` for origin-style patterns.
96
+ */
97
+ declare function matchesDomainList(domain: string, allowedDomains: string[]): boolean;
98
+ /**
99
+ * Validate a configuration entry for either domain or origin contexts.
100
+ * Non-throwing: returns { valid, info? } where info can carry non-fatal hints.
101
+ *
102
+ * - Domain context: accepts exact domains and domain wildcard patterns.
103
+ * - Origin context: accepts
104
+ * - exact origins,
105
+ * - protocol-only wildcards like "https://*",
106
+ * - protocol + domain wildcard like "https://*.example.com",
107
+ * - bare domains (treated like domain context).
108
+ *
109
+ * Common rules:
110
+ * - Only full-label wildcards are allowed ("*" or "**"); partial label wildcards are invalid.
111
+ * - All-wildcards domain patterns (e.g., "*.*") are invalid. The global "*" may be allowed
112
+ * in origin context when explicitly enabled via options.
113
+ * - Wildcards cannot target IP tails.
114
+ * - PSL tail guard also rejects pseudo-TLD suffix wildcards like `*.localhost`.
115
+ */
116
+ type WildcardKind = 'none' | 'global' | 'protocol' | 'subdomain';
117
+ type ValidationResult = {
118
+ valid: boolean;
119
+ info?: string;
120
+ wildcardKind: WildcardKind;
121
+ };
122
+ declare function validateConfigEntry(entry: string, context: 'domain' | 'origin', options?: {
123
+ allowGlobalWildcard?: boolean;
124
+ allowProtocolWildcard?: boolean;
125
+ }): ValidationResult;
126
+ /**
127
+ * Helper function to check origin list with wildcard support.
128
+ * Supports exact matches, wildcard matches, and normalization.
129
+ *
130
+ * Exact origins may use non-HTTP(S) schemes and are compared exactly.
131
+ * Wildcard matching remains HTTP(S)-only.
132
+ * Blank allowlist entries are ignored after trimming.
133
+ * Special case: single "*" matches any valid HTTP(S) origin.
134
+ *
135
+ * @param origin - The origin to check (undefined for requests without Origin header)
136
+ * @param allowedOrigins - Array of allowed origin patterns
137
+ * @param opts - Options for handling edge cases
138
+ * @param opts.treatNoOriginAsAllowed - If true, allows requests without Origin header when "*" is in the allowed list
139
+ */
140
+ declare function matchesOriginList(origin: string | undefined, allowedOrigins: string[], opts?: {
141
+ treatNoOriginAsAllowed?: boolean;
142
+ }): boolean;
143
+ /**
144
+ * Helper function to check if origin matches any pattern in a list (credentials-safe).
145
+ *
146
+ * Exact origins may use non-HTTP(S) schemes and are compared exactly.
147
+ * When `allowWildcardSubdomains` is enabled, only host subdomain wildcard
148
+ * patterns are honored. Global "*" and protocol-only wildcards such as
149
+ * "https://*" are intentionally not honored in credentials mode.
150
+ * Blank allowlist entries are ignored after trimming.
151
+ */
152
+ declare function matchesCORSCredentialsList(origin: string | undefined, allowedOrigins: string[], options?: {
153
+ allowWildcardSubdomains?: boolean;
154
+ }): boolean;
155
+ /**
156
+ * Result of parsing a Host header
157
+ */
158
+ interface ParsedHost {
159
+ /** Domain/hostname with brackets stripped (e.g., "[::1]" → "::1") */
160
+ domain: string;
161
+ /** Port number as string, or empty string if no port specified */
162
+ port: string;
163
+ }
164
+ /**
165
+ * Parse Host header into domain and port components
166
+ * Supports IPv6 brackets and handles port extraction with strict validation
167
+ *
168
+ * This function is commonly used to parse the HTTP Host header,
169
+ * which may contain:
170
+ * - Regular hostnames: "example.com" or "example.com:8080"
171
+ * - IPv6 addresses: "[::1]" or "[::1]:8080"
172
+ * - IPv4 addresses: "127.0.0.1" or "127.0.0.1:8080"
173
+ *
174
+ * The returned domain has brackets stripped for normalization
175
+ * (e.g., "[::1]" → "::1"), while port is returned separately.
176
+ *
177
+ * **Strict validation:** For bracketed IPv6 addresses, after the closing bracket `]`,
178
+ * only the following are valid:
179
+ * - Nothing (end of string): `[::1]` → valid
180
+ * - Port with colon: `[::1]:8080` → valid
181
+ * - Any other characters: `[::1]garbage`, `[::1][::2]` → returns empty (malformed)
182
+ *
183
+ * @param host - Host header value (hostname[:port] or [ipv6][:port])
184
+ * @returns Object with domain (without brackets) and port (empty string if no port).
185
+ * Returns `{ domain: '', port: '' }` for malformed input.
186
+ *
187
+ * @example
188
+ * parseHostHeader('example.com:8080')
189
+ * // => { domain: 'example.com', port: '8080' }
190
+ *
191
+ * parseHostHeader('[::1]:8080')
192
+ * // => { domain: '::1', port: '8080' }
193
+ *
194
+ * parseHostHeader('[2001:db8::1]')
195
+ * // => { domain: '2001:db8::1', port: '' }
196
+ *
197
+ * parseHostHeader('localhost')
198
+ * // => { domain: 'localhost', port: '' }
199
+ *
200
+ * parseHostHeader('[::1][::2]') // malformed
201
+ * // => { domain: '', port: '' }
202
+ */
203
+ declare function parseHostHeader(host: string): ParsedHost;
204
+ /**
205
+ * Helper function to check if domain is apex (no subdomain)
206
+ * Uses tldts to properly handle multi-part TLDs like .co.uk
207
+ */
208
+ declare function isApexDomain(domain: string): boolean;
209
+
210
+ export { type ParsedHost, type ValidationResult, type WildcardKind, canonicalizeBracketedIPv6Content, checkDNSLength, isApexDomain, isIPAddress, isIPv4, isIPv6, matchesCORSCredentialsList, matchesDomainList, matchesOriginList, matchesWildcardDomain, matchesWildcardOrigin, normalizeDomain, normalizeOrigin, parseHostHeader, safeParseURL, validateConfigEntry };
@@ -0,0 +1,210 @@
1
+ export { getDomain, getSubdomain } from 'tldts';
2
+
3
+ /**
4
+ * Check DNS length constraints for hostnames (non-throwing):
5
+ * - each label <= 63 octets
6
+ * - total FQDN <= 255 octets
7
+ * - max 127 labels (theoretical DNS limit)
8
+ * Assumes ASCII input (post-TR46 processing).
9
+ */
10
+ declare function checkDNSLength(host: string): boolean;
11
+ /**
12
+ * Check if a string is an IPv4 address
13
+ */
14
+ declare function isIPv4(str: string): boolean;
15
+ /**
16
+ * Check if a string is an IPv6 address
17
+ */
18
+ declare function isIPv6(str: string): boolean;
19
+ /**
20
+ * Check if a string is an IP address (IPv4 or IPv6)
21
+ */
22
+ declare function isIPAddress(str: string): boolean;
23
+ /**
24
+ * Canonicalize IPv6 literal content for deterministic origin comparison.
25
+ * Uses the platform URL parser so the result matches WHATWG URL origin semantics.
26
+ */
27
+ declare function canonicalizeBracketedIPv6Content(content: string): string;
28
+ /**
29
+ * Normalize a domain name for consistent comparison
30
+ * Handles trim, lowercase, a single trailing-dot FQDN form, NFC normalization,
31
+ * and punycode conversion for IDN safety. Returns the canonical host form
32
+ * without a trailing dot. Repeated trailing dots are rejected as invalid.
33
+ * IP literals are canonicalized to a stable WHATWG URL-compatible form.
34
+ */
35
+ declare function normalizeDomain(domain: string): string;
36
+
37
+ declare function safeParseURL(input: string): URL | null;
38
+ /**
39
+ * Normalize a bare origin for consistent comparison.
40
+ * Returns the canonical origin form with a normalized hostname,
41
+ * lowercase scheme, no trailing slash, and default ports removed
42
+ * (80 for http, 443 for https).
43
+ */
44
+ declare function normalizeOrigin(origin: string): string;
45
+ /**
46
+ * Smart wildcard matching for domains (apex must be explicit)
47
+ *
48
+ * Special case: a single "*" matches any host (domains and IPs).
49
+ * For non-global patterns, apex domains must be listed explicitly.
50
+ *
51
+ * Pattern matching rules:
52
+ * - "*.example.com" matches DIRECT subdomains only:
53
+ * - "api.example.com" ✅ (direct subdomain)
54
+ * - "app.api.example.com" ❌ (nested subdomain - use ** for this)
55
+ * - "**.example.com" matches ALL subdomains (including nested):
56
+ * - "api.example.com" ✅ (direct subdomain)
57
+ * - "app.api.example.com" ✅ (nested subdomain)
58
+ * - "v2.app.api.example.com" ✅ (deep nesting)
59
+ * - "*.*.example.com" matches exactly TWO subdomain levels:
60
+ * - "a.b.example.com" ✅ (two levels)
61
+ * - "api.example.com" ❌ (one level)
62
+ * - "x.y.z.example.com" ❌ (three levels)
63
+ */
64
+ declare function matchesWildcardDomain(domain: string, pattern: string): boolean;
65
+ /**
66
+ * Smart origin wildcard matching for CORS with URL parsing
67
+ * Supports protocol-specific wildcards and domain wildcards:
68
+ * - * - matches any valid HTTP(S) origin (global wildcard)
69
+ * - https://* or http://* - matches any domain with specific protocol
70
+ * - *.example.com - matches direct subdomains with any protocol (ignores port)
71
+ * - **.example.com - matches all subdomains including nested with any protocol
72
+ * - https://*.example.com or http://*.example.com - matches direct subdomains with specific protocol
73
+ * - https://**.example.com or http://**.example.com - matches all subdomains including nested with specific protocol
74
+ *
75
+ * Protocol support:
76
+ * - For CORS, only http/https are supported; non-HTTP(S) origins never match
77
+ * - Invalid or non-HTTP(S) schemes are rejected early for security
78
+ *
79
+ * Special cases:
80
+ * - "null" origins: Cannot be matched by wildcard patterns, only by exact string inclusion in arrays
81
+ * (Security note: sandboxed/file/data contexts can emit literal "null". Treat as lower trust; do not
82
+ * allow via "*" or host wildcards. Include the literal "null" explicitly if you want to allow it.)
83
+ * - Apex domains (example.com) must be listed explicitly, wildcards ignore port numbers
84
+ * - Invalid URLs that fail parsing are treated as literal strings (no wildcard matching)
85
+ */
86
+ declare function matchesWildcardOrigin(origin: string, pattern: string): boolean;
87
+ /**
88
+ * Check if a domain matches any pattern in a list
89
+ * Supports exact matches, wildcards, and normalization
90
+ *
91
+ * Validation:
92
+ * - Origin-style patterns (e.g., "https://*.example.com") are NOT allowed in domain lists.
93
+ * If any entry contains "://", an error will be thrown to surface misconfiguration early.
94
+ * - Empty or whitespace-only entries are ignored.
95
+ * Use `matchesOriginList` for origin-style patterns.
96
+ */
97
+ declare function matchesDomainList(domain: string, allowedDomains: string[]): boolean;
98
+ /**
99
+ * Validate a configuration entry for either domain or origin contexts.
100
+ * Non-throwing: returns { valid, info? } where info can carry non-fatal hints.
101
+ *
102
+ * - Domain context: accepts exact domains and domain wildcard patterns.
103
+ * - Origin context: accepts
104
+ * - exact origins,
105
+ * - protocol-only wildcards like "https://*",
106
+ * - protocol + domain wildcard like "https://*.example.com",
107
+ * - bare domains (treated like domain context).
108
+ *
109
+ * Common rules:
110
+ * - Only full-label wildcards are allowed ("*" or "**"); partial label wildcards are invalid.
111
+ * - All-wildcards domain patterns (e.g., "*.*") are invalid. The global "*" may be allowed
112
+ * in origin context when explicitly enabled via options.
113
+ * - Wildcards cannot target IP tails.
114
+ * - PSL tail guard also rejects pseudo-TLD suffix wildcards like `*.localhost`.
115
+ */
116
+ type WildcardKind = 'none' | 'global' | 'protocol' | 'subdomain';
117
+ type ValidationResult = {
118
+ valid: boolean;
119
+ info?: string;
120
+ wildcardKind: WildcardKind;
121
+ };
122
+ declare function validateConfigEntry(entry: string, context: 'domain' | 'origin', options?: {
123
+ allowGlobalWildcard?: boolean;
124
+ allowProtocolWildcard?: boolean;
125
+ }): ValidationResult;
126
+ /**
127
+ * Helper function to check origin list with wildcard support.
128
+ * Supports exact matches, wildcard matches, and normalization.
129
+ *
130
+ * Exact origins may use non-HTTP(S) schemes and are compared exactly.
131
+ * Wildcard matching remains HTTP(S)-only.
132
+ * Blank allowlist entries are ignored after trimming.
133
+ * Special case: single "*" matches any valid HTTP(S) origin.
134
+ *
135
+ * @param origin - The origin to check (undefined for requests without Origin header)
136
+ * @param allowedOrigins - Array of allowed origin patterns
137
+ * @param opts - Options for handling edge cases
138
+ * @param opts.treatNoOriginAsAllowed - If true, allows requests without Origin header when "*" is in the allowed list
139
+ */
140
+ declare function matchesOriginList(origin: string | undefined, allowedOrigins: string[], opts?: {
141
+ treatNoOriginAsAllowed?: boolean;
142
+ }): boolean;
143
+ /**
144
+ * Helper function to check if origin matches any pattern in a list (credentials-safe).
145
+ *
146
+ * Exact origins may use non-HTTP(S) schemes and are compared exactly.
147
+ * When `allowWildcardSubdomains` is enabled, only host subdomain wildcard
148
+ * patterns are honored. Global "*" and protocol-only wildcards such as
149
+ * "https://*" are intentionally not honored in credentials mode.
150
+ * Blank allowlist entries are ignored after trimming.
151
+ */
152
+ declare function matchesCORSCredentialsList(origin: string | undefined, allowedOrigins: string[], options?: {
153
+ allowWildcardSubdomains?: boolean;
154
+ }): boolean;
155
+ /**
156
+ * Result of parsing a Host header
157
+ */
158
+ interface ParsedHost {
159
+ /** Domain/hostname with brackets stripped (e.g., "[::1]" → "::1") */
160
+ domain: string;
161
+ /** Port number as string, or empty string if no port specified */
162
+ port: string;
163
+ }
164
+ /**
165
+ * Parse Host header into domain and port components
166
+ * Supports IPv6 brackets and handles port extraction with strict validation
167
+ *
168
+ * This function is commonly used to parse the HTTP Host header,
169
+ * which may contain:
170
+ * - Regular hostnames: "example.com" or "example.com:8080"
171
+ * - IPv6 addresses: "[::1]" or "[::1]:8080"
172
+ * - IPv4 addresses: "127.0.0.1" or "127.0.0.1:8080"
173
+ *
174
+ * The returned domain has brackets stripped for normalization
175
+ * (e.g., "[::1]" → "::1"), while port is returned separately.
176
+ *
177
+ * **Strict validation:** For bracketed IPv6 addresses, after the closing bracket `]`,
178
+ * only the following are valid:
179
+ * - Nothing (end of string): `[::1]` → valid
180
+ * - Port with colon: `[::1]:8080` → valid
181
+ * - Any other characters: `[::1]garbage`, `[::1][::2]` → returns empty (malformed)
182
+ *
183
+ * @param host - Host header value (hostname[:port] or [ipv6][:port])
184
+ * @returns Object with domain (without brackets) and port (empty string if no port).
185
+ * Returns `{ domain: '', port: '' }` for malformed input.
186
+ *
187
+ * @example
188
+ * parseHostHeader('example.com:8080')
189
+ * // => { domain: 'example.com', port: '8080' }
190
+ *
191
+ * parseHostHeader('[::1]:8080')
192
+ * // => { domain: '::1', port: '8080' }
193
+ *
194
+ * parseHostHeader('[2001:db8::1]')
195
+ * // => { domain: '2001:db8::1', port: '' }
196
+ *
197
+ * parseHostHeader('localhost')
198
+ * // => { domain: 'localhost', port: '' }
199
+ *
200
+ * parseHostHeader('[::1][::2]') // malformed
201
+ * // => { domain: '', port: '' }
202
+ */
203
+ declare function parseHostHeader(host: string): ParsedHost;
204
+ /**
205
+ * Helper function to check if domain is apex (no subdomain)
206
+ * Uses tldts to properly handle multi-part TLDs like .co.uk
207
+ */
208
+ declare function isApexDomain(domain: string): boolean;
209
+
210
+ export { type ParsedHost, type ValidationResult, type WildcardKind, canonicalizeBracketedIPv6Content, checkDNSLength, isApexDomain, isIPAddress, isIPv4, isIPv6, matchesCORSCredentialsList, matchesDomainList, matchesOriginList, matchesWildcardDomain, matchesWildcardOrigin, normalizeDomain, normalizeOrigin, parseHostHeader, safeParseURL, validateConfigEntry };