| import { IOptions } from './options'; | |
| /** | |
| * Check if `vhost` is a valid suffix of `hostname` (top-domain) | |
| * | |
| * It means that `vhost` needs to be a suffix of `hostname` and we then need to | |
| * make sure that: either they are equal, or the character preceding `vhost` in | |
| * `hostname` is a '.' (it should not be a partial label). | |
| * | |
| * * hostname = 'not.evil.com' and vhost = 'vil.com' => not ok | |
| * * hostname = 'not.evil.com' and vhost = 'evil.com' => ok | |
| * * hostname = 'not.evil.com' and vhost = 'not.evil.com' => ok | |
| */ | |
| function shareSameDomainSuffix(hostname: string, vhost: string): boolean { | |
| if (hostname.endsWith(vhost)) { | |
| return ( | |
| hostname.length === vhost.length || | |
| hostname[hostname.length - vhost.length - 1] === '.' | |
| ); | |
| } | |
| return false; | |
| } | |
| /** | |
| * Given a hostname and its public suffix, extract the general domain. | |
| */ | |
| function extractDomainWithSuffix( | |
| hostname: string, | |
| publicSuffix: string, | |
| ): string { | |
| // Locate the index of the last '.' in the part of the `hostname` preceding | |
| // the public suffix. | |
| // | |
| // examples: | |
| // 1. not.evil.co.uk => evil.co.uk | |
| // ^ ^ | |
| // | | start of public suffix | |
| // | index of the last dot | |
| // | |
| // 2. example.co.uk => example.co.uk | |
| // ^ ^ | |
| // | | start of public suffix | |
| // | | |
| // | (-1) no dot found before the public suffix | |
| const publicSuffixIndex = hostname.length - publicSuffix.length - 2; | |
| const lastDotBeforeSuffixIndex = hostname.lastIndexOf('.', publicSuffixIndex); | |
| // No '.' found, then `hostname` is the general domain (no sub-domain) | |
| if (lastDotBeforeSuffixIndex === -1) { | |
| return hostname; | |
| } | |
| // Extract the part between the last '.' | |
| return hostname.slice(lastDotBeforeSuffixIndex + 1); | |
| } | |
| /** | |
| * Detects the domain based on rules and upon and a host string | |
| */ | |
| export default function getDomain( | |
| suffix: string, | |
| hostname: string, | |
| options: IOptions, | |
| ): string | null { | |
| // Check if `hostname` ends with a member of `validHosts`. | |
| if (options.validHosts !== null) { | |
| const validHosts = options.validHosts; | |
| for (const vhost of validHosts) { | |
| if (/*@__INLINE__*/ shareSameDomainSuffix(hostname, vhost)) { | |
| return vhost; | |
| } | |
| } | |
| } | |
| let numberOfLeadingDots = 0; | |
| if (hostname.startsWith('.')) { | |
| while ( | |
| numberOfLeadingDots < hostname.length && | |
| hostname[numberOfLeadingDots] === '.' | |
| ) { | |
| numberOfLeadingDots += 1; | |
| } | |
| } | |
| // If `hostname` is a valid public suffix, then there is no domain to return. | |
| // Since we already know that `getPublicSuffix` returns a suffix of `hostname` | |
| // there is no need to perform a string comparison and we only compare the | |
| // size. | |
| if (suffix.length === hostname.length - numberOfLeadingDots) { | |
| return null; | |
| } | |
| // To extract the general domain, we start by identifying the public suffix | |
| // (if any), then consider the domain to be the public suffix with one added | |
| // level of depth. (e.g.: if hostname is `not.evil.co.uk` and public suffix: | |
| // `co.uk`, then we take one more level: `evil`, giving the final result: | |
| // `evil.co.uk`). | |
| return /*@__INLINE__*/ extractDomainWithSuffix(hostname, suffix); | |
| } | |