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); | |
} | |