|
'use strict'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function shareSameDomainSuffix(hostname, vhost) { |
|
if (hostname.endsWith(vhost)) { |
|
return (hostname.length === vhost.length || |
|
hostname[hostname.length - vhost.length - 1] === '.'); |
|
} |
|
return false; |
|
} |
|
|
|
|
|
|
|
function extractDomainWithSuffix(hostname, publicSuffix) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const publicSuffixIndex = hostname.length - publicSuffix.length - 2; |
|
const lastDotBeforeSuffixIndex = hostname.lastIndexOf('.', publicSuffixIndex); |
|
|
|
if (lastDotBeforeSuffixIndex === -1) { |
|
return hostname; |
|
} |
|
|
|
return hostname.slice(lastDotBeforeSuffixIndex + 1); |
|
} |
|
|
|
|
|
|
|
function getDomain(suffix, hostname, options) { |
|
|
|
if (options.validHosts !== null) { |
|
const validHosts = options.validHosts; |
|
for (const vhost of validHosts) { |
|
if ( shareSameDomainSuffix(hostname, vhost)) { |
|
return vhost; |
|
} |
|
} |
|
} |
|
let numberOfLeadingDots = 0; |
|
if (hostname.startsWith('.')) { |
|
while (numberOfLeadingDots < hostname.length && |
|
hostname[numberOfLeadingDots] === '.') { |
|
numberOfLeadingDots += 1; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
if (suffix.length === hostname.length - numberOfLeadingDots) { |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
return extractDomainWithSuffix(hostname, suffix); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function getDomainWithoutSuffix(domain, suffix) { |
|
|
|
|
|
|
|
return domain.slice(0, -suffix.length - 1); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function extractHostname(url, urlIsValidHostname) { |
|
let start = 0; |
|
let end = url.length; |
|
let hasUpper = false; |
|
|
|
if (!urlIsValidHostname) { |
|
|
|
if (url.startsWith('data:')) { |
|
return null; |
|
} |
|
|
|
while (start < url.length && url.charCodeAt(start) <= 32) { |
|
start += 1; |
|
} |
|
|
|
while (end > start + 1 && url.charCodeAt(end - 1) <= 32) { |
|
end -= 1; |
|
} |
|
|
|
if (url.charCodeAt(start) === 47 && |
|
url.charCodeAt(start + 1) === 47 ) { |
|
start += 2; |
|
} |
|
else { |
|
const indexOfProtocol = url.indexOf(':/', start); |
|
if (indexOfProtocol !== -1) { |
|
|
|
|
|
|
|
const protocolSize = indexOfProtocol - start; |
|
const c0 = url.charCodeAt(start); |
|
const c1 = url.charCodeAt(start + 1); |
|
const c2 = url.charCodeAt(start + 2); |
|
const c3 = url.charCodeAt(start + 3); |
|
const c4 = url.charCodeAt(start + 4); |
|
if (protocolSize === 5 && |
|
c0 === 104 && |
|
c1 === 116 && |
|
c2 === 116 && |
|
c3 === 112 && |
|
c4 === 115 ) ; |
|
else if (protocolSize === 4 && |
|
c0 === 104 && |
|
c1 === 116 && |
|
c2 === 116 && |
|
c3 === 112 ) ; |
|
else if (protocolSize === 3 && |
|
c0 === 119 && |
|
c1 === 115 && |
|
c2 === 115 ) ; |
|
else if (protocolSize === 2 && |
|
c0 === 119 && |
|
c1 === 115 ) ; |
|
else { |
|
|
|
for (let i = start; i < indexOfProtocol; i += 1) { |
|
const lowerCaseCode = url.charCodeAt(i) | 32; |
|
if (!(((lowerCaseCode >= 97 && lowerCaseCode <= 122) || |
|
(lowerCaseCode >= 48 && lowerCaseCode <= 57) || |
|
lowerCaseCode === 46 || |
|
lowerCaseCode === 45 || |
|
lowerCaseCode === 43) |
|
)) { |
|
return null; |
|
} |
|
} |
|
} |
|
|
|
start = indexOfProtocol + 2; |
|
while (url.charCodeAt(start) === 47 ) { |
|
start += 1; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
let indexOfIdentifier = -1; |
|
let indexOfClosingBracket = -1; |
|
let indexOfPort = -1; |
|
for (let i = start; i < end; i += 1) { |
|
const code = url.charCodeAt(i); |
|
if (code === 35 || |
|
code === 47 || |
|
code === 63 |
|
) { |
|
end = i; |
|
break; |
|
} |
|
else if (code === 64) { |
|
|
|
indexOfIdentifier = i; |
|
} |
|
else if (code === 93) { |
|
|
|
indexOfClosingBracket = i; |
|
} |
|
else if (code === 58) { |
|
|
|
indexOfPort = i; |
|
} |
|
else if (code >= 65 && code <= 90) { |
|
hasUpper = true; |
|
} |
|
} |
|
|
|
if (indexOfIdentifier !== -1 && |
|
indexOfIdentifier > start && |
|
indexOfIdentifier < end) { |
|
start = indexOfIdentifier + 1; |
|
} |
|
|
|
if (url.charCodeAt(start) === 91 ) { |
|
if (indexOfClosingBracket !== -1) { |
|
return url.slice(start + 1, indexOfClosingBracket).toLowerCase(); |
|
} |
|
return null; |
|
} |
|
else if (indexOfPort !== -1 && indexOfPort > start && indexOfPort < end) { |
|
|
|
end = indexOfPort; |
|
} |
|
} |
|
|
|
while (end > start + 1 && url.charCodeAt(end - 1) === 46 ) { |
|
end -= 1; |
|
} |
|
const hostname = start !== 0 || end !== url.length ? url.slice(start, end) : url; |
|
if (hasUpper) { |
|
return hostname.toLowerCase(); |
|
} |
|
return hostname; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function isProbablyIpv4(hostname) { |
|
|
|
if (hostname.length < 7) { |
|
return false; |
|
} |
|
|
|
if (hostname.length > 15) { |
|
return false; |
|
} |
|
let numberOfDots = 0; |
|
for (let i = 0; i < hostname.length; i += 1) { |
|
const code = hostname.charCodeAt(i); |
|
if (code === 46 ) { |
|
numberOfDots += 1; |
|
} |
|
else if (code < 48 || code > 57 ) { |
|
return false; |
|
} |
|
} |
|
return (numberOfDots === 3 && |
|
hostname.charCodeAt(0) !== 46 && |
|
hostname.charCodeAt(hostname.length - 1) !== 46 ); |
|
} |
|
|
|
|
|
|
|
function isProbablyIpv6(hostname) { |
|
if (hostname.length < 3) { |
|
return false; |
|
} |
|
let start = hostname.startsWith('[') ? 1 : 0; |
|
let end = hostname.length; |
|
if (hostname[end - 1] === ']') { |
|
end -= 1; |
|
} |
|
|
|
|
|
|
|
if (end - start > 39) { |
|
return false; |
|
} |
|
let hasColon = false; |
|
for (; start < end; start += 1) { |
|
const code = hostname.charCodeAt(start); |
|
if (code === 58 ) { |
|
hasColon = true; |
|
} |
|
else if (!(((code >= 48 && code <= 57) || |
|
(code >= 97 && code <= 102) || |
|
(code >= 65 && code <= 90)) |
|
)) { |
|
return false; |
|
} |
|
} |
|
return hasColon; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function isIp(hostname) { |
|
return isProbablyIpv6(hostname) || isProbablyIpv4(hostname); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isValidAscii(code) { |
|
return ((code >= 97 && code <= 122) || (code >= 48 && code <= 57) || code > 127); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function isValidHostname (hostname) { |
|
if (hostname.length > 255) { |
|
return false; |
|
} |
|
if (hostname.length === 0) { |
|
return false; |
|
} |
|
if ( |
|
!isValidAscii(hostname.charCodeAt(0)) && |
|
hostname.charCodeAt(0) !== 46 && |
|
hostname.charCodeAt(0) !== 95 |
|
) { |
|
return false; |
|
} |
|
|
|
let lastDotIndex = -1; |
|
let lastCharCode = -1; |
|
const len = hostname.length; |
|
for (let i = 0; i < len; i += 1) { |
|
const code = hostname.charCodeAt(i); |
|
if (code === 46 ) { |
|
if ( |
|
|
|
i - lastDotIndex > 64 || |
|
|
|
lastCharCode === 46 || |
|
|
|
lastCharCode === 45 || |
|
|
|
lastCharCode === 95) { |
|
return false; |
|
} |
|
lastDotIndex = i; |
|
} |
|
else if (!( (isValidAscii(code) || code === 45 || code === 95))) { |
|
|
|
return false; |
|
} |
|
lastCharCode = code; |
|
} |
|
return ( |
|
|
|
len - lastDotIndex - 1 <= 63 && |
|
|
|
|
|
|
|
lastCharCode !== 45); |
|
} |
|
|
|
function setDefaultsImpl({ allowIcannDomains = true, allowPrivateDomains = false, detectIp = true, extractHostname = true, mixedInputs = true, validHosts = null, validateHostname = true, }) { |
|
return { |
|
allowIcannDomains, |
|
allowPrivateDomains, |
|
detectIp, |
|
extractHostname, |
|
mixedInputs, |
|
validHosts, |
|
validateHostname, |
|
}; |
|
} |
|
const DEFAULT_OPTIONS = setDefaultsImpl({}); |
|
function setDefaults(options) { |
|
if (options === undefined) { |
|
return DEFAULT_OPTIONS; |
|
} |
|
return setDefaultsImpl(options); |
|
} |
|
|
|
|
|
|
|
|
|
function getSubdomain(hostname, domain) { |
|
|
|
if (domain.length === hostname.length) { |
|
return ''; |
|
} |
|
return hostname.slice(0, -domain.length - 1); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function getEmptyResult() { |
|
return { |
|
domain: null, |
|
domainWithoutSuffix: null, |
|
hostname: null, |
|
isIcann: null, |
|
isIp: null, |
|
isPrivate: null, |
|
publicSuffix: null, |
|
subdomain: null, |
|
}; |
|
} |
|
function resetResult(result) { |
|
result.domain = null; |
|
result.domainWithoutSuffix = null; |
|
result.hostname = null; |
|
result.isIcann = null; |
|
result.isIp = null; |
|
result.isPrivate = null; |
|
result.publicSuffix = null; |
|
result.subdomain = null; |
|
} |
|
function parseImpl(url, step, suffixLookup, partialOptions, result) { |
|
const options = setDefaults(partialOptions); |
|
|
|
|
|
|
|
if (typeof url !== 'string') { |
|
return result; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!options.extractHostname) { |
|
result.hostname = url; |
|
} |
|
else if (options.mixedInputs) { |
|
result.hostname = extractHostname(url, isValidHostname(url)); |
|
} |
|
else { |
|
result.hostname = extractHostname(url, false); |
|
} |
|
if (step === 0 || result.hostname === null) { |
|
return result; |
|
} |
|
|
|
if (options.detectIp) { |
|
result.isIp = isIp(result.hostname); |
|
if (result.isIp) { |
|
return result; |
|
} |
|
} |
|
|
|
|
|
if (options.validateHostname && |
|
options.extractHostname && |
|
!isValidHostname(result.hostname)) { |
|
result.hostname = null; |
|
return result; |
|
} |
|
|
|
suffixLookup(result.hostname, options, result); |
|
if (step === 2 || result.publicSuffix === null) { |
|
return result; |
|
} |
|
|
|
result.domain = getDomain(result.publicSuffix, result.hostname, options); |
|
if (step === 3 || result.domain === null) { |
|
return result; |
|
} |
|
|
|
result.subdomain = getSubdomain(result.hostname, result.domain); |
|
if (step === 4 ) { |
|
return result; |
|
} |
|
|
|
result.domainWithoutSuffix = getDomainWithoutSuffix(result.domain, result.publicSuffix); |
|
return result; |
|
} |
|
|
|
function fastPath (hostname, options, out) { |
|
|
|
|
|
if (!options.allowPrivateDomains && hostname.length > 3) { |
|
const last = hostname.length - 1; |
|
const c3 = hostname.charCodeAt(last); |
|
const c2 = hostname.charCodeAt(last - 1); |
|
const c1 = hostname.charCodeAt(last - 2); |
|
const c0 = hostname.charCodeAt(last - 3); |
|
if (c3 === 109 && |
|
c2 === 111 && |
|
c1 === 99 && |
|
c0 === 46 ) { |
|
out.isIcann = true; |
|
out.isPrivate = false; |
|
out.publicSuffix = 'com'; |
|
return true; |
|
} |
|
else if (c3 === 103 && |
|
c2 === 114 && |
|
c1 === 111 && |
|
c0 === 46 ) { |
|
out.isIcann = true; |
|
out.isPrivate = false; |
|
out.publicSuffix = 'org'; |
|
return true; |
|
} |
|
else if (c3 === 117 && |
|
c2 === 100 && |
|
c1 === 101 && |
|
c0 === 46 ) { |
|
out.isIcann = true; |
|
out.isPrivate = false; |
|
out.publicSuffix = 'edu'; |
|
return true; |
|
} |
|
else if (c3 === 118 && |
|
c2 === 111 && |
|
c1 === 103 && |
|
c0 === 46 ) { |
|
out.isIcann = true; |
|
out.isPrivate = false; |
|
out.publicSuffix = 'gov'; |
|
return true; |
|
} |
|
else if (c3 === 116 && |
|
c2 === 101 && |
|
c1 === 110 && |
|
c0 === 46 ) { |
|
out.isIcann = true; |
|
out.isPrivate = false; |
|
out.publicSuffix = 'net'; |
|
return true; |
|
} |
|
else if (c3 === 101 && |
|
c2 === 100 && |
|
c1 === 46 ) { |
|
out.isIcann = true; |
|
out.isPrivate = false; |
|
out.publicSuffix = 'de'; |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
exports.fastPathLookup = fastPath; |
|
exports.getEmptyResult = getEmptyResult; |
|
exports.parseImpl = parseImpl; |
|
exports.resetResult = resetResult; |
|
exports.setDefaults = setDefaults; |
|
|
|
|