Coverage Report

Created: 2018-09-25 14:53

/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