summaryrefslogtreecommitdiffstats
path: root/toolkit/system/windowsproxy
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/system/windowsproxy')
-rw-r--r--toolkit/system/windowsproxy/ProxyUtils.cpp162
-rw-r--r--toolkit/system/windowsproxy/ProxyUtils.h21
-rw-r--r--toolkit/system/windowsproxy/components.conf13
-rw-r--r--toolkit/system/windowsproxy/moz.build18
-rw-r--r--toolkit/system/windowsproxy/nsWindowsSystemProxySettings.cpp247
-rw-r--r--toolkit/system/windowsproxy/tests/gtest/TestProxyBypassRules.cpp47
-rw-r--r--toolkit/system/windowsproxy/tests/gtest/moz.build18
7 files changed, 526 insertions, 0 deletions
diff --git a/toolkit/system/windowsproxy/ProxyUtils.cpp b/toolkit/system/windowsproxy/ProxyUtils.cpp
new file mode 100644
index 0000000000..37af2f8f4a
--- /dev/null
+++ b/toolkit/system/windowsproxy/ProxyUtils.cpp
@@ -0,0 +1,162 @@
+/* -*- 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/. */
+
+#include "ProxyUtils.h"
+
+#include "mozilla/IntegerRange.h"
+#include "nsReadableUtils.h"
+#include "nsTArray.h"
+#include "prnetdb.h"
+#include "prtypes.h"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+
+/**
+ * Normalize the short IP form into the complete form.
+ * For example, it converts "192.168" into "192.168.0.0"
+ */
+static void NormalizeAddr(const nsACString& aAddr, nsCString& aNormalized) {
+ nsTArray<nsCString> addr;
+ ParseString(aAddr, '.', addr);
+ aNormalized = StringJoin("."_ns, IntegerRange(4),
+ [&addr](nsACString& dst, const int i) {
+ if (i < addr.Length()) {
+ dst.Append(addr[i]);
+ } else {
+ dst.Append('0');
+ }
+ });
+}
+
+static PRUint32 MaskIPv4Addr(PRUint32 aAddr, uint16_t aMaskLen) {
+ if (aMaskLen == 32) {
+ return aAddr;
+ }
+ return PR_htonl(PR_ntohl(aAddr) & (~0L << (32 - aMaskLen)));
+}
+
+static void MaskIPv6Addr(PRIPv6Addr& aAddr, uint16_t aMaskLen) {
+ if (aMaskLen == 128) {
+ return;
+ }
+
+ if (aMaskLen > 96) {
+ aAddr.pr_s6_addr32[3] =
+ PR_htonl(PR_ntohl(aAddr.pr_s6_addr32[3]) & (~0L << (128 - aMaskLen)));
+ } else if (aMaskLen > 64) {
+ aAddr.pr_s6_addr32[3] = 0;
+ aAddr.pr_s6_addr32[2] =
+ PR_htonl(PR_ntohl(aAddr.pr_s6_addr32[2]) & (~0L << (96 - aMaskLen)));
+ } else if (aMaskLen > 32) {
+ aAddr.pr_s6_addr32[3] = 0;
+ aAddr.pr_s6_addr32[2] = 0;
+ aAddr.pr_s6_addr32[1] =
+ PR_htonl(PR_ntohl(aAddr.pr_s6_addr32[1]) & (~0L << (64 - aMaskLen)));
+ } else {
+ aAddr.pr_s6_addr32[3] = 0;
+ aAddr.pr_s6_addr32[2] = 0;
+ aAddr.pr_s6_addr32[1] = 0;
+ aAddr.pr_s6_addr32[0] =
+ PR_htonl(PR_ntohl(aAddr.pr_s6_addr32[0]) & (~0L << (32 - aMaskLen)));
+ }
+
+ return;
+}
+
+static bool IsMatchMask(const nsACString& aHost, const nsACString& aOverride) {
+ nsresult rv;
+
+ auto tokenEnd = aOverride.FindChar('/');
+ if (tokenEnd == -1) {
+ return false;
+ }
+
+ nsAutoCString prefixStr(
+ Substring(aOverride, tokenEnd + 1, aOverride.Length() - tokenEnd - 1));
+ auto maskLen = prefixStr.ToInteger(&rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ nsAutoCString override(aOverride);
+ NormalizeAddr(Substring(aOverride, 0, tokenEnd), override);
+
+ PRNetAddr prAddrHost;
+ PRNetAddr prAddrOverride;
+ if (PR_SUCCESS !=
+ PR_StringToNetAddr(PromiseFlatCString(aHost).get(), &prAddrHost) ||
+ PR_SUCCESS != PR_StringToNetAddr(override.get(), &prAddrOverride)) {
+ return false;
+ }
+
+ if (prAddrHost.raw.family == PR_AF_INET &&
+ prAddrOverride.raw.family == PR_AF_INET) {
+ return MaskIPv4Addr(prAddrHost.inet.ip, maskLen) ==
+ MaskIPv4Addr(prAddrOverride.inet.ip, maskLen);
+ } else if (prAddrHost.raw.family == PR_AF_INET6 &&
+ prAddrOverride.raw.family == PR_AF_INET6) {
+ MaskIPv6Addr(prAddrHost.ipv6.ip, maskLen);
+ MaskIPv6Addr(prAddrOverride.ipv6.ip, maskLen);
+
+ return memcmp(&prAddrHost.ipv6.ip, &prAddrOverride.ipv6.ip,
+ sizeof(PRIPv6Addr)) == 0;
+ }
+
+ return false;
+}
+
+static bool IsMatchWildcard(const nsACString& aHost,
+ const nsACString& aOverride) {
+ nsAutoCString host(aHost);
+ nsAutoCString override(aOverride);
+
+ int32_t overrideLength = override.Length();
+ int32_t tokenStart = 0;
+ int32_t offset = 0;
+ bool star = false;
+
+ while (tokenStart < overrideLength) {
+ int32_t tokenEnd = override.FindChar('*', tokenStart);
+ if (tokenEnd == tokenStart) {
+ // Star is the first character in the token.
+ star = true;
+ tokenStart++;
+ // If the character following the '*' is a '.' character then skip
+ // it so that "*.foo.com" allows "foo.com".
+ if (override.FindChar('.', tokenStart) == tokenStart) {
+ nsAutoCString token(Substring(override, tokenStart + 1,
+ overrideLength - tokenStart - 1));
+ if (host.Equals(token)) {
+ return true;
+ }
+ }
+ } else {
+ if (tokenEnd == -1) {
+ tokenEnd = overrideLength; // no '*' char, match rest of string
+ }
+ nsAutoCString token(
+ Substring(override, tokenStart, tokenEnd - tokenStart));
+ offset = host.Find(token, /* aIgnoreCase = */ false, offset);
+ if (offset == -1 || (!star && offset)) {
+ return false;
+ }
+ star = false;
+ tokenStart = tokenEnd;
+ offset += token.Length();
+ }
+ }
+
+ return (star || (offset == static_cast<int32_t>(host.Length())));
+}
+
+bool IsHostProxyEntry(const nsACString& aHost, const nsACString& aOverride) {
+ return IsMatchMask(aHost, aOverride) || IsMatchWildcard(aHost, aOverride);
+}
+
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
diff --git a/toolkit/system/windowsproxy/ProxyUtils.h b/toolkit/system/windowsproxy/ProxyUtils.h
new file mode 100644
index 0000000000..547ef643b5
--- /dev/null
+++ b/toolkit/system/windowsproxy/ProxyUtils.h
@@ -0,0 +1,21 @@
+/* -*- 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 mozilla_toolkit_system_windowsproxy_ProxyUtils_h
+#define mozilla_toolkit_system_windowsproxy_ProxyUtils_h
+
+#include "nsString.h"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+
+bool IsHostProxyEntry(const nsACString& aHost, const nsACString& aOverride);
+
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
+
+#endif // mozilla_toolkit_system_windowsproxy_ProxyUtils_h
diff --git a/toolkit/system/windowsproxy/components.conf b/toolkit/system/windowsproxy/components.conf
new file mode 100644
index 0000000000..4c8c62fc43
--- /dev/null
+++ b/toolkit/system/windowsproxy/components.conf
@@ -0,0 +1,13 @@
+# -*- 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/.
+
+Classes = [
+ {
+ 'cid': '{4e22d3ea-aaa2-436e-ada4-9247de57d367}',
+ 'contract_ids': ['@mozilla.org/system-proxy-settings;1'],
+ 'type': 'nsWindowsSystemProxySettings',
+ },
+]
diff --git a/toolkit/system/windowsproxy/moz.build b/toolkit/system/windowsproxy/moz.build
new file mode 100644
index 0000000000..8cc1f085f7
--- /dev/null
+++ b/toolkit/system/windowsproxy/moz.build
@@ -0,0 +1,18 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking")
+
+TEST_DIRS += ["tests/gtest"]
+
+SOURCES += ["nsWindowsSystemProxySettings.cpp", "ProxyUtils.cpp"]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/toolkit/system/windowsproxy/nsWindowsSystemProxySettings.cpp b/toolkit/system/windowsproxy/nsWindowsSystemProxySettings.cpp
new file mode 100644
index 0000000000..72ff740788
--- /dev/null
+++ b/toolkit/system/windowsproxy/nsWindowsSystemProxySettings.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+#include <windows.h>
+#include <ras.h>
+#include <wininet.h>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "nsISystemProxySettings.h"
+#include "mozilla/Components.h"
+#include "nsPrintfCString.h"
+#include "nsNetCID.h"
+#include "nsThreadUtils.h"
+#include "GeckoProfiler.h"
+#include "prnetdb.h"
+#include "ProxyUtils.h"
+
+class nsWindowsSystemProxySettings final : public nsISystemProxySettings {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISYSTEMPROXYSETTINGS
+
+ nsWindowsSystemProxySettings(){};
+
+ private:
+ ~nsWindowsSystemProxySettings(){};
+
+ bool MatchOverride(const nsACString& aHost);
+ bool PatternMatch(const nsACString& aHost, const nsACString& aOverride);
+};
+
+NS_IMPL_ISUPPORTS(nsWindowsSystemProxySettings, nsISystemProxySettings)
+
+NS_IMETHODIMP
+nsWindowsSystemProxySettings::GetMainThreadOnly(bool* aMainThreadOnly) {
+ // bug 1366133: if you change this to main thread only, please handle
+ // nsProtocolProxyService::Resolve_Internal carefully to avoid hang on main
+ // thread.
+ *aMainThreadOnly = false;
+ return NS_OK;
+}
+
+static void SetProxyResult(const char* aType, const nsACString& aHostPort,
+ nsACString& aResult) {
+ aResult.AssignASCII(aType);
+ aResult.Append(' ');
+ aResult.Append(aHostPort);
+}
+
+static void SetProxyResultDirect(nsACString& aResult) {
+ // For whatever reason, a proxy is not to be used.
+ aResult.AssignLiteral("DIRECT");
+}
+
+static nsresult ReadInternetOption(uint32_t aOption, uint32_t& aFlags,
+ nsAString& aValue) {
+ // Bug 1366133: InternetGetConnectedStateExW() may cause hangs
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ DWORD connFlags = 0;
+ WCHAR connName[RAS_MaxEntryName + 1];
+ MOZ_SEH_TRY {
+ InternetGetConnectedStateExW(&connFlags, connName,
+ mozilla::ArrayLength(connName), 0);
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { return NS_ERROR_FAILURE; }
+
+ INTERNET_PER_CONN_OPTIONW options[2];
+ options[0].dwOption = INTERNET_PER_CONN_FLAGS_UI;
+ options[1].dwOption = aOption;
+
+ INTERNET_PER_CONN_OPTION_LISTW list;
+ list.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LISTW);
+ list.pszConnection =
+ connFlags & INTERNET_CONNECTION_MODEM ? connName : nullptr;
+ list.dwOptionCount = mozilla::ArrayLength(options);
+ list.dwOptionError = 0;
+ list.pOptions = options;
+
+ unsigned long size = sizeof(INTERNET_PER_CONN_OPTION_LISTW);
+ if (!InternetQueryOptionW(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION,
+ &list, &size)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aFlags = options[0].Value.dwValue;
+ aValue.Assign(options[1].Value.pszValue);
+ GlobalFree(options[1].Value.pszValue);
+
+ return NS_OK;
+}
+
+bool nsWindowsSystemProxySettings::MatchOverride(const nsACString& aHost) {
+ nsresult rv;
+ uint32_t flags = 0;
+ nsAutoString buf;
+
+ rv = ReadInternetOption(INTERNET_PER_CONN_PROXY_BYPASS, flags, buf);
+ if (NS_FAILED(rv)) return false;
+
+ NS_ConvertUTF16toUTF8 cbuf(buf);
+
+ nsAutoCString host(aHost);
+ int32_t start = 0;
+ int32_t end = cbuf.Length();
+
+ // Windows formats its proxy override list in the form:
+ // server;server;server where 'server' is a server name pattern or IP
+ // address, or "<local>". "<local>" must be translated to
+ // "localhost;127.0.0.1".
+ // In a server name pattern, a '*' character matches any substring and
+ // all other characters must match themselves; the whole pattern must match
+ // the whole hostname.
+ while (true) {
+ int32_t delimiter = cbuf.FindCharInSet(" ;", start);
+ if (delimiter == -1) delimiter = end;
+
+ if (delimiter != start) {
+ const nsAutoCString override(Substring(cbuf, start, delimiter - start));
+ if (override.EqualsLiteral("<local>")) {
+ PRNetAddr addr;
+ bool isIpAddr = (PR_StringToNetAddr(host.get(), &addr) == PR_SUCCESS);
+
+ // Don't use proxy for local hosts (plain hostname, no dots)
+ if (!isIpAddr && !host.Contains('.')) {
+ return true;
+ }
+
+ if (host.EqualsLiteral("127.0.0.1") || host.EqualsLiteral("::1")) {
+ return true;
+ }
+ } else if (PatternMatch(host, override)) {
+ return true;
+ }
+ }
+
+ if (delimiter == end) break;
+ start = ++delimiter;
+ }
+
+ return false;
+}
+
+bool nsWindowsSystemProxySettings::PatternMatch(const nsACString& aHost,
+ const nsACString& aOverride) {
+ return mozilla::toolkit::system::IsHostProxyEntry(aHost, aOverride);
+}
+
+nsresult nsWindowsSystemProxySettings::GetPACURI(nsACString& aResult) {
+ AUTO_PROFILER_LABEL("nsWindowsSystemProxySettings::GetPACURI", OTHER);
+ nsresult rv;
+ uint32_t flags = 0;
+ nsAutoString buf;
+
+ rv = ReadInternetOption(INTERNET_PER_CONN_AUTOCONFIG_URL, flags, buf);
+ if (!(flags & PROXY_TYPE_AUTO_PROXY_URL)) {
+ aResult.Truncate();
+ return rv;
+ }
+
+ if (NS_SUCCEEDED(rv)) aResult = NS_ConvertUTF16toUTF8(buf);
+ return rv;
+}
+
+nsresult nsWindowsSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
+ const nsACString& aScheme,
+ const nsACString& aHost,
+ const int32_t aPort,
+ nsACString& aResult) {
+ nsresult rv;
+ uint32_t flags = 0;
+ nsAutoString buf;
+
+ rv = ReadInternetOption(INTERNET_PER_CONN_PROXY_SERVER, flags, buf);
+ if (NS_FAILED(rv) || !(flags & PROXY_TYPE_PROXY)) {
+ SetProxyResultDirect(aResult);
+ return NS_OK;
+ }
+
+ if (MatchOverride(aHost)) {
+ SetProxyResultDirect(aResult);
+ return NS_OK;
+ }
+
+ NS_ConvertUTF16toUTF8 cbuf(buf);
+
+ constexpr auto kSocksPrefix = "socks="_ns;
+ nsAutoCString prefix;
+ ToLowerCase(aScheme, prefix);
+
+ prefix.Append('=');
+
+ nsAutoCString specificProxy;
+ nsAutoCString defaultProxy;
+ nsAutoCString socksProxy;
+ int32_t start = 0;
+ int32_t end = cbuf.Length();
+
+ while (true) {
+ int32_t delimiter = cbuf.FindCharInSet(" ;", start);
+ if (delimiter == -1) delimiter = end;
+
+ if (delimiter != start) {
+ const nsAutoCString proxy(Substring(cbuf, start, delimiter - start));
+ if (proxy.FindChar('=') == -1) {
+ // If a proxy name is listed by itself, it is used as the
+ // default proxy for any protocols that do not have a specific
+ // proxy specified.
+ // (http://msdn.microsoft.com/en-us/library/aa383996%28VS.85%29.aspx)
+ defaultProxy = proxy;
+ } else if (proxy.Find(prefix) == 0) {
+ // To list a proxy for a specific protocol, the string must
+ // follow the format "<protocol>=<protocol>://<proxy_name>".
+ // (http://msdn.microsoft.com/en-us/library/aa383996%28VS.85%29.aspx)
+ specificProxy = Substring(proxy, prefix.Length());
+ break;
+ } else if (proxy.Find(kSocksPrefix) == 0) {
+ // SOCKS proxy.
+ socksProxy =
+ Substring(proxy, kSocksPrefix.Length()); // "socks=" length.
+ }
+ }
+
+ if (delimiter == end) break;
+ start = ++delimiter;
+ }
+
+ if (!specificProxy.IsEmpty())
+ SetProxyResult("PROXY", specificProxy,
+ aResult); // Protocol-specific proxy.
+ else if (!defaultProxy.IsEmpty())
+ SetProxyResult("PROXY", defaultProxy, aResult); // Default proxy.
+ else if (!socksProxy.IsEmpty())
+ SetProxyResult("SOCKS", socksProxy, aResult); // SOCKS proxy.
+ else
+ SetProxyResultDirect(aResult); // Direct connection.
+
+ return NS_OK;
+}
+
+NS_IMPL_COMPONENT_FACTORY(nsWindowsSystemProxySettings) {
+ return mozilla::MakeAndAddRef<nsWindowsSystemProxySettings>()
+ .downcast<nsISupports>();
+}
diff --git a/toolkit/system/windowsproxy/tests/gtest/TestProxyBypassRules.cpp b/toolkit/system/windowsproxy/tests/gtest/TestProxyBypassRules.cpp
new file mode 100644
index 0000000000..9ccb216388
--- /dev/null
+++ b/toolkit/system/windowsproxy/tests/gtest/TestProxyBypassRules.cpp
@@ -0,0 +1,47 @@
+/* -*- 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 http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "ProxyUtils.h"
+
+using namespace mozilla::toolkit::system;
+
+TEST(WindowsProxy, TestProxyBypassRules)
+{
+ EXPECT_TRUE(IsHostProxyEntry("mozilla.org"_ns, "mozilla.org"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("mozilla.org"_ns, "*mozilla.org"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("mozilla.org"_ns, "*.mozilla.org"_ns));
+ EXPECT_FALSE(IsHostProxyEntry("notmozilla.org"_ns, "*.mozilla.org"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("www.mozilla.org"_ns, "*mozilla.org"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("www.mozilla.org"_ns, "*.mozilla.org"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("www.mozilla.com"_ns, "*.mozilla.*"_ns));
+}
+
+TEST(WindowsProxy, TestProxyBypassRulesIPv4)
+{
+ EXPECT_TRUE(IsHostProxyEntry("192.168.1.1"_ns, "192.168.1.*"_ns));
+ EXPECT_FALSE(IsHostProxyEntry("192.168.1.1"_ns, "192.168.2.*"_ns));
+
+ EXPECT_TRUE(IsHostProxyEntry("10.1.2.3"_ns, "10.0.0.0/8"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("192.168.192.1"_ns, "192.168/16"_ns));
+ EXPECT_FALSE(IsHostProxyEntry("192.168.192.1"_ns, "192.168/17"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("192.168.192.1"_ns, "192.168.128/17"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("192.168.1.1"_ns, "192.168.1.1/32"_ns));
+}
+
+TEST(WindowsProxy, TestProxyBypassRulesIPv6)
+{
+ EXPECT_TRUE(IsHostProxyEntry("2001:0DB8:ABCD:0012:0123:4567:89AB:CDEF"_ns,
+ "2001:db8:abcd:0012::0/64"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("2001:0DB8:ABCD:0012:0000:4567:89AB:CDEF"_ns,
+ "2001:db8:abcd:0012::0/80"_ns));
+ EXPECT_FALSE(IsHostProxyEntry("2001:0DB8:ABCD:0012:0123:4567:89AB:CDEF"_ns,
+ "2001:db8:abcd:0012::0/80"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("2001:0DB8:ABCD:0012:0000:0000:89AB:CDEF"_ns,
+ "2001:db8:abcd:0012::0/96"_ns));
+ EXPECT_FALSE(IsHostProxyEntry("2001:0DB8:ABCD:0012:0123:4567:89AB:CDEF"_ns,
+ "2001:db8:abcd:0012::0/96"_ns));
+}
diff --git a/toolkit/system/windowsproxy/tests/gtest/moz.build b/toolkit/system/windowsproxy/tests/gtest/moz.build
new file mode 100644
index 0000000000..46501cdf86
--- /dev/null
+++ b/toolkit/system/windowsproxy/tests/gtest/moz.build
@@ -0,0 +1,18 @@
+# -*- 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/.
+
+UNIFIED_SOURCES += [
+ "TestProxyBypassRules.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/toolkit/system/windowsproxy",
+]
+
+FINAL_LIBRARY = "xul-gtest"
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wshadow"]