summaryrefslogtreecommitdiffstats
path: root/netwerk/dns/nsIDNService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/dns/nsIDNService.cpp')
-rw-r--r--netwerk/dns/nsIDNService.cpp959
1 files changed, 959 insertions, 0 deletions
diff --git a/netwerk/dns/nsIDNService.cpp b/netwerk/dns/nsIDNService.cpp
new file mode 100644
index 000000000..d4f31027e
--- /dev/null
+++ b/netwerk/dns/nsIDNService.cpp
@@ -0,0 +1,959 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsIDNService.h"
+#include "nsReadableUtils.h"
+#include "nsCRT.h"
+#include "nsUnicharUtils.h"
+#include "nsUnicodeProperties.h"
+#include "nsUnicodeScriptCodes.h"
+#include "harfbuzz/hb.h"
+#include "nsIServiceManager.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIObserverService.h"
+#include "nsISupportsPrimitives.h"
+#include "punycode.h"
+
+#ifdef IDNA2008
+// Currently we use the non-transitional processing option -- see
+// http://unicode.org/reports/tr46/
+// To switch to transitional processing, change the value of this flag
+// and kTransitionalProcessing in netwerk/test/unit/test_idna2008.js to true
+// (revert bug 1218179).
+const bool kIDNA2008_TransitionalProcessing = false;
+
+#include "ICUUtils.h"
+#endif
+
+using namespace mozilla::unicode;
+
+//-----------------------------------------------------------------------------
+// RFC 1034 - 3.1. Name space specifications and terminology
+static const uint32_t kMaxDNSNodeLen = 63;
+// RFC 3490 - 5. ACE prefix
+static const char kACEPrefix[] = "xn--";
+#define kACEPrefixLen 4
+
+//-----------------------------------------------------------------------------
+
+#define NS_NET_PREF_IDNBLACKLIST "network.IDN.blacklist_chars"
+#define NS_NET_PREF_SHOWPUNYCODE "network.IDN_show_punycode"
+#define NS_NET_PREF_IDNWHITELIST "network.IDN.whitelist."
+#define NS_NET_PREF_IDNUSEWHITELIST "network.IDN.use_whitelist"
+#define NS_NET_PREF_IDNRESTRICTION "network.IDN.restriction_profile"
+
+inline bool isOnlySafeChars(const nsAFlatString& in,
+ const nsAFlatString& blacklist)
+{
+ return (blacklist.IsEmpty() ||
+ in.FindCharInSet(blacklist) == kNotFound);
+}
+
+//-----------------------------------------------------------------------------
+// nsIDNService
+//-----------------------------------------------------------------------------
+
+/* Implementation file */
+NS_IMPL_ISUPPORTS(nsIDNService,
+ nsIIDNService,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+nsresult nsIDNService::Init()
+{
+ nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs)
+ prefs->GetBranch(NS_NET_PREF_IDNWHITELIST, getter_AddRefs(mIDNWhitelistPrefBranch));
+
+ nsCOMPtr<nsIPrefBranch> prefInternal(do_QueryInterface(prefs));
+ if (prefInternal) {
+ prefInternal->AddObserver(NS_NET_PREF_IDNBLACKLIST, this, true);
+ prefInternal->AddObserver(NS_NET_PREF_SHOWPUNYCODE, this, true);
+ prefInternal->AddObserver(NS_NET_PREF_IDNRESTRICTION, this, true);
+ prefInternal->AddObserver(NS_NET_PREF_IDNUSEWHITELIST, this, true);
+ prefsChanged(prefInternal, nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsIDNService::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsCOMPtr<nsIPrefBranch> prefBranch( do_QueryInterface(aSubject) );
+ if (prefBranch)
+ prefsChanged(prefBranch, aData);
+ }
+ return NS_OK;
+}
+
+void nsIDNService::prefsChanged(nsIPrefBranch *prefBranch, const char16_t *pref)
+{
+ if (!pref || NS_LITERAL_STRING(NS_NET_PREF_IDNBLACKLIST).Equals(pref)) {
+ nsCOMPtr<nsISupportsString> blacklist;
+ nsresult rv = prefBranch->GetComplexValue(NS_NET_PREF_IDNBLACKLIST,
+ NS_GET_IID(nsISupportsString),
+ getter_AddRefs(blacklist));
+ if (NS_SUCCEEDED(rv))
+ blacklist->ToString(getter_Copies(mIDNBlacklist));
+ else
+ mIDNBlacklist.Truncate();
+ }
+ if (!pref || NS_LITERAL_STRING(NS_NET_PREF_SHOWPUNYCODE).Equals(pref)) {
+ bool val;
+ if (NS_SUCCEEDED(prefBranch->GetBoolPref(NS_NET_PREF_SHOWPUNYCODE, &val)))
+ mShowPunycode = val;
+ }
+ if (!pref || NS_LITERAL_STRING(NS_NET_PREF_IDNUSEWHITELIST).Equals(pref)) {
+ bool val;
+ if (NS_SUCCEEDED(prefBranch->GetBoolPref(NS_NET_PREF_IDNUSEWHITELIST,
+ &val)))
+ mIDNUseWhitelist = val;
+ }
+ if (!pref || NS_LITERAL_STRING(NS_NET_PREF_IDNRESTRICTION).Equals(pref)) {
+ nsXPIDLCString profile;
+ if (NS_FAILED(prefBranch->GetCharPref(NS_NET_PREF_IDNRESTRICTION,
+ getter_Copies(profile)))) {
+ profile.Truncate();
+ }
+ if (profile.EqualsLiteral("moderate")) {
+ mRestrictionProfile = eModeratelyRestrictiveProfile;
+ } else if (profile.EqualsLiteral("high")) {
+ mRestrictionProfile = eHighlyRestrictiveProfile;
+ } else {
+ mRestrictionProfile = eASCIIOnlyProfile;
+ }
+ }
+}
+
+nsIDNService::nsIDNService()
+ : mShowPunycode(false)
+ , mIDNUseWhitelist(false)
+{
+#ifdef IDNA2008
+ uint32_t IDNAOptions = UIDNA_CHECK_BIDI | UIDNA_CHECK_CONTEXTJ;
+ if (!kIDNA2008_TransitionalProcessing) {
+ IDNAOptions |= UIDNA_NONTRANSITIONAL_TO_UNICODE;
+ }
+ UErrorCode errorCode = U_ZERO_ERROR;
+ mIDNA = uidna_openUTS46(IDNAOptions, &errorCode);
+#else
+ if (idn_success != idn_nameprep_create(nullptr, &mNamePrepHandle))
+ mNamePrepHandle = nullptr;
+
+ mNormalizer = do_GetService(NS_UNICODE_NORMALIZER_CONTRACTID);
+ /* member initializers and constructor code */
+#endif
+}
+
+nsIDNService::~nsIDNService()
+{
+#ifdef IDNA2008
+ uidna_close(mIDNA);
+#else
+ idn_nameprep_destroy(mNamePrepHandle);
+#endif
+}
+
+#ifdef IDNA2008
+nsresult
+nsIDNService::IDNA2008ToUnicode(const nsACString& input, nsAString& output)
+{
+ NS_ConvertUTF8toUTF16 inputStr(input);
+ UIDNAInfo info = UIDNA_INFO_INITIALIZER;
+ UErrorCode errorCode = U_ZERO_ERROR;
+ int32_t inLen = inputStr.Length();
+ int32_t outMaxLen = kMaxDNSNodeLen + 1;
+ UChar outputBuffer[kMaxDNSNodeLen + 1];
+
+ int32_t outLen = uidna_labelToUnicode(mIDNA, (const UChar*)inputStr.get(),
+ inLen, outputBuffer, outMaxLen,
+ &info, &errorCode);
+ if (info.errors != 0) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (U_SUCCESS(errorCode)) {
+ ICUUtils::AssignUCharArrayToString(outputBuffer, outLen, output);
+ }
+
+ nsresult rv = ICUUtils::UErrorToNsResult(errorCode);
+ if (rv == NS_ERROR_FAILURE) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+ return rv;
+}
+
+nsresult
+nsIDNService::IDNA2008StringPrep(const nsAString& input,
+ nsAString& output,
+ stringPrepFlag flag)
+{
+ UIDNAInfo info = UIDNA_INFO_INITIALIZER;
+ UErrorCode errorCode = U_ZERO_ERROR;
+ int32_t inLen = input.Length();
+ int32_t outMaxLen = kMaxDNSNodeLen + 1;
+ UChar outputBuffer[kMaxDNSNodeLen + 1];
+
+ int32_t outLen =
+ uidna_labelToUnicode(mIDNA, (const UChar*)PromiseFlatString(input).get(),
+ inLen, outputBuffer, outMaxLen, &info, &errorCode);
+ nsresult rv = ICUUtils::UErrorToNsResult(errorCode);
+ if (rv == NS_ERROR_FAILURE) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Output the result of nameToUnicode even if there were errors
+ ICUUtils::AssignUCharArrayToString(outputBuffer, outLen, output);
+
+ if (flag == eStringPrepIgnoreErrors) {
+ return NS_OK;
+ }
+
+ if (info.errors != 0) {
+ if (flag == eStringPrepForDNS) {
+ output.Truncate();
+ }
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+
+ return rv;
+}
+#endif
+
+NS_IMETHODIMP nsIDNService::ConvertUTF8toACE(const nsACString & input, nsACString & ace)
+{
+ return UTF8toACE(input, ace, eStringPrepForDNS);
+}
+
+nsresult nsIDNService::UTF8toACE(const nsACString & input, nsACString & ace,
+ stringPrepFlag flag)
+{
+ nsresult rv;
+ NS_ConvertUTF8toUTF16 ustr(input);
+
+ // map ideographic period to ASCII period etc.
+ normalizeFullStops(ustr);
+
+ uint32_t len, offset;
+ len = 0;
+ offset = 0;
+ nsAutoCString encodedBuf;
+
+ nsAString::const_iterator start, end;
+ ustr.BeginReading(start);
+ ustr.EndReading(end);
+ ace.Truncate();
+
+ // encode nodes if non ASCII
+ while (start != end) {
+ len++;
+ if (*start++ == (char16_t)'.') {
+ rv = stringPrepAndACE(Substring(ustr, offset, len - 1), encodedBuf, flag);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ace.Append(encodedBuf);
+ ace.Append('.');
+ offset += len;
+ len = 0;
+ }
+ }
+
+ // encode the last node if non ASCII
+ if (len) {
+ rv = stringPrepAndACE(Substring(ustr, offset, len), encodedBuf, flag);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ace.Append(encodedBuf);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsIDNService::ConvertACEtoUTF8(const nsACString & input, nsACString & _retval)
+{
+ return ACEtoUTF8(input, _retval, eStringPrepForDNS);
+}
+
+nsresult nsIDNService::ACEtoUTF8(const nsACString & input, nsACString & _retval,
+ stringPrepFlag flag)
+{
+ // RFC 3490 - 4.2 ToUnicode
+ // ToUnicode never fails. If any step fails, then the original input
+ // sequence is returned immediately in that step.
+
+ uint32_t len = 0, offset = 0;
+ nsAutoCString decodedBuf;
+
+ nsACString::const_iterator start, end;
+ input.BeginReading(start);
+ input.EndReading(end);
+ _retval.Truncate();
+
+ // loop and decode nodes
+ while (start != end) {
+ len++;
+ if (*start++ == '.') {
+ if (NS_FAILED(decodeACE(Substring(input, offset, len - 1), decodedBuf,
+ flag))) {
+ _retval.Assign(input);
+ return NS_OK;
+ }
+
+ _retval.Append(decodedBuf);
+ _retval.Append('.');
+ offset += len;
+ len = 0;
+ }
+ }
+ // decode the last node
+ if (len) {
+ if (NS_FAILED(decodeACE(Substring(input, offset, len), decodedBuf,
+ flag)))
+ _retval.Assign(input);
+ else
+ _retval.Append(decodedBuf);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsIDNService::IsACE(const nsACString & input, bool *_retval)
+{
+ const char *data = input.BeginReading();
+ uint32_t dataLen = input.Length();
+
+ // look for the ACE prefix in the input string. it may occur
+ // at the beginning of any segment in the domain name. for
+ // example: "www.xn--ENCODED.com"
+
+ const char *p = PL_strncasestr(data, kACEPrefix, dataLen);
+
+ *_retval = p && (p == data || *(p - 1) == '.');
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsIDNService::Normalize(const nsACString & input,
+ nsACString & output)
+{
+ // protect against bogus input
+ NS_ENSURE_TRUE(IsUTF8(input), NS_ERROR_UNEXPECTED);
+
+ NS_ConvertUTF8toUTF16 inUTF16(input);
+ normalizeFullStops(inUTF16);
+
+ // pass the domain name to stringprep label by label
+ nsAutoString outUTF16, outLabel;
+
+ uint32_t len = 0, offset = 0;
+ nsresult rv;
+ nsAString::const_iterator start, end;
+ inUTF16.BeginReading(start);
+ inUTF16.EndReading(end);
+
+ while (start != end) {
+ len++;
+ if (*start++ == char16_t('.')) {
+ rv = stringPrep(Substring(inUTF16, offset, len - 1), outLabel,
+ eStringPrepIgnoreErrors);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outUTF16.Append(outLabel);
+ outUTF16.Append(char16_t('.'));
+ offset += len;
+ len = 0;
+ }
+ }
+ if (len) {
+ rv = stringPrep(Substring(inUTF16, offset, len), outLabel,
+ eStringPrepIgnoreErrors);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outUTF16.Append(outLabel);
+ }
+
+ CopyUTF16toUTF8(outUTF16, output);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsIDNService::ConvertToDisplayIDN(const nsACString & input, bool * _isASCII, nsACString & _retval)
+{
+ // If host is ACE, then convert to UTF-8 if the host is in the IDN whitelist.
+ // Else, if host is already UTF-8, then make sure it is normalized per IDN.
+
+ nsresult rv = NS_OK;
+
+ // Even if the hostname is not ASCII, individual labels may still be ACE, so
+ // test IsACE before testing IsASCII
+ bool isACE;
+ IsACE(input, &isACE);
+
+ if (IsASCII(input)) {
+ // first, canonicalize the host to lowercase, for whitelist lookup
+ _retval = input;
+ ToLowerCase(_retval);
+
+ if (isACE && !mShowPunycode) {
+ // ACEtoUTF8() can't fail, but might return the original ACE string
+ nsAutoCString temp(_retval);
+ // If the domain is in the whitelist, return the host in UTF-8.
+ // Otherwise convert from ACE to UTF8 only those labels which are
+ // considered safe for display
+ ACEtoUTF8(temp, _retval, isInWhitelist(temp) ?
+ eStringPrepIgnoreErrors : eStringPrepForUI);
+ *_isASCII = IsASCII(_retval);
+ } else {
+ *_isASCII = true;
+ }
+ } else {
+ // We have to normalize the hostname before testing against the domain
+ // whitelist (see bug 315411), and to ensure the entire string gets
+ // normalized.
+ //
+ // Normalization and the tests for safe display below, assume that the
+ // input is Unicode, so first convert any ACE labels to UTF8
+ if (isACE) {
+ nsAutoCString temp;
+ ACEtoUTF8(input, temp, eStringPrepIgnoreErrors);
+ rv = Normalize(temp, _retval);
+ } else {
+ rv = Normalize(input, _retval);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ if (mShowPunycode && NS_SUCCEEDED(UTF8toACE(_retval, _retval,
+ eStringPrepIgnoreErrors))) {
+ *_isASCII = true;
+ return NS_OK;
+ }
+
+ // normalization could result in an ASCII-only hostname. alternatively, if
+ // the host is converted to ACE by the normalizer, then the host may contain
+ // unsafe characters, so leave it ACE encoded. see bug 283016, bug 301694, and bug 309311.
+ *_isASCII = IsASCII(_retval);
+ if (!*_isASCII && !isInWhitelist(_retval)) {
+ // UTF8toACE with eStringPrepForUI may return a domain name where
+ // some labels are in UTF-8 and some are in ACE, depending on
+ // whether they are considered safe for display
+ rv = UTF8toACE(_retval, _retval, eStringPrepForUI);
+ *_isASCII = IsASCII(_retval);
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+static nsresult utf16ToUcs4(const nsAString& in,
+ uint32_t *out,
+ uint32_t outBufLen,
+ uint32_t *outLen)
+{
+ uint32_t i = 0;
+ nsAString::const_iterator start, end;
+ in.BeginReading(start);
+ in.EndReading(end);
+
+ while (start != end) {
+ char16_t curChar;
+
+ curChar= *start++;
+
+ if (start != end &&
+ NS_IS_HIGH_SURROGATE(curChar) &&
+ NS_IS_LOW_SURROGATE(*start)) {
+ out[i] = SURROGATE_TO_UCS4(curChar, *start);
+ ++start;
+ }
+ else
+ out[i] = curChar;
+
+ i++;
+ if (i >= outBufLen)
+ return NS_ERROR_MALFORMED_URI;
+ }
+ out[i] = (uint32_t)'\0';
+ *outLen = i;
+ return NS_OK;
+}
+
+#ifndef IDNA2008
+static void ucs4toUtf16(const uint32_t *in, nsAString& out)
+{
+ while (*in) {
+ if (!IS_IN_BMP(*in)) {
+ out.Append((char16_t) H_SURROGATE(*in));
+ out.Append((char16_t) L_SURROGATE(*in));
+ }
+ else
+ out.Append((char16_t) *in);
+ in++;
+ }
+}
+#endif
+
+static nsresult punycode(const nsAString& in, nsACString& out)
+{
+ uint32_t ucs4Buf[kMaxDNSNodeLen + 1];
+ uint32_t ucs4Len = 0u;
+ nsresult rv = utf16ToUcs4(in, ucs4Buf, kMaxDNSNodeLen, &ucs4Len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // need maximum 20 bits to encode 16 bit Unicode character
+ // (include null terminator)
+ const uint32_t kEncodedBufSize = kMaxDNSNodeLen * 20 / 8 + 1 + 1;
+ char encodedBuf[kEncodedBufSize];
+ punycode_uint encodedLength = kEncodedBufSize;
+
+ enum punycode_status status = punycode_encode(ucs4Len,
+ ucs4Buf,
+ nullptr,
+ &encodedLength,
+ encodedBuf);
+
+ if (punycode_success != status ||
+ encodedLength >= kEncodedBufSize)
+ return NS_ERROR_MALFORMED_URI;
+
+ encodedBuf[encodedLength] = '\0';
+ out.Assign(nsDependentCString(kACEPrefix) + nsDependentCString(encodedBuf));
+
+ return rv;
+}
+
+// RFC 3454
+//
+// 1) Map -- For each character in the input, check if it has a mapping
+// and, if so, replace it with its mapping. This is described in section 3.
+//
+// 2) Normalize -- Possibly normalize the result of step 1 using Unicode
+// normalization. This is described in section 4.
+//
+// 3) Prohibit -- Check for any characters that are not allowed in the
+// output. If any are found, return an error. This is described in section
+// 5.
+//
+// 4) Check bidi -- Possibly check for right-to-left characters, and if any
+// are found, make sure that the whole string satisfies the requirements
+// for bidirectional strings. If the string does not satisfy the requirements
+// for bidirectional strings, return an error. This is described in section 6.
+//
+// 5) Check unassigned code points -- If allowUnassigned is false, check for
+// any unassigned Unicode points and if any are found return an error.
+// This is described in section 7.
+//
+nsresult nsIDNService::stringPrep(const nsAString& in, nsAString& out,
+ stringPrepFlag flag)
+{
+#ifdef IDNA2008
+ return IDNA2008StringPrep(in, out, flag);
+#else
+ if (!mNamePrepHandle || !mNormalizer)
+ return NS_ERROR_FAILURE;
+
+ uint32_t ucs4Buf[kMaxDNSNodeLen + 1];
+ uint32_t ucs4Len;
+ nsresult rv = utf16ToUcs4(in, ucs4Buf, kMaxDNSNodeLen, &ucs4Len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // map
+ idn_result_t idn_err;
+
+ uint32_t namePrepBuf[kMaxDNSNodeLen * 3]; // map up to three characters
+ idn_err = idn_nameprep_map(mNamePrepHandle, (const uint32_t *) ucs4Buf,
+ (uint32_t *) namePrepBuf, kMaxDNSNodeLen * 3);
+ NS_ENSURE_TRUE(idn_err == idn_success, NS_ERROR_MALFORMED_URI);
+
+ nsAutoString namePrepStr;
+ ucs4toUtf16(namePrepBuf, namePrepStr);
+ if (namePrepStr.Length() >= kMaxDNSNodeLen)
+ return NS_ERROR_MALFORMED_URI;
+
+ // normalize
+ nsAutoString normlizedStr;
+ rv = mNormalizer->NormalizeUnicodeNFKC(namePrepStr, normlizedStr);
+ if (normlizedStr.Length() >= kMaxDNSNodeLen)
+ return NS_ERROR_MALFORMED_URI;
+
+ // set the result string
+ out.Assign(normlizedStr);
+
+ if (flag == eStringPrepIgnoreErrors) {
+ return NS_OK;
+ }
+
+ // prohibit
+ const uint32_t *found = nullptr;
+ idn_err = idn_nameprep_isprohibited(mNamePrepHandle,
+ (const uint32_t *) ucs4Buf, &found);
+ if (idn_err != idn_success || found) {
+ rv = NS_ERROR_MALFORMED_URI;
+ } else {
+ // check bidi
+ idn_err = idn_nameprep_isvalidbidi(mNamePrepHandle,
+ (const uint32_t *) ucs4Buf, &found);
+ if (idn_err != idn_success || found) {
+ rv = NS_ERROR_MALFORMED_URI;
+ } else if (flag == eStringPrepForUI) {
+ // check unassigned code points
+ idn_err = idn_nameprep_isunassigned(mNamePrepHandle,
+ (const uint32_t *) ucs4Buf, &found);
+ if (idn_err != idn_success || found) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+ }
+ }
+
+ if (flag == eStringPrepForDNS && NS_FAILED(rv)) {
+ out.Truncate();
+ }
+
+ return rv;
+#endif
+}
+
+nsresult nsIDNService::stringPrepAndACE(const nsAString& in, nsACString& out,
+ stringPrepFlag flag)
+{
+ nsresult rv = NS_OK;
+
+ out.Truncate();
+
+ if (in.Length() > kMaxDNSNodeLen) {
+ NS_WARNING("IDN node too large");
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (IsASCII(in)) {
+ LossyCopyUTF16toASCII(in, out);
+ return NS_OK;
+ }
+
+ nsAutoString strPrep;
+ rv = stringPrep(in, strPrep, flag);
+ if (flag == eStringPrepForDNS) {
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (IsASCII(strPrep)) {
+ LossyCopyUTF16toASCII(strPrep, out);
+ return NS_OK;
+ }
+
+ if (flag == eStringPrepForUI && NS_SUCCEEDED(rv) && isLabelSafe(in)) {
+ CopyUTF16toUTF8(strPrep, out);
+ return NS_OK;
+ }
+
+ rv = punycode(strPrep, out);
+ // Check that the encoded output isn't larger than the maximum length
+ // of a DNS node per RFC 1034.
+ // This test isn't necessary in the code paths above where the input
+ // is ASCII (since the output will be the same length as the input) or
+ // where we convert to UTF-8 (since the output is only used for
+ // display in the UI and not passed to DNS and can legitimately be
+ // longer than the limit).
+ if (out.Length() > kMaxDNSNodeLen) {
+ NS_WARNING("IDN node too large");
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return rv;
+}
+
+// RFC 3490
+// 1) Whenever dots are used as label separators, the following characters
+// MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full
+// stop), U+FF0E (fullwidth full stop), U+FF61 (halfwidth ideographic full
+// stop).
+
+void nsIDNService::normalizeFullStops(nsAString& s)
+{
+ nsAString::const_iterator start, end;
+ s.BeginReading(start);
+ s.EndReading(end);
+ int32_t index = 0;
+
+ while (start != end) {
+ switch (*start) {
+ case 0x3002:
+ case 0xFF0E:
+ case 0xFF61:
+ s.Replace(index, 1, NS_LITERAL_STRING("."));
+ break;
+ default:
+ break;
+ }
+ start++;
+ index++;
+ }
+}
+
+nsresult nsIDNService::decodeACE(const nsACString& in, nsACString& out,
+ stringPrepFlag flag)
+{
+ bool isAce;
+ IsACE(in, &isAce);
+ if (!isAce) {
+ out.Assign(in);
+ return NS_OK;
+ }
+
+ nsAutoString utf16;
+#ifdef IDNA2008
+ nsresult result = IDNA2008ToUnicode(in, utf16);
+ NS_ENSURE_SUCCESS(result, result);
+#else
+ // RFC 3490 - 4.2 ToUnicode
+ // The ToUnicode output never contains more code points than its input.
+ punycode_uint output_length = in.Length() - kACEPrefixLen + 1;
+ auto *output = new punycode_uint[output_length];
+ NS_ENSURE_TRUE(output, NS_ERROR_OUT_OF_MEMORY);
+
+ enum punycode_status status = punycode_decode(in.Length() - kACEPrefixLen,
+ PromiseFlatCString(in).get() + kACEPrefixLen,
+ &output_length,
+ output,
+ nullptr);
+ if (status != punycode_success) {
+ delete [] output;
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ // UCS4 -> UTF8
+ output[output_length] = 0;
+ ucs4toUtf16(output, utf16);
+ delete [] output;
+#endif
+ if (flag != eStringPrepForUI || isLabelSafe(utf16)) {
+ CopyUTF16toUTF8(utf16, out);
+ } else {
+ out.Assign(in);
+ return NS_OK;
+ }
+
+ // Validation: encode back to ACE and compare the strings
+ nsAutoCString ace;
+ nsresult rv = UTF8toACE(out, ace, flag);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (flag == eStringPrepForDNS &&
+ !ace.Equals(in, nsCaseInsensitiveCStringComparator())) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return NS_OK;
+}
+
+bool nsIDNService::isInWhitelist(const nsACString &host)
+{
+ if (mIDNUseWhitelist && mIDNWhitelistPrefBranch) {
+ nsAutoCString tld(host);
+ // make sure the host is ACE for lookup and check that there are no
+ // unassigned codepoints
+ if (!IsASCII(tld) && NS_FAILED(UTF8toACE(tld, tld, eStringPrepForDNS))) {
+ return false;
+ }
+
+ // truncate trailing dots first
+ tld.Trim(".");
+ int32_t pos = tld.RFind(".");
+ if (pos == kNotFound)
+ return false;
+
+ tld.Cut(0, pos + 1);
+
+ bool safe;
+ if (NS_SUCCEEDED(mIDNWhitelistPrefBranch->GetBoolPref(tld.get(), &safe)))
+ return safe;
+ }
+
+ return false;
+}
+
+bool nsIDNService::isLabelSafe(const nsAString &label)
+{
+ if (!isOnlySafeChars(PromiseFlatString(label), mIDNBlacklist)) {
+ return false;
+ }
+
+ // We should never get here if the label is ASCII
+ NS_ASSERTION(!IsASCII(label), "ASCII label in IDN checking");
+ if (mRestrictionProfile == eASCIIOnlyProfile) {
+ return false;
+ }
+
+ nsAString::const_iterator current, end;
+ label.BeginReading(current);
+ label.EndReading(end);
+
+ Script lastScript = Script::INVALID;
+ uint32_t previousChar = 0;
+ uint32_t savedNumberingSystem = 0;
+// Simplified/Traditional Chinese check temporarily disabled -- bug 857481
+#if 0
+ HanVariantType savedHanVariant = HVT_NotHan;
+#endif
+
+ int32_t savedScript = -1;
+
+ while (current != end) {
+ uint32_t ch = *current++;
+
+ if (NS_IS_HIGH_SURROGATE(ch) && current != end &&
+ NS_IS_LOW_SURROGATE(*current)) {
+ ch = SURROGATE_TO_UCS4(ch, *current++);
+ }
+
+ // Check for restricted characters; aspirational scripts are NOT permitted,
+ // in anticipation of the category being merged into Limited-Use scripts
+ // in the upcoming (Unicode 10.0-based) revision of UAX #31.
+ XidmodType xm = GetIdentifierModification(ch);
+ if (xm != XIDMOD_RECOMMENDED &&
+ xm != XIDMOD_INCLUSION) {
+ return false;
+ }
+
+ // Check for mixed script
+ Script script = GetScriptCode(ch);
+ if (script != Script::COMMON &&
+ script != Script::INHERITED &&
+ script != lastScript) {
+ if (illegalScriptCombo(script, savedScript)) {
+ return false;
+ }
+ lastScript = script;
+ }
+
+ // Check for mixed numbering systems
+ if (GetGeneralCategory(ch) ==
+ HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER) {
+ uint32_t zeroCharacter = ch - GetNumericValue(ch);
+ if (savedNumberingSystem == 0) {
+ // If we encounter a decimal number, save the zero character from that
+ // numbering system.
+ savedNumberingSystem = zeroCharacter;
+ } else if (zeroCharacter != savedNumberingSystem) {
+ return false;
+ }
+ }
+
+ // Check for consecutive non-spacing marks
+ if (previousChar != 0 &&
+ previousChar == ch &&
+ GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) {
+ return false;
+ }
+
+ // Simplified/Traditional Chinese check temporarily disabled -- bug 857481
+#if 0
+
+ // Check for both simplified-only and traditional-only Chinese characters
+ HanVariantType hanVariant = GetHanVariant(ch);
+ if (hanVariant == HVT_SimplifiedOnly || hanVariant == HVT_TraditionalOnly) {
+ if (savedHanVariant == HVT_NotHan) {
+ savedHanVariant = hanVariant;
+ } else if (hanVariant != savedHanVariant) {
+ return false;
+ }
+ }
+#endif
+
+ previousChar = ch;
+ }
+ return true;
+}
+
+// Scripts that we care about in illegalScriptCombo
+static const Script scriptTable[] = {
+ Script::BOPOMOFO, Script::CYRILLIC, Script::GREEK,
+ Script::HANGUL, Script::HAN, Script::HIRAGANA,
+ Script::KATAKANA, Script::LATIN };
+
+#define BOPO 0
+#define CYRL 1
+#define GREK 2
+#define HANG 3
+#define HANI 4
+#define HIRA 5
+#define KATA 6
+#define LATN 7
+#define OTHR 8
+#define JPAN 9 // Latin + Han + Hiragana + Katakana
+#define CHNA 10 // Latin + Han + Bopomofo
+#define KORE 11 // Latin + Han + Hangul
+#define HNLT 12 // Latin + Han (could be any of the above combinations)
+#define FAIL 13
+
+static inline int32_t findScriptIndex(Script aScript)
+{
+ int32_t tableLength = sizeof(scriptTable) / sizeof(int32_t);
+ for (int32_t index = 0; index < tableLength; ++index) {
+ if (aScript == scriptTable[index]) {
+ return index;
+ }
+ }
+ return OTHR;
+}
+
+static const int32_t scriptComboTable[13][9] = {
+/* thisScript: BOPO CYRL GREK HANG HANI HIRA KATA LATN OTHR
+ * savedScript */
+ /* BOPO */ { BOPO, FAIL, FAIL, FAIL, CHNA, FAIL, FAIL, CHNA, FAIL },
+ /* CYRL */ { FAIL, CYRL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL },
+ /* GREK */ { FAIL, FAIL, GREK, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL },
+ /* HANG */ { FAIL, FAIL, FAIL, HANG, KORE, FAIL, FAIL, KORE, FAIL },
+ /* HANI */ { CHNA, FAIL, FAIL, KORE, HANI, JPAN, JPAN, HNLT, FAIL },
+ /* HIRA */ { FAIL, FAIL, FAIL, FAIL, JPAN, HIRA, JPAN, JPAN, FAIL },
+ /* KATA */ { FAIL, FAIL, FAIL, FAIL, JPAN, JPAN, KATA, JPAN, FAIL },
+ /* LATN */ { CHNA, FAIL, FAIL, KORE, HNLT, JPAN, JPAN, LATN, OTHR },
+ /* OTHR */ { FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, OTHR, FAIL },
+ /* JPAN */ { FAIL, FAIL, FAIL, FAIL, JPAN, JPAN, JPAN, JPAN, FAIL },
+ /* CHNA */ { CHNA, FAIL, FAIL, FAIL, CHNA, FAIL, FAIL, CHNA, FAIL },
+ /* KORE */ { FAIL, FAIL, FAIL, KORE, KORE, FAIL, FAIL, KORE, FAIL },
+ /* HNLT */ { CHNA, FAIL, FAIL, KORE, HNLT, JPAN, JPAN, HNLT, FAIL }
+};
+
+bool nsIDNService::illegalScriptCombo(Script script, int32_t& savedScript)
+{
+ if (savedScript == -1) {
+ savedScript = findScriptIndex(script);
+ return false;
+ }
+
+ savedScript = scriptComboTable[savedScript] [findScriptIndex(script)];
+ /*
+ * Special case combinations that depend on which profile is in use
+ * In the Highly Restrictive profile Latin is not allowed with any
+ * other script
+ *
+ * In the Moderately Restrictive profile Latin mixed with any other
+ * single script is allowed.
+ */
+ return ((savedScript == OTHR &&
+ mRestrictionProfile == eHighlyRestrictiveProfile) ||
+ savedScript == FAIL);
+}
+
+#undef BOPO
+#undef CYRL
+#undef GREK
+#undef HANG
+#undef HANI
+#undef HIRA
+#undef KATA
+#undef LATN
+#undef OTHR
+#undef JPAN
+#undef CHNA
+#undef KORE
+#undef HNLT
+#undef FAIL