/** * 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 | 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; }