Source code

Revision control

Copy as Markdown

Other Tools

/* 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;
},
},
};
}
};