File size: 6,937 Bytes
3401f26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/**
 * Implement a factory allowing to plug different implementations of suffix
 * lookup (e.g.: using a trie or the packed hashes datastructures). This is used
 * and exposed in `tldts.ts` and `tldts-experimental.ts` bundle entrypoints.
 */

import getDomain from './domain';
import getDomainWithoutSuffix from './domain-without-suffix';
import extractHostname, {
  extractedHostnameValidated,
} from './extract-hostname';
import isIp from './is-ip';
import isSpecialUse from './is-special-use';
import isValidHostname from './is-valid';
import { IPublicSuffix, ISuffixLookupOptions } from './lookup/interface';
import { IOptions, setDefaults } from './options';
import getSubdomain from './subdomain';

export interface IResult {
  // `hostname` is either a registered name (including but not limited to a
  // hostname), or an IP address, directly extracted from the input URL. IPv4
  // addresses are in dot-decimal notation. IPv6 is returned without its
  // surrounding brackets; both bracketed (in URLs, e.g. `http://[::1]/`) and
  // bare unbracketed (e.g. `2a01:e35::1`) IPv6 literals are accepted.
  hostname: string | null;

  // Is `hostname` an IP? (IPv4 or IPv6)
  isIp: boolean | null;

  // `hostname` split between subdomain, domain and its public suffix (if any)
  subdomain: string | null;
  domain: string | null;
  publicSuffix: string | null;
  domainWithoutSuffix: string | null;

  // Specifies if `publicSuffix` comes from the ICANN or PRIVATE section of the list
  isIcann: boolean | null;
  isPrivate: boolean | null;

  // Is `hostname` a special-use domain from the IANA registry (RFC 6761 et al.:
  // e.g. `localhost`, `*.test`, `*.local`, `*.onion`, `home.arpa`)? `isIcann`/
  // `isPrivate` do not identify these (most are not in the Public Suffix List;
  // the few that are appear as ordinary ICANN suffixes). `null` unless the
  // `detectSpecialUse` option is enabled (see is-special-use.ts).
  isSpecialUse: boolean | null;
}

export function getEmptyResult(): IResult {
  return {
    domain: null,
    domainWithoutSuffix: null,
    hostname: null,
    isIcann: null,
    isIp: null,
    isPrivate: null,
    isSpecialUse: null,
    publicSuffix: null,
    subdomain: null,
  };
}

export function resetResult(result: IResult): void {
  result.domain = null;
  result.domainWithoutSuffix = null;
  result.hostname = null;
  result.isIcann = null;
  result.isIp = null;
  result.isPrivate = null;
  result.isSpecialUse = null;
  result.publicSuffix = null;
  result.subdomain = null;
}

// Flags representing steps in the `parse` function. They are used to implement
// an early stop mechanism (simulating some form of laziness) to avoid doing
// more work than necessary to perform a given action (e.g.: we don't need to
// extract the domain and subdomain if we are only interested in public suffix).
export const enum FLAG {
  HOSTNAME,
  IS_VALID,
  PUBLIC_SUFFIX,
  DOMAIN,
  SUB_DOMAIN,
  ALL,
}

export function parseImpl(
  url: string,
  step: FLAG,
  suffixLookup: (
    _1: string,
    _2: ISuffixLookupOptions,
    _3: IPublicSuffix,
  ) => void,
  partialOptions: Partial<IOptions> | undefined,
  result: IResult,
): IResult {
  const options: IOptions = /*@__INLINE__*/ setDefaults(partialOptions);

  // Very fast approximate check to make sure `url` is a string. This is needed
  // because the library will not necessarily be used in a typed setup and
  // values of arbitrary types might be given as argument.
  if (typeof url !== 'string') {
    return result;
  }

  // Extract hostname from `url` only if needed. This can be made optional
  // using `options.extractHostname`. This option will typically be used
  // whenever we are sure the inputs to `parse` are already hostnames and not
  // arbitrary URLs.
  //
  // `mixedInput` allows to specify if we expect a mix of URLs and hostnames
  // as input. If only hostnames are expected then `extractHostname` can be
  // set to `false` to speed-up parsing. If only URLs are expected then
  // `mixedInputs` can be set to `false`. The `mixedInputs` is only a hint
  // and will not change the behavior of the library.
  // Whether `url` itself was already a valid hostname (only computed on the
  // mixedInputs path). Lets us skip the post-extraction validation below when
  // extractHostname returned `url` unchanged (same reference).
  let urlIsValid = false;
  if (!options.extractHostname) {
    result.hostname = url;
  } else if (options.mixedInputs) {
    urlIsValid = isValidHostname(url);
    result.hostname = extractHostname(
      url,
      urlIsValid,
      options.validateHostname,
    );
  } else {
    result.hostname = extractHostname(url, false, options.validateHostname);
  }

  // Check if `hostname` is a valid ip address
  if (options.detectIp && result.hostname !== null) {
    result.isIp = isIp(result.hostname);
    if (result.isIp) {
      return result;
    }
  }

  // Perform hostname validation if enabled. If hostname is not valid, no need to
  // go further as there will be no valid domain or sub-domain. This validation
  // is applied before any early returns to ensure consistent behavior across
  // all API methods including getHostname().
  if (
    options.validateHostname &&
    options.extractHostname &&
    result.hostname !== null &&
    // Skip the re-scan when `url` was already validated and extractHostname
    // returned it unchanged (same reference => identical string, still valid).
    !(urlIsValid && result.hostname === url) &&
    // Skip the re-scan when extractHostname already validated the host inline
    // (a confirmed-valid simple authority — see extract-hostname.ts).
    !extractedHostnameValidated &&
    !isValidHostname(result.hostname)
  ) {
    result.hostname = null;
    return result;
  }

  if (step === FLAG.HOSTNAME || result.hostname === null) {
    return result;
  }

  // Flag special-use domains, only when opted in (`detectSpecialUse`) and only
  // for the full `parse()` result (FLAG.ALL). Computed here, before the
  // public-suffix/domain early-returns below, so single-label names like
  // `localhost` (which have no registrable domain) are still flagged.
  if (step === FLAG.ALL && options.detectSpecialUse) {
    result.isSpecialUse = isSpecialUse(result.hostname);
  }

  // Extract public suffix
  suffixLookup(result.hostname, options, result);
  if (step === FLAG.PUBLIC_SUFFIX || result.publicSuffix === null) {
    return result;
  }

  // Extract domain
  result.domain = getDomain(result.publicSuffix, result.hostname, options);
  if (step === FLAG.DOMAIN || result.domain === null) {
    return result;
  }

  // Extract subdomain
  result.subdomain = getSubdomain(result.hostname, result.domain);
  if (step === FLAG.SUB_DOMAIN) {
    return result;
  }

  // Extract domain without suffix
  result.domainWithoutSuffix = getDomainWithoutSuffix(
    result.domain,
    result.publicSuffix,
  );

  return result;
}