michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsIDNService.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsCRT.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsUnicodeProperties.h" michael@0: #include "nsUnicodeScriptCodes.h" michael@0: #include "harfbuzz/hb.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "punycode.h" michael@0: michael@0: michael@0: using namespace mozilla::unicode; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // RFC 1034 - 3.1. Name space specifications and terminology michael@0: static const uint32_t kMaxDNSNodeLen = 63; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: #define NS_NET_PREF_IDNTESTBED "network.IDN_testbed" michael@0: #define NS_NET_PREF_IDNPREFIX "network.IDN_prefix" michael@0: #define NS_NET_PREF_IDNBLACKLIST "network.IDN.blacklist_chars" michael@0: #define NS_NET_PREF_SHOWPUNYCODE "network.IDN_show_punycode" michael@0: #define NS_NET_PREF_IDNWHITELIST "network.IDN.whitelist." michael@0: #define NS_NET_PREF_IDNUSEWHITELIST "network.IDN.use_whitelist" michael@0: #define NS_NET_PREF_IDNRESTRICTION "network.IDN.restriction_profile" michael@0: michael@0: inline bool isOnlySafeChars(const nsAFlatString& in, michael@0: const nsAFlatString& blacklist) michael@0: { michael@0: return (blacklist.IsEmpty() || michael@0: in.FindCharInSet(blacklist) == kNotFound); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIDNService michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: /* Implementation file */ michael@0: NS_IMPL_ISUPPORTS(nsIDNService, michael@0: nsIIDNService, michael@0: nsIObserver, michael@0: nsISupportsWeakReference) michael@0: michael@0: nsresult nsIDNService::Init() michael@0: { michael@0: nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); michael@0: if (prefs) michael@0: prefs->GetBranch(NS_NET_PREF_IDNWHITELIST, getter_AddRefs(mIDNWhitelistPrefBranch)); michael@0: michael@0: nsCOMPtr prefInternal(do_QueryInterface(prefs)); michael@0: if (prefInternal) { michael@0: prefInternal->AddObserver(NS_NET_PREF_IDNTESTBED, this, true); michael@0: prefInternal->AddObserver(NS_NET_PREF_IDNPREFIX, this, true); michael@0: prefInternal->AddObserver(NS_NET_PREF_IDNBLACKLIST, this, true); michael@0: prefInternal->AddObserver(NS_NET_PREF_SHOWPUNYCODE, this, true); michael@0: prefInternal->AddObserver(NS_NET_PREF_IDNRESTRICTION, this, true); michael@0: prefInternal->AddObserver(NS_NET_PREF_IDNUSEWHITELIST, this, true); michael@0: prefsChanged(prefInternal, nullptr); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsIDNService::Observe(nsISupports *aSubject, michael@0: const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { michael@0: nsCOMPtr prefBranch( do_QueryInterface(aSubject) ); michael@0: if (prefBranch) michael@0: prefsChanged(prefBranch, aData); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsIDNService::prefsChanged(nsIPrefBranch *prefBranch, const char16_t *pref) michael@0: { michael@0: if (!pref || NS_LITERAL_STRING(NS_NET_PREF_IDNTESTBED).Equals(pref)) { michael@0: bool val; michael@0: if (NS_SUCCEEDED(prefBranch->GetBoolPref(NS_NET_PREF_IDNTESTBED, &val))) michael@0: mMultilingualTestBed = val; michael@0: } michael@0: if (!pref || NS_LITERAL_STRING(NS_NET_PREF_IDNPREFIX).Equals(pref)) { michael@0: nsXPIDLCString prefix; michael@0: nsresult rv = prefBranch->GetCharPref(NS_NET_PREF_IDNPREFIX, getter_Copies(prefix)); michael@0: if (NS_SUCCEEDED(rv) && prefix.Length() <= kACEPrefixLen) michael@0: PL_strncpyz(nsIDNService::mACEPrefix, prefix.get(), kACEPrefixLen + 1); michael@0: } michael@0: if (!pref || NS_LITERAL_STRING(NS_NET_PREF_IDNBLACKLIST).Equals(pref)) { michael@0: nsCOMPtr blacklist; michael@0: nsresult rv = prefBranch->GetComplexValue(NS_NET_PREF_IDNBLACKLIST, michael@0: NS_GET_IID(nsISupportsString), michael@0: getter_AddRefs(blacklist)); michael@0: if (NS_SUCCEEDED(rv)) michael@0: blacklist->ToString(getter_Copies(mIDNBlacklist)); michael@0: else michael@0: mIDNBlacklist.Truncate(); michael@0: } michael@0: if (!pref || NS_LITERAL_STRING(NS_NET_PREF_SHOWPUNYCODE).Equals(pref)) { michael@0: bool val; michael@0: if (NS_SUCCEEDED(prefBranch->GetBoolPref(NS_NET_PREF_SHOWPUNYCODE, &val))) michael@0: mShowPunycode = val; michael@0: } michael@0: if (!pref || NS_LITERAL_STRING(NS_NET_PREF_IDNUSEWHITELIST).Equals(pref)) { michael@0: bool val; michael@0: if (NS_SUCCEEDED(prefBranch->GetBoolPref(NS_NET_PREF_IDNUSEWHITELIST, michael@0: &val))) michael@0: mIDNUseWhitelist = val; michael@0: } michael@0: if (!pref || NS_LITERAL_STRING(NS_NET_PREF_IDNRESTRICTION).Equals(pref)) { michael@0: nsXPIDLCString profile; michael@0: if (NS_FAILED(prefBranch->GetCharPref(NS_NET_PREF_IDNRESTRICTION, michael@0: getter_Copies(profile)))) { michael@0: profile.Truncate(); michael@0: } michael@0: if (profile.Equals(NS_LITERAL_CSTRING("moderate"))) { michael@0: mRestrictionProfile = eModeratelyRestrictiveProfile; michael@0: } else if (profile.Equals(NS_LITERAL_CSTRING("high"))) { michael@0: mRestrictionProfile = eHighlyRestrictiveProfile; michael@0: } else { michael@0: mRestrictionProfile = eASCIIOnlyProfile; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsIDNService::nsIDNService() michael@0: { michael@0: // initialize to the official prefix (RFC 3490 "5. ACE prefix") michael@0: const char kIDNSPrefix[] = "xn--"; michael@0: strcpy(mACEPrefix, kIDNSPrefix); michael@0: michael@0: mMultilingualTestBed = false; michael@0: michael@0: if (idn_success != idn_nameprep_create(nullptr, &mNamePrepHandle)) michael@0: mNamePrepHandle = nullptr; michael@0: michael@0: mNormalizer = do_GetService(NS_UNICODE_NORMALIZER_CONTRACTID); michael@0: /* member initializers and constructor code */ michael@0: } michael@0: michael@0: nsIDNService::~nsIDNService() michael@0: { michael@0: idn_nameprep_destroy(mNamePrepHandle); michael@0: } michael@0: michael@0: /* ACString ConvertUTF8toACE (in AUTF8String input); */ michael@0: NS_IMETHODIMP nsIDNService::ConvertUTF8toACE(const nsACString & input, nsACString & ace) michael@0: { michael@0: return UTF8toACE(input, ace, true, true); michael@0: } michael@0: michael@0: nsresult nsIDNService::SelectiveUTF8toACE(const nsACString& input, nsACString& ace) michael@0: { michael@0: return UTF8toACE(input, ace, true, false); michael@0: } michael@0: michael@0: nsresult nsIDNService::UTF8toACE(const nsACString & input, nsACString & ace, bool allowUnassigned, bool convertAllLabels) michael@0: { michael@0: nsresult rv; michael@0: NS_ConvertUTF8toUTF16 ustr(input); michael@0: michael@0: // map ideographic period to ASCII period etc. michael@0: normalizeFullStops(ustr); michael@0: michael@0: michael@0: uint32_t len, offset; michael@0: len = 0; michael@0: offset = 0; michael@0: nsAutoCString encodedBuf; michael@0: michael@0: nsAString::const_iterator start, end; michael@0: ustr.BeginReading(start); michael@0: ustr.EndReading(end); michael@0: ace.Truncate(); michael@0: michael@0: // encode nodes if non ASCII michael@0: while (start != end) { michael@0: len++; michael@0: if (*start++ == (char16_t)'.') { michael@0: rv = stringPrepAndACE(Substring(ustr, offset, len - 1), encodedBuf, michael@0: allowUnassigned, convertAllLabels); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: ace.Append(encodedBuf); michael@0: ace.Append('.'); michael@0: offset += len; michael@0: len = 0; michael@0: } michael@0: } michael@0: michael@0: // add extra node for multilingual test bed michael@0: if (mMultilingualTestBed) michael@0: ace.AppendLiteral("mltbd."); michael@0: // encode the last node if non ASCII michael@0: if (len) { michael@0: rv = stringPrepAndACE(Substring(ustr, offset, len), encodedBuf, michael@0: allowUnassigned, convertAllLabels); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: ace.Append(encodedBuf); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* AUTF8String convertACEtoUTF8(in ACString input); */ michael@0: NS_IMETHODIMP nsIDNService::ConvertACEtoUTF8(const nsACString & input, nsACString & _retval) michael@0: { michael@0: return ACEtoUTF8(input, _retval, true, true); michael@0: } michael@0: michael@0: nsresult nsIDNService::SelectiveACEtoUTF8(const nsACString& input, nsACString& _retval) michael@0: { michael@0: return ACEtoUTF8(input, _retval, false, false); michael@0: } michael@0: michael@0: nsresult nsIDNService::ACEtoUTF8(const nsACString & input, nsACString & _retval, michael@0: bool allowUnassigned, bool convertAllLabels) michael@0: { michael@0: // RFC 3490 - 4.2 ToUnicode michael@0: // ToUnicode never fails. If any step fails, then the original input michael@0: // sequence is returned immediately in that step. michael@0: michael@0: uint32_t len = 0, offset = 0; michael@0: nsAutoCString decodedBuf; michael@0: michael@0: nsACString::const_iterator start, end; michael@0: input.BeginReading(start); michael@0: input.EndReading(end); michael@0: _retval.Truncate(); michael@0: michael@0: // loop and decode nodes michael@0: while (start != end) { michael@0: len++; michael@0: if (*start++ == '.') { michael@0: if (NS_FAILED(decodeACE(Substring(input, offset, len - 1), decodedBuf, michael@0: allowUnassigned, convertAllLabels))) { michael@0: _retval.Assign(input); michael@0: return NS_OK; michael@0: } michael@0: michael@0: _retval.Append(decodedBuf); michael@0: _retval.Append('.'); michael@0: offset += len; michael@0: len = 0; michael@0: } michael@0: } michael@0: // decode the last node michael@0: if (len) { michael@0: if (NS_FAILED(decodeACE(Substring(input, offset, len), decodedBuf, michael@0: allowUnassigned, convertAllLabels))) michael@0: _retval.Assign(input); michael@0: else michael@0: _retval.Append(decodedBuf); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* boolean isACE(in ACString input); */ michael@0: NS_IMETHODIMP nsIDNService::IsACE(const nsACString & input, bool *_retval) michael@0: { michael@0: nsACString::const_iterator begin; michael@0: input.BeginReading(begin); michael@0: michael@0: const char *data = begin.get(); michael@0: uint32_t dataLen = begin.size_forward(); michael@0: michael@0: // look for the ACE prefix in the input string. it may occur michael@0: // at the beginning of any segment in the domain name. for michael@0: // example: "www.xn--ENCODED.com" michael@0: michael@0: const char *p = PL_strncasestr(data, mACEPrefix, dataLen); michael@0: michael@0: *_retval = p && (p == data || *(p - 1) == '.'); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* AUTF8String normalize(in AUTF8String input); */ michael@0: NS_IMETHODIMP nsIDNService::Normalize(const nsACString & input, nsACString & output) michael@0: { michael@0: // protect against bogus input michael@0: NS_ENSURE_TRUE(IsUTF8(input), NS_ERROR_UNEXPECTED); michael@0: michael@0: NS_ConvertUTF8toUTF16 inUTF16(input); michael@0: normalizeFullStops(inUTF16); michael@0: michael@0: // pass the domain name to stringprep label by label michael@0: nsAutoString outUTF16, outLabel; michael@0: michael@0: uint32_t len = 0, offset = 0; michael@0: nsresult rv; michael@0: nsAString::const_iterator start, end; michael@0: inUTF16.BeginReading(start); michael@0: inUTF16.EndReading(end); michael@0: michael@0: while (start != end) { michael@0: len++; michael@0: if (*start++ == char16_t('.')) { michael@0: rv = stringPrep(Substring(inUTF16, offset, len - 1), outLabel, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: outUTF16.Append(outLabel); michael@0: outUTF16.Append(char16_t('.')); michael@0: offset += len; michael@0: len = 0; michael@0: } michael@0: } michael@0: if (len) { michael@0: rv = stringPrep(Substring(inUTF16, offset, len), outLabel, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: outUTF16.Append(outLabel); michael@0: } michael@0: michael@0: CopyUTF16toUTF8(outUTF16, output); michael@0: if (!isOnlySafeChars(outUTF16, mIDNBlacklist)) michael@0: return ConvertUTF8toACE(output, output); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsIDNService::ConvertToDisplayIDN(const nsACString & input, bool * _isASCII, nsACString & _retval) michael@0: { michael@0: // If host is ACE, then convert to UTF-8 if the host is in the IDN whitelist. michael@0: // Else, if host is already UTF-8, then make sure it is normalized per IDN. michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: // Even if the hostname is not ASCII, individual labels may still be ACE, so michael@0: // test IsACE before testing IsASCII michael@0: bool isACE; michael@0: IsACE(input, &isACE); michael@0: michael@0: if (IsASCII(input)) { michael@0: // first, canonicalize the host to lowercase, for whitelist lookup michael@0: _retval = input; michael@0: ToLowerCase(_retval); michael@0: michael@0: if (isACE && !mShowPunycode) { michael@0: // ACEtoUTF8() can't fail, but might return the original ACE string michael@0: nsAutoCString temp(_retval); michael@0: if (isInWhitelist(temp)) { michael@0: // If the domain is in the whitelist, return the host in UTF-8 michael@0: ACEtoUTF8(temp, _retval, false, true); michael@0: } else { michael@0: // Otherwise convert from ACE to UTF8 only those labels which are michael@0: // considered safe for display michael@0: SelectiveACEtoUTF8(temp, _retval); michael@0: } michael@0: *_isASCII = IsASCII(_retval); michael@0: } else { michael@0: *_isASCII = true; michael@0: } michael@0: } else { michael@0: // We have to normalize the hostname before testing against the domain michael@0: // whitelist (see bug 315411), and to ensure the entire string gets michael@0: // normalized. michael@0: // michael@0: // Normalization and the tests for safe display below, assume that the michael@0: // input is Unicode, so first convert any ACE labels to UTF8 michael@0: if (isACE) { michael@0: nsAutoCString temp; michael@0: ACEtoUTF8(input, temp, false, true); michael@0: rv = Normalize(temp, _retval); michael@0: } else { michael@0: rv = Normalize(input, _retval); michael@0: } michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (mShowPunycode && NS_SUCCEEDED(ConvertUTF8toACE(_retval, _retval))) { michael@0: *_isASCII = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // normalization could result in an ASCII-only hostname. alternatively, if michael@0: // the host is converted to ACE by the normalizer, then the host may contain michael@0: // unsafe characters, so leave it ACE encoded. see bug 283016, bug 301694, and bug 309311. michael@0: *_isASCII = IsASCII(_retval); michael@0: if (!*_isASCII && !isInWhitelist(_retval)) { michael@0: // SelectiveUTF8toACE may return a domain name where some labels are in UTF-8 michael@0: // and some are in ACE, depending on whether they are considered safe for michael@0: // display michael@0: rv = SelectiveUTF8toACE(_retval, _retval); michael@0: *_isASCII = IsASCII(_retval); michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: static nsresult utf16ToUcs4(const nsAString& in, michael@0: uint32_t *out, michael@0: uint32_t outBufLen, michael@0: uint32_t *outLen) michael@0: { michael@0: uint32_t i = 0; michael@0: nsAString::const_iterator start, end; michael@0: in.BeginReading(start); michael@0: in.EndReading(end); michael@0: michael@0: while (start != end) { michael@0: char16_t curChar; michael@0: michael@0: curChar= *start++; michael@0: michael@0: if (start != end && michael@0: NS_IS_HIGH_SURROGATE(curChar) && michael@0: NS_IS_LOW_SURROGATE(*start)) { michael@0: out[i] = SURROGATE_TO_UCS4(curChar, *start); michael@0: ++start; michael@0: } michael@0: else michael@0: out[i] = curChar; michael@0: michael@0: i++; michael@0: if (i >= outBufLen) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: out[i] = (uint32_t)'\0'; michael@0: *outLen = i; michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void ucs4toUtf16(const uint32_t *in, nsAString& out) michael@0: { michael@0: while (*in) { michael@0: if (!IS_IN_BMP(*in)) { michael@0: out.Append((char16_t) H_SURROGATE(*in)); michael@0: out.Append((char16_t) L_SURROGATE(*in)); michael@0: } michael@0: else michael@0: out.Append((char16_t) *in); michael@0: in++; michael@0: } michael@0: } michael@0: michael@0: static nsresult punycode(const char* prefix, const nsAString& in, nsACString& out) michael@0: { michael@0: uint32_t ucs4Buf[kMaxDNSNodeLen + 1]; michael@0: uint32_t ucs4Len; michael@0: nsresult rv = utf16ToUcs4(in, ucs4Buf, kMaxDNSNodeLen, &ucs4Len); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // need maximum 20 bits to encode 16 bit Unicode character michael@0: // (include null terminator) michael@0: const uint32_t kEncodedBufSize = kMaxDNSNodeLen * 20 / 8 + 1 + 1; michael@0: char encodedBuf[kEncodedBufSize]; michael@0: punycode_uint encodedLength = kEncodedBufSize; michael@0: michael@0: enum punycode_status status = punycode_encode(ucs4Len, michael@0: ucs4Buf, michael@0: nullptr, michael@0: &encodedLength, michael@0: encodedBuf); michael@0: michael@0: if (punycode_success != status || michael@0: encodedLength >= kEncodedBufSize) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: encodedBuf[encodedLength] = '\0'; michael@0: out.Assign(nsDependentCString(prefix) + nsDependentCString(encodedBuf)); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: static nsresult encodeToRACE(const char* prefix, const nsAString& in, nsACString& out) michael@0: { michael@0: // need maximum 20 bits to encode 16 bit Unicode character michael@0: // (include null terminator) michael@0: const uint32_t kEncodedBufSize = kMaxDNSNodeLen * 20 / 8 + 1 + 1; michael@0: michael@0: // set up a work buffer for RACE encoder michael@0: char16_t temp[kMaxDNSNodeLen + 2]; michael@0: temp[0] = 0xFFFF; // set a place holder (to be filled by get_compress_mode) michael@0: temp[in.Length() + 1] = (char16_t)'\0'; michael@0: michael@0: nsAString::const_iterator start, end; michael@0: in.BeginReading(start); michael@0: in.EndReading(end); michael@0: michael@0: for (uint32_t i = 1; start != end; i++) michael@0: temp[i] = *start++; michael@0: michael@0: // encode nodes if non ASCII michael@0: michael@0: char encodedBuf[kEncodedBufSize]; michael@0: idn_result_t result = race_compress_encode((const unsigned short *) temp, michael@0: get_compress_mode((unsigned short *) temp + 1), michael@0: encodedBuf, kEncodedBufSize); michael@0: if (idn_success != result) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: out.Assign(prefix); michael@0: out.Append(encodedBuf); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // RFC 3454 michael@0: // michael@0: // 1) Map -- For each character in the input, check if it has a mapping michael@0: // and, if so, replace it with its mapping. This is described in section 3. michael@0: // michael@0: // 2) Normalize -- Possibly normalize the result of step 1 using Unicode michael@0: // normalization. This is described in section 4. michael@0: // michael@0: // 3) Prohibit -- Check for any characters that are not allowed in the michael@0: // output. If any are found, return an error. This is described in section michael@0: // 5. michael@0: // michael@0: // 4) Check bidi -- Possibly check for right-to-left characters, and if any michael@0: // are found, make sure that the whole string satisfies the requirements michael@0: // for bidirectional strings. If the string does not satisfy the requirements michael@0: // for bidirectional strings, return an error. This is described in section 6. michael@0: // michael@0: // 5) Check unassigned code points -- If allowUnassigned is false, check for michael@0: // any unassigned Unicode points and if any are found return an error. michael@0: // This is described in section 7. michael@0: // michael@0: nsresult nsIDNService::stringPrep(const nsAString& in, nsAString& out, michael@0: bool allowUnassigned) michael@0: { michael@0: if (!mNamePrepHandle || !mNormalizer) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: uint32_t ucs4Buf[kMaxDNSNodeLen + 1]; michael@0: uint32_t ucs4Len; michael@0: nsresult rv = utf16ToUcs4(in, ucs4Buf, kMaxDNSNodeLen, &ucs4Len); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // map michael@0: idn_result_t idn_err; michael@0: michael@0: uint32_t namePrepBuf[kMaxDNSNodeLen * 3]; // map up to three characters michael@0: idn_err = idn_nameprep_map(mNamePrepHandle, (const uint32_t *) ucs4Buf, michael@0: (uint32_t *) namePrepBuf, kMaxDNSNodeLen * 3); michael@0: NS_ENSURE_TRUE(idn_err == idn_success, NS_ERROR_FAILURE); michael@0: michael@0: nsAutoString namePrepStr; michael@0: ucs4toUtf16(namePrepBuf, namePrepStr); michael@0: if (namePrepStr.Length() >= kMaxDNSNodeLen) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // normalize michael@0: nsAutoString normlizedStr; michael@0: rv = mNormalizer->NormalizeUnicodeNFKC(namePrepStr, normlizedStr); michael@0: if (normlizedStr.Length() >= kMaxDNSNodeLen) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // prohibit michael@0: const uint32_t *found = nullptr; michael@0: idn_err = idn_nameprep_isprohibited(mNamePrepHandle, michael@0: (const uint32_t *) ucs4Buf, &found); michael@0: if (idn_err != idn_success || found) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // check bidi michael@0: idn_err = idn_nameprep_isvalidbidi(mNamePrepHandle, michael@0: (const uint32_t *) ucs4Buf, &found); michael@0: if (idn_err != idn_success || found) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (!allowUnassigned) { michael@0: // check unassigned code points michael@0: idn_err = idn_nameprep_isunassigned(mNamePrepHandle, michael@0: (const uint32_t *) ucs4Buf, &found); michael@0: if (idn_err != idn_success || found) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // set the result string michael@0: out.Assign(normlizedStr); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsIDNService::encodeToACE(const nsAString& in, nsACString& out) michael@0: { michael@0: // RACE encode is supported for existing testing environment michael@0: if (!strcmp("bq--", mACEPrefix)) michael@0: return encodeToRACE(mACEPrefix, in, out); michael@0: michael@0: // use punycoce michael@0: return punycode(mACEPrefix, in, out); michael@0: } michael@0: michael@0: nsresult nsIDNService::stringPrepAndACE(const nsAString& in, nsACString& out, michael@0: bool allowUnassigned, michael@0: bool convertAllLabels) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: out.Truncate(); michael@0: michael@0: if (in.Length() > kMaxDNSNodeLen) { michael@0: NS_WARNING("IDN node too large"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (IsASCII(in)) michael@0: LossyCopyUTF16toASCII(in, out); michael@0: else if (!convertAllLabels && isLabelSafe(in)) michael@0: CopyUTF16toUTF8(in, out); michael@0: else { michael@0: nsAutoString strPrep; michael@0: rv = stringPrep(in, strPrep, allowUnassigned); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (IsASCII(strPrep)) michael@0: LossyCopyUTF16toASCII(strPrep, out); michael@0: else michael@0: rv = encodeToACE(strPrep, out); michael@0: } michael@0: // Check that the encoded output isn't larger than the maximum length of an michael@0: // DNS node per RFC 1034. michael@0: // This test isn't necessary in the code paths above where the input is michael@0: // ASCII (since the output will be the same length as the input) or where michael@0: // we convert to UTF-8 (since the output is only used for display in the michael@0: // UI and not passed to DNS and can legitimately be longer than the limit). michael@0: if (out.Length() > kMaxDNSNodeLen) { michael@0: NS_WARNING("IDN node too large"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // RFC 3490 michael@0: // 1) Whenever dots are used as label separators, the following characters michael@0: // MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full michael@0: // stop), U+FF0E (fullwidth full stop), U+FF61 (halfwidth ideographic full michael@0: // stop). michael@0: michael@0: void nsIDNService::normalizeFullStops(nsAString& s) michael@0: { michael@0: nsAString::const_iterator start, end; michael@0: s.BeginReading(start); michael@0: s.EndReading(end); michael@0: int32_t index = 0; michael@0: michael@0: while (start != end) { michael@0: switch (*start) { michael@0: case 0x3002: michael@0: case 0xFF0E: michael@0: case 0xFF61: michael@0: s.Replace(index, 1, NS_LITERAL_STRING(".")); michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: start++; michael@0: index++; michael@0: } michael@0: } michael@0: michael@0: nsresult nsIDNService::decodeACE(const nsACString& in, nsACString& out, michael@0: bool allowUnassigned, bool convertAllLabels) michael@0: { michael@0: bool isAce; michael@0: IsACE(in, &isAce); michael@0: if (!isAce) { michael@0: out.Assign(in); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // RFC 3490 - 4.2 ToUnicode michael@0: // The ToUnicode output never contains more code points than its input. michael@0: punycode_uint output_length = in.Length() - kACEPrefixLen + 1; michael@0: punycode_uint *output = new punycode_uint[output_length]; michael@0: NS_ENSURE_TRUE(output, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: enum punycode_status status = punycode_decode(in.Length() - kACEPrefixLen, michael@0: PromiseFlatCString(in).get() + kACEPrefixLen, michael@0: &output_length, michael@0: output, michael@0: nullptr); michael@0: if (status != punycode_success) { michael@0: delete [] output; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // UCS4 -> UTF8 michael@0: output[output_length] = 0; michael@0: nsAutoString utf16; michael@0: ucs4toUtf16(output, utf16); michael@0: delete [] output; michael@0: if (!convertAllLabels && !isLabelSafe(utf16)) { michael@0: out.Assign(in); michael@0: return NS_OK; michael@0: } michael@0: if (!isOnlySafeChars(utf16, mIDNBlacklist)) michael@0: return NS_ERROR_FAILURE; michael@0: CopyUTF16toUTF8(utf16, out); michael@0: michael@0: // Validation: encode back to ACE and compare the strings michael@0: nsAutoCString ace; michael@0: nsresult rv = UTF8toACE(out, ace, allowUnassigned, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!ace.Equals(in, nsCaseInsensitiveCStringComparator())) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool nsIDNService::isInWhitelist(const nsACString &host) michael@0: { michael@0: if (mIDNUseWhitelist && mIDNWhitelistPrefBranch) { michael@0: nsAutoCString tld(host); michael@0: // make sure the host is ACE for lookup and check that there are no michael@0: // unassigned codepoints michael@0: if (!IsASCII(tld) && NS_FAILED(UTF8toACE(tld, tld, false, true))) { michael@0: return false; michael@0: } michael@0: michael@0: // truncate trailing dots first michael@0: tld.Trim("."); michael@0: int32_t pos = tld.RFind("."); michael@0: if (pos == kNotFound) michael@0: return false; michael@0: michael@0: tld.Cut(0, pos + 1); michael@0: michael@0: bool safe; michael@0: if (NS_SUCCEEDED(mIDNWhitelistPrefBranch->GetBoolPref(tld.get(), &safe))) michael@0: return safe; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool nsIDNService::isLabelSafe(const nsAString &label) michael@0: { michael@0: // We should never get here if the label is ASCII michael@0: NS_ASSERTION(!IsASCII(label), "ASCII label in IDN checking"); michael@0: if (mRestrictionProfile == eASCIIOnlyProfile) { michael@0: return false; michael@0: } michael@0: michael@0: nsAString::const_iterator current, end; michael@0: label.BeginReading(current); michael@0: label.EndReading(end); michael@0: michael@0: int32_t lastScript = MOZ_SCRIPT_INVALID; michael@0: uint32_t previousChar = 0; michael@0: uint32_t savedNumberingSystem = 0; michael@0: // Simplified/Traditional Chinese check temporarily disabled -- bug 857481 michael@0: #if 0 michael@0: HanVariantType savedHanVariant = HVT_NotHan; michael@0: #endif michael@0: michael@0: int32_t savedScript = -1; michael@0: michael@0: while (current != end) { michael@0: uint32_t ch = *current++; michael@0: michael@0: if (NS_IS_HIGH_SURROGATE(ch) && current != end && michael@0: NS_IS_LOW_SURROGATE(*current)) { michael@0: ch = SURROGATE_TO_UCS4(ch, *current++); michael@0: } michael@0: michael@0: // Check for restricted characters; aspirational scripts are permitted michael@0: XidmodType xm = GetIdentifierModification(ch); michael@0: int32_t script = GetScriptCode(ch); michael@0: if (xm > XIDMOD_RECOMMENDED && michael@0: !(xm == XIDMOD_LIMITED_USE && michael@0: (script == MOZ_SCRIPT_CANADIAN_ABORIGINAL || michael@0: script == MOZ_SCRIPT_MIAO || michael@0: script == MOZ_SCRIPT_MONGOLIAN || michael@0: script == MOZ_SCRIPT_TIFINAGH || michael@0: script == MOZ_SCRIPT_YI))) { michael@0: return false; michael@0: } michael@0: michael@0: // Check for mixed script michael@0: if (script != MOZ_SCRIPT_COMMON && michael@0: script != MOZ_SCRIPT_INHERITED && michael@0: script != lastScript) { michael@0: if (illegalScriptCombo(script, savedScript)) { michael@0: return false; michael@0: } michael@0: lastScript = script; michael@0: } michael@0: michael@0: // Check for mixed numbering systems michael@0: if (GetGeneralCategory(ch) == michael@0: HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER) { michael@0: uint32_t zeroCharacter = ch - GetNumericValue(ch); michael@0: if (savedNumberingSystem == 0) { michael@0: // If we encounter a decimal number, save the zero character from that michael@0: // numbering system. michael@0: savedNumberingSystem = zeroCharacter; michael@0: } else if (zeroCharacter != savedNumberingSystem) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Check for consecutive non-spacing marks michael@0: if (previousChar != 0 && michael@0: previousChar == ch && michael@0: GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) { michael@0: return false; michael@0: } michael@0: michael@0: // Simplified/Traditional Chinese check temporarily disabled -- bug 857481 michael@0: #if 0 michael@0: michael@0: // Check for both simplified-only and traditional-only Chinese characters michael@0: HanVariantType hanVariant = GetHanVariant(ch); michael@0: if (hanVariant == HVT_SimplifiedOnly || hanVariant == HVT_TraditionalOnly) { michael@0: if (savedHanVariant == HVT_NotHan) { michael@0: savedHanVariant = hanVariant; michael@0: } else if (hanVariant != savedHanVariant) { michael@0: return false; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: previousChar = ch; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // Scripts that we care about in illegalScriptCombo michael@0: static const int32_t scriptTable[] = { michael@0: MOZ_SCRIPT_BOPOMOFO, MOZ_SCRIPT_CYRILLIC, MOZ_SCRIPT_GREEK, michael@0: MOZ_SCRIPT_HANGUL, MOZ_SCRIPT_HAN, MOZ_SCRIPT_HIRAGANA, michael@0: MOZ_SCRIPT_KATAKANA, MOZ_SCRIPT_LATIN }; michael@0: michael@0: #define BOPO 0 michael@0: #define CYRL 1 michael@0: #define GREK 2 michael@0: #define HANG 3 michael@0: #define HANI 4 michael@0: #define HIRA 5 michael@0: #define KATA 6 michael@0: #define LATN 7 michael@0: #define OTHR 8 michael@0: #define JPAN 9 // Latin + Han + Hiragana + Katakana michael@0: #define CHNA 10 // Latin + Han + Bopomofo michael@0: #define KORE 11 // Latin + Han + Hangul michael@0: #define HNLT 12 // Latin + Han (could be any of the above combinations) michael@0: #define FAIL 13 michael@0: michael@0: static inline int32_t findScriptIndex(int32_t aScript) michael@0: { michael@0: int32_t tableLength = sizeof(scriptTable) / sizeof(int32_t); michael@0: for (int32_t index = 0; index < tableLength; ++index) { michael@0: if (aScript == scriptTable[index]) { michael@0: return index; michael@0: } michael@0: } michael@0: return OTHR; michael@0: } michael@0: michael@0: static const int32_t scriptComboTable[13][9] = { michael@0: /* thisScript: BOPO CYRL GREK HANG HANI HIRA KATA LATN OTHR michael@0: * savedScript */ michael@0: /* BOPO */ { BOPO, FAIL, FAIL, FAIL, CHNA, FAIL, FAIL, CHNA, FAIL }, michael@0: /* CYRL */ { FAIL, CYRL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL }, michael@0: /* GREK */ { FAIL, FAIL, GREK, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL }, michael@0: /* HANG */ { FAIL, FAIL, FAIL, HANG, KORE, FAIL, FAIL, KORE, FAIL }, michael@0: /* HANI */ { CHNA, FAIL, FAIL, KORE, HANI, JPAN, JPAN, HNLT, FAIL }, michael@0: /* HIRA */ { FAIL, FAIL, FAIL, FAIL, JPAN, HIRA, JPAN, JPAN, FAIL }, michael@0: /* KATA */ { FAIL, FAIL, FAIL, FAIL, JPAN, JPAN, KATA, JPAN, FAIL }, michael@0: /* LATN */ { CHNA, FAIL, FAIL, KORE, HNLT, JPAN, JPAN, LATN, OTHR }, michael@0: /* OTHR */ { FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, OTHR, FAIL }, michael@0: /* JPAN */ { FAIL, FAIL, FAIL, FAIL, JPAN, JPAN, JPAN, JPAN, FAIL }, michael@0: /* CHNA */ { CHNA, FAIL, FAIL, FAIL, CHNA, FAIL, FAIL, CHNA, FAIL }, michael@0: /* KORE */ { FAIL, FAIL, FAIL, KORE, KORE, FAIL, FAIL, KORE, FAIL }, michael@0: /* HNLT */ { CHNA, FAIL, FAIL, KORE, HNLT, JPAN, JPAN, HNLT, FAIL } michael@0: }; michael@0: michael@0: bool nsIDNService::illegalScriptCombo(int32_t script, int32_t& savedScript) michael@0: { michael@0: if (savedScript == -1) { michael@0: savedScript = findScriptIndex(script); michael@0: return false; michael@0: } michael@0: michael@0: savedScript = scriptComboTable[savedScript] [findScriptIndex(script)]; michael@0: /* michael@0: * Special case combinations that depend on which profile is in use michael@0: * In the Highly Restrictive profile Latin is not allowed with any michael@0: * other script michael@0: * michael@0: * In the Moderately Restrictive profile Latin mixed with any other michael@0: * single script is allowed. michael@0: */ michael@0: return ((savedScript == OTHR && michael@0: mRestrictionProfile == eHighlyRestrictiveProfile) || michael@0: savedScript == FAIL); michael@0: } michael@0: michael@0: #undef BOPO michael@0: #undef CYRL michael@0: #undef GREK michael@0: #undef HANG michael@0: #undef HANI michael@0: #undef HIRA michael@0: #undef KATA michael@0: #undef LATN michael@0: #undef OTHR michael@0: #undef JPAN michael@0: #undef CHNA michael@0: #undef KORE michael@0: #undef HNLT michael@0: #undef FAIL