更新
This commit is contained in:
+199
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user