tldts-experimental 7.1.1 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -447,7 +447,7 @@ function isProbablyIpv6(hostname) {
447
447
  }
448
448
  else if (!(((code >= 48 && code <= 57) || // 0-9
449
449
  (code >= 97 && code <= 102) || // a-f
450
- (code >= 65 && code <= 90)) // A-F
450
+ (code >= 65 && code <= 70)) // A-F (RFC 4291 §2.2: an IPv6 hextet is hex digits only)
451
451
  )) {
452
452
  return false;
453
453
  }
@@ -463,6 +463,68 @@ function isIp(hostname) {
463
463
  return isProbablyIpv6(hostname) || isProbablyIpv4(hostname);
464
464
  }
465
465
 
466
+ /**
467
+ * Special-use domain names from the IANA "Special-Use Domain Names" registry:
468
+ * the authoritative list, created by RFC 6761 and maintained as new RFCs add to
469
+ * it: https://www.iana.org/assignments/special-use-domain-names/
470
+ * Snapshot: 2026-05-24. (RFC 6761 is not obsoleted; draft-hoffman-rfc6761bis
471
+ * proposes to retire its prose but keep this registry, so the registry is the
472
+ * source of truth; re-sync this list against it.)
473
+ *
474
+ * These names never correspond to a public registration, yet neither
475
+ * `isIcann` nor `isPrivate` marks one as special-use: most are absent from the
476
+ * Public Suffix List (so `a.test` looks like a registrable domain), and the
477
+ * few that are listed (`onion`, `home.arpa`) appear there as ordinary ICANN
478
+ * suffixes. `isSpecialUse` is the single signal that covers them all.
479
+ *
480
+ * Per the registry and RFC 6761 ("and any names falling within these domains"),
481
+ * the designation covers each listed name AND all of its sub-domains. DNS labels
482
+ * are case-insensitive (RFC 4343); `hostname` is expected to be already
483
+ * lower-cased and trailing-dot-stripped, as produced by `extractHostname`, the
484
+ * same normalization the Public-Suffix-List lookup relies on.
485
+ *
486
+ * Two groups of registry entries are intentionally excluded: the numeric
487
+ * reverse-DNS delegation zones (`10.in-addr.arpa`, the `*.ip6.arpa` ranges, …),
488
+ * which are reverse-DNS PTR zones rather than hostnames and whose parents
489
+ * (`in-addr.arpa`/`ip6.arpa`) are already in the Public Suffix List; and the
490
+ * deprecated `eap-noob.arpa` entry.
491
+ */
492
+ const SPECIAL_USE_DOMAINS = [
493
+ 'test', // RFC 6761
494
+ 'localhost', // RFC 6761
495
+ 'invalid', // RFC 6761
496
+ 'example', // RFC 6761
497
+ 'example.com', // RFC 6761
498
+ 'example.net', // RFC 6761
499
+ 'example.org', // RFC 6761
500
+ 'local', // RFC 6762 (mDNS)
501
+ 'onion', // RFC 7686 (Tor)
502
+ 'alt', // RFC 9476
503
+ 'home.arpa', // RFC 8375
504
+ 'ipv4only.arpa', // RFC 8880
505
+ 'resolver.arpa', // RFC 9462
506
+ 'service.arpa', // RFC 9665
507
+ '6tisch.arpa', // RFC 9031
508
+ 'eap.arpa', // RFC 9965
509
+ ];
510
+ /**
511
+ * Return `true` if `hostname` is, or is a sub-domain of, a special-use domain
512
+ * (see the registry note above). Expects an already-normalized `hostname`.
513
+ */
514
+ function isSpecialUse(hostname) {
515
+ for (const name of SPECIAL_USE_DOMAINS) {
516
+ // Match on a label boundary: `hostname` is either exactly `name` or ends
517
+ // with `.name` (so `latest` is not matched by `test`, nor `myexample.com`
518
+ // by `example.com`).
519
+ if (hostname.endsWith(name) &&
520
+ (hostname.length === name.length ||
521
+ hostname.charCodeAt(hostname.length - name.length - 1) === 46) /* '.' */) {
522
+ return true;
523
+ }
524
+ }
525
+ return false;
526
+ }
527
+
466
528
  /**
467
529
  * Implements fast shallow verification of hostnames. This does not perform a
468
530
  * struct check on the content of labels (classes of Unicode characters, etc.)
@@ -529,11 +591,12 @@ function isValidHostname (hostname) {
529
591
  lastCharCode !== 45);
530
592
  }
531
593
 
532
- function setDefaultsImpl({ allowIcannDomains = true, allowPrivateDomains = false, detectIp = true, extractHostname = true, mixedInputs = true, validHosts = null, validateHostname = true, }) {
594
+ function setDefaultsImpl({ allowIcannDomains = true, allowPrivateDomains = false, detectIp = true, detectSpecialUse = false, extractHostname = true, mixedInputs = true, validHosts = null, validateHostname = true, }) {
533
595
  return {
534
596
  allowIcannDomains,
535
597
  allowPrivateDomains,
536
598
  detectIp,
599
+ detectSpecialUse,
537
600
  extractHostname,
538
601
  mixedInputs,
539
602
  validHosts,
@@ -572,6 +635,7 @@ function getEmptyResult() {
572
635
  isIcann: null,
573
636
  isIp: null,
574
637
  isPrivate: null,
638
+ isSpecialUse: null,
575
639
  publicSuffix: null,
576
640
  subdomain: null,
577
641
  };
@@ -583,6 +647,7 @@ function resetResult(result) {
583
647
  result.isIcann = null;
584
648
  result.isIp = null;
585
649
  result.isPrivate = null;
650
+ result.isSpecialUse = null;
586
651
  result.publicSuffix = null;
587
652
  result.subdomain = null;
588
653
  }
@@ -642,6 +707,13 @@ function parseImpl(url, step, suffixLookup, partialOptions, result) {
642
707
  if (step === 0 /* FLAG.HOSTNAME */ || result.hostname === null) {
643
708
  return result;
644
709
  }
710
+ // Flag special-use domains, only when opted in (`detectSpecialUse`) and only
711
+ // for the full `parse()` result (FLAG.ALL). Computed here, before the
712
+ // public-suffix/domain early-returns below, so single-label names like
713
+ // `localhost` (which have no registrable domain) are still flagged.
714
+ if (step === 5 /* FLAG.ALL */ && options.detectSpecialUse) {
715
+ result.isSpecialUse = isSpecialUse(result.hostname);
716
+ }
645
717
  // Extract public suffix
646
718
  suffixLookup(result.hostname, options, result);
647
719
  if (step === 2 /* FLAG.PUBLIC_SUFFIX */ || result.publicSuffix === null) {