/src/mozilla-central/netwerk/dns/nsIDNService.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "mozilla/Preferences.h" |
7 | | #include "nsIDNService.h" |
8 | | #include "nsReadableUtils.h" |
9 | | #include "nsCRT.h" |
10 | | #include "nsUnicharUtils.h" |
11 | | #include "nsUnicodeProperties.h" |
12 | | #include "nsUnicodeScriptCodes.h" |
13 | | #include "harfbuzz/hb.h" |
14 | | #include "nsIServiceManager.h" |
15 | | #include "nsIObserverService.h" |
16 | | #include "nsISupportsPrimitives.h" |
17 | | #include "punycode.h" |
18 | | |
19 | | // Currently we use the non-transitional processing option -- see |
20 | | // http://unicode.org/reports/tr46/ |
21 | | // To switch to transitional processing, change the value of this flag |
22 | | // and kTransitionalProcessing in netwerk/test/unit/test_idna2008.js to true |
23 | | // (revert bug 1218179). |
24 | | const bool kIDNA2008_TransitionalProcessing = false; |
25 | | |
26 | | #include "ICUUtils.h" |
27 | | #include "unicode/uscript.h" |
28 | | |
29 | | using namespace mozilla::unicode; |
30 | | using mozilla::Preferences; |
31 | | |
32 | | //----------------------------------------------------------------------------- |
33 | | // RFC 1034 - 3.1. Name space specifications and terminology |
34 | | static const uint32_t kMaxDNSNodeLen = 63; |
35 | | // RFC 3490 - 5. ACE prefix |
36 | | static const char kACEPrefix[] = "xn--"; |
37 | | #define kACEPrefixLen 4 |
38 | | |
39 | | //----------------------------------------------------------------------------- |
40 | | |
41 | 3 | #define NS_NET_PREF_IDNBLACKLIST "network.IDN.blacklist_chars" |
42 | | #define NS_NET_PREF_SHOWPUNYCODE "network.IDN_show_punycode" |
43 | 3 | #define NS_NET_PREF_IDNWHITELIST "network.IDN.whitelist." |
44 | | #define NS_NET_PREF_IDNUSEWHITELIST "network.IDN.use_whitelist" |
45 | | #define NS_NET_PREF_IDNRESTRICTION "network.IDN.restriction_profile" |
46 | | |
47 | | inline bool isOnlySafeChars(const nsString& in, const nsString& blacklist) |
48 | 26.8k | { |
49 | 26.8k | return (blacklist.IsEmpty() || |
50 | 26.8k | in.FindCharInSet(blacklist) == kNotFound); |
51 | 26.8k | } |
52 | | |
53 | | //----------------------------------------------------------------------------- |
54 | | // nsIDNService |
55 | | //----------------------------------------------------------------------------- |
56 | | |
57 | | /* Implementation file */ |
58 | | NS_IMPL_ISUPPORTS(nsIDNService, |
59 | | nsIIDNService, |
60 | | nsISupportsWeakReference) |
61 | | |
62 | | static const char* gCallbackPrefs[] = { |
63 | | NS_NET_PREF_IDNBLACKLIST, |
64 | | NS_NET_PREF_SHOWPUNYCODE, |
65 | | NS_NET_PREF_IDNRESTRICTION, |
66 | | NS_NET_PREF_IDNUSEWHITELIST, |
67 | | nullptr, |
68 | | }; |
69 | | |
70 | | nsresult nsIDNService::Init() |
71 | 3 | { |
72 | 3 | MOZ_ASSERT(NS_IsMainThread()); |
73 | 3 | MutexAutoLock lock(mLock); |
74 | 3 | |
75 | 3 | nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); |
76 | 3 | if (prefs) |
77 | 3 | prefs->GetBranch(NS_NET_PREF_IDNWHITELIST, getter_AddRefs(mIDNWhitelistPrefBranch)); |
78 | 3 | |
79 | 3 | Preferences::RegisterPrefixCallbacks(PrefChanged, gCallbackPrefs, this); |
80 | 3 | prefsChanged(nullptr); |
81 | 3 | |
82 | 3 | return NS_OK; |
83 | 3 | } |
84 | | |
85 | | void nsIDNService::prefsChanged(const char *pref) |
86 | 3 | { |
87 | 3 | MOZ_ASSERT(NS_IsMainThread()); |
88 | 3 | mLock.AssertCurrentThreadOwns(); |
89 | 3 | |
90 | 3 | if (!pref || NS_LITERAL_CSTRING(NS_NET_PREF_IDNBLACKLIST).Equals(pref)) { |
91 | 3 | nsAutoCString blacklist; |
92 | 3 | nsresult rv = Preferences::GetCString(NS_NET_PREF_IDNBLACKLIST, |
93 | 3 | blacklist); |
94 | 3 | if (NS_SUCCEEDED(rv)) { |
95 | 3 | CopyUTF8toUTF16(blacklist, mIDNBlacklist); |
96 | 3 | } else { |
97 | 0 | mIDNBlacklist.Truncate(); |
98 | 0 | } |
99 | 3 | } |
100 | 3 | if (!pref || NS_LITERAL_CSTRING(NS_NET_PREF_SHOWPUNYCODE).Equals(pref)) { |
101 | 3 | bool val; |
102 | 3 | if (NS_SUCCEEDED(Preferences::GetBool(NS_NET_PREF_SHOWPUNYCODE, &val))) |
103 | 3 | mShowPunycode = val; |
104 | 3 | } |
105 | 3 | if (!pref || NS_LITERAL_CSTRING(NS_NET_PREF_IDNUSEWHITELIST).Equals(pref)) { |
106 | 3 | bool val; |
107 | 3 | if (NS_SUCCEEDED(Preferences::GetBool(NS_NET_PREF_IDNUSEWHITELIST, |
108 | 3 | &val))) |
109 | 3 | mIDNUseWhitelist = val; |
110 | 3 | } |
111 | 3 | if (!pref || NS_LITERAL_CSTRING(NS_NET_PREF_IDNRESTRICTION).Equals(pref)) { |
112 | 3 | nsAutoCString profile; |
113 | 3 | if (NS_FAILED(Preferences::GetCString(NS_NET_PREF_IDNRESTRICTION, |
114 | 3 | profile))) { |
115 | 0 | profile.Truncate(); |
116 | 0 | } |
117 | 3 | if (profile.EqualsLiteral("moderate")) { |
118 | 0 | mRestrictionProfile = eModeratelyRestrictiveProfile; |
119 | 3 | } else if (profile.EqualsLiteral("high")) { |
120 | 3 | mRestrictionProfile = eHighlyRestrictiveProfile; |
121 | 3 | } else { |
122 | 0 | mRestrictionProfile = eASCIIOnlyProfile; |
123 | 0 | } |
124 | 3 | } |
125 | 3 | } |
126 | | |
127 | | nsIDNService::nsIDNService() |
128 | | : mLock("DNService pref value lock") |
129 | | , mShowPunycode(false) |
130 | | , mRestrictionProfile(static_cast<restrictionProfile>(0)) |
131 | | , mIDNUseWhitelist(false) |
132 | 3 | { |
133 | 3 | MOZ_ASSERT(NS_IsMainThread()); |
134 | 3 | |
135 | 3 | uint32_t IDNAOptions = UIDNA_CHECK_BIDI | UIDNA_CHECK_CONTEXTJ; |
136 | 3 | if (!kIDNA2008_TransitionalProcessing) { |
137 | 3 | IDNAOptions |= UIDNA_NONTRANSITIONAL_TO_UNICODE; |
138 | 3 | } |
139 | 3 | UErrorCode errorCode = U_ZERO_ERROR; |
140 | 3 | mIDNA = uidna_openUTS46(IDNAOptions, &errorCode); |
141 | 3 | } |
142 | | |
143 | | nsIDNService::~nsIDNService() |
144 | 0 | { |
145 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
146 | 0 |
|
147 | 0 | Preferences::UnregisterPrefixCallbacks(PrefChanged, gCallbackPrefs, this); |
148 | 0 |
|
149 | 0 | uidna_close(mIDNA); |
150 | 0 | } |
151 | | |
152 | | nsresult |
153 | | nsIDNService::IDNA2008ToUnicode(const nsACString& input, nsAString& output) |
154 | 7.68k | { |
155 | 7.68k | NS_ConvertUTF8toUTF16 inputStr(input); |
156 | 7.68k | UIDNAInfo info = UIDNA_INFO_INITIALIZER; |
157 | 7.68k | UErrorCode errorCode = U_ZERO_ERROR; |
158 | 7.68k | int32_t inLen = inputStr.Length(); |
159 | 7.68k | int32_t outMaxLen = kMaxDNSNodeLen + 1; |
160 | 7.68k | UChar outputBuffer[kMaxDNSNodeLen + 1]; |
161 | 7.68k | |
162 | 7.68k | int32_t outLen = uidna_labelToUnicode(mIDNA, (const UChar*)inputStr.get(), |
163 | 7.68k | inLen, outputBuffer, outMaxLen, |
164 | 7.68k | &info, &errorCode); |
165 | 7.68k | if (info.errors != 0) { |
166 | 1.24k | return NS_ERROR_MALFORMED_URI; |
167 | 1.24k | } |
168 | 6.44k | |
169 | 6.44k | if (U_SUCCESS(errorCode)) { |
170 | 6.44k | ICUUtils::AssignUCharArrayToString(outputBuffer, outLen, output); |
171 | 6.44k | } |
172 | 6.44k | |
173 | 6.44k | nsresult rv = ICUUtils::UErrorToNsResult(errorCode); |
174 | 6.44k | if (rv == NS_ERROR_FAILURE) { |
175 | 0 | rv = NS_ERROR_MALFORMED_URI; |
176 | 0 | } |
177 | 6.44k | return rv; |
178 | 6.44k | } |
179 | | |
180 | | nsresult |
181 | | nsIDNService::IDNA2008StringPrep(const nsAString& input, |
182 | | nsAString& output, |
183 | | stringPrepFlag flag) |
184 | 81.9k | { |
185 | 81.9k | UIDNAInfo info = UIDNA_INFO_INITIALIZER; |
186 | 81.9k | UErrorCode errorCode = U_ZERO_ERROR; |
187 | 81.9k | int32_t inLen = input.Length(); |
188 | 81.9k | int32_t outMaxLen = kMaxDNSNodeLen + 1; |
189 | 81.9k | UChar outputBuffer[kMaxDNSNodeLen + 1]; |
190 | 81.9k | |
191 | 81.9k | int32_t outLen = |
192 | 81.9k | uidna_labelToUnicode(mIDNA, (const UChar*)PromiseFlatString(input).get(), |
193 | 81.9k | inLen, outputBuffer, outMaxLen, &info, &errorCode); |
194 | 81.9k | nsresult rv = ICUUtils::UErrorToNsResult(errorCode); |
195 | 81.9k | if (rv == NS_ERROR_FAILURE) { |
196 | 2.40k | rv = NS_ERROR_MALFORMED_URI; |
197 | 2.40k | } |
198 | 81.9k | NS_ENSURE_SUCCESS(rv, rv); |
199 | 81.9k | |
200 | 81.9k | // Output the result of nameToUnicode even if there were errors. |
201 | 81.9k | // But in the case of invalid punycode, the uidna_labelToUnicode result |
202 | 81.9k | // appears to get an appended U+FFFD REPLACEMENT CHARACTER, which will |
203 | 81.9k | // confuse our subsequent processing, so we drop that. |
204 | 81.9k | // (https://bugzilla.mozilla.org/show_bug.cgi?id=1399540#c9) |
205 | 81.9k | if ((info.errors & UIDNA_ERROR_PUNYCODE) && |
206 | 79.5k | outLen > 0 && outputBuffer[outLen - 1] == 0xfffd) { |
207 | 0 | --outLen; |
208 | 0 | } |
209 | 79.5k | ICUUtils::AssignUCharArrayToString(outputBuffer, outLen, output); |
210 | 79.5k | |
211 | 79.5k | if (flag == eStringPrepIgnoreErrors) { |
212 | 34.0k | return NS_OK; |
213 | 34.0k | } |
214 | 45.4k | |
215 | 45.4k | if (info.errors != 0) { |
216 | 9.78k | if (flag == eStringPrepForDNS) { |
217 | 0 | output.Truncate(); |
218 | 0 | } |
219 | 9.78k | rv = NS_ERROR_MALFORMED_URI; |
220 | 9.78k | } |
221 | 45.4k | |
222 | 45.4k | return rv; |
223 | 45.4k | } |
224 | | |
225 | | NS_IMETHODIMP nsIDNService::ConvertUTF8toACE(const nsACString & input, nsACString & ace) |
226 | 5.26k | { |
227 | 5.26k | return UTF8toACE(input, ace, eStringPrepForDNS); |
228 | 5.26k | } |
229 | | |
230 | | nsresult nsIDNService::UTF8toACE(const nsACString & input, nsACString & ace, |
231 | | stringPrepFlag flag) |
232 | 29.1k | { |
233 | 29.1k | nsresult rv; |
234 | 29.1k | NS_ConvertUTF8toUTF16 ustr(input); |
235 | 29.1k | |
236 | 29.1k | // map ideographic period to ASCII period etc. |
237 | 29.1k | normalizeFullStops(ustr); |
238 | 29.1k | |
239 | 29.1k | uint32_t len, offset; |
240 | 29.1k | len = 0; |
241 | 29.1k | offset = 0; |
242 | 29.1k | nsAutoCString encodedBuf; |
243 | 29.1k | |
244 | 29.1k | nsAString::const_iterator start, end; |
245 | 29.1k | ustr.BeginReading(start); |
246 | 29.1k | ustr.EndReading(end); |
247 | 29.1k | ace.Truncate(); |
248 | 29.1k | |
249 | 29.1k | // encode nodes if non ASCII |
250 | 645k | while (start != end) { |
251 | 616k | len++; |
252 | 616k | if (*start++ == (char16_t)'.') { |
253 | 28.1k | rv = stringPrepAndACE(Substring(ustr, offset, len - 1), encodedBuf, flag); |
254 | 28.1k | NS_ENSURE_SUCCESS(rv, rv); |
255 | 28.1k | |
256 | 28.1k | ace.Append(encodedBuf); |
257 | 27.8k | ace.Append('.'); |
258 | 27.8k | offset += len; |
259 | 27.8k | len = 0; |
260 | 27.8k | } |
261 | 616k | } |
262 | 29.1k | |
263 | 29.1k | // encode the last node if non ASCII |
264 | 29.1k | if (len) { |
265 | 27.6k | rv = stringPrepAndACE(Substring(ustr, offset, len), encodedBuf, flag); |
266 | 27.6k | NS_ENSURE_SUCCESS(rv, rv); |
267 | 27.6k | |
268 | 27.6k | ace.Append(encodedBuf); |
269 | 25.2k | } |
270 | 28.8k | |
271 | 28.8k | return NS_OK; |
272 | 28.8k | } |
273 | | |
274 | | NS_IMETHODIMP nsIDNService::ConvertACEtoUTF8(const nsACString & input, nsACString & _retval) |
275 | 0 | { |
276 | 0 | return ACEtoUTF8(input, _retval, eStringPrepForDNS); |
277 | 0 | } |
278 | | |
279 | | nsresult nsIDNService::ACEtoUTF8(const nsACString & input, nsACString & _retval, |
280 | | stringPrepFlag flag) |
281 | 1.49k | { |
282 | 1.49k | // RFC 3490 - 4.2 ToUnicode |
283 | 1.49k | // ToUnicode never fails. If any step fails, then the original input |
284 | 1.49k | // sequence is returned immediately in that step. |
285 | 1.49k | // |
286 | 1.49k | // Note that this refers to the decoding of a single label. |
287 | 1.49k | // ACEtoUTF8 may be called with a sequence of labels separated by dots; |
288 | 1.49k | // this test applies individually to each label. |
289 | 1.49k | |
290 | 1.49k | uint32_t len = 0, offset = 0; |
291 | 1.49k | nsAutoCString decodedBuf; |
292 | 1.49k | |
293 | 1.49k | nsACString::const_iterator start, end; |
294 | 1.49k | input.BeginReading(start); |
295 | 1.49k | input.EndReading(end); |
296 | 1.49k | _retval.Truncate(); |
297 | 1.49k | |
298 | 1.49k | // loop and decode nodes |
299 | 110k | while (start != end) { |
300 | 108k | len++; |
301 | 108k | if (*start++ == '.') { |
302 | 8.79k | nsDependentCSubstring origLabel(input, offset, len - 1); |
303 | 8.79k | if (NS_FAILED(decodeACE(origLabel, decodedBuf, flag))) { |
304 | 399 | // If decoding failed, use the original input sequence |
305 | 399 | // for this label. |
306 | 399 | _retval.Append(origLabel); |
307 | 8.39k | } else { |
308 | 8.39k | _retval.Append(decodedBuf); |
309 | 8.39k | } |
310 | 8.79k | |
311 | 8.79k | _retval.Append('.'); |
312 | 8.79k | offset += len; |
313 | 8.79k | len = 0; |
314 | 8.79k | } |
315 | 108k | } |
316 | 1.49k | // decode the last node |
317 | 1.49k | if (len) { |
318 | 1.39k | nsDependentCSubstring origLabel(input, offset, len); |
319 | 1.39k | if (NS_FAILED(decodeACE(origLabel, decodedBuf, flag))) { |
320 | 843 | _retval.Append(origLabel); |
321 | 843 | } else { |
322 | 549 | _retval.Append(decodedBuf); |
323 | 549 | } |
324 | 1.39k | } |
325 | 1.49k | |
326 | 1.49k | return NS_OK; |
327 | 1.49k | } |
328 | | |
329 | | NS_IMETHODIMP nsIDNService::IsACE(const nsACString & input, bool *_retval) |
330 | 1.09M | { |
331 | 1.09M | const char *data = input.BeginReading(); |
332 | 1.09M | uint32_t dataLen = input.Length(); |
333 | 1.09M | |
334 | 1.09M | // look for the ACE prefix in the input string. it may occur |
335 | 1.09M | // at the beginning of any segment in the domain name. for |
336 | 1.09M | // example: "www.xn--ENCODED.com" |
337 | 1.09M | |
338 | 1.09M | const char *p = PL_strncasestr(data, kACEPrefix, dataLen); |
339 | 1.09M | |
340 | 1.09M | *_retval = p && (p == data || *(p - 1) == '.'); |
341 | 1.09M | return NS_OK; |
342 | 1.09M | } |
343 | | |
344 | | NS_IMETHODIMP nsIDNService::Normalize(const nsACString & input, |
345 | | nsACString & output) |
346 | 24.2k | { |
347 | 24.2k | // protect against bogus input |
348 | 24.2k | NS_ENSURE_TRUE(IsUTF8(input), NS_ERROR_UNEXPECTED); |
349 | 24.2k | |
350 | 24.2k | NS_ConvertUTF8toUTF16 inUTF16(input); |
351 | 22.6k | normalizeFullStops(inUTF16); |
352 | 22.6k | |
353 | 22.6k | // pass the domain name to stringprep label by label |
354 | 22.6k | nsAutoString outUTF16, outLabel; |
355 | 22.6k | |
356 | 22.6k | uint32_t len = 0, offset = 0; |
357 | 22.6k | nsresult rv; |
358 | 22.6k | nsAString::const_iterator start, end; |
359 | 22.6k | inUTF16.BeginReading(start); |
360 | 22.6k | inUTF16.EndReading(end); |
361 | 22.6k | |
362 | 694k | while (start != end) { |
363 | 671k | len++; |
364 | 671k | if (*start++ == char16_t('.')) { |
365 | 14.4k | rv = stringPrep(Substring(inUTF16, offset, len - 1), outLabel, |
366 | 14.4k | eStringPrepIgnoreErrors); |
367 | 14.4k | NS_ENSURE_SUCCESS(rv, rv); |
368 | 14.4k | |
369 | 14.4k | outUTF16.Append(outLabel); |
370 | 14.4k | outUTF16.Append(char16_t('.')); |
371 | 14.4k | offset += len; |
372 | 14.4k | len = 0; |
373 | 14.4k | } |
374 | 671k | } |
375 | 22.6k | if (len) { |
376 | 21.7k | rv = stringPrep(Substring(inUTF16, offset, len), outLabel, |
377 | 21.7k | eStringPrepIgnoreErrors); |
378 | 21.7k | NS_ENSURE_SUCCESS(rv, rv); |
379 | 21.7k | |
380 | 21.7k | outUTF16.Append(outLabel); |
381 | 19.3k | } |
382 | 22.6k | |
383 | 22.6k | CopyUTF16toUTF8(outUTF16, output); |
384 | 20.2k | return NS_OK; |
385 | 22.6k | } |
386 | | |
387 | | namespace { |
388 | | |
389 | | class MOZ_STACK_CLASS MutexSettableAutoUnlock final |
390 | | { |
391 | | Mutex* mMutex; |
392 | | public: |
393 | | MutexSettableAutoUnlock() |
394 | | : mMutex(nullptr) |
395 | 1.08M | { } |
396 | | |
397 | | void |
398 | | Acquire(mozilla::Mutex& aMutex) |
399 | 0 | { |
400 | 0 | MOZ_ASSERT(!mMutex); |
401 | 0 | mMutex = &aMutex; |
402 | 0 | mMutex->Lock(); |
403 | 0 | } |
404 | | |
405 | | ~MutexSettableAutoUnlock() |
406 | 1.08M | { |
407 | 1.08M | if (mMutex) { |
408 | 0 | mMutex->Unlock(); |
409 | 0 | } |
410 | 1.08M | } |
411 | | }; |
412 | | |
413 | | } // anonymous namespace |
414 | | |
415 | | NS_IMETHODIMP nsIDNService::ConvertToDisplayIDN(const nsACString & input, bool * _isASCII, nsACString & _retval) |
416 | 1.08M | { |
417 | 1.08M | MutexSettableAutoUnlock lock; |
418 | 1.08M | if (!NS_IsMainThread()) { |
419 | 0 | lock.Acquire(mLock); |
420 | 0 | } |
421 | 1.08M | |
422 | 1.08M | // If host is ACE, then convert to UTF-8 if the host is in the IDN whitelist. |
423 | 1.08M | // Else, if host is already UTF-8, then make sure it is normalized per IDN. |
424 | 1.08M | |
425 | 1.08M | nsresult rv = NS_OK; |
426 | 1.08M | |
427 | 1.08M | // Even if the hostname is not ASCII, individual labels may still be ACE, so |
428 | 1.08M | // test IsACE before testing IsASCII |
429 | 1.08M | bool isACE; |
430 | 1.08M | IsACE(input, &isACE); |
431 | 1.08M | |
432 | 1.08M | if (IsASCII(input)) { |
433 | 1.06M | // first, canonicalize the host to lowercase, for whitelist lookup |
434 | 1.06M | _retval = input; |
435 | 1.06M | ToLowerCase(_retval); |
436 | 1.06M | |
437 | 1.06M | if (isACE && !mShowPunycode) { |
438 | 936 | // ACEtoUTF8() can't fail, but might return the original ACE string |
439 | 936 | nsAutoCString temp(_retval); |
440 | 936 | // If the domain is in the whitelist, return the host in UTF-8. |
441 | 936 | // Otherwise convert from ACE to UTF8 only those labels which are |
442 | 936 | // considered safe for display |
443 | 936 | ACEtoUTF8(temp, _retval, isInWhitelist(temp) ? |
444 | 936 | eStringPrepIgnoreErrors : eStringPrepForUI); |
445 | 936 | *_isASCII = IsASCII(_retval); |
446 | 1.06M | } else { |
447 | 1.06M | *_isASCII = true; |
448 | 1.06M | } |
449 | 1.06M | } else { |
450 | 24.2k | // We have to normalize the hostname before testing against the domain |
451 | 24.2k | // whitelist (see bug 315411), and to ensure the entire string gets |
452 | 24.2k | // normalized. |
453 | 24.2k | // |
454 | 24.2k | // Normalization and the tests for safe display below, assume that the |
455 | 24.2k | // input is Unicode, so first convert any ACE labels to UTF8 |
456 | 24.2k | if (isACE) { |
457 | 561 | nsAutoCString temp; |
458 | 561 | ACEtoUTF8(input, temp, eStringPrepIgnoreErrors); |
459 | 561 | rv = Normalize(temp, _retval); |
460 | 23.7k | } else { |
461 | 23.7k | rv = Normalize(input, _retval); |
462 | 23.7k | } |
463 | 24.2k | if (NS_FAILED(rv)) return rv; |
464 | 20.2k | |
465 | 20.2k | if (mShowPunycode && NS_SUCCEEDED(UTF8toACE(_retval, _retval, |
466 | 20.2k | eStringPrepIgnoreErrors))) { |
467 | 0 | *_isASCII = true; |
468 | 0 | return NS_OK; |
469 | 0 | } |
470 | 20.2k | |
471 | 20.2k | // normalization could result in an ASCII-only hostname. alternatively, if |
472 | 20.2k | // the host is converted to ACE by the normalizer, then the host may contain |
473 | 20.2k | // unsafe characters, so leave it ACE encoded. see bug 283016, bug 301694, and bug 309311. |
474 | 20.2k | *_isASCII = IsASCII(_retval); |
475 | 20.2k | if (!*_isASCII && !isInWhitelist(_retval)) { |
476 | 18.7k | // UTF8toACE with eStringPrepForUI may return a domain name where |
477 | 18.7k | // some labels are in UTF-8 and some are in ACE, depending on |
478 | 18.7k | // whether they are considered safe for display |
479 | 18.7k | rv = UTF8toACE(_retval, _retval, eStringPrepForUI); |
480 | 18.7k | *_isASCII = IsASCII(_retval); |
481 | 18.7k | return rv; |
482 | 18.7k | } |
483 | 1.06M | } |
484 | 1.06M | |
485 | 1.06M | return NS_OK; |
486 | 1.06M | } |
487 | | |
488 | | //----------------------------------------------------------------------------- |
489 | | |
490 | | static nsresult utf16ToUcs4(const nsAString& in, |
491 | | uint32_t *out, |
492 | | uint32_t outBufLen, |
493 | | uint32_t *outLen) |
494 | 30.8k | { |
495 | 30.8k | uint32_t i = 0; |
496 | 30.8k | nsAString::const_iterator start, end; |
497 | 30.8k | in.BeginReading(start); |
498 | 30.8k | in.EndReading(end); |
499 | 30.8k | |
500 | 447k | while (start != end) { |
501 | 416k | char16_t curChar; |
502 | 416k | |
503 | 416k | curChar= *start++; |
504 | 416k | |
505 | 416k | if (start != end && |
506 | 416k | NS_IS_HIGH_SURROGATE(curChar) && |
507 | 416k | NS_IS_LOW_SURROGATE(*start)) { |
508 | 0 | out[i] = SURROGATE_TO_UCS4(curChar, *start); |
509 | 0 | ++start; |
510 | 0 | } |
511 | 416k | else |
512 | 416k | out[i] = curChar; |
513 | 416k | |
514 | 416k | i++; |
515 | 416k | if (i >= outBufLen) |
516 | 592 | return NS_ERROR_MALFORMED_URI; |
517 | 416k | } |
518 | 30.8k | out[i] = (uint32_t)'\0'; |
519 | 30.2k | *outLen = i; |
520 | 30.2k | return NS_OK; |
521 | 30.8k | } |
522 | | |
523 | | static nsresult punycode(const nsAString& in, nsACString& out) |
524 | 30.8k | { |
525 | 30.8k | uint32_t ucs4Buf[kMaxDNSNodeLen + 1]; |
526 | 30.8k | uint32_t ucs4Len = 0u; |
527 | 30.8k | nsresult rv = utf16ToUcs4(in, ucs4Buf, kMaxDNSNodeLen, &ucs4Len); |
528 | 30.8k | NS_ENSURE_SUCCESS(rv, rv); |
529 | 30.8k | |
530 | 30.8k | // need maximum 20 bits to encode 16 bit Unicode character |
531 | 30.8k | // (include null terminator) |
532 | 30.8k | const uint32_t kEncodedBufSize = kMaxDNSNodeLen * 20 / 8 + 1 + 1; |
533 | 30.2k | char encodedBuf[kEncodedBufSize]; |
534 | 30.2k | punycode_uint encodedLength = kEncodedBufSize; |
535 | 30.2k | |
536 | 30.2k | enum punycode_status status = punycode_encode(ucs4Len, |
537 | 30.2k | ucs4Buf, |
538 | 30.2k | nullptr, |
539 | 30.2k | &encodedLength, |
540 | 30.2k | encodedBuf); |
541 | 30.2k | |
542 | 30.2k | if (punycode_success != status || |
543 | 30.2k | encodedLength >= kEncodedBufSize) |
544 | 0 | return NS_ERROR_MALFORMED_URI; |
545 | 30.2k | |
546 | 30.2k | encodedBuf[encodedLength] = '\0'; |
547 | 30.2k | out.Assign(nsDependentCString(kACEPrefix) + nsDependentCString(encodedBuf)); |
548 | 30.2k | |
549 | 30.2k | return rv; |
550 | 30.2k | } |
551 | | |
552 | | // RFC 3454 |
553 | | // |
554 | | // 1) Map -- For each character in the input, check if it has a mapping |
555 | | // and, if so, replace it with its mapping. This is described in section 3. |
556 | | // |
557 | | // 2) Normalize -- Possibly normalize the result of step 1 using Unicode |
558 | | // normalization. This is described in section 4. |
559 | | // |
560 | | // 3) Prohibit -- Check for any characters that are not allowed in the |
561 | | // output. If any are found, return an error. This is described in section |
562 | | // 5. |
563 | | // |
564 | | // 4) Check bidi -- Possibly check for right-to-left characters, and if any |
565 | | // are found, make sure that the whole string satisfies the requirements |
566 | | // for bidirectional strings. If the string does not satisfy the requirements |
567 | | // for bidirectional strings, return an error. This is described in section 6. |
568 | | // |
569 | | // 5) Check unassigned code points -- If allowUnassigned is false, check for |
570 | | // any unassigned Unicode points and if any are found return an error. |
571 | | // This is described in section 7. |
572 | | // |
573 | | nsresult nsIDNService::stringPrep(const nsAString& in, nsAString& out, |
574 | | stringPrepFlag flag) |
575 | 81.9k | { |
576 | 81.9k | return IDNA2008StringPrep(in, out, flag); |
577 | 81.9k | } |
578 | | |
579 | | nsresult nsIDNService::stringPrepAndACE(const nsAString& in, nsACString& out, |
580 | | stringPrepFlag flag) |
581 | 55.7k | { |
582 | 55.7k | nsresult rv = NS_OK; |
583 | 55.7k | |
584 | 55.7k | out.Truncate(); |
585 | 55.7k | |
586 | 55.7k | if (in.Length() > kMaxDNSNodeLen) { |
587 | 828 | NS_WARNING("IDN node too large"); |
588 | 828 | return NS_ERROR_MALFORMED_URI; |
589 | 828 | } |
590 | 54.9k | |
591 | 54.9k | if (IsASCII(in)) { |
592 | 9.17k | LossyCopyUTF16toASCII(in, out); |
593 | 9.17k | return NS_OK; |
594 | 9.17k | } |
595 | 45.7k | |
596 | 45.7k | nsAutoString strPrep; |
597 | 45.7k | rv = stringPrep(in, strPrep, flag); |
598 | 45.7k | if (flag == eStringPrepForDNS) { |
599 | 14.9k | NS_ENSURE_SUCCESS(rv, rv); |
600 | 14.9k | } |
601 | 45.7k | |
602 | 45.7k | if (IsASCII(strPrep)) { |
603 | 0 | LossyCopyUTF16toASCII(strPrep, out); |
604 | 0 | return NS_OK; |
605 | 0 | } |
606 | 45.7k | |
607 | 45.7k | if (flag == eStringPrepForUI && NS_SUCCEEDED(rv) && isLabelSafe(in)) { |
608 | 14.9k | CopyUTF16toUTF8(strPrep, out); |
609 | 14.9k | return NS_OK; |
610 | 14.9k | } |
611 | 30.8k | |
612 | 30.8k | rv = punycode(strPrep, out); |
613 | 30.8k | // Check that the encoded output isn't larger than the maximum length |
614 | 30.8k | // of a DNS node per RFC 1034. |
615 | 30.8k | // This test isn't necessary in the code paths above where the input |
616 | 30.8k | // is ASCII (since the output will be the same length as the input) or |
617 | 30.8k | // where we convert to UTF-8 (since the output is only used for |
618 | 30.8k | // display in the UI and not passed to DNS and can legitimately be |
619 | 30.8k | // longer than the limit). |
620 | 30.8k | if (out.Length() > kMaxDNSNodeLen) { |
621 | 1.26k | NS_WARNING("IDN node too large"); |
622 | 1.26k | return NS_ERROR_MALFORMED_URI; |
623 | 1.26k | } |
624 | 29.5k | |
625 | 29.5k | return rv; |
626 | 29.5k | } |
627 | | |
628 | | // RFC 3490 |
629 | | // 1) Whenever dots are used as label separators, the following characters |
630 | | // MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full |
631 | | // stop), U+FF0E (fullwidth full stop), U+FF61 (halfwidth ideographic full |
632 | | // stop). |
633 | | |
634 | | void nsIDNService::normalizeFullStops(nsAString& s) |
635 | 51.7k | { |
636 | 51.7k | nsAString::const_iterator start, end; |
637 | 51.7k | s.BeginReading(start); |
638 | 51.7k | s.EndReading(end); |
639 | 51.7k | int32_t index = 0; |
640 | 51.7k | |
641 | 1.35M | while (start != end) { |
642 | 1.30M | switch (*start) { |
643 | 1.30M | case 0x3002: |
644 | 0 | case 0xFF0E: |
645 | 0 | case 0xFF61: |
646 | 0 | s.ReplaceLiteral(index, 1, u"."); |
647 | 0 | break; |
648 | 1.30M | default: |
649 | 1.30M | break; |
650 | 1.30M | } |
651 | 1.30M | start++; |
652 | 1.30M | index++; |
653 | 1.30M | } |
654 | 51.7k | } |
655 | | |
656 | | nsresult nsIDNService::decodeACE(const nsACString& in, nsACString& out, |
657 | | stringPrepFlag flag) |
658 | 10.1k | { |
659 | 10.1k | bool isAce; |
660 | 10.1k | IsACE(in, &isAce); |
661 | 10.1k | if (!isAce) { |
662 | 2.49k | out.Assign(in); |
663 | 2.49k | return NS_OK; |
664 | 2.49k | } |
665 | 7.68k | |
666 | 7.68k | nsAutoString utf16; |
667 | 7.68k | nsresult result = IDNA2008ToUnicode(in, utf16); |
668 | 7.68k | NS_ENSURE_SUCCESS(result, result); |
669 | 7.68k | |
670 | 7.68k | if (flag != eStringPrepForUI || isLabelSafe(utf16)) { |
671 | 5.08k | CopyUTF16toUTF8(utf16, out); |
672 | 5.08k | } else { |
673 | 1.35k | out.Assign(in); |
674 | 1.35k | return NS_OK; |
675 | 1.35k | } |
676 | 5.08k | |
677 | 5.08k | // Validation: encode back to ACE and compare the strings |
678 | 5.08k | nsAutoCString ace; |
679 | 5.08k | nsresult rv = UTF8toACE(out, ace, flag); |
680 | 5.08k | NS_ENSURE_SUCCESS(rv, rv); |
681 | 5.08k | |
682 | 5.08k | if (flag == eStringPrepForDNS && |
683 | 5.08k | !ace.Equals(in, nsCaseInsensitiveCStringComparator())) { |
684 | 0 | return NS_ERROR_MALFORMED_URI; |
685 | 0 | } |
686 | 5.08k | |
687 | 5.08k | return NS_OK; |
688 | 5.08k | } |
689 | | |
690 | | bool nsIDNService::isInWhitelist(const nsACString &host) |
691 | 19.7k | { |
692 | 19.7k | if (!NS_IsMainThread()) { |
693 | 0 | mLock.AssertCurrentThreadOwns(); |
694 | 0 | } |
695 | 19.7k | |
696 | 19.7k | if (mIDNUseWhitelist && mIDNWhitelistPrefBranch) { |
697 | 0 | nsAutoCString tld(host); |
698 | 0 | // make sure the host is ACE for lookup and check that there are no |
699 | 0 | // unassigned codepoints |
700 | 0 | if (!IsASCII(tld) && NS_FAILED(UTF8toACE(tld, tld, eStringPrepForDNS))) { |
701 | 0 | return false; |
702 | 0 | } |
703 | 0 | |
704 | 0 | // truncate trailing dots first |
705 | 0 | tld.Trim("."); |
706 | 0 | int32_t pos = tld.RFind("."); |
707 | 0 | if (pos == kNotFound) |
708 | 0 | return false; |
709 | 0 | |
710 | 0 | tld.Cut(0, pos + 1); |
711 | 0 |
|
712 | 0 | bool safe; |
713 | 0 | if (NS_SUCCEEDED(mIDNWhitelistPrefBranch->GetBoolPref(tld.get(), &safe))) |
714 | 0 | return safe; |
715 | 19.7k | } |
716 | 19.7k | |
717 | 19.7k | return false; |
718 | 19.7k | } |
719 | | |
720 | | bool nsIDNService::isLabelSafe(const nsAString &label) |
721 | 26.8k | { |
722 | 26.8k | if (!NS_IsMainThread()) { |
723 | 0 | mLock.AssertCurrentThreadOwns(); |
724 | 0 | } |
725 | 26.8k | |
726 | 26.8k | if (!isOnlySafeChars(PromiseFlatString(label), mIDNBlacklist)) { |
727 | 3.21k | return false; |
728 | 3.21k | } |
729 | 23.6k | |
730 | 23.6k | // We should never get here if the label is ASCII |
731 | 23.6k | NS_ASSERTION(!IsASCII(label), "ASCII label in IDN checking"); |
732 | 23.6k | if (mRestrictionProfile == eASCIIOnlyProfile) { |
733 | 0 | return false; |
734 | 0 | } |
735 | 23.6k | |
736 | 23.6k | nsAString::const_iterator current, end; |
737 | 23.6k | label.BeginReading(current); |
738 | 23.6k | label.EndReading(end); |
739 | 23.6k | |
740 | 23.6k | Script lastScript = Script::INVALID; |
741 | 23.6k | uint32_t previousChar = 0; |
742 | 23.6k | uint32_t baseChar = 0; // last non-diacritic seen (base char for marks) |
743 | 23.6k | uint32_t savedNumberingSystem = 0; |
744 | 23.6k | // Simplified/Traditional Chinese check temporarily disabled -- bug 857481 |
745 | | #if 0 |
746 | | HanVariantType savedHanVariant = HVT_NotHan; |
747 | | #endif |
748 | | |
749 | 23.6k | int32_t savedScript = -1; |
750 | 23.6k | |
751 | 108k | while (current != end) { |
752 | 88.5k | uint32_t ch = *current++; |
753 | 88.5k | |
754 | 88.5k | if (NS_IS_HIGH_SURROGATE(ch) && current != end && |
755 | 88.5k | NS_IS_LOW_SURROGATE(*current)) { |
756 | 34 | ch = SURROGATE_TO_UCS4(ch, *current++); |
757 | 34 | } |
758 | 88.5k | |
759 | 88.5k | IdentifierType idType = GetIdentifierType(ch); |
760 | 88.5k | if (idType == IDTYPE_RESTRICTED) { |
761 | 2.38k | return false; |
762 | 2.38k | } |
763 | 86.2k | MOZ_ASSERT(idType == IDTYPE_ALLOWED); |
764 | 86.2k | |
765 | 86.2k | // Check for mixed script |
766 | 86.2k | Script script = GetScriptCode(ch); |
767 | 86.2k | if (script != Script::COMMON && |
768 | 86.2k | script != Script::INHERITED && |
769 | 86.2k | script != lastScript) { |
770 | 23.7k | if (illegalScriptCombo(script, savedScript)) { |
771 | 1.18k | return false; |
772 | 1.18k | } |
773 | 85.0k | } |
774 | 85.0k | |
775 | 85.0k | // Check for mixed numbering systems |
776 | 85.0k | auto genCat = GetGeneralCategory(ch); |
777 | 85.0k | if (genCat == HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER) { |
778 | 17.0k | uint32_t zeroCharacter = ch - GetNumericValue(ch); |
779 | 17.0k | if (savedNumberingSystem == 0) { |
780 | 12.0k | // If we encounter a decimal number, save the zero character from that |
781 | 12.0k | // numbering system. |
782 | 12.0k | savedNumberingSystem = zeroCharacter; |
783 | 12.0k | } else if (zeroCharacter != savedNumberingSystem) { |
784 | 0 | return false; |
785 | 0 | } |
786 | 85.0k | } |
787 | 85.0k | |
788 | 85.0k | if (genCat == HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) { |
789 | 5.90k | // Check for consecutive non-spacing marks. |
790 | 5.90k | if (previousChar != 0 && previousChar == ch) { |
791 | 312 | return false; |
792 | 312 | } |
793 | 5.59k | // Check for marks whose expected script doesn't match the base script. |
794 | 5.59k | if (lastScript != Script::INVALID) { |
795 | 1.73k | const size_t kMaxScripts = 32; // more than ample for current values |
796 | 1.73k | // of ScriptExtensions property |
797 | 1.73k | UScriptCode scripts[kMaxScripts]; |
798 | 1.73k | UErrorCode errorCode = U_ZERO_ERROR; |
799 | 1.73k | int nScripts = uscript_getScriptExtensions(ch, scripts, kMaxScripts, |
800 | 1.73k | &errorCode); |
801 | 1.73k | MOZ_ASSERT(U_SUCCESS(errorCode), "uscript_getScriptExtensions failed"); |
802 | 1.73k | if (U_FAILURE(errorCode)) { |
803 | 0 | return false; |
804 | 0 | } |
805 | 1.73k | // nScripts will always be >= 1, because even for undefined characters |
806 | 1.73k | // uscript_getScriptExtensions will return Script::INVALID. |
807 | 1.73k | // If the mark just has script=COMMON or INHERITED, we can't check any |
808 | 1.73k | // more carefully, but if it has specific scriptExtension codes, then |
809 | 1.73k | // assume those are the only valid scripts to use it with. |
810 | 1.73k | if (nScripts > 1 || |
811 | 1.73k | (Script(scripts[0]) != Script::COMMON && |
812 | 1.73k | Script(scripts[0]) != Script::INHERITED)) { |
813 | 1.73k | while (--nScripts >= 0) { |
814 | 1.73k | if (Script(scripts[nScripts]) == lastScript) { |
815 | 1.73k | break; |
816 | 1.73k | } |
817 | 1.73k | } |
818 | 1.73k | if (nScripts == -1) { |
819 | 0 | return false; |
820 | 0 | } |
821 | 5.59k | } |
822 | 1.73k | } |
823 | 5.59k | // Check for diacritics on dotless-i, which would be indistinguishable |
824 | 5.59k | // from normal accented letter i. |
825 | 5.59k | if (baseChar == 0x0131 && |
826 | 5.59k | ((ch >= 0x0300 && ch <= 0x0314) || ch == 0x031a)) { |
827 | 0 | return false; |
828 | 0 | } |
829 | 79.1k | } else { |
830 | 79.1k | baseChar = ch; |
831 | 79.1k | } |
832 | 85.0k | |
833 | 85.0k | if (script != Script::COMMON && script != Script::INHERITED) { |
834 | 66.1k | lastScript = script; |
835 | 66.1k | } |
836 | 84.7k | |
837 | 84.7k | // Simplified/Traditional Chinese check temporarily disabled -- bug 857481 |
838 | | #if 0 |
839 | | |
840 | | // Check for both simplified-only and traditional-only Chinese characters |
841 | | HanVariantType hanVariant = GetHanVariant(ch); |
842 | | if (hanVariant == HVT_SimplifiedOnly || hanVariant == HVT_TraditionalOnly) { |
843 | | if (savedHanVariant == HVT_NotHan) { |
844 | | savedHanVariant = hanVariant; |
845 | | } else if (hanVariant != savedHanVariant) { |
846 | | return false; |
847 | | } |
848 | | } |
849 | | #endif |
850 | | |
851 | 84.7k | previousChar = ch; |
852 | 84.7k | } |
853 | 23.6k | return true; |
854 | 23.6k | } |
855 | | |
856 | | // Scripts that we care about in illegalScriptCombo |
857 | | static const Script scriptTable[] = { |
858 | | Script::BOPOMOFO, Script::CYRILLIC, Script::GREEK, |
859 | | Script::HANGUL, Script::HAN, Script::HIRAGANA, |
860 | | Script::KATAKANA, Script::LATIN }; |
861 | | |
862 | | #define BOPO 0 |
863 | | #define CYRL 1 |
864 | | #define GREK 2 |
865 | | #define HANG 3 |
866 | | #define HANI 4 |
867 | | #define HIRA 5 |
868 | | #define KATA 6 |
869 | | #define LATN 7 |
870 | 6.32k | #define OTHR 8 |
871 | | #define JPAN 9 // Latin + Han + Hiragana + Katakana |
872 | | #define CHNA 10 // Latin + Han + Bopomofo |
873 | | #define KORE 11 // Latin + Han + Hangul |
874 | | #define HNLT 12 // Latin + Han (could be any of the above combinations) |
875 | 304 | #define FAIL 13 |
876 | | |
877 | | static inline int32_t findScriptIndex(Script aScript) |
878 | 23.7k | { |
879 | 23.7k | int32_t tableLength = mozilla::ArrayLength(scriptTable); |
880 | 190k | for (int32_t index = 0; index < tableLength; ++index) { |
881 | 186k | if (aScript == scriptTable[index]) { |
882 | 19.7k | return index; |
883 | 19.7k | } |
884 | 186k | } |
885 | 23.7k | return OTHR; |
886 | 23.7k | } |
887 | | |
888 | | static const int32_t scriptComboTable[13][9] = { |
889 | | /* thisScript: BOPO CYRL GREK HANG HANI HIRA KATA LATN OTHR |
890 | | * savedScript */ |
891 | | /* BOPO */ { BOPO, FAIL, FAIL, FAIL, CHNA, FAIL, FAIL, CHNA, FAIL }, |
892 | | /* CYRL */ { FAIL, CYRL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL }, |
893 | | /* GREK */ { FAIL, FAIL, GREK, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL }, |
894 | | /* HANG */ { FAIL, FAIL, FAIL, HANG, KORE, FAIL, FAIL, KORE, FAIL }, |
895 | | /* HANI */ { CHNA, FAIL, FAIL, KORE, HANI, JPAN, JPAN, HNLT, FAIL }, |
896 | | /* HIRA */ { FAIL, FAIL, FAIL, FAIL, JPAN, HIRA, JPAN, JPAN, FAIL }, |
897 | | /* KATA */ { FAIL, FAIL, FAIL, FAIL, JPAN, JPAN, KATA, JPAN, FAIL }, |
898 | | /* LATN */ { CHNA, FAIL, FAIL, KORE, HNLT, JPAN, JPAN, LATN, OTHR }, |
899 | | /* OTHR */ { FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, OTHR, FAIL }, |
900 | | /* JPAN */ { FAIL, FAIL, FAIL, FAIL, JPAN, JPAN, JPAN, JPAN, FAIL }, |
901 | | /* CHNA */ { CHNA, FAIL, FAIL, FAIL, CHNA, FAIL, FAIL, CHNA, FAIL }, |
902 | | /* KORE */ { FAIL, FAIL, FAIL, KORE, KORE, FAIL, FAIL, KORE, FAIL }, |
903 | | /* HNLT */ { CHNA, FAIL, FAIL, KORE, HNLT, JPAN, JPAN, HNLT, FAIL } |
904 | | }; |
905 | | |
906 | | bool nsIDNService::illegalScriptCombo(Script script, int32_t& savedScript) |
907 | 23.7k | { |
908 | 23.7k | if (!NS_IsMainThread()) { |
909 | 0 | mLock.AssertCurrentThreadOwns(); |
910 | 0 | } |
911 | 23.7k | |
912 | 23.7k | if (savedScript == -1) { |
913 | 22.5k | savedScript = findScriptIndex(script); |
914 | 22.5k | return false; |
915 | 22.5k | } |
916 | 1.19k | |
917 | 1.19k | savedScript = scriptComboTable[savedScript] [findScriptIndex(script)]; |
918 | 1.19k | /* |
919 | 1.19k | * Special case combinations that depend on which profile is in use |
920 | 1.19k | * In the Highly Restrictive profile Latin is not allowed with any |
921 | 1.19k | * other script |
922 | 1.19k | * |
923 | 1.19k | * In the Moderately Restrictive profile Latin mixed with any other |
924 | 1.19k | * single script is allowed. |
925 | 1.19k | */ |
926 | 1.19k | return ((savedScript == OTHR && |
927 | 1.19k | mRestrictionProfile == eHighlyRestrictiveProfile) || |
928 | 1.19k | savedScript == FAIL); |
929 | 1.19k | } |
930 | | |
931 | | #undef BOPO |
932 | | #undef CYRL |
933 | | #undef GREK |
934 | | #undef HANG |
935 | | #undef HANI |
936 | | #undef HIRA |
937 | | #undef KATA |
938 | | #undef LATN |
939 | | #undef OTHR |
940 | | #undef JPAN |
941 | | #undef CHNA |
942 | | #undef KORE |
943 | | #undef HNLT |
944 | | #undef FAIL |