/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
XPCOMUtils.defineLazyServiceGetter(
this,
"idn",
"@mozilla.org/network/idn-service;1",
Ci.nsIIDNService
);
var { ExtensionError } = ExtensionUtils;
const invalidHostnameError = hostname =>
new ExtensionError(`Invalid hostname: ${hostname}`);
const removeIPv6Brackets = hostname =>
hostname.startsWith(
"[") && hostname.endsWith(
"]") && hostname.includes(
":")
? hostname.slice(
1, -
1)
: hostname;
// Note: hostname should not be an IP address.
function ensureValidHostname(hostname) {
// "*" is accepted by domainToASCII but should be invalid.
if (hostname && !hostname.includes(
"*")) {
try {
return idn.domainToASCII(hostname);
}
catch {}
}
throw invalidHostnameError(hostname);
}
this.publicSuffix =
class extends ExtensionAPI {
getAPI() {
return {
publicSuffix: {
isKnownSuffix:
function (hostname) {
try {
const suffix = Services.eTLD.getKnownPublicSuffixFromHost(hostname);
if (suffix) {
return suffix === idn.convertUTF8toACE(hostname);
}
}
catch {}
return false;
},
getKnownSuffix:
function (hostname) {
let suffix;
try {
suffix =
Services.eTLD.getKnownPublicSuffixFromHost(
removeIPv6Brackets(hostname)
) ||
null;
}
catch (e) {
if (e.result === Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
if (hostname.includes(
":") && !hostname.startsWith(
"[")) {
// IPv6 address without brackets.
throw invalidHostnameError(hostname);
}
return null;
}
if (e.result !== Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
throw invalidHostnameError(hostname);
}
}
// Now that we know the hostname is not an IP address,
// check for invalid characters
ensureValidHostname(hostname);
return suffix ||
null;
},
getDomain:
function (hostname, options) {
let domain;
try {
// Note: returned domain may have an unknown suffix
domain =
Services.eTLD.getBaseDomainFromHost(
removeIPv6Brackets(hostname)
) ||
null;
}
catch (e) {
if (e.result === Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
if (hostname.includes(
":") && !hostname.startsWith(
"[")) {
// IPv6 address without brackets.
throw invalidHostnameError(hostname);
}
return options.allowIPAddress ? hostname :
null;
}
if (e.result !== Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
throw invalidHostnameError(hostname);
}
}
// Now that we know the hostname is not an IP address,
// check for invalid characters
const ascii = ensureValidHostname(hostname);
if (!domain) {
if (!options.allowPlainSuffix) {
return null;
}
domain = ascii;
}
else if (
!options.allowUnknownSuffix &&
!Services.eTLD.hasKnownPublicSuffixFromHost(domain)
) {
return null;
}
if (options.encoding ===
"display") {
domain = idn.domainToDisplay(domain);
}
return domain;
},
},
};
}
};