diff options
Diffstat (limited to 'netwerk/dns/nsIDNService.cpp')
-rw-r--r-- | netwerk/dns/nsIDNService.cpp | 959 |
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 |