Bug 1502097 - (Part 2) Define IDN blocklist as ranges of characters [ {firstChar, lastChar}* ] r=jfkthame,dragana
authorValentin Gosu <valentin.gosu@gmail.com>
Sat, 24 Nov 2018 12:04:34 +0000
changeset 507183 6040483f1f0aa53d3049ce30209328eb0e2c27f2
parent 507182 ed995224a05aa692d3fc09b9ec82351894672c73
child 507184 326ed3b7c89498850b2830fbaa18b850841ecaec
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjfkthame, dragana
bugs1502097
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1502097 - (Part 2) Define IDN blocklist as ranges of characters [ {firstChar, lastChar}* ] r=jfkthame,dragana * Changes the format of the blocklist from a list of characters to a list of character ranges. Binary search still works, and it is easier to include large ranges of characters in the blocklist. * Moves logic for handling the blocklist to IDNBlocklistUtils.h/.cpp * Changes NS_EscapeURL to take a function that determines if a character is blocked. This way the type of the array doesn't matter. Differential Revision: https://phabricator.services.mozilla.com/D12210
intl/uconv/nsTextToSubURI.cpp
intl/uconv/nsTextToSubURI.h
netwerk/dns/IDNBlocklistUtils.cpp
netwerk/dns/IDNBlocklistUtils.h
netwerk/dns/IDNCharacterBlocklist.inc
netwerk/dns/moz.build
netwerk/dns/nsIDNService.cpp
netwerk/dns/nsIDNService.h
xpcom/io/nsEscape.cpp
xpcom/io/nsEscape.h
--- a/intl/uconv/nsTextToSubURI.cpp
+++ b/intl/uconv/nsTextToSubURI.cpp
@@ -9,20 +9,16 @@
 #include "nsCRT.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Encoding.h"
 #include "mozilla/Preferences.h"
 #include "nsISupportsPrimitives.h"
 
 using namespace mozilla;
 
-static const char16_t sNetworkIDNBlocklistChars[] = {
-#include "../../netwerk/dns/IDNCharacterBlocklist.inc"
-};
-
 nsTextToSubURI::~nsTextToSubURI()
 {
 }
 
 NS_IMPL_ISUPPORTS(nsTextToSubURI, nsITextToSubURI)
 
 NS_IMETHODIMP
 nsTextToSubURI::ConvertAndEscape(const nsACString& aCharset,
@@ -112,60 +108,48 @@ nsTextToSubURI::convertURItoUnicode(cons
   }
   return encoding->DecodeWithoutBOMHandlingAndWithoutReplacement(aURI, aOut);
 }
 
 NS_IMETHODIMP  nsTextToSubURI::UnEscapeURIForUI(const nsACString & aCharset,
                                                 const nsACString &aURIFragment,
                                                 nsAString &_retval)
 {
-  nsresult rv;
   nsAutoCString unescapedSpec;
   // skip control octets (0x00 - 0x1f and 0x7f) when unescaping
   NS_UnescapeURL(PromiseFlatCString(aURIFragment),
                  esc_SkipControl | esc_AlwaysCopy, unescapedSpec);
 
   // in case of failure, return escaped URI
   // Test for != NS_OK rather than NS_FAILED, because incomplete multi-byte
   // sequences are also considered failure in this context
   if (convertURItoUnicode(
                 PromiseFlatCString(aCharset), unescapedSpec, _retval)
       != NS_OK) {
     // assume UTF-8 instead of ASCII  because hostname (IDN) may be in UTF-8
     CopyUTF8toUTF16(aURIFragment, _retval);
   }
 
   // If there are any characters that are unsafe for URIs, reescape those.
-  if (mUnsafeChars.IsEmpty()) {
-    mUnsafeChars.AppendElements(
-      sNetworkIDNBlocklistChars, ArrayLength(sNetworkIDNBlocklistChars));
-
-    nsAutoString extraAllowed;
-    Preferences::GetString("network.IDN.extra_allowed_chars",
-                           extraAllowed);
+  if (mIDNBlocklist.IsEmpty()) {
+    mozilla::net::InitializeBlocklist(mIDNBlocklist);
     // we allow SPACE and IDEOGRAPHIC SPACE in this method
-    extraAllowed.Append(u' ');
-    extraAllowed.Append(0x3000);
-    mUnsafeChars.RemoveElementsBy([&](char16_t c) {
-      return extraAllowed.FindChar(c, 0) != -1;
-    });
+    mozilla::net::RemoveCharFromBlocklist(u' ', mIDNBlocklist);
+    mozilla::net::RemoveCharFromBlocklist(0x3000, mIDNBlocklist);
+  }
 
-    nsAutoString extraBlocked;
-    rv = Preferences::GetString("network.IDN.extra_blocked_chars",
-                                extraBlocked);
-    if (NS_SUCCEEDED(rv) && !extraBlocked.IsEmpty()) {
-      mUnsafeChars.AppendElements(
-        static_cast<const char16_t*>(extraBlocked.Data()),
-        extraBlocked.Length());
-      mUnsafeChars.Sort();
-    }
-  }
+  MOZ_ASSERT(!mIDNBlocklist.IsEmpty());
   const nsPromiseFlatString& unescapedResult = PromiseFlatString(_retval);
   nsString reescapedSpec;
-  _retval = NS_EscapeURL(unescapedResult, mUnsafeChars, reescapedSpec);
+  _retval =
+    NS_EscapeURL(unescapedResult,
+                 [&](char16_t aChar) -> bool {
+                   return mozilla::net::CharInBlocklist(aChar, mIDNBlocklist);
+                 },
+                 reescapedSpec);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsTextToSubURI::UnEscapeNonAsciiURI(const nsACString& aCharset,
                                     const nsACString& aURIFragment,
                                     nsAString& _retval)
--- a/intl/uconv/nsTextToSubURI.h
+++ b/intl/uconv/nsTextToSubURI.h
@@ -4,28 +4,29 @@
 // version 2.0 (the "License"). You can obtain a copy of the License at
 // http://mozilla.org/MPL/2.0/.
 #ifndef nsTextToSubURI_h__
 #define nsTextToSubURI_h__
 
 #include "nsITextToSubURI.h"
 #include "nsString.h"
 #include "nsTArray.h"
+#include "mozilla/net/IDNBlocklistUtils.h"
 
 class nsTextToSubURI: public nsITextToSubURI
 {
   NS_DECL_ISUPPORTS
   NS_DECL_NSITEXTTOSUBURI
 
 private:
   virtual ~nsTextToSubURI();
 
   // We assume that the URI is encoded as UTF-8.
   nsresult convertURItoUnicode(const nsCString& aCharset,
                                const nsCString& aURI,
                                nsAString &_retval);
 
   // Characters defined in netwerk/dns/IDNCharacterBlocklist.inc or via the
   // network.IDN.extra_allowed_chars and network.IDN.extra_blocked_chars prefs.
-  nsTArray<char16_t> mUnsafeChars;
+  nsTArray<mozilla::net::BlocklistRange> mIDNBlocklist;
 };
 
 #endif // nsTextToSubURI_h__
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/IDNBlocklistUtils.cpp
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IDNBlocklistUtils.h"
+
+namespace mozilla {
+namespace net {
+
+static constexpr char16_t sBlocklistPairs[][2] = {
+#include "IDNCharacterBlocklist.inc"
+};
+
+void
+RemoveCharFromBlocklist(char16_t aChar, nsTArray<BlocklistRange>& aBlocklist)
+{
+  auto pos = aBlocklist.BinaryIndexOf(aChar, BlocklistPairToCharComparator());
+  if (pos == nsTArray<BlocklistRange>::NoIndex) {
+    return;
+  }
+
+  auto& pair = aBlocklist[pos];
+
+  // If the matched range has a length of one, we can just remove it
+  if (pair.second() == pair.first()) {
+    aBlocklist.RemoveElementAt(pos);
+    return;
+  }
+
+  // If the character matches the first element in the range, just update
+  // the range.
+  if (aChar == pair.first()) {
+    pair.first() = pair.first() + 1;
+    return;
+  }
+
+  // Also if it matches the last character in the range, we just update it.
+  if (aChar == pair.second()) {
+    pair.second() = pair.second() - 1;
+    return;
+  }
+
+  // Our character is in the middle of the range, splitting it in two.
+  // We update the matched range to reflect the values before the character,
+  // and insert a new range that represents the values after.
+  char16_t lastElement = pair.second();
+  pair.second() = aChar - 1;
+  aBlocklist.InsertElementAt(
+    pos + 1, mozilla::MakePair(char16_t(aChar + 1), lastElement));
+}
+
+void
+InitializeBlocklist(nsTArray<BlocklistRange>& aBlocklist)
+{
+  aBlocklist.Clear();
+  for (auto const& arr : sBlocklistPairs) {
+    // The hardcoded pairs are already sorted.
+    aBlocklist.AppendElement(mozilla::MakePair(arr[0], arr[1]));
+  }
+
+  nsAutoString extraAllowed;
+  nsresult rv =
+    Preferences::GetString("network.IDN.extra_allowed_chars", extraAllowed);
+  if (NS_SUCCEEDED(rv) && !extraAllowed.IsEmpty()) {
+    const char16_t* cur = extraAllowed.BeginReading();
+    const char16_t* end = extraAllowed.EndReading();
+    // Characters in the allowed list are removed from the blocklist.
+    for (; cur < end; ++cur) {
+      RemoveCharFromBlocklist(*cur, aBlocklist);
+    }
+  }
+
+  nsAutoString extraBlocked;
+  rv = Preferences::GetString("network.IDN.extra_blocked_chars", extraBlocked);
+  // We add each extra blocked character to the blocklist as a separate range.
+  if (NS_SUCCEEDED(rv) && !extraBlocked.IsEmpty()) {
+    for (size_t i = 0; i < extraBlocked.Length(); ++i) {
+      aBlocklist.AppendElement(
+        mozilla::MakePair(extraBlocked[i], extraBlocked[i]));
+    }
+    aBlocklist.Sort(BlocklistEntryComparator());
+  }
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/IDNBlocklistUtils.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef IDNBlocklistUtils_h__
+#define IDNBlocklistUtils_h__
+
+#include "mozilla/Pair.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace net {
+
+// A blocklist range is defined as all of the characters between:
+// { firstCharacterInRange, lastCharacterInRange }
+typedef mozilla::Pair<char16_t, char16_t> BlocklistRange;
+
+// Used to perform a binary search of the needle in the sorted array of pairs
+class BlocklistPairToCharComparator
+{
+public:
+  bool Equals(const BlocklistRange& pair, char16_t needle) const
+  {
+    // If the needle is between pair.first() and pair.second() it
+    // is part of the range.
+    return pair.first() <= needle && needle <= pair.second();
+  }
+
+  bool LessThan(const BlocklistRange& pair, char16_t needle) const
+  {
+    // The needle has to be larger than the second value,
+    // otherwise it may be equal.
+    return pair.second() < needle;
+  }
+};
+
+// Used to sort the array of pairs
+class BlocklistEntryComparator
+{
+public:
+  bool Equals(const BlocklistRange& a, const BlocklistRange& b) const
+  {
+    return a.first() == b.first() && a.second() == b.second();
+  }
+
+  bool LessThan(const BlocklistRange& a, const BlocklistRange& b) const
+  {
+    return a.first() < b.first();
+  }
+};
+
+// Returns true if the char can be found in the blocklist
+inline bool
+CharInBlocklist(char16_t aChar, const nsTArray<BlocklistRange>& aBlocklist)
+{
+  return aBlocklist.ContainsSorted(aChar, BlocklistPairToCharComparator());
+}
+
+// Initializes the blocklist based on the statically defined list and the
+// values of the following preferences:
+//     - network.IDN.extra_allowed_chars
+//     - network.IDN.extra_blocked_chars
+void
+InitializeBlocklist(nsTArray<BlocklistRange>& aBlocklist);
+
+void
+RemoveCharFromBlocklist(char16_t aChar, nsTArray<BlocklistRange>& aBlocklist);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // IDNBlocklistUtils_h__
--- a/netwerk/dns/IDNCharacterBlocklist.inc
+++ b/netwerk/dns/IDNCharacterBlocklist.inc
@@ -1,55 +1,62 @@
+// This file contains the IDN character blocklist.
+// Each entry represents a range of blocked characters.
+// Ranges are defined as:
+// { firstCharacterInRange, lastCharacterInRange }
+// IMPORTANT: Make sure this list is sorted in ascending order
+
+
 // ASCII Space
-0x0020,
-0x00A0,
-0x00BC, 0x00BD, 0x00BE,
-0x01C3,
-0x02D0,
-0x0337, 0x0338,
-0x0589, 0x058A,
-0x05C3,
-0x05F4,
-0x0609, 0x060A,
-0x066A,
-0x06D4,
-0x0701, 0x0702, 0x0703, 0x0704,
-0x115F, 0x1160,
-0x1735,
-0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x200B,
-0x200E, 0x200F, 0x2010,
-0x2019,
-0x2024,
-0x2027, 0x2028, 0x2029, 0x202A, 0x202B, 0x202C, 0x202D, 0x202E, 0x202F,
-0x2039, 0x203A,
-0x2041,
-0x2044,
-0x2052,
-0x205F,
-0x2153, 0x2154, 0x2155, 0x2156, 0x2157, 0x2158, 0x2159, 0x215A, 0x215B, 0x215C, 0x215D, 0x215E, 0x215F,
-0x2215,
-0x2236,
-0x23AE,
-0x2571,
-0x29F6,
-0x29F8,
-0x2AFB,
-0x2AFD,
-0x2FF0, 0x2FF1, 0x2FF2, 0x2FF3, 0x2FF4, 0x2FF5, 0x2FF6, 0x2FF7, 0x2FF8, 0x2FF9, 0x2FFA, 0x2FFB,
+{ 0x0020, 0x0020 },
+{ 0x00A0, 0x00A0 },
+{ 0x00BC, 0x00BE },
+{ 0x01C3, 0x01C3 },
+{ 0x02D0, 0x02D0 },
+{ 0x0337, 0x0338 },
+{ 0x0589, 0x058A },
+{ 0x05C3, 0x05C3 },
+{ 0x05F4, 0x05F4 },
+{ 0x0609, 0x060A },
+{ 0x066A, 0x066A },
+{ 0x06D4, 0x06D4 },
+{ 0x0701, 0x0704 },
+{ 0x115F, 0x1160 },
+{ 0x1735, 0x1735 },
+{ 0x2000, 0x200B },
+{ 0x200E, 0x2010 },
+{ 0x2019, 0x2019 },
+{ 0x2024, 0x2024 },
+{ 0x2027, 0x202F },
+{ 0x2039, 0x203A },
+{ 0x2041, 0x2041 },
+{ 0x2044, 0x2044 },
+{ 0x2052, 0x2052 },
+{ 0x205F, 0x205F },
+{ 0x2153, 0x215F },
+{ 0x2215, 0x2215 },
+{ 0x2236, 0x2236 },
+{ 0x23AE, 0x23AE },
+{ 0x2571, 0x2571 },
+{ 0x29F6, 0x29F6 },
+{ 0x29F8, 0x29F8 },
+{ 0x2AFB, 0x2AFB },
+{ 0x2AFD, 0x2AFD },
+{ 0x2FF0, 0x2FFB },
 // Ideographic Space
-0x3000,
-0x3002,
-0x3014, 0x3015,
-0x3033,
-0x30A0,
-0x3164,
-0x321D, 0x321E,
-0x33AE, 0x33AF,
-0x33C6,
-0x33DF,
-0xFE14, 0xFE15,
-0xFE3F,
-0xFE5D, 0xFE5E,
-0xFEFF,
-0xFF0E, 0xFF0F,
-0xFF61,
-0xFFA0,
-0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC, 0xFFFD
+{ 0x3000, 0x3000 },
+{ 0x3002, 0x3002 },
+{ 0x3014, 0x3015 },
+{ 0x3033, 0x3033 },
+{ 0x30A0, 0x30A0 },
+{ 0x3164, 0x3164 },
+{ 0x321D, 0x321E },
+{ 0x33AE, 0x33AF },
+{ 0x33C6, 0x33C6 },
+{ 0x33DF, 0x33DF },
+{ 0xFE14, 0xFE15 },
+{ 0xFE3F, 0xFE3F },
+{ 0xFE5D, 0xFE5E },
+{ 0xFEFF, 0xFEFF },
+{ 0xFF0E, 0xFF0F },
+{ 0xFF61, 0xFF61 },
+{ 0xFFA0, 0xFFA0 },
+{ 0xFFF9, 0xFFFD },
--- a/netwerk/dns/moz.build
+++ b/netwerk/dns/moz.build
@@ -24,32 +24,34 @@ XPIDL_SOURCES += [
 XPIDL_MODULE = 'necko_dns'
 
 EXPORTS.mozilla.net += [
     'ChildDNSService.h',
     'DNS.h',
     'DNSListenerProxy.h',
     'DNSRequestChild.h',
     'DNSRequestParent.h',
+    'IDNBlocklistUtils.h',
     'PDNSParams.h',
     'TRRService.h',
 ]
 
 SOURCES += [
     'nsEffectiveTLDService.cpp', # Excluded from UNIFIED_SOURCES due to special build flags.
     'nsHostResolver.cpp', # Redefines LOG
 ]
 
 UNIFIED_SOURCES += [
     'ChildDNSService.cpp',
     'DNS.cpp',
     'DNSListenerProxy.cpp',
     'DNSRequestChild.cpp',
     'DNSRequestParent.cpp',
     'GetAddrInfo.cpp',
+    'IDNBlocklistUtils.cpp',
     'nsDNSService2.cpp',
     'nsIDNService.cpp',
     'punycode.c',
     'TRR.cpp',
     'TRRService.cpp',
 ]
 
 IPDL_SOURCES = [
--- a/netwerk/dns/nsIDNService.cpp
+++ b/netwerk/dns/nsIDNService.cpp
@@ -21,21 +21,18 @@
 // To switch to transitional processing, change the value of this flag
 // and kTransitionalProcessing in netwerk/test/unit/test_idna2008.js to true
 // (revert bug 1218179).
 const bool kIDNA2008_TransitionalProcessing = false;
 
 #include "ICUUtils.h"
 #include "unicode/uscript.h"
 
-static const char16_t sBlocklistChars[] = {
-#include "IDNCharacterBlocklist.inc"
-};
-
 using namespace mozilla::unicode;
+using namespace mozilla::net;
 using mozilla::Preferences;
 
 //-----------------------------------------------------------------------------
 // RFC 1034 - 3.1. Name space specifications and terminology
 static const uint32_t kMaxDNSNodeLen = 63;
 // RFC 3490 - 5.   ACE prefix
 static const char kACEPrefix[] = "xn--";
 #define kACEPrefixLen 4
@@ -45,28 +42,26 @@ static const char kACEPrefix[] = "xn--";
 #define NS_NET_PREF_EXTRAALLOWED "network.IDN.extra_allowed_chars"
 #define NS_NET_PREF_EXTRABLOCKED "network.IDN.extra_blocked_chars"
 #define NS_NET_PREF_SHOWPUNYCODE    "network.IDN_show_punycode"
 #define NS_NET_PREF_IDNWHITELIST    "network.IDN.whitelist."
 #define NS_NET_PREF_IDNUSEWHITELIST "network.IDN.use_whitelist"
 #define NS_NET_PREF_IDNRESTRICTION  "network.IDN.restriction_profile"
 
 static inline bool
-isOnlySafeChars(const nsString& in, const nsTArray<char16_t>& aBlockList)
+isOnlySafeChars(const nsString& in, const nsTArray<BlocklistRange>& aBlocklist)
 {
-  if (aBlockList.IsEmpty()) {
+  if (aBlocklist.IsEmpty()) {
     return true;
   }
   const char16_t* cur = in.BeginReading();
   const char16_t* end = in.EndReading();
 
   for (; cur < end; ++cur) {
-    size_t unused;
-    if (mozilla::BinarySearch(aBlockList, 0, aBlockList.Length(), *cur,
-                              &unused)) {
+    if (CharInBlocklist(*cur, aBlocklist)) {
       return false;
     }
   }
   return true;
 }
 
 //-----------------------------------------------------------------------------
 // nsIDNService
@@ -92,55 +87,31 @@ nsresult nsIDNService::Init()
   MutexAutoLock lock(mLock);
 
   nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
   if (prefs)
     prefs->GetBranch(NS_NET_PREF_IDNWHITELIST, getter_AddRefs(mIDNWhitelistPrefBranch));
 
   Preferences::RegisterPrefixCallbacks(PrefChanged, gCallbackPrefs, this);
   prefsChanged(nullptr);
-  InitializeBlocklist();
+  InitializeBlocklist(mIDNBlocklist);
 
   return NS_OK;
 }
 
-void
-nsIDNService::InitializeBlocklist()
-{
-  mIDNBlocklist.Clear();
-  mIDNBlocklist.AppendElements(sBlocklistChars,
-                               mozilla::ArrayLength(sBlocklistChars));
-  nsAutoString extraAllowed;
-  nsresult rv = Preferences::GetString(NS_NET_PREF_EXTRAALLOWED, extraAllowed);
-
-  if (NS_SUCCEEDED(rv) && !extraAllowed.IsEmpty()) {
-    mIDNBlocklist.RemoveElementsBy([&](char16_t c) {
-      return extraAllowed.FindChar(c, 0) != -1;
-    });
-  }
-
-  nsAutoString extraBlocked;
-  rv = Preferences::GetString(NS_NET_PREF_EXTRABLOCKED, extraBlocked);
-  if (NS_SUCCEEDED(rv) && !extraBlocked.IsEmpty()) {
-    mIDNBlocklist.AppendElements(
-      static_cast<const char16_t*>(extraBlocked.Data()), extraBlocked.Length());
-    mIDNBlocklist.Sort();
-  }
-}
-
 void nsIDNService::prefsChanged(const char *pref)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mLock.AssertCurrentThreadOwns();
 
   if (pref && NS_LITERAL_CSTRING(NS_NET_PREF_EXTRAALLOWED).Equals(pref)) {
-    InitializeBlocklist();
+    InitializeBlocklist(mIDNBlocklist);
   }
   if (pref && NS_LITERAL_CSTRING(NS_NET_PREF_EXTRABLOCKED).Equals(pref)) {
-    InitializeBlocklist();
+    InitializeBlocklist(mIDNBlocklist);
   }
   if (!pref || NS_LITERAL_CSTRING(NS_NET_PREF_SHOWPUNYCODE).Equals(pref)) {
     bool val;
     if (NS_SUCCEEDED(Preferences::GetBool(NS_NET_PREF_SHOWPUNYCODE, &val)))
       mShowPunycode = val;
   }
   if (!pref || NS_LITERAL_CSTRING(NS_NET_PREF_IDNUSEWHITELIST).Equals(pref)) {
     bool val;
--- a/netwerk/dns/nsIDNService.h
+++ b/netwerk/dns/nsIDNService.h
@@ -8,16 +8,17 @@
 
 #include "nsIIDNService.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
 #include "nsUnicodeScriptCodes.h"
 #include "nsWeakReference.h"
 
 #include "unicode/uidna.h"
+#include "mozilla/net/IDNBlocklistUtils.h"
 
 #include "nsString.h"
 
 class nsIPrefBranch;
 
 //-----------------------------------------------------------------------------
 // nsIDNService
 //-----------------------------------------------------------------------------
@@ -90,18 +91,16 @@ private:
    *  label individually, so the output may contain some labels in
    *  punycode and some in UTF-8
    */
   nsresult UTF8toACE(const nsACString& input, nsACString& ace,
                      stringPrepFlag flag);
   nsresult ACEtoUTF8(const nsACString& input, nsACString& _retval,
                      stringPrepFlag flag);
 
-  void InitializeBlocklist();
-
   bool isInWhitelist(const nsACString &host);
   void prefsChanged(const char *pref);
 
   static void PrefChanged(const char* aPref, nsIDNService* aSelf)
   {
     mozilla::MutexAutoLock lock(aSelf->mLock);
     aSelf->prefsChanged(aPref);
   }
@@ -170,17 +169,17 @@ private:
   // |mIDNUseWhitelist|.
   //
   // These members can only be updated on the main thread and
   // read on any thread. Therefore, acquiring the mutex is required
   // only for threads other than the main thread.
   mozilla::Mutex mLock;
 
   // guarded by mLock
-  nsTArray<char16_t> mIDNBlocklist;
+  nsTArray<mozilla::net::BlocklistRange> mIDNBlocklist;
 
   /**
    * Flag set by the pref network.IDN_show_punycode. When it is true,
    * IDNs containing non-ASCII characters are always displayed to the
    * user in punycode
    *
    * guarded by mLock
    */
--- a/xpcom/io/nsEscape.cpp
+++ b/xpcom/io/nsEscape.cpp
@@ -475,40 +475,41 @@ NS_EscapeURL(const nsAString& aStr, uint
 
   if (result) {
     return aResult;
   }
   return aStr;
 }
 
 // Starting at aStr[aStart] find the first index in aStr that matches any
-// character in aForbidden. Return false if not found.
+// character that is forbidden by aFunction. Return false if not found.
 static bool
-FindFirstMatchFrom(const nsString& aStr, size_t aStart,
-                   const nsTArray<char16_t>& aForbidden, size_t* aIndex)
+FindFirstMatchFrom(const nsString& aStr,
+                   size_t aStart,
+                   const std::function<bool(char16_t)>& aFunction,
+                   size_t* aIndex)
 {
-  const size_t len = aForbidden.Length();
   for (size_t j = aStart, l = aStr.Length(); j < l; ++j) {
-    size_t unused;
-    if (mozilla::BinarySearch(aForbidden, 0, len, aStr[j], &unused)) {
+    if (aFunction(aStr[j])) {
       *aIndex = j;
       return true;
     }
   }
   return false;
 }
 
 const nsAString&
-NS_EscapeURL(const nsString& aStr, const nsTArray<char16_t>& aForbidden,
+NS_EscapeURL(const nsString& aStr,
+             const std::function<bool(char16_t)>& aFunction,
              nsAString& aResult)
 {
   bool didEscape = false;
   for (size_t i = 0, strLen = aStr.Length(); i < strLen; ) {
     size_t j;
-    if (MOZ_UNLIKELY(FindFirstMatchFrom(aStr, i, aForbidden, &j))) {
+    if (MOZ_UNLIKELY(FindFirstMatchFrom(aStr, i, aFunction, &j))) {
       if (i == 0) {
         didEscape = true;
         aResult.Truncate();
         aResult.SetCapacity(aStr.Length());
       }
       if (j != i) {
         // The substring from 'i' up to 'j' that needs no escaping.
         aResult.Append(nsDependentSubstring(aStr, i, j - i));
--- a/xpcom/io/nsEscape.h
+++ b/xpcom/io/nsEscape.h
@@ -7,16 +7,17 @@
 /*	First checked in on 98/12/03 by John R. McMullen, derived from net.h/mkparse.c. */
 
 #ifndef _ESCAPE_H_
 #define _ESCAPE_H_
 
 #include "nscore.h"
 #include "nsError.h"
 #include "nsString.h"
+#include <functional>
 
 /**
  * Valid mask values for nsEscape
  * Note: these values are copied in nsINetUtil.idl. Any changes should be kept
  * in sync.
  */
 typedef enum {
   url_All       = 0,       // %-escape every byte unconditionally
@@ -206,24 +207,24 @@ NS_UnescapeURL(const nsACString& aStr, u
 }
 
 const nsAString&
 NS_EscapeURL(const nsAString& aStr, uint32_t aFlags, nsAString& aResult);
 
 /**
  * Percent-escapes all characters in aStr that occurs in aForbidden.
  * @param aStr the input URL string
- * @param aForbidden the characters that should be escaped if found in aStr
- * @note that aForbidden MUST be sorted (low to high)
+ * @param aFunction returns true for characters that should be escaped
  * @param aResult the result if some characters were escaped
  * @return aResult if some characters were escaped, or aStr otherwise (aResult
  *         is unmodified in that case)
  */
 const nsAString&
-NS_EscapeURL(const nsString& aStr, const nsTArray<char16_t>& aForbidden,
+NS_EscapeURL(const nsString& aStr,
+             const std::function<bool(char16_t)>& aFunction,
              nsAString& aResult);
 
 /**
  * CString version of nsEscape. Returns true on success, false
  * on out of memory. To reverse this function, use NS_UnescapeURL.
  */
 inline bool
 NS_Escape(const nsACString& aOriginal, nsACString& aEscaped,