Bug 1573051 - Use both SHParseDisplayName and CreateUri to validate a uri. r=aklotz, a=RyanVM
authorToshihito Kikuchi <tkikuchi@mozilla.com>
Wed, 21 Aug 2019 19:53:48 +0000
changeset 545232 b438d15fb66f211c6916db846b05b3860918ecc4
parent 545231 79ab9ee7d7e9714c21874dd237660ac8ee7872c7
child 545233 ef23f491d904eb4f8c49c3431496b8c110edbbac
push id2131
push userffxbld-merge
push dateMon, 26 Aug 2019 18:30:20 +0000
treeherdermozilla-release@b19ffb3ca153 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaklotz, RyanVM
bugs1573051
milestone69.0
Bug 1573051 - Use both SHParseDisplayName and CreateUri to validate a uri. r=aklotz, a=RyanVM For launching with an external protocol handler on Windows, we validate a uri before sending it to `ShellExecute`, by converting a string into `PIDL` using `SHParseDisplayName` and extract a string back from PIDL using `IShellFolder::GetDisplayNameOf`. The problem was that if a fragment, a string following a hash mark (#), is always dropped after this validation. This is caused by the intended design of Windows. A proposed fix is to use `CreateUri` for validation, which is used behind `IShellFolder::GetDisplayNameOf`. However, we also keep `SHParseDisplayName` because there are cases where `CreateUri` succeeds while `SHParseDisplayName` fails such as a non-existent `file:` uri and we want to keep the same validation result for those cases. Adding `CreateUri` broke MinGW build because of our toolkit issue. We use dynamic linking for MinGW build in the meantime. This patch adds a new unittest to make sure the new validation logic behaves the same as the old one except the fragment issue. Differential Revision: https://phabricator.services.mozilla.com/D42943
testing/cppunittest.ini
toolkit/library/moz.build
uriloader/exthandler/win/nsMIMEInfoWin.cpp
widget/windows/ShellHeaderOnlyUtils.h
widget/windows/UrlmonHeaderOnlyUtils.h
widget/windows/moz.build
widget/windows/tests/TestUriValidation.cpp
widget/windows/tests/TestUrisToValidate.h
widget/windows/tests/moz.build
--- a/testing/cppunittest.ini
+++ b/testing/cppunittest.ini
@@ -29,16 +29,18 @@ skip-if = os != 'win'
 [TestJSONWriter]
 [TestLinkedList]
 [TestMacroArgs]
 [TestMacroForEach]
 [TestMathAlgorithms]
 [TestMaybe]
 [TestNativeNt]
 skip-if = os != 'win'
+[TestUriValidation]
+skip-if = os != 'win'
 [TestBaseProfiler]
 [TestNonDereferenceable]
 [TestNotNull]
 [TestParseFTPList]
 [TestPLDHash]
 [TestPair]
 [TestPoisonArea]
 skip-if = os == 'android' # Bug 1147630
--- a/toolkit/library/moz.build
+++ b/toolkit/library/moz.build
@@ -23,16 +23,17 @@ def Libxul(name, output_category=None):
         SHARED_LIBRARY_NAME = 'xul'
 
     DELAYLOAD_DLLS += [
         'comdlg32.dll',
         'hid.dll',
         'msimg32.dll',
         'netapi32.dll',
         'secur32.dll',
+        'urlmon.dll',
         'wininet.dll',
         'winspool.drv'
     ]
 
     if CONFIG['ACCESSIBILITY']:
         DELAYLOAD_DLLS += ['oleacc.dll']
 
     if CONFIG['MOZ_WEBRTC']:
--- a/uriloader/exthandler/win/nsMIMEInfoWin.cpp
+++ b/uriloader/exthandler/win/nsMIMEInfoWin.cpp
@@ -17,16 +17,17 @@
 #include "shlobj.h"
 #include "windows.h"
 #include "nsIWindowsRegKey.h"
 #include "nsIProcess.h"
 #include "nsUnicharUtils.h"
 #include "nsITextToSubURI.h"
 #include "nsVariant.h"
 #include "mozilla/ShellHeaderOnlyUtils.h"
+#include "mozilla/UrlmonHeaderOnlyUtils.h"
 #include "mozilla/UniquePtrExtensions.h"
 
 #define RUNDLL32_EXE L"\\rundll32.exe"
 
 NS_IMPL_ISUPPORTS_INHERITED(nsMIMEInfoWin, nsMIMEInfoBase, nsIPropertyBag)
 
 nsMIMEInfoWin::~nsMIMEInfoWin() {}
 
@@ -220,32 +221,34 @@ nsresult nsMIMEInfoWin::LoadUriInternal(
         do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (NS_FAILED(textToSubURI->UnEscapeNonAsciiURI(NS_LITERAL_CSTRING("UTF-8"),
                                                     urlSpec, utf16Spec))) {
       CopyASCIItoUTF16(urlSpec, utf16Spec);
     }
 
-    // Ask the shell to parse |utf16Spec| to avoid malformed URLs. Failure is
-    // indicative of a potential security issue so we should bail out if so.
-    UniqueAbsolutePidl pidl = ShellParseDisplayName(utf16Spec.get());
-    if (!pidl) {
+    // Ask the shell/urlmon to parse |utf16Spec| to avoid malformed URLs.
+    // Failure is indicative of a potential security issue so we should
+    // bail out if so.
+    LauncherResult<_bstr_t> validatedUri = UrlmonValidateUri(utf16Spec.get());
+    if (validatedUri.isErr()) {
       return NS_ERROR_FAILURE;
     }
 
     _variant_t args;
     _variant_t verb(L"open");
     _variant_t workingDir;
     _variant_t showCmd(SW_SHOWNORMAL);
 
     // Ask Explorer to ShellExecute on our behalf, as some URL handlers do not
     // start correctly when inheriting our process's process migitations.
     mozilla::LauncherVoidResult shellExecuteOk =
-        mozilla::ShellExecuteByExplorer(pidl, args, verb, workingDir, showCmd);
+        mozilla::ShellExecuteByExplorer(validatedUri.unwrap(), args, verb,
+                                        workingDir, showCmd);
     if (shellExecuteOk.isErr()) {
       return NS_ERROR_FAILURE;
     }
   }
 
   return rv;
 }
 
--- a/widget/windows/ShellHeaderOnlyUtils.h
+++ b/widget/windows/ShellHeaderOnlyUtils.h
@@ -150,64 +150,23 @@ inline LauncherVoidResult ShellExecuteBy
 #endif  // defined(MOZ_REDEFINE_SHELLEXECUTE)
 
   return Ok();
 }
 
 using UniqueAbsolutePidl =
     UniquePtr<RemovePointer<PIDLIST_ABSOLUTE>::Type, CoTaskMemFreeDeleter>;
 
-inline UniqueAbsolutePidl ShellParseDisplayName(const wchar_t* aPath) {
+inline LauncherResult<PIDLIST_ABSOLUTE> ShellParseDisplayName(
+    const wchar_t* aPath) {
   PIDLIST_ABSOLUTE rawAbsPidl = nullptr;
   SFGAOF sfgao;
   HRESULT hr = ::SHParseDisplayName(aPath, nullptr, &rawAbsPidl, 0, &sfgao);
   if (FAILED(hr)) {
-    return nullptr;
-  }
-
-  return UniqueAbsolutePidl(rawAbsPidl);
-}
-
-/**
- * Since IShellDispatch2::ShellExecute does not accept a PIDL as one of its
- * parameters, we need to convert a PIDL back to a path. This overload handles
- * that conversion and then proceeds with the ShellExecuteByExplorer call.
- */
-inline LauncherVoidResult ShellExecuteByExplorer(
-    const UniqueAbsolutePidl& aPath, const _variant_t& aArgs,
-    const _variant_t& aVerb, const _variant_t& aWorkingDir,
-    const _variant_t& aShowCmd) {
-  // |aPath| is an absolute path. IShellFolder::GetDisplayNameOf requires a
-  // valid child ID, so the first thing we need to resolve is the IShellFolder
-  // for |aPath|'s parent, as well as the childId that represents |aPath|.
-  // Fortunately SHBindToParent does exactly that!
-  PCUITEMID_CHILD childId = nullptr;
-  RefPtr<IShellFolder> parentFolder;
-  HRESULT hr = ::SHBindToParent(aPath.get(), IID_IShellFolder,
-                                getter_AddRefs(parentFolder), &childId);
-  if (FAILED(hr)) {
     return LAUNCHER_ERROR_FROM_HRESULT(hr);
   }
 
-  // Now we retrieve the display name of |childId|, telling the shell that we
-  // plan to have the string parsed.
-  STRRET strret;
-  hr = parentFolder->GetDisplayNameOf(childId, SHGDN_FORPARSING, &strret);
-  if (FAILED(hr)) {
-    return LAUNCHER_ERROR_FROM_HRESULT(hr);
-  }
-
-  // Since ShellExecuteByExplorer wants a BSTR, we convert |strret|. Calling
-  // StrRetToBSTR is advantageous since it automatically takes care of
-  // freeing any dynamically allocated memory in |strret|.
-  _bstr_t bstrPath;
-  hr = ::StrRetToBSTR(&strret, nullptr, bstrPath.GetAddress());
-  if (FAILED(hr)) {
-    return LAUNCHER_ERROR_FROM_HRESULT(hr);
-  }
-
-  // Now we have a bstr_t so we can invoke the overload.
-  return ShellExecuteByExplorer(bstrPath, aArgs, aVerb, aWorkingDir, aShowCmd);
+  return rawAbsPidl;
 }
 
 }  // namespace mozilla
 
 #endif  // mozilla_ShellHeaderOnlyUtils_h
new file mode 100644
--- /dev/null
+++ b/widget/windows/UrlmonHeaderOnlyUtils.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_UrlmonHeaderOnlyUtils_h
+#define mozilla_UrlmonHeaderOnlyUtils_h
+
+#include "mozilla/ShellHeaderOnlyUtils.h"
+
+namespace mozilla {
+
+/**
+ * We used to validate a uri with SHParseDisplayName to mitigate the Windows
+ * bug (Bug 394974).  However, Bug 1573051 revealed an issue that a fragment,
+ * a string following a hash mark (#), is dropped when we extract a string
+ * from PIDL.  This is the intended behavior of Windows.
+ *
+ * To deal with the fragment issue as well as keeping our mitigation, we
+ * decided to use CreateUri to validate a uri string, but we also keep using
+ * SHParseDisplayName as a pre-check.  This is because there are several
+ * cases where CreateUri succeeds while SHParseDisplayName fails such as
+ * a non-existent file: uri.
+ *
+ * To minimize the impact of introducing CreateUri into the validation logic,
+ * we try to mimic the logic of windows_storage!IUriToPidl (ieframe!IUriToPidl
+ * in Win7) which is executed behind SHParseDisplayName.
+ * What IUriToPidl does is:
+ *   1) If a given uri has a fragment, removes a fragment.
+ *   2) Takes an absolute uri if it's available in the given uri, otherwise
+ *      takes a raw uri.
+ *
+ * As we need to get a full uri including a fragment, this function does 2).
+ */
+inline LauncherResult<_bstr_t> UrlmonValidateUri(const wchar_t* aUri) {
+  LauncherResult<PIDLIST_ABSOLUTE> rawAbsPidl = ShellParseDisplayName(aUri);
+  if (rawAbsPidl.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(rawAbsPidl);
+  }
+  UniqueAbsolutePidl pidl(rawAbsPidl.unwrap());
+
+#ifndef __MINGW32__
+  const auto createUri = CreateUri;
+#else
+  HMODULE urlmonDll = GetModuleHandleW(L"urlmon");
+  if (!urlmonDll) {
+    return LAUNCHER_ERROR_FROM_LAST();
+  }
+
+  const auto createUri = reinterpret_cast<decltype(CreateUri)*>(
+      GetProcAddress(urlmonDll, "CreateUri"));
+  if (!createUri) {
+    return LAUNCHER_ERROR_FROM_LAST();
+  }
+#endif
+
+  // The value of |flags| is the same value as used in ieframe!_EnsureIUri in
+  // Win7, which is called behind SHParseDisplayName.  In Win10, on the other
+  // hand, an flag 0x03000000 is also passed to CreateUri, but we don't
+  // specify it because it's undocumented and unknown.
+  constexpr DWORD flags =
+      Uri_CREATE_NO_DECODE_EXTRA_INFO | Uri_CREATE_CANONICALIZE |
+      Uri_CREATE_CRACK_UNKNOWN_SCHEMES | Uri_CREATE_PRE_PROCESS_HTML_URI |
+      Uri_CREATE_IE_SETTINGS;
+  RefPtr<IUri> uri;
+  HRESULT hr = createUri(aUri, flags, 0, getter_AddRefs(uri));
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  _bstr_t bstrUri;
+
+  hr = uri->GetAbsoluteUri(bstrUri.GetAddress());
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  if (hr == S_FALSE) {
+    hr = uri->GetRawUri(bstrUri.GetAddress());
+    if (FAILED(hr)) {
+      return LAUNCHER_ERROR_FROM_HRESULT(hr);
+    }
+  }
+
+  return bstrUri;
+}
+
+}  // namespace mozilla
+
+#endif  // mozilla_UrlmonHeaderOnlyUtils_h
--- a/widget/windows/moz.build
+++ b/widget/windows/moz.build
@@ -15,16 +15,17 @@ TEST_DIRS += ['tests']
 EXPORTS += [
     'nsdefs.h',
     'WindowHook.h',
     'WinUtils.h',
 ]
 
 EXPORTS.mozilla += [
     'ShellHeaderOnlyUtils.h',
+    'UrlmonHeaderOnlyUtils.h',
     'WindowsConsole.h',
     'WinHeaderOnlyUtils.h',
 ]
 
 EXPORTS.mozilla.widget += [
     'AudioSession.h',
     'CompositorWidgetChild.h',
     'CompositorWidgetParent.h',
@@ -152,15 +153,16 @@ for var in ('MOZ_ENABLE_D3D10_LAYER'):
         DEFINES[var] = True
 
 RESFILE = 'widget.res'
 
 CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
 
 OS_LIBS += [
     'rpcrt4',
+    'urlmon',
 ]
 
 if CONFIG['CC_TYPE'] == 'clang-cl':
     SOURCES += [
         'ToastNotification.cpp',
         'ToastNotificationHandler.cpp',
     ]
new file mode 100644
--- /dev/null
+++ b/widget/windows/tests/TestUriValidation.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/UrlmonHeaderOnlyUtils.h"
+#include "TestUrisToValidate.h"
+
+#include <urlmon.h>
+
+using namespace mozilla;
+
+static LauncherResult<_bstr_t> ShellValidateUri(const wchar_t* aUri) {
+  LauncherResult<PIDLIST_ABSOLUTE> rawAbsPidl = ShellParseDisplayName(aUri);
+  if (rawAbsPidl.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(rawAbsPidl);
+  }
+  UniqueAbsolutePidl pidl(rawAbsPidl.unwrap());
+
+  // |pidl| is an absolute path. IShellFolder::GetDisplayNameOf requires a
+  // valid child ID, so the first thing we need to resolve is the IShellFolder
+  // for |pidl|'s parent, as well as the childId that represents |pidl|.
+  // Fortunately SHBindToParent does exactly that!
+  PCUITEMID_CHILD childId = nullptr;
+  RefPtr<IShellFolder> parentFolder;
+  HRESULT hr = SHBindToParent(pidl.get(), IID_IShellFolder,
+                              getter_AddRefs(parentFolder), &childId);
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  // Now we retrieve the display name of |childId|, telling the shell that we
+  // plan to have the string parsed.
+  STRRET strret;
+  hr = parentFolder->GetDisplayNameOf(childId, SHGDN_FORPARSING, &strret);
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  // StrRetToBSTR automatically takes care of freeing any dynamically
+  // allocated memory in |strret|.
+  _bstr_t bstrUri;
+  hr = StrRetToBSTR(&strret, nullptr, bstrUri.GetAddress());
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  return bstrUri;
+}
+
+static LauncherResult<_bstr_t> GetFragment(const wchar_t* aUri) {
+#ifndef __MINGW32__
+  const auto createUri = CreateUri;
+#else
+  HMODULE urlmonDll = GetModuleHandleW(L"urlmon");
+  if (!urlmonDll) {
+    return LAUNCHER_ERROR_FROM_LAST();
+  }
+
+  const auto createUri = reinterpret_cast<decltype(CreateUri)*>(
+      GetProcAddress(urlmonDll, "CreateUri"));
+  if (!createUri) {
+    return LAUNCHER_ERROR_FROM_LAST();
+  }
+#endif
+
+  constexpr DWORD flags =
+      Uri_CREATE_NO_DECODE_EXTRA_INFO | Uri_CREATE_CANONICALIZE |
+      Uri_CREATE_CRACK_UNKNOWN_SCHEMES | Uri_CREATE_PRE_PROCESS_HTML_URI |
+      Uri_CREATE_IE_SETTINGS;
+  RefPtr<IUri> uri;
+  HRESULT hr = createUri(aUri, flags, 0, getter_AddRefs(uri));
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+
+  _bstr_t bstrFragment;
+  hr = uri->GetFragment(bstrFragment.GetAddress());
+  if (FAILED(hr)) {
+    return LAUNCHER_ERROR_FROM_HRESULT(hr);
+  }
+  return bstrFragment;
+}
+
+static bool RunSingleTest(const wchar_t* aUri) {
+  LauncherResult<_bstr_t> uriOld = ShellValidateUri(aUri),
+                          uriNew = UrlmonValidateUri(aUri);
+  if (uriOld.isErr() != uriNew.isErr()) {
+    printf("TEST-FAILED | UriValidation | Validation result mismatch on %S\n",
+           aUri);
+    return false;
+  }
+
+  if (uriOld.isErr()) {
+    if (uriOld.unwrapErr().mError != uriNew.unwrapErr().mError) {
+      printf("TEST-FAILED | UriValidation | Error code mismatch on %S\n", aUri);
+      return false;
+    }
+    return true;
+  }
+
+  LauncherResult<_bstr_t> bstrFragment = GetFragment(aUri);
+  if (bstrFragment.isErr()) {
+    printf("TEST-FAILED | UriValidation | Failed to get a fragment from %S\n",
+           aUri);
+    return false;
+  }
+
+  // We validate a uri with two logics: the current one UrlmonValidateUri and
+  // the older one ShellValidateUri, to make sure the same validation result.
+  // We introduced UrlmonValidateUri because ShellValidateUri drops a fragment
+  // in a uri due to the design of Windows.  To bypass the fragment issue, we
+  // extract a fragment and appends it into the validated string, and compare.
+  _bstr_t bstrUriOldCorrected = uriOld.unwrap() + bstrFragment.unwrap();
+  const _bstr_t& bstrUriNew = uriNew.unwrap();
+  if (bstrUriOldCorrected != bstrUriNew) {
+    printf("TEST-FAILED | UriValidation | %S %S %S\n", aUri,
+           static_cast<const wchar_t*>(bstrUriOldCorrected),
+           static_cast<const wchar_t*>(bstrUriNew));
+    return false;
+  }
+
+  return true;
+}
+
+int wmain(int argc, wchar_t* argv[]) {
+  HRESULT hr = CoInitialize(nullptr);
+  if (FAILED(hr)) {
+    return 1;
+  }
+
+  bool isOk = true;
+
+  if (argc == 2) {
+    isOk = RunSingleTest(argv[1]);
+  } else {
+    for (const wchar_t*& testUri : kTestUris) {
+      if (!RunSingleTest(testUri)) {
+        isOk = false;
+      }
+    }
+  }
+
+  CoUninitialize();
+  return isOk ? 0 : 1;
+}
new file mode 100644
--- /dev/null
+++ b/widget/windows/tests/TestUrisToValidate.h
@@ -0,0 +1,471 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_TestUrisToValidate_h
+#define mozilla_TestUrisToValidate_h
+
+const wchar_t* kTestUris[] = {
+    L"callto:%.txt",
+    L"callto:%00.txt",
+    L"callto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"fdaction:%.txt",
+    L"fdaction:%00.txt",
+    L"fdaction:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"feed:%.txt",
+    L"feed:%00.txt",
+    L"feed:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"feeds:%.txt",
+    L"feeds:%00.txt",
+    L"feeds:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"file:///%.txt",
+    L"file:///%00.txt",
+    L"file:///%41%2D%31%5Ftest%22ing?%41%31%00.txt",
+    L"firefox.url:%.txt",
+    L"firefox.url:%00.txt",
+    L"firefox.url:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"firefoxurl:%.txt",
+    L"firefoxurl:%00.txt",
+    L"firefoxurl:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"ftp:%.txt",
+    L"ftp:%00.txt",
+    L"ftp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"gopher:%.txt",
+    L"gopher:%00.txt",
+    L"gopher:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"gtalk:%.txt",
+    L"gtalk:%00.txt",
+    L"gtalk:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"HTTP:%.txt",
+    L"HTTP:%00.txt",
+    L"HTTP:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"http:%.txt",
+    L"http:%00.txt",
+    L"http:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"https://bug389580.bmoattachments.org/%.txt",
+    L"https://bug389580.bmoattachments.org/%00.txt",
+    L"https://bug389580.bmoattachments.org/"
+    L"%41%2D%31%5Ftest%22ing?%41%31%00.txt",
+    L"ie.ftp:%.txt",
+    L"ie.ftp:%00.txt",
+    L"ie.ftp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"ie.http:%.txt",
+    L"ie.http:%00.txt",
+    L"ie.http:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"ie.https:%.txt",
+    L"ie.https:%00.txt",
+    L"ie.https:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"irc:%.txt",
+    L"irc:%00.txt",
+    L"irc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"ircs:%.txt",
+    L"ircs:%00.txt",
+    L"ircs:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"itms:%.txt",
+    L"itms:%00.txt",
+    L"itms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"itmss:%.txt",
+    L"itmss:%00.txt",
+    L"itmss:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"itpc:%.txt",
+    L"itpc:%00.txt",
+    L"itpc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"itunes.assocprotocol.itms:%.txt",
+    L"itunes.assocprotocol.itms:%00.txt",
+    L"itunes.assocprotocol.itms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"itunes.assocprotocol.itmss:%.txt",
+    L"itunes.assocprotocol.itmss:%00.txt",
+    L"itunes.assocprotocol.itmss:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"itunes.assocprotocol.itpc:%.txt",
+    L"itunes.assocprotocol.itpc:%00.txt",
+    L"itunes.assocprotocol.itpc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"ldap:%.txt",
+    L"ldap:%00.txt",
+    L"ldap:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"mailto:%.txt",
+    L"mailto:%00.txt",
+    L"mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"mms:%.txt",
+    L"mms:%00.txt",
+    L"mms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"mmst:%.txt",
+    L"mmst:%00.txt",
+    L"mmst:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"mmst:%.txt",
+    L"mmst:%00.txt",
+    L"mmst:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"mmsu:%.txt",
+    L"mmsu:%00.txt",
+    L"mmsu:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"mmsu:%.txt",
+    L"mmsu:%00.txt",
+    L"mmsu:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"https://bug389580.bmoattachments.org/"
+    L"Mozilla%20Thunderbird.Url.Mailto:%.txt",
+    L"https://bug389580.bmoattachments.org/"
+    L"Mozilla%20Thunderbird.Url.Mailto:%00.txt",
+    L"https://bug389580.bmoattachments.org/"
+    L"Mozilla%20Thunderbird.Url.Mailto:%41%2D%31%5Ftest%22ing?%41%31%00.txt",
+    L"navigatorurl:%.txt",
+    L"navigatorurl:%00.txt",
+    L"navigatorurl:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"news:%.txt",
+    L"news:%00.txt",
+    L"news:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"nntp:%.txt",
+    L"nntp:%00.txt",
+    L"nntp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"oms:%.txt",
+    L"oms:%00.txt",
+    L"oms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"outlook:%.txt",
+    L"outlook:%00.txt",
+    L"outlook:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"outlook.url.feed:%.txt",
+    L"outlook.url.feed:%00.txt",
+    L"outlook.url.feed:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"outlook.url.mailto:%.txt",
+    L"outlook.url.mailto:%00.txt",
+    L"outlook.url.mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"outlook.url.webcal:%.txt",
+    L"outlook.url.webcal:%00.txt",
+    L"outlook.url.webcal:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"outlookfeed:%.txt",
+    L"outlookfeed:%00.txt",
+    L"outlookfeed:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"outlookfeeds:%.txt",
+    L"outlookfeeds:%00.txt",
+    L"outlookfeeds:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"pnm:%.txt",
+    L"pnm:%00.txt",
+    L"pnm:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"prls.intappfile.ftp:%.txt",
+    L"prls.intappfile.ftp:%00.txt",
+    L"prls.intappfile.ftp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"prls.intappfile.http:%.txt",
+    L"prls.intappfile.http:%00.txt",
+    L"prls.intappfile.http:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"prls.intappfile.https:%.txt",
+    L"prls.intappfile.https:%00.txt",
+    L"prls.intappfile.https:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"prls.intappfile.mailto:%.txt",
+    L"prls.intappfile.mailto:%00.txt",
+    L"prls.intappfile.mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"rlogin:%.txt",
+    L"rlogin:%00.txt",
+    L"rlogin:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"rtsp:%.txt",
+    L"rtsp:%00.txt",
+    L"rtsp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"scp:%.txt",
+    L"scp:%00.txt",
+    L"scp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"sftp:%.txt",
+    L"sftp:%00.txt",
+    L"sftp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"sip:%.txt",
+    L"sip:%00.txt",
+    L"sip:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"skype:%.txt",
+    L"skype:%00.txt",
+    L"skype:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"snews:%.txt",
+    L"snews:%00.txt",
+    L"snews:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"telnet:%.txt",
+    L"telnet:%00.txt",
+    L"telnet:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"thunderbird.url.mailto:%.txt",
+    L"thunderbird.url.mailto:%00.txt",
+    L"thunderbird.url.mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"thunderbird.url.news:%.txt",
+    L"thunderbird.url.news:%00.txt",
+    L"thunderbird.url.news:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"tn3270:%.txt",
+    L"tn3270:%00.txt",
+    L"tn3270:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"tscrec4:%.txt",
+    L"tscrec4:%00.txt",
+    L"tscrec4:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"webcal:%.txt",
+    L"webcal:%00.txt",
+    L"webcal:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"webcal:%.txt",
+    L"webcal:%00.txt",
+    L"webcal:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"webcals:%.txt",
+    L"webcals:%00.txt",
+    L"webcals:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"windowscalendar.urlwebcal.1:%.txt",
+    L"windowscalendar.urlwebcal.1:%00.txt",
+    L"windowscalendar.urlwebcal.1:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"windowsmail.url.mailto:%.txt",
+    L"windowsmail.url.mailto:%00.txt",
+    L"windowsmail.url.mailto:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"windowsmail.url.news:%.txt",
+    L"windowsmail.url.news:%00.txt",
+    L"windowsmail.url.news:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"windowsmail.url.nntp:%.txt",
+    L"windowsmail.url.nntp:%00.txt",
+    L"windowsmail.url.nntp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"windowsmail.url.snews:%.txt",
+    L"windowsmail.url.snews:%00.txt",
+    L"windowsmail.url.snews:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"wmp11.assocprotocol.mms:%.txt",
+    L"wmp11.assocprotocol.mms:%00.txt",
+    L"wmp11.assocprotocol.mms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"wpc:%.txt",
+    L"wpc:%00.txt",
+    L"wpc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"ymsgr:%.txt",
+    L"ymsgr:%00.txt",
+    L"ymsgr:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"acrobat:%.txt",
+    L"acrobat:%00.txt",
+    L"acrobat:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"acsui:%.txt",
+    L"acsui:%00.txt",
+    L"acsui:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"aim:%.txt",
+    L"aim:%00.txt",
+    L"aim:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"aim:%.txt",
+    L"aim:%00.txt",
+    L"aim:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"allc8.commands.2:%.txt",
+    L"allc8.commands.2:%00.txt",
+    L"allc8.commands.2:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"allholdem.commands.2:%.txt",
+    L"allholdem.commands.2:%00.txt",
+    L"allholdem.commands.2:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"allpoker.commands.2:%.txt",
+    L"allpoker.commands.2:%00.txt",
+    L"allpoker.commands.2:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"aolautofix:%.txt",
+    L"aolautofix:%00.txt",
+    L"aolautofix:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"aolds:%.txt",
+    L"aolds:%00.txt",
+    L"aolds:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"bc:%.txt",
+    L"bc:%00.txt",
+    L"bc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"bctp:%.txt",
+    L"bctp:%00.txt",
+    L"bctp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"bittorrent:%.txt",
+    L"bittorrent:%00.txt",
+    L"bittorrent:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"camfrog:%.txt",
+    L"camfrog:%00.txt",
+    L"camfrog:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"csi:%.txt",
+    L"csi:%00.txt",
+    L"csi:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"cvs:%.txt",
+    L"cvs:%00.txt",
+    L"cvs:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"daap:%.txt",
+    L"daap:%00.txt",
+    L"daap:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"ed2k:%.txt",
+    L"ed2k:%00.txt",
+    L"ed2k:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"explorer.assocprotocol.search-ms:%.txt",
+    L"explorer.assocprotocol.search-ms:%00.txt",
+    L"explorer.assocprotocol.search-ms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"gizmoproject:%.txt",
+    L"gizmoproject:%00.txt",
+    L"gizmoproject:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"gnet:%.txt",
+    L"gnet:%00.txt",
+    L"gnet:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"gnutella:%.txt",
+    L"gnutella:%00.txt",
+    L"gnutella:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"gsarcade:%.txt",
+    L"gsarcade:%00.txt",
+    L"gsarcade:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"hcp:%.txt",
+    L"hcp:%00.txt",
+    L"hcp:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"icquser:%.txt",
+    L"icquser:%00.txt",
+    L"icquser:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"icy:%.txt",
+    L"icy:%00.txt",
+    L"icy:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"imesync:%.txt",
+    L"imesync:%00.txt",
+    L"imesync:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"itunes.assocprotocol.daap:%.txt",
+    L"itunes.assocprotocol.daap:%00.txt",
+    L"itunes.assocprotocol.daap:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"itunes.assocprotocol.pcast:%.txt",
+    L"itunes.assocprotocol.pcast:%00.txt",
+    L"itunes.assocprotocol.pcast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"joost:%.txt",
+    L"joost:%00.txt",
+    L"joost:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"m4macdrive:%.txt",
+    L"m4macdrive:%00.txt",
+    L"m4macdrive:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"magnet:%.txt",
+    L"magnet:%00.txt",
+    L"magnet:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"mapi:%.txt",
+    L"mapi:%00.txt",
+    L"mapi:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"mc12:%.txt",
+    L"mc12:%00.txt",
+    L"mc12:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"mediajukebox:%.txt",
+    L"mediajukebox:%00.txt",
+    L"mediajukebox:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"morpheus:%.txt",
+    L"morpheus:%00.txt",
+    L"morpheus:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"mp2p:%.txt",
+    L"mp2p:%00.txt",
+    L"mp2p:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"mpodcast:%.txt",
+    L"mpodcast:%00.txt",
+    L"mpodcast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"msbd:%.txt",
+    L"msbd:%00.txt",
+    L"msbd:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"msbd:%.txt",
+    L"msbd:%00.txt",
+    L"msbd:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"msdigitallocker:%.txt",
+    L"msdigitallocker:%00.txt",
+    L"msdigitallocker:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"outlook.url.stssync:%.txt",
+    L"outlook.url.stssync:%00.txt",
+    L"outlook.url.stssync:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"p2p:%.txt",
+    L"p2p:%00.txt",
+    L"p2p:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"pando:%.txt",
+    L"pando:%00.txt",
+    L"pando:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"pcast:%.txt",
+    L"pcast:%00.txt",
+    L"pcast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"picasa:%.txt",
+    L"picasa:%00.txt",
+    L"picasa:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"plaxo:%.txt",
+    L"plaxo:%00.txt",
+    L"plaxo:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"play:%.txt",
+    L"play:%00.txt",
+    L"play:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"podcast:%.txt",
+    L"podcast:%00.txt",
+    L"podcast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"ppmate:%.txt",
+    L"ppmate:%00.txt",
+    L"ppmate:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"ppmates:%.txt",
+    L"ppmates:%00.txt",
+    L"ppmates:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"ppstream:%.txt",
+    L"ppstream:%00.txt",
+    L"ppstream:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"quicktime:%.txt",
+    L"quicktime:%00.txt",
+    L"quicktime:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"realplayer.autoplay.6:%.txt",
+    L"realplayer.autoplay.6:%00.txt",
+    L"realplayer.autoplay.6:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"realplayer.cdburn.6:%.txt",
+    L"realplayer.cdburn.6:%00.txt",
+    L"realplayer.cdburn.6:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"rhap:%.txt",
+    L"rhap:%00.txt",
+    L"rhap:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"sc:%.txt",
+    L"sc:%00.txt",
+    L"sc:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"search-ms:%.txt",
+    L"search-ms:%00.txt",
+    L"search-ms:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"shareaza:%.txt",
+    L"shareaza:%00.txt",
+    L"shareaza:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"shell:%.txt",
+    L"shell:%00.txt",
+    L"shell:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"shout:%.txt",
+    L"shout:%00.txt",
+    L"shout:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"sig2dat:%.txt",
+    L"sig2dat:%00.txt",
+    L"sig2dat:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"sop:%.txt",
+    L"sop:%00.txt",
+    L"sop:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"steam:%.txt",
+    L"steam:%00.txt",
+    L"steam:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"stssync:%.txt",
+    L"stssync:%00.txt",
+    L"stssync:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"svn:%.txt",
+    L"svn:%00.txt",
+    L"svn:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"svn+ssh:%.txt",
+    L"svn+ssh:%00.txt",
+    L"svn+ssh:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"synacast:%.txt",
+    L"synacast:%00.txt",
+    L"synacast:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"torrent:%.txt",
+    L"torrent:%00.txt",
+    L"torrent:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"tsvn:%.txt",
+    L"tsvn:%00.txt",
+    L"tsvn:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"tvants:%.txt",
+    L"tvants:%00.txt",
+    L"tvants:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"tvu:%.txt",
+    L"tvu:%00.txt",
+    L"tvu:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"unsv:%.txt",
+    L"unsv:%00.txt",
+    L"unsv:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"uvox:%.txt",
+    L"uvox:%00.txt",
+    L"uvox:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"ventrilo:%.txt",
+    L"ventrilo:%00.txt",
+    L"ventrilo:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"vs:%.txt",
+    L"vs:%00.txt",
+    L"vs:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"zune:%.txt",
+    L"zune:%00.txt",
+    L"zune:%41%2D%31%5Ftest&quot;ing?%41%31%00.txt",
+    L"https://example.com/?a=123&b=456",
+    L"https://example.com/#123?a=123&b=456",
+    L"https://example.com/?#123a=123&b=456",
+    L"https://example.com/?a=123&b=456#123",
+    L"mailto:%41%42%23%31",
+    L"mailto:%41%42%23%31#fragment",
+    L"news:%41%42%23%31",
+    L"news:%41%42%23%31#fragment",
+    L"microsoft-edge:%41%42%23%31",
+    L"microsoft-edge:%41%42%23%31#fragment",
+    L"microsoft-edge:%41%42%23%31#fragment#",
+    L"microsoft-edge:%41%42%23%31####",
+    L"something-unknown:",
+    L"something-unknown:x=123",
+    L"something-unknown:?=123",
+    L"something-unknown:#code=0123456789%200123456789&x=01234567890123456789",
+};
+
+#endif  // mozilla_TestUrisToValidate_h
--- a/widget/windows/tests/moz.build
+++ b/widget/windows/tests/moz.build
@@ -1,6 +1,29 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+GeckoCppUnitTests(
+    [
+      'TestUriValidation',
+    ],
+    linkage=None
+)
+
+LOCAL_INCLUDES += [
+]
+
+OS_LIBS += [
+  'oleaut32',
+  'ole32',
+  'shell32',
+  'shlwapi',
+  'urlmon',
+]
+
+if CONFIG['OS_TARGET'] == 'WINNT' and CONFIG['CC_TYPE'] in ('gcc', 'clang'):
+    # This allows us to use wmain as the entry point on mingw
+    LDFLAGS += [
+        '-municode',
+    ]