100 lines
3.1 KiB
TypeScript
100 lines
3.1 KiB
TypeScript
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);
|
|
}
|