tldts 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/README.md +19 -0
- package/dist/cjs/index.js +74 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/es6/tsconfig.bundle.tsbuildinfo +1 -1
- package/dist/index.cjs.min.js +1 -1
- package/dist/index.cjs.min.js.map +1 -1
- package/dist/index.esm.min.js +1 -1
- package/dist/index.esm.min.js.map +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/index.umd.min.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -93,6 +93,10 @@ your inputs.
|
|
|
93
93
|
validateHostname: boolean;
|
|
94
94
|
// Perform IP address detection (default: true).
|
|
95
95
|
detectIp: boolean;
|
|
96
|
+
// Detect IANA special-use domains (RFC 6761 et al.) and expose the result as
|
|
97
|
+
// `isSpecialUse` (default: false). Off by default so the common path does no
|
|
98
|
+
// extra work; the field stays `null` unless this is enabled.
|
|
99
|
+
detectSpecialUse: boolean;
|
|
96
100
|
// Assume that both URLs and hostnames can be given as input (default: true)
|
|
97
101
|
// If set to `false` we assume only URLs will be given as input, which
|
|
98
102
|
// speed-ups processing.
|
|
@@ -181,6 +185,21 @@ tldts.parse('tldts@emailprovider.co.uk'); // email
|
|
|
181
185
|
| `isIcann` | `bool` | Does TLD come from ICANN part of the list |
|
|
182
186
|
| `isPrivate` | `bool` | Does TLD come from Private part of the list |
|
|
183
187
|
| `isIP` | `bool` | Is `hostname` an IP address? |
|
|
188
|
+
| `isSpecialUse` | `bool` | Is `hostname` an IANA special-use domain? |
|
|
189
|
+
|
|
190
|
+
## Special-use domains (RFC 6761 / IANA)
|
|
191
|
+
|
|
192
|
+
Set `{ detectSpecialUse: true }` to flag reserved special-use names such as `localhost`, `*.test`, `*.local`, `*.onion`, and `home.arpa` via the `isSpecialUse` result field. `isIcann`/`isPrivate` don't identify these: most aren't in the Public Suffix List, and the few that are (e.g. `onion`, `home.arpa`) appear there as ordinary ICANN suffixes. The field is `null` unless the option is enabled, so the default path does no extra work:
|
|
193
|
+
|
|
194
|
+
```js
|
|
195
|
+
parse('http://printer.local/', { detectSpecialUse: true });
|
|
196
|
+
// { ...
|
|
197
|
+
// isSpecialUse: true,
|
|
198
|
+
// publicSuffix: 'local',
|
|
199
|
+
// subdomain: '' }
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The list tracks the IANA [Special-Use Domain Names](https://www.iana.org/assignments/special-use-domain-names/) registry.
|
|
184
203
|
|
|
185
204
|
## Single purpose methods
|
|
186
205
|
|
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 <=
|
|
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) {
|