Bug 356831 - Proxy autodiscovery doesn't check DHCP (option 252) r=bagder,valentin
authorPolly Shaw <polly.shaw@gmail.com>
Sun, 22 Apr 2018 18:13:11 +0100
changeset 460570 204bb43af94351678a0a639bbc0bc7476e91d3aa
parent 460569 e9b1d02e24f0f461032e80005d32aa00ec87d775
child 460571 c8c27bdc5f543b688f23104afab52bfcded76ec7
push id165
push userfmarier@mozilla.com
push dateMon, 30 Apr 2018 23:50:51 +0000
reviewersbagder, valentin
bugs356831
milestone61.0a1
Bug 356831 - Proxy autodiscovery doesn't check DHCP (option 252) r=bagder,valentin This patch addresses an issue with Firefox's proxy detection on networks which do not have their a proxy auto-configuration (PAC) file hosted at http://wpad/wpad.dat, and instead make use of DHCP option 252 for broadcasting the address of the PAC file. See https://findproxyforurl.com/wpad-introduction/ for an introduction to the protocol. Prior to this patch, proxy auto-detect missed out the DHCP query stage, and just looked for a PAC file at http://wpad/wpad.dat This patch only addresses the issue for Firefox on Windows, although it defines a DHCP client interface which could be implemented on other platforms. The high-level components of this patch are: * nsIDHCPClient.idl - this is an interface which has been defined for querying the DHCP server. * nsPACMan.cpp - where previously when the PAC URL was simply set to a constant of http://wpad/wpad.dat, it now dispatches an asynchronous command to the proxy thread. The class ExecutePACThreadAction has been augmented to include an instruction to 'ConfigureWPAD' (Configure Web-proxy auto-detect), and a new class, 'ConfigureWPADComplete' has been created to relay the result (the URL of the PAC file) back to the nsPACMan object. * nsProtocolProxyService.cpp Minor changes to reflect the fact that the PAC URL not being set does not always mean there is no PAC to be used; instead it could be in the process of being detected. * TestPACMan.cpp This is a new file, and tests only the DHCP auto-detect functionality. Some tests use multiple threads, as they test the non-blocking proxy detection. * DHCPUtils.cpp A class containing the main logic for querying DHCP. * WindowsNetworkFunctionsWrapper.cpp A very thin wrapper around the Windows API calls needed by DHCPUtils. This class was introduced so it could be mocked out in tests. * nsWindowsDHCPClient.cpp * An implementation of the interface defined in nsIDHCPClient.idl. Fairly thin: most logic is implemented in DHCPUtils. * TestDHCPUtils.cpp Tests for DHCPUtils and nsWindowsDHCPClient MozReview-Commit-ID: HinC1UevOon
modules/libpref/init/all.js
netwerk/base/moz.build
netwerk/base/nsIDHCPClient.idl
netwerk/base/nsPACMan.cpp
netwerk/base/nsPACMan.h
netwerk/base/nsProtocolProxyService.cpp
netwerk/base/nsProtocolProxyService.h
netwerk/build/nsNetCID.h
netwerk/test/gtest/TestPACMan.cpp
netwerk/test/gtest/moz.build
toolkit/moz.build
toolkit/system/windowsDHCPClient/DHCPUtils.cpp
toolkit/system/windowsDHCPClient/DHCPUtils.h
toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.cpp
toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.h
toolkit/system/windowsDHCPClient/moz.build
toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.cpp
toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.h
toolkit/system/windowsDHCPClient/tests/gtest/TestDHCPUtils.cpp
toolkit/system/windowsDHCPClient/tests/gtest/moz.build
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2159,16 +2159,17 @@ pref("network.cookie.thirdparty.sessionO
 pref("network.cookie.thirdparty.nonsecureSessionOnly", false);
 pref("network.cookie.leave-secure-alone",   true);
 pref("network.cookie.same-site.enabled",    true); // Honor the SameSite cookie attribute
 pref("network.cookie.ipc.sync",             false);
 pref("network.cookie.lifetimePolicy",       0); // 0-accept, 1-dontUse 2-acceptForSession, 3-acceptForNDays
 pref("network.cookie.prefsMigrated",        false);
 pref("network.cookie.lifetime.days",        90); // Ignored unless network.cookie.lifetimePolicy is 3.
 
+pref("network.proxy.enable_wpad_over_dhcp", true);
 // The PAC file to load.  Ignored unless network.proxy.type is 2.
 pref("network.proxy.autoconfig_url", "");
 // Strip off paths when sending URLs to PAC scripts
 pref("network.proxy.autoconfig_url.include_path", false);
 
 // If we cannot load the PAC file, then try again (doubling from interval_min
 // until we reach interval_max or the PAC file is successfully loaded).
 pref("network.proxy.autoconfig_retry_interval_min", 5);    // 5 seconds
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -33,16 +33,17 @@ XPIDL_SOURCES += [
     'nsIChannelWithDivertableParentListener.idl',
     'nsIChildChannel.idl',
     'nsIClassifiedChannel.idl',
     'nsIClassOfService.idl',
     'nsIContentSniffer.idl',
     'nsIDashboard.idl',
     'nsIDashboardEventNotifier.idl',
     'nsIDeprecationWarner.idl',
+    'nsIDHCPClient.idl',
     'nsIDivertableChannel.idl',
     'nsIDownloader.idl',
     'nsIEncodedChannel.idl',
     'nsIExternalProtocolHandler.idl',
     'nsIFileStreams.idl',
     'nsIFileURL.idl',
     'nsIForcePendingChannel.idl',
     'nsIFormPOSTActionChannel.idl',
new file mode 100644
--- /dev/null
+++ b/netwerk/base/nsIDHCPClient.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsISupports.idl"
+
+/**
+ * This interface allows the proxy code to access the DHCP Options in a platform-specific way
+ */
+[scriptable, uuid(aee75dc0-be1a-46b9-9e0c-31a6899c175c)]
+interface nsIDHCPClient : nsISupports
+{
+
+    /**
+    * returns the DHCP Option designated by the option parameter
+    */
+    ACString getOption(in uint8_t option);
+};
--- a/netwerk/base/nsPACMan.cpp
+++ b/netwerk/base/nsPACMan.cpp
@@ -1,36 +1,41 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "nsPACMan.h"
-#include "nsThreadUtils.h"
+
+#include "mozilla/Preferences.h"
+#include "nsContentUtils.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsIAuthPrompt.h"
-#include "nsIPromptFactory.h"
+#include "nsIDHCPClient.h"
 #include "nsIHttpChannel.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
-#include "nsNetUtil.h"
-#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIPromptFactory.h"
+#include "nsIProtocolProxyService.h"
 #include "nsISystemProxySettings.h"
-#include "nsContentUtils.h"
-#include "mozilla/Preferences.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
 
 //-----------------------------------------------------------------------------
 
 namespace mozilla {
 namespace net {
 
 LazyLogModule gProxyLog("proxy");
 
 #undef LOG
 #define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args)
+#define MOZ_WPAD_URL "http://wpad/wpad.dat"
+#define MOZ_DHCP_WPAD_OPTION 252
 
 // The PAC thread does evaluations of both PAC files and
 // nsISystemProxySettings because they can both block the calling thread and we
 // don't want that on the main thread
 
 // Check to see if the underlying request was not an error page in the case of
 // a HTTP request.  For other types of channels, just return true.
 static bool
@@ -70,16 +75,36 @@ GetExtraJSContextHeapSize()
 
       extraSize = value;
     }
   }
 
   return extraSize < 0 ? 0 : extraSize;
 }
 
+// Read network proxy type from preference
+// Used to verify that the preference is WPAD in nsPACMan::ConfigureWPAD
+nsresult
+GetNetworkProxyTypeFromPref(int32_t* type)
+{
+  *type = 0;
+  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+  if (!prefs) {
+    LOG(("Failed to get a preference service object"));
+    return NS_ERROR_FACTORY_NOT_REGISTERED;
+  }
+  nsresult rv = prefs->GetIntPref("network.proxy.type", type);
+  if (!NS_SUCCEEDED(rv)) {
+    LOG(("Failed to retrieve network.proxy.type from prefs"));
+    return rv;
+  }
+  LOG(("network.proxy.type pref retrieved: %d\n", *type));
+  return NS_OK;
+}
 
 //-----------------------------------------------------------------------------
 
 // The ExecuteCallback runnable is triggered by
 // nsPACManCallback::OnQueryComplete on the Main thread when its completion is
 // discovered on the pac thread
 
 class ExecuteCallback final : public Runnable
@@ -87,22 +112,22 @@ class ExecuteCallback final : public Run
 public:
   ExecuteCallback(nsPACManCallback* aCallback, nsresult status)
     : Runnable("net::ExecuteCallback")
     , mCallback(aCallback)
     , mStatus(status)
   {
   }
 
-  void SetPACString(const nsCString &pacString)
+  void SetPACString(const nsACString &pacString)
   {
     mPACString = pacString;
   }
 
-  void SetPACURL(const nsCString &pacURL)
+  void SetPACURL(const nsACString &pacURL)
   {
     mPACURL = pacURL;
   }
 
   NS_IMETHOD Run() override
   {
     mCallback->OnQueryComplete(mStatus, mPACString, mPACURL);
     mCallback = nullptr;
@@ -186,55 +211,90 @@ public:
   {
     MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
     mPACMan->mLoader = nullptr;
     mPACMan->PostProcessPendingQ();
     return NS_OK;
   }
 
 private:
-    RefPtr<nsPACMan> mPACMan;
+  RefPtr<nsPACMan> mPACMan;
+};
+
+
+//-----------------------------------------------------------------------------
+
+// ConfigureWPADComplete allows the PAC thread to tell the main thread that
+// the URL for the PAC file has been found
+class ConfigureWPADComplete final : public Runnable
+{
+public:
+  ConfigureWPADComplete(nsPACMan *aPACMan, const nsACString &aPACURISpec)
+    : Runnable("net::ConfigureWPADComplete"),
+     mPACMan(aPACMan),
+     mPACURISpec(aPACURISpec)
+  {
+  }
+
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+    mPACMan->AssignPACURISpec(mPACURISpec);
+    mPACMan->ContinueLoadingAfterPACUriKnown();
+    return NS_OK;
+  }
+
+private:
+  RefPtr<nsPACMan> mPACMan;
+  nsCString mPACURISpec;
 };
 
 //-----------------------------------------------------------------------------
 
 // ExecutePACThreadAction is used to proxy actions from the main
-// thread onto the PAC thread. There are 3 options: process the queue,
-// cancel the queue, and setup the javascript context with a new PAC file
+// thread onto the PAC thread. There are 4 options: process the queue,
+// cancel the queue, query DHCP for the PAC option
+// and setup the javascript context with a new PAC file
 
 class ExecutePACThreadAction final : public Runnable
 {
 public:
   // by default we just process the queue
   explicit ExecutePACThreadAction(nsPACMan* aPACMan)
     : Runnable("net::ExecutePACThreadAction")
     , mPACMan(aPACMan)
     , mCancel(false)
     , mCancelStatus(NS_OK)
     , mSetupPAC(false)
     , mExtraHeapSize(0)
+    , mConfigureWPAD(false)
   { }
 
   void CancelQueue (nsresult status)
   {
     mCancel = true;
     mCancelStatus = status;
   }
 
   void SetupPAC (const char *text,
                  uint32_t datalen,
-                 nsCString &pacURI,
+                 const nsACString &pacURI,
                  uint32_t extraHeapSize)
   {
     mSetupPAC = true;
     mSetupPACData.Assign(text, datalen);
     mSetupPACURI = pacURI;
     mExtraHeapSize = extraHeapSize;
   }
 
+  void ConfigureWPAD()
+  {
+    mConfigureWPAD = true;
+  }
+
   NS_IMETHOD Run() override
   {
     MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
     if (mCancel) {
       mPACMan->CancelPendingQ(mCancelStatus);
       mCancel = false;
       return NS_OK;
     }
@@ -249,30 +309,40 @@ public:
                          mExtraHeapSize,
                          target);
 
       RefPtr<PACLoadComplete> runnable = new PACLoadComplete(mPACMan);
       mPACMan->Dispatch(runnable.forget());
       return NS_OK;
     }
 
+    if (mConfigureWPAD) {
+      nsAutoCString spec;
+      mConfigureWPAD = false;
+      mPACMan->ConfigureWPAD(spec);
+      RefPtr<ConfigureWPADComplete> runnable = new ConfigureWPADComplete(mPACMan, spec);
+      NS_DispatchToMainThread(runnable);
+      return NS_OK;
+    }
+
     mPACMan->ProcessPendingQ();
     return NS_OK;
   }
 
 private:
   RefPtr<nsPACMan> mPACMan;
 
   bool      mCancel;
   nsresult  mCancelStatus;
 
   bool                 mSetupPAC;
   uint32_t             mExtraHeapSize;
-  nsCString            mSetupPACData;
-  nsCString            mSetupPACURI;
+  nsCString           mSetupPACData;
+  nsCString           mSetupPACURI;
+  bool                 mConfigureWPAD;
 };
 
 //-----------------------------------------------------------------------------
 
 PendingPACQuery::PendingPACQuery(nsPACMan* pacMan,
                                  nsIURI* uri,
                                  nsPACManCallback* callback,
                                  bool mainThreadResponse)
@@ -283,30 +353,30 @@ PendingPACQuery::PendingPACQuery(nsPACMa
 {
   uri->GetAsciiSpec(mSpec);
   uri->GetAsciiHost(mHost);
   uri->GetScheme(mScheme);
   uri->GetPort(&mPort);
 }
 
 void
-PendingPACQuery::Complete(nsresult status, const nsCString &pacString)
+PendingPACQuery::Complete(nsresult status, const nsACString &pacString)
 {
   if (!mCallback)
     return;
   RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, status);
   runnable->SetPACString(pacString);
   if (mOnMainThreadOnly)
     mPACMan->Dispatch(runnable.forget());
   else
     runnable->Run();
 }
 
 void
-PendingPACQuery::UseAlternatePACFile(const nsCString &pacURL)
+PendingPACQuery::UseAlternatePACFile(const nsACString &pacURL)
 {
   if (!mCallback)
     return;
 
   RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, NS_OK);
   runnable->SetPACURL(pacURL);
   if (mOnMainThreadOnly)
     mPACMan->Dispatch(runnable.forget());
@@ -331,16 +401,19 @@ static const char *kPACIncludePath =
   "network.proxy.autoconfig_url.include_path";
 
 nsPACMan::nsPACMan(nsIEventTarget *mainThreadEventTarget)
   : NeckoTargetHolder(mainThreadEventTarget)
   , mLoadPending(false)
   , mShutdown(false)
   , mLoadFailureCount(0)
   , mInProgress(false)
+  , mAutoDetect(false)
+  , mWPADOverDHCPEnabled(false)
+  , mProxyConfigType(0)
 {
   MOZ_ASSERT(NS_IsMainThread(), "pacman must be created on main thread");
   if (!sThreadLocalSetup){
     sThreadLocalSetup = true;
     PR_NewThreadPrivateIndex(&sThreadLocalIndex, nullptr);
   }
   mPAC.SetThreadLocalIndex(sThreadLocalIndex);
   mIncludePath = Preferences::GetBool(kPACIncludePath, false);
@@ -390,17 +463,17 @@ nsPACMan::AsyncGetProxyForURI(nsIURI *ur
   if (mShutdown)
     return NS_ERROR_NOT_AVAILABLE;
 
   // Maybe Reload PAC
   if (!mPACURISpec.IsEmpty() && !mScheduledReload.IsNull() &&
       TimeStamp::Now() > mScheduledReload) {
     LOG(("nsPACMan::AsyncGetProxyForURI reload as scheduled\n"));
 
-    LoadPACFromURI(EmptyCString());
+    LoadPACFromURI(EmptyCString(), true);
   }
 
   RefPtr<PendingPACQuery> query =
     new PendingPACQuery(this, uri, callback, mainThreadResponse);
 
   if (IsPACURI(uri)) {
     // deal with this directly instead of queueing it
     query->Complete(NS_OK, EmptyCString());
@@ -423,26 +496,25 @@ nsPACMan::PostQuery(PendingPACQuery *que
   // add a reference to the query while it is in the pending list
   RefPtr<PendingPACQuery> addref(query);
   mPendingQ.insertBack(addref.forget().take());
   ProcessPendingQ();
   return NS_OK;
 }
 
 nsresult
-nsPACMan::LoadPACFromURI(const nsCString &spec)
+nsPACMan::LoadPACFromURI(const nsACString &aSpec, bool aIsScheduledReload)
 {
   NS_ENSURE_STATE(!mShutdown);
-  NS_ENSURE_ARG(!spec.IsEmpty() || !mPACURISpec.IsEmpty());
 
   nsCOMPtr<nsIStreamLoader> loader =
       do_CreateInstance(NS_STREAMLOADER_CONTRACTID);
   NS_ENSURE_STATE(loader);
 
-  LOG(("nsPACMan::LoadPACFromURI %s\n", spec.get()));
+  LOG(("nsPACMan::LoadPACFromURI aSpec: %s, aIsScheduledReload:%s\n", aSpec.BeginReading(), aIsScheduledReload? "true" : "false"));
   // Since we might get called from nsProtocolProxyService::Init, we need to
   // post an event back to the main thread before we try to use the IO service.
   //
   // But, we need to flag ourselves as loading, so that we queue up any PAC
   // queries the enter between now and when we actually load the PAC file.
 
   if (!mLoadPending) {
     nsCOMPtr<nsIRunnable> runnable =
@@ -453,40 +525,114 @@ nsPACMan::LoadPACFromURI(const nsCString
     if (NS_FAILED(rv))
       return rv;
     mLoadPending = true;
   }
 
   CancelExistingLoad();
 
   mLoader = loader;
-  if (!spec.IsEmpty()) {
-    mPACURISpec = spec;
+  if (!aIsScheduledReload) {
     mPACURIRedirectSpec.Truncate();
     mNormalPACURISpec.Truncate(); // set at load time
     mLoadFailureCount = 0;  // reset
+    mAutoDetect = aSpec.IsEmpty();
+    mPACURISpec.Assign(aSpec);
   }
 
   // reset to Null
   mScheduledReload = TimeStamp();
   return NS_OK;
 }
 
+nsresult
+nsPACMan::GetPACFromDHCP(nsACString &aSpec)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+  if (!mDHCPClient) {
+    LOG(("nsPACMan::GetPACFromDHCP DHCP option %d query failed because there is no DHCP client available\n", MOZ_DHCP_WPAD_OPTION));
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+  nsresult rv;
+  rv = mDHCPClient->GetOption(MOZ_DHCP_WPAD_OPTION, aSpec);
+  if (NS_FAILED(rv)) {
+    LOG(("nsPACMan::GetPACFromDHCP DHCP option %d query failed with result %d\n", MOZ_DHCP_WPAD_OPTION, (uint32_t)rv));
+  } else {
+    LOG(("nsPACMan::GetPACFromDHCP DHCP option %d query succeeded, finding PAC URL %s\n", MOZ_DHCP_WPAD_OPTION, aSpec.BeginReading()));
+  }
+  return rv;
+}
+
+nsresult
+nsPACMan::ConfigureWPAD(nsACString &aSpec)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
+
+  MOZ_RELEASE_ASSERT(mProxyConfigType == nsIProtocolProxyService::PROXYCONFIG_WPAD,
+            "WPAD is being executed when not selected by user");
+
+  aSpec.Truncate();
+  if (mWPADOverDHCPEnabled) {
+    GetPACFromDHCP(aSpec);
+  }
+
+  if (aSpec.IsEmpty()) {
+    // We diverge from the WPAD spec here in that we don't walk the
+    // hosts's FQDN, stripping components until we hit a TLD.  Doing so
+    // is dangerous in the face of an incomplete list of TLDs, and TLDs
+    // get added over time.  We could consider doing only a single
+    // substitution of the first component, if that proves to help
+    // compatibility.
+    aSpec.AssignLiteral(MOZ_WPAD_URL);
+  }
+  return NS_OK;
+}
+
+void
+nsPACMan::AssignPACURISpec(const nsACString &aSpec)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+  mPACURISpec.Assign(aSpec);
+}
+
 void
 nsPACMan::StartLoading()
 {
   MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
   mLoadPending = false;
 
   // CancelExistingLoad was called...
   if (!mLoader) {
     PostCancelPendingQ(NS_ERROR_ABORT);
     return;
   }
 
+  if (mAutoDetect) {
+    GetNetworkProxyTypeFromPref(&mProxyConfigType);
+    RefPtr<ExecutePACThreadAction> wpadConfigurer =
+      new ExecutePACThreadAction(this);
+    wpadConfigurer->ConfigureWPAD();
+    if (mPACThread) {
+      mPACThread->Dispatch(wpadConfigurer, nsIEventTarget::DISPATCH_NORMAL);
+    }
+  } else {
+    ContinueLoadingAfterPACUriKnown();
+  }
+}
+
+void
+nsPACMan::ContinueLoadingAfterPACUriKnown()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+
+  // CancelExistingLoad was called...
+  if (!mLoader) {
+    PostCancelPendingQ(NS_ERROR_ABORT);
+    return;
+  }
   if (NS_SUCCEEDED(mLoader->Init(this, nullptr))) {
     // Always hit the origin server when loading PAC.
     nsCOMPtr<nsIIOService> ios = do_GetIOService();
     if (ios) {
       nsCOMPtr<nsIChannel> channel;
       nsCOMPtr<nsIURI> pacURI;
       NS_NewURI(getter_AddRefs(pacURI), mPACURISpec);
 
@@ -794,16 +940,18 @@ nsPACMan::AsyncOnChannelRedirect(nsIChan
   callback->OnRedirectVerifyCallback(NS_OK);
   return NS_OK;
 }
 
 nsresult
 nsPACMan::Init(nsISystemProxySettings *systemProxySettings)
 {
   mSystemProxySettings = systemProxySettings;
+  mDHCPClient = do_GetService(NS_DHCPCLIENT_CONTRACTID);
+
 
   nsresult rv =
     NS_NewNamedThread("ProxyResolution", getter_AddRefs(mPACThread));
 
   return rv;
 }
 
 } // namespace net
--- a/netwerk/base/nsPACMan.h
+++ b/netwerk/base/nsPACMan.h
@@ -2,33 +2,34 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 nsPACMan_h__
 #define nsPACMan_h__
 
-#include "nsIStreamLoader.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Logging.h"
+#include "mozilla/net/NeckoTargetHolder.h"
+#include "mozilla/TimeStamp.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIChannelEventSink.h"
 #include "nsIInterfaceRequestor.h"
-#include "nsIChannelEventSink.h"
-#include "ProxyAutoConfig.h"
+#include "nsIStreamLoader.h"
 #include "nsThreadUtils.h"
 #include "nsIURI.h"
-#include "nsCOMPtr.h"
 #include "nsString.h"
-#include "mozilla/Attributes.h"
-#include "mozilla/LinkedList.h"
-#include "nsAutoPtr.h"
-#include "mozilla/TimeStamp.h"
-#include "mozilla/Logging.h"
-#include "mozilla/Atomics.h"
-#include "mozilla/net/NeckoTargetHolder.h"
+#include "ProxyAutoConfig.h"
 
 class nsISystemProxySettings;
+class nsIDHCPClient;
 class nsIThread;
 
 namespace mozilla {
 namespace net {
 
 class nsPACMan;
 class WaitForThreadShutdown;
 
@@ -47,31 +48,31 @@ public:
    *        This parameter holds the value of the PAC string.  It is empty when
    *        status is a failure code.
    * @param newPACURL
    *        This parameter holds the URL of a new PAC file that should be loaded
    *        before the query is evaluated again. At least one of pacString and
    *        newPACURL should be 0 length.
    */
   virtual void OnQueryComplete(nsresult status,
-                               const nsCString &pacString,
-                               const nsCString &newPACURL) = 0;
+                               const nsACString &pacString,
+                               const nsACString &newPACURL) = 0;
 };
 
 class PendingPACQuery final : public Runnable,
                               public LinkedListElement<PendingPACQuery>
 {
 public:
   PendingPACQuery(nsPACMan *pacMan, nsIURI *uri,
                   nsPACManCallback *callback,
                   bool mainThreadResponse);
 
   // can be called from either thread
-  void Complete(nsresult status, const nsCString &pacString);
-  void UseAlternatePACFile(const nsCString &pacURL);
+  void Complete(nsresult status, const nsACString &pacString);
+  void UseAlternatePACFile(const nsACString &pacURL);
 
   nsCString                  mSpec;
   nsCString                  mScheme;
   nsCString                  mHost;
   int32_t                    mPort;
 
   NS_IMETHOD Run(void) override;     /* Runnable */
 
@@ -126,17 +127,17 @@ public:
    * This method may be called to reload the PAC file.  While we are loading
    * the PAC file, any asynchronous PAC queries will be queued up to be
    * processed once the PAC file finishes loading.
    *
    * @param pacSpec
    *        The non normalized uri spec of this URI used for comparison with
    *        system proxy settings to determine if the PAC uri has changed.
    */
-  nsresult LoadPACFromURI(const nsCString &pacSpec);
+  nsresult LoadPACFromURI(const nsACString &aSpec, bool aIsScheduledReload = false);
 
   /**
    * Returns true if we are currently loading the PAC file.
    */
   bool IsLoading() { return mLoader != nullptr; }
 
   /**
    * Returns true if the given URI matches the URI of our PAC file or the
@@ -161,71 +162,91 @@ public:
     nsresult rv = uri->GetSpec(tmp);
     if (NS_FAILED(rv)) {
       return false;
     }
 
     return IsPACURI(tmp);
   }
 
+  bool IsUsingWPAD() {
+    return mAutoDetect;
+  }
+
   nsresult Init(nsISystemProxySettings *);
   static nsPACMan *sInstance;
 
   // PAC thread operations only
   void ProcessPendingQ();
   void CancelPendingQ(nsresult);
 
+  void SetWPADOverDHCPEnabled(bool aValue) { mWPADOverDHCPEnabled = aValue; }
+
 private:
   NS_DECL_NSISTREAMLOADEROBSERVER
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSICHANNELEVENTSINK
 
   friend class PendingPACQuery;
   friend class PACLoadComplete;
+  friend class ConfigureWPADComplete;
   friend class ExecutePACThreadAction;
   friend class WaitForThreadShutdown;
+  friend class TestPACMan;
 
   ~nsPACMan();
 
   /**
    * Cancel any existing load if any.
    */
   void CancelExistingLoad();
 
   /**
    * Start loading the PAC file.
    */
   void StartLoading();
 
   /**
+   * Continue loading the PAC file.
+   */
+  void ContinueLoadingAfterPACUriKnown();
+
+  /**
    * Reload the PAC file if there is reason to.
    */
   void MaybeReloadPAC();
 
   /**
    * Called when we fail to load the PAC file.
    */
   void OnLoadFailure();
 
   /**
    * PostQuery() only runs on the PAC thread and it is used to
    * place a pendingPACQuery into the queue and potentially
    * execute the queue if it was otherwise empty
    */
   nsresult PostQuery(PendingPACQuery *query);
 
+  // Having found the PAC URI on the PAC thread, copy it to a string which
+  // can be altered on the main thread.
+  void AssignPACURISpec(const nsACString &aSpec);
+
   // PAC thread operations only
   void PostProcessPendingQ();
   void PostCancelPendingQ(nsresult);
   bool ProcessPending();
+  nsresult GetPACFromDHCP(nsACString &aSpec);
+  nsresult ConfigureWPAD(nsACString &aSpec);
 
 private:
   ProxyAutoConfig mPAC;
   nsCOMPtr<nsIThread>           mPACThread;
   nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
+  nsCOMPtr<nsIDHCPClient> mDHCPClient;
 
   LinkedList<PendingPACQuery> mPendingQ; /* pac thread only */
 
   // These specs are not nsIURI so that they can be used off the main thread.
   // The non-normalized versions are directly from the configuration, the
   // normalized version has been extracted from an nsIURI
   nsCString                    mPACURISpec;
   nsCString                    mPACURIRedirectSpec;
@@ -234,16 +255,19 @@ private:
   nsCOMPtr<nsIStreamLoader>    mLoader;
   bool                         mLoadPending;
   Atomic<bool, Relaxed>        mShutdown;
   TimeStamp                    mScheduledReload;
   uint32_t                     mLoadFailureCount;
 
   bool                         mInProgress;
   bool                         mIncludePath;
+  bool                         mAutoDetect;
+  bool                         mWPADOverDHCPEnabled;
+  int32_t                      mProxyConfigType;
 };
 
 extern LazyLogModule gProxyLog;
 
 } // namespace net
 } // namespace mozilla
 
 #endif  // nsPACMan_h__
--- a/netwerk/base/nsProtocolProxyService.cpp
+++ b/netwerk/base/nsProtocolProxyService.cpp
@@ -57,17 +57,16 @@ namespace net {
 #undef LOG
 #define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args)
 
 //----------------------------------------------------------------------------
 
 #define PROXY_PREF_BRANCH  "network.proxy"
 #define PROXY_PREF(x)      PROXY_PREF_BRANCH "." x
 
-#define WPAD_URL "http://wpad/wpad.dat"
 
 //----------------------------------------------------------------------------
 
 // This structure is intended to be allocated on the stack
 struct nsProtocolInfo {
     nsAutoCString scheme;
     uint32_t flags;
     int32_t defaultPort;
@@ -306,18 +305,18 @@ public:
         return rv;
     }
 
 private:
 
     // Called asynchronously, so we do not need to post another PLEvent
     // before calling DoCallback.
     void OnQueryComplete(nsresult status,
-                         const nsCString &pacString,
-                         const nsCString &newPACURL) override
+                         const nsACString &pacString,
+                         const nsACString &newPACURL) override
     {
         // If we've already called DoCallback then, nothing more to do.
         if (!mCallback)
             return;
 
         // Provided we haven't been canceled...
         if (mStatus == NS_OK) {
             mStatus = status;
@@ -816,16 +815,17 @@ nsProtocolProxyService::nsProtocolProxyS
     , mProxyConfig(PROXYCONFIG_DIRECT)
     , mHTTPProxyPort(-1)
     , mFTPProxyPort(-1)
     , mHTTPSProxyPort(-1)
     , mSOCKSProxyPort(-1)
     , mSOCKSProxyVersion(4)
     , mSOCKSProxyRemoteDNS(false)
     , mProxyOverTLS(true)
+    , mWPADOverDHCPEnabled(false)
     , mPACMan(nullptr)
     , mSessionStart(PR_Now())
     , mFailedProxyTimeout(30 * 60) // 30 minute default
     , mIsShutdown(false)
 {
 }
 
 nsProtocolProxyService::~nsProtocolProxyService()
@@ -1082,16 +1082,22 @@ nsProtocolProxyService::PrefsChanged(nsI
         proxy_GetBoolPref(prefBranch, PROXY_PREF("socks_remote_dns"),
                           mSOCKSProxyRemoteDNS);
 
     if (!pref || !strcmp(pref, PROXY_PREF("proxy_over_tls"))) {
         proxy_GetBoolPref(prefBranch, PROXY_PREF("proxy_over_tls"),
                           mProxyOverTLS);
     }
 
+    if (!pref || !strcmp(pref, PROXY_PREF("enable_wpad_over_dhcp"))) {
+        proxy_GetBoolPref(prefBranch, PROXY_PREF("enable_wpad_over_dhcp"),
+                          mWPADOverDHCPEnabled);
+        reloadPAC = true;
+    }
+
     if (!pref || !strcmp(pref, PROXY_PREF("failover_timeout")))
         proxy_GetIntPref(prefBranch, PROXY_PREF("failover_timeout"),
                          mFailedProxyTimeout);
 
     if (!pref || !strcmp(pref, PROXY_PREF("no_proxies_on"))) {
         rv = prefBranch->GetCharPref(PROXY_PREF("no_proxies_on"), tempString);
         if (NS_SUCCEEDED(rv))
             LoadHostFilters(tempString);
@@ -1114,29 +1120,25 @@ nsProtocolProxyService::PrefsChanged(nsI
         tempString.Truncate();
         if (mProxyConfig == PROXYCONFIG_PAC) {
             prefBranch->GetCharPref(PROXY_PREF("autoconfig_url"), tempString);
             if (mPACMan && !mPACMan->IsPACURI(tempString)) {
                 LOG(("PAC Thread URI Changed - Reset Pac Thread"));
                 ResetPACThread();
             }
         } else if (mProxyConfig == PROXYCONFIG_WPAD) {
-            // We diverge from the WPAD spec here in that we don't walk the
-            // hosts's FQDN, stripping components until we hit a TLD.  Doing so
-            // is dangerous in the face of an incomplete list of TLDs, and TLDs
-            // get added over time.  We could consider doing only a single
-            // substitution of the first component, if that proves to help
-            // compatibility.
-            tempString.AssignLiteral(WPAD_URL);
+            LOG(("Auto-detecting proxy - Reset Pac Thread"));
+            ResetPACThread();
         } else if (mSystemProxySettings) {
             // Get System Proxy settings if available
             AsyncConfigureFromPAC(false, false);
         }
-        if (!tempString.IsEmpty())
-            ConfigureFromPAC(tempString, false);
+        if (!tempString.IsEmpty() || mProxyConfig == PROXYCONFIG_WPAD) {
+            ConfigureFromPAC(tempString, true);
+        }
     }
 }
 
 bool
 nsProtocolProxyService::CanUseProxy(nsIURI *aURI, int32_t defaultPort)
 {
     if (mHostFiltersArray.Length() == 0 && !mFilterLocalHosts)
         return true;
@@ -1477,19 +1479,17 @@ nsProtocolProxyService::SetupPACThread(n
     if (mSystemProxySettings &&
         NS_SUCCEEDED(mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly)) &&
         !mainThreadOnly) {
         rv = mPACMan->Init(mSystemProxySettings);
     }
     else {
         rv = mPACMan->Init(nullptr);
     }
-
     if (NS_FAILED(rv)) {
-        mPACMan->Shutdown();
         mPACMan = nullptr;
     }
     return rv;
 }
 
 nsresult
 nsProtocolProxyService::ResetPACThread()
 {
@@ -1503,21 +1503,25 @@ nsProtocolProxyService::ResetPACThread()
 
 nsresult
 nsProtocolProxyService::ConfigureFromPAC(const nsCString &spec,
                                          bool forceReload)
 {
     nsresult rv = SetupPACThread();
     NS_ENSURE_SUCCESS(rv, rv);
 
-    if (mPACMan->IsPACURI(spec) && !forceReload)
+    bool autodetect = spec.IsEmpty();
+    if (!forceReload && ((!autodetect && mPACMan->IsPACURI(spec)) ||
+                         (autodetect && mPACMan->IsUsingWPAD()))) {
         return NS_OK;
+    }
 
     mFailedProxies.Clear();
 
+    mPACMan->SetWPADOverDHCPEnabled(mWPADOverDHCPEnabled);
     return mPACMan->LoadPACFromURI(spec);
 }
 
 void
 nsProtocolProxyService::ProcessPACString(const nsCString &pacString,
                                          uint32_t aResolveFlags,
                                          nsIProxyInfo **result)
 {
@@ -1560,27 +1564,25 @@ nsProtocolProxyService::ReloadPAC()
     int32_t type;
     nsresult rv = prefs->GetIntPref(PROXY_PREF("type"), &type);
     if (NS_FAILED(rv))
         return NS_OK;
 
     nsAutoCString pacSpec;
     if (type == PROXYCONFIG_PAC)
         prefs->GetCharPref(PROXY_PREF("autoconfig_url"), pacSpec);
-    else if (type == PROXYCONFIG_WPAD)
-        pacSpec.AssignLiteral(WPAD_URL);
     else if (type == PROXYCONFIG_SYSTEM) {
         if (mSystemProxySettings) {
             AsyncConfigureFromPAC(true, true);
         } else {
             ResetPACThread();
         }
     }
 
-    if (!pacSpec.IsEmpty())
+    if (!pacSpec.IsEmpty() || type == PROXYCONFIG_WPAD)
         ConfigureFromPAC(pacSpec, true);
     return NS_OK;
 }
 
 // When sync interface is removed this can go away too
 // The nsPACManCallback portion of this implementation should be run
 // off the main thread, because it uses a condvar for signaling and
 // the main thread is blocking on that condvar -
@@ -1594,18 +1596,18 @@ class nsAsyncBridgeRequest final  : publ
         : mMutex("nsDeprecatedCallback")
         , mCondVar(mMutex, "nsDeprecatedCallback")
         , mStatus(NS_OK)
         , mCompleted(false)
     {
     }
 
     void OnQueryComplete(nsresult status,
-                         const nsCString &pacString,
-                         const nsCString &newPACURL) override
+                          const nsACString &pacString,
+                          const nsACString &newPACURL) override
     {
         MutexAutoLock lock(mMutex);
         mCompleted = true;
         mStatus = status;
         mPACString = pacString;
         mPACURL = newPACURL;
         mCondVar.Notify();
     }
--- a/netwerk/base/nsProtocolProxyService.h
+++ b/netwerk/base/nsProtocolProxyService.h
@@ -395,16 +395,17 @@ protected:
 
     // mSOCKSProxyTarget could be a host, a domain socket path,
     // or a named-pipe name.
     nsCString                    mSOCKSProxyTarget;
     int32_t                      mSOCKSProxyPort;
     int32_t                      mSOCKSProxyVersion;
     bool                         mSOCKSProxyRemoteDNS;
     bool                         mProxyOverTLS;
+    bool                         mWPADOverDHCPEnabled;
 
     RefPtr<nsPACMan>           mPACMan;  // non-null if we are using PAC
     nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
 
     PRTime                       mSessionStart;
     nsFailedProxyTable           mFailedProxies;
     int32_t                      mFailedProxyTimeout;
 
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -330,16 +330,20 @@
 // component implementing nsIIncrementalDownload.
 #define NS_INCREMENTALDOWNLOAD_CONTRACTID \
     "@mozilla.org/network/incremental-download;1"
 
 // component implementing nsISystemProxySettings.
 #define NS_SYSTEMPROXYSETTINGS_CONTRACTID \
     "@mozilla.org/system-proxy-settings;1"
 
+// component implementing nsIDHCPClient.
+#define NS_DHCPCLIENT_CONTRACTID \
+    "@mozilla.org/dhcp-client;1"
+
 // service implementing nsIStreamTransportService
 #define NS_STREAMTRANSPORTSERVICE_CONTRACTID \
     "@mozilla.org/network/stream-transport-service;1"
 #define NS_STREAMTRANSPORTSERVICE_CID \
 { /* 0885d4f8-f7b8-4cda-902e-94ba38bc256e */         \
     0x0885d4f8,                                      \
     0xf7b8,                                          \
     0x4cda,                                          \
new file mode 100644
--- /dev/null
+++ b/netwerk/test/gtest/TestPACMan.cpp
@@ -0,0 +1,322 @@
+#include "gtest/gtest.h"
+#include "nsServiceManagerUtils.h"
+#include "../../../xpcom/threads/nsThreadManager.h"
+#include "nsIDHCPClient.h"
+#include "nsIPrefBranch.h"
+#include "nsComponentManager.h"
+#include "mozilla/ModuleUtils.h"
+#include "../../base/nsPACMan.h"
+
+
+#define TEST_WPAD_DHCP_OPTION "http://pac/pac.dat"
+#define TEST_ASSIGNED_PAC_URL "http://assignedpac/pac.dat"
+#define WPAD_PREF 4
+#define NETWORK_PROXY_TYPE_PREF_NAME "network.proxy.type"
+#define GETTING_NETWORK_PROXY_TYPE_FAILED -1
+
+nsCString WPADOptionResult;
+
+namespace mozilla {
+namespace net {
+
+nsresult
+SetNetworkProxyType(int32_t pref)
+{
+  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+  if (!prefs) {
+    return NS_ERROR_FACTORY_NOT_REGISTERED;
+  }
+  return prefs->SetIntPref(NETWORK_PROXY_TYPE_PREF_NAME, pref);
+}
+
+nsresult
+GetNetworkProxyType(int32_t* pref)
+{
+  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+  if (!prefs) {
+    return NS_ERROR_FACTORY_NOT_REGISTERED;
+  }
+  return prefs->GetIntPref(NETWORK_PROXY_TYPE_PREF_NAME, pref);
+}
+
+class nsTestDHCPClient final : public nsIDHCPClient
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIDHCPCLIENT
+
+  nsTestDHCPClient() {};
+  nsresult Init(){
+    return NS_OK;
+  };
+
+private:
+   ~nsTestDHCPClient() {};
+};
+
+NS_IMETHODIMP
+  nsTestDHCPClient::GetOption(uint8_t option, nsACString & _retval)
+  {
+    _retval.Assign(WPADOptionResult);
+    return NS_OK;
+  }
+
+NS_IMPL_ISUPPORTS(nsTestDHCPClient, nsIDHCPClient)
+
+
+#define NS_TESTDHCPCLIENTSERVICE_CID  /* {FEBF1D69-4D7D-4891-9524-045AD18B5592} */\
+    { 0xFEBF1D69, 0x4D7D, 0x4891, \
+         {0x95, 0x24, 0x04, 0x5a, 0xd1, 0x8b, 0x55, 0x92 } }
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsTestDHCPClient, Init)
+NS_DEFINE_NAMED_CID(NS_TESTDHCPCLIENTSERVICE_CID);
+
+static const mozilla::Module::CIDEntry kSysDHCPClientCIDs[] = {
+  { &kNS_TESTDHCPCLIENTSERVICE_CID, false, nullptr, nsTestDHCPClientConstructor },
+  { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kSysDHCPClientContracts[] = {
+  { NS_DHCPCLIENT_CONTRACTID, &kNS_TESTDHCPCLIENTSERVICE_CID },
+  { nullptr }
+};
+
+static const mozilla::Module kSysDHCPClientModule = {
+  mozilla::Module::kVersion,
+  kSysDHCPClientCIDs,
+  kSysDHCPClientContracts
+};
+
+NSMODULE_DEFN(nsDHCPClientModule) = &kSysDHCPClientModule;
+
+void
+SetOptionResult(const char* result)
+{
+  WPADOptionResult.Assign(result);
+}
+
+class ProcessPendingEventsAction final : public Runnable
+{
+public:
+  // by default we just process the queue
+  ProcessPendingEventsAction()
+  : Runnable("net::ProcessPendingEventsAction")
+  { }
+
+
+  NS_IMETHOD
+  Run() override
+  {
+    if (NS_HasPendingEvents(nullptr)) {
+      NS_WARNING("Found pending requests on PAC thread");
+      nsresult rv;
+      rv = NS_ProcessPendingEvents(nullptr);
+      EXPECT_EQ(NS_OK, rv);
+    }
+    NS_WARNING("No pending requests on PAC thread");
+    return NS_OK;
+  }
+
+};
+
+
+class TestPACMan : public ::testing::Test {
+  protected:
+
+    RefPtr<nsPACMan> mPACMan;
+
+    void
+    ProcessAllEvents()
+    {
+      ProcessPendingEventsOnPACThread();
+      nsresult rv;
+      while (NS_HasPendingEvents(nullptr)) {
+        NS_WARNING("Pending events on main thread");
+        rv = NS_ProcessPendingEvents(nullptr);
+        ASSERT_EQ(NS_OK, rv);
+        ProcessPendingEventsOnPACThread();
+      }
+      NS_WARNING("End of pending events on main thread");
+    }
+
+
+    // This method is used to ensure that all pending events on the main thread
+    // and the Proxy thread are processsed.
+    // It iterates over ProcessAllEvents because simply calling ProcessAllEvents once
+    // did not reliably process the events on both threads on all platforms.
+    void
+    ProcessAllEventsTenTimes(){
+      for (int i = 0; i < 10; i++) {
+        ProcessAllEvents();
+      }
+    }
+
+    virtual void
+    SetUp()
+    {
+      ASSERT_EQ(NS_OK, GetNetworkProxyType(&originalNetworkProxyTypePref));
+      nsFactoryEntry* factoryEntry = nsComponentManagerImpl::gComponentManager
+          ->GetFactoryEntry(kNS_TESTDHCPCLIENTSERVICE_CID);
+      if (factoryEntry) {
+        nsresult rv = nsComponentManagerImpl::gComponentManager->UnregisterFactory(kNS_TESTDHCPCLIENTSERVICE_CID, factoryEntry->mFactory);
+        ASSERT_EQ(NS_OK, rv);
+      }
+      nsComponentManagerImpl::gComponentManager->RegisterModule(&kSysDHCPClientModule, nullptr);
+
+      mPACMan = new nsPACMan(nullptr);
+      mPACMan->SetWPADOverDHCPEnabled(true);
+      mPACMan->Init(nullptr);
+      ASSERT_EQ(NS_OK, SetNetworkProxyType(WPAD_PREF));
+
+    }
+
+    virtual void
+    TearDown()
+    {
+
+      mPACMan->Shutdown();
+      if (originalNetworkProxyTypePref != GETTING_NETWORK_PROXY_TYPE_FAILED) {
+        ASSERT_EQ(NS_OK, SetNetworkProxyType(originalNetworkProxyTypePref));
+      }
+    }
+
+    nsCOMPtr<nsIDHCPClient>
+    GetPACManDHCPCient()
+    {
+      return mPACMan->mDHCPClient;
+    }
+
+    void
+    SetPACManDHCPCient(nsCOMPtr<nsIDHCPClient> aValue)
+    {
+       mPACMan->mDHCPClient = aValue;
+    }
+
+    void
+    AssertPACSpecEqualTo(const char* aExpected)
+    {
+      ASSERT_STREQ(aExpected, mPACMan->mPACURISpec.Data());
+    }
+
+  private:
+
+    int32_t originalNetworkProxyTypePref = GETTING_NETWORK_PROXY_TYPE_FAILED;
+
+    void ProcessPendingEventsOnPACThread(){
+      RefPtr<ProcessPendingEventsAction> action =
+          new ProcessPendingEventsAction();
+
+      mPACMan->mPACThread->Dispatch(action, nsIEventTarget::DISPATCH_SYNC);
+    }
+};
+
+
+
+TEST_F(TestPACMan, TestCreateDHCPClientAndGetOption) {
+
+  SetOptionResult(TEST_WPAD_DHCP_OPTION);
+  nsCString spec;
+  GetPACManDHCPCient()->GetOption(252, spec);
+  ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, spec.Data());
+}
+
+TEST_F(TestPACMan, TestCreateDHCPClientAndGetEmptyOption) {
+    SetOptionResult("");
+    nsCString spec;
+    spec.AssignLiteral(TEST_ASSIGNED_PAC_URL);
+    GetPACManDHCPCient()->GetOption(252, spec);
+    ASSERT_TRUE(spec.IsEmpty());
+}
+
+TEST_F(TestPACMan, WhenTheDHCPClientExistsAndDHCPIsNonEmptyDHCPOptionIsUsedAsPACUri) {
+
+  SetOptionResult(TEST_WPAD_DHCP_OPTION);
+
+  mPACMan->LoadPACFromURI(EmptyCString());
+
+  ProcessAllEventsTenTimes();
+
+  ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data());
+  AssertPACSpecEqualTo(TEST_WPAD_DHCP_OPTION);
+
+}
+
+TEST_F(TestPACMan, WhenTheDHCPResponseIsEmptyWPADDefaultsToStandardURL) {
+
+  SetOptionResult(EmptyCString().Data());
+
+  mPACMan->LoadPACFromURI(EmptyCString());
+  ASSERT_TRUE(NS_HasPendingEvents(nullptr));
+
+  ProcessAllEventsTenTimes();
+
+  ASSERT_STREQ("", WPADOptionResult.Data());
+  AssertPACSpecEqualTo("http://wpad/wpad.dat");
+
+}
+
+TEST_F(TestPACMan, WhenThereIsNoDHCPClientWPADDefaultsToStandardURL) {
+
+  SetOptionResult(TEST_WPAD_DHCP_OPTION);
+
+  SetPACManDHCPCient(nullptr);
+
+  mPACMan->LoadPACFromURI(EmptyCString());
+
+  ProcessAllEventsTenTimes();
+
+  ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data());
+  AssertPACSpecEqualTo("http://wpad/wpad.dat");
+
+}
+
+TEST_F(TestPACMan, WhenWPADOverDHCPIsPreffedOffWPADDefaultsToStandardURL) {
+
+  SetOptionResult(TEST_WPAD_DHCP_OPTION);
+
+  mPACMan->SetWPADOverDHCPEnabled(false);
+  mPACMan->LoadPACFromURI(EmptyCString());
+
+  ProcessAllEventsTenTimes();
+
+  ASSERT_STREQ(TEST_WPAD_DHCP_OPTION, WPADOptionResult.Data());
+  AssertPACSpecEqualTo("http://wpad/wpad.dat");
+
+}
+
+TEST_F(TestPACMan, WhenPACUriIsSetDirectlyItIsUsedRatherThanWPAD) {
+
+  SetOptionResult(TEST_WPAD_DHCP_OPTION);
+
+  nsCString spec;
+  spec.AssignLiteral(TEST_ASSIGNED_PAC_URL);
+
+  mPACMan->LoadPACFromURI(spec);
+
+  ProcessAllEventsTenTimes();
+
+  AssertPACSpecEqualTo(TEST_ASSIGNED_PAC_URL);
+
+}
+
+TEST_F(TestPACMan, WhenAScheduledReloadOfAssignedPACHappensTheAssignedPACSpecStaysRatherThanReadingFromDHCP) {
+
+  SetOptionResult(TEST_WPAD_DHCP_OPTION);
+  nsCString spec;
+  spec.AssignLiteral(TEST_ASSIGNED_PAC_URL);
+  mPACMan->LoadPACFromURI(spec);
+
+  ProcessAllEventsTenTimes();
+
+  mPACMan->LoadPACFromURI(EmptyCString(), true);
+
+  ProcessAllEventsTenTimes();
+
+  AssertPACSpecEqualTo(TEST_ASSIGNED_PAC_URL);
+
+}
+
+} // namespace net
+} // namespace mozilla
--- a/netwerk/test/gtest/moz.build
+++ b/netwerk/test/gtest/moz.build
@@ -4,16 +4,17 @@
 # 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 += [
     'TestBufferedInputStream.cpp',
     'TestHeaders.cpp',
     'TestHttpAuthUtils.cpp',
     'TestMozURL.cpp',
+    'TestPACMan.cpp',
     'TestPartiallySeekableInputStream.cpp',
     'TestProtocolProxyService.cpp',
     'TestReadStreamToString.cpp',
     'TestServerTimingHeader.cpp',
     'TestStandardURL.cpp',
     'TestURIMutator.cpp',
 ]
 
@@ -24,8 +25,13 @@ TEST_DIRS += [
 LOCAL_INCLUDES += [
     '/netwerk/base',
     '/xpcom/tests/gtest',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul-gtest'
+
+LOCAL_INCLUDES += [
+    '!/xpcom',
+    '/xpcom/components'
+]
--- a/toolkit/moz.build
+++ b/toolkit/moz.build
@@ -35,17 +35,18 @@ if CONFIG['MOZ_MAINTENANCE_SERVICE']:
 
 DIRS += ['xre']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk3':
     DIRS += ['system/unixproxy']
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     DIRS += ['system/osxproxy']
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
-    DIRS += ['system/windowsproxy']
+    DIRS += ['system/windowsproxy',
+            'system/windowsDHCPClient']
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     DIRS += ['system/androidproxy']
 
 TEST_HARNESS_FILES.testing.mochitest.browser.toolkit.crashreporter.test.browser += [
     'crashreporter/test/browser/crashreport.sjs',
 ]
 
 with Files('moz.*'):
new file mode 100644
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/DHCPUtils.cpp
@@ -0,0 +1,256 @@
+/* -*- 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 "DHCPUtils.h"
+#include <vector>
+#include "mozilla\Logging.h"
+#include "nsString.h"
+
+
+#define MOZ_WORKING_BUFFER_SIZE_NETWORK_ADAPTERS 15000
+#define MOZ_WORKING_BUFFER_SIZE_DHCP_PARAMS 1000
+#define MOZ_MAX_TRIES 3
+namespace mozilla {
+namespace toolkit {
+namespace system {
+namespace windowsDHCPClient {
+
+//
+// The comments on this page reference the following Microsoft documentation pages (both retrieved 2017-06-27)
+// [1] https://msdn.microsoft.com/en-us/library/windows/desktop/aa365915(v=vs.85).aspx
+// [2] https://msdn.microsoft.com/en-us/library/windows/desktop/aa363298(v=vs.85).aspx
+mozilla::LazyLogModule gDhcpUtilsLog("dhcpUtils");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gDhcpUtilsLog, LogLevel::Debug, args)
+
+bool
+IsCurrentAndHasDHCP(PIP_ADAPTER_ADDRESSES aAddresses)
+{
+  return aAddresses->OperStatus == 1 &&
+      (aAddresses->Dhcpv4Server.iSockaddrLength ||
+      aAddresses->Dhcpv6Server.iSockaddrLength);
+}
+
+nsresult
+GetActiveDHCPNetworkAdapterName(nsACString& aNetworkAdapterName,
+    WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper)
+{
+  /* Declare and initialize variables */
+
+  uint32_t dwSize = 0;
+  uint32_t dwRetVal = 0;
+  nsresult rv = NS_ERROR_FAILURE;
+
+  // Set the flags to pass to GetAdaptersAddresses
+  uint32_t flags = GAA_FLAG_INCLUDE_PREFIX;
+
+  // default to unspecified address family (both)
+  uint32_t family = AF_UNSPEC;
+
+  // Allocate a 15 KB buffer to start with.
+  uint32_t outBufLen = MOZ_WORKING_BUFFER_SIZE_NETWORK_ADAPTERS;
+  uint32_t iterations = 0;
+
+  aNetworkAdapterName.Truncate();
+
+  // Now we try calling the GetAdaptersAddresses method until the return value
+  // is not ERROR_BUFFER_OVERFLOW. According to [1]
+  //
+  //
+  // > When the return value is ERROR_BUFFER_OVERFLOW, the SizePointer parameter returned
+  // > points to the required size of the buffer to hold the adapter information.
+  // > Note that it is possible for the buffer size required for the IP_ADAPTER_ADDRESSES
+  // > structures pointed to by the AdapterAddresses parameter to change between
+  // > subsequent calls to the GetAdaptersAddresses function if an adapter address
+  // > is added or removed. However, this method of using the GetAdaptersAddresses
+  // > function is strongly discouraged. This method requires calling the
+  // > GetAdaptersAddresses function multiple times.
+  // >
+  // > The recommended method of calling the GetAdaptersAddresses function is
+  // > to pre-allocate a 15KB working buffer pointed to by the AdapterAddresses parameter.
+  // > On typical computers, this dramatically reduces the chances that the
+  // > GetAdaptersAddresses function returns ERROR_BUFFER_OVERFLOW, which would require
+  // > calling GetAdaptersAddresses function multiple times.
+  //
+  //
+  // The possibility of the buffer size changing between calls to
+  // GetAdaptersAddresses is why we allow the following code to be called several times,
+  // rather than just the two that would be neccessary if we could rely on the
+  // value returned in outBufLen being the true size needed.
+
+  std::vector<IP_ADAPTER_ADDRESSES> pAddresses;
+  do {
+    pAddresses.resize(outBufLen/sizeof(IP_ADAPTER_ADDRESSES));
+
+    dwRetVal =
+      aWindowsNetworkFunctionsWrapper->GetAdaptersAddressesWrapped(
+        family, flags, nullptr, pAddresses.data(), (PULONG)&outBufLen);
+
+    if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
+      iterations++;
+    }
+  } while (dwRetVal == ERROR_BUFFER_OVERFLOW && iterations < MOZ_MAX_TRIES);
+
+  switch(dwRetVal) {
+    case NO_ERROR:
+      {
+        // set default return value if we don't find a suitable network adapter
+        rv = NS_ERROR_NOT_AVAILABLE;
+        PIP_ADAPTER_ADDRESSES pCurrAddresses = pAddresses.data();
+        while (pCurrAddresses) {
+          if (IsCurrentAndHasDHCP(pCurrAddresses)) {
+            rv = NS_OK;
+            aNetworkAdapterName.Assign(pCurrAddresses->AdapterName);
+            break;
+          }
+          pCurrAddresses = pCurrAddresses->Next;
+        }
+      }
+      break;
+    case ERROR_NO_DATA:
+      rv = NS_ERROR_NOT_AVAILABLE;
+      break;
+    default:
+      MOZ_LOG(gDhcpUtilsLog, mozilla::LogLevel::Warning,
+              ("GetAdaptersAddresses returned %d", dwRetVal));
+      rv = NS_ERROR_FAILURE;
+      break;
+  }
+  return rv;
+}
+
+
+DWORD
+IterateDHCPInformRequestsUntilBufferLargeEnough(
+    DHCPCAPI_PARAMS&     aDhcpRequestedOptionParams,
+     wchar_t*            aWideNetworkAdapterName,
+     std::vector<char>&  aBuffer,
+     WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper)
+{
+  uint32_t iterations = 0;
+  uint32_t outBufLen = MOZ_WORKING_BUFFER_SIZE_DHCP_PARAMS;
+
+  DHCPCAPI_PARAMS_ARRAY RequestParams = {
+    1,  // only one option to request
+    &aDhcpRequestedOptionParams
+  };
+
+  // According to [2],
+  // the following is for 'Optional data to be requested,
+  // in addition to the data requested in the RecdParams array.'
+  // We are not requesting anything in addition, so this is empty.
+  DHCPCAPI_PARAMS_ARRAY SendParams = {
+    0,
+    nullptr
+  };
+
+  DWORD winAPIResponse;
+  // Now we try calling the DHCPRequestParams method until the return value
+  // is not ERROR_MORE_DATA. According to [2]:
+  //
+  //
+  // > Note that the required size of Buffer may increase during the time that elapses
+  // > between the initial function call's return and a subsequent call;
+  // > therefore, the required size of Buffer (indicated in pSize)
+  // > provides an indication of the approximate size required of Buffer,
+  // > rather than guaranteeing that subsequent calls will return successfully
+  // > if Buffer is set to the size indicated in pSize.
+  //
+  //
+  // This is why we allow this DHCPRequestParams to be called several times,
+  // rather than just the two that would be neccessary if we could rely on the
+  // value returned in outBufLen being the true size needed.
+  do {
+    aBuffer.resize(outBufLen);
+
+    winAPIResponse = aWindowsNetworkFunctionsWrapper->DhcpRequestParamsWrapped(
+      DHCPCAPI_REQUEST_SYNCHRONOUS, // Flags
+      nullptr,                         // Reserved
+      aWideNetworkAdapterName,               // Adapter Name
+      nullptr,                         // not using class id
+      SendParams,                         // sent parameters
+      RequestParams,                // requesting params
+      (PBYTE)aBuffer.data(),            // buffer for the output of RequestParams
+      (PULONG)&outBufLen,                      // buffer size
+      nullptr                          // Request ID for persistent requests - not needed here
+    );
+
+    if (winAPIResponse == ERROR_MORE_DATA) {
+      iterations++;
+    }
+  } while (winAPIResponse == ERROR_MORE_DATA && iterations < MOZ_MAX_TRIES);
+  return winAPIResponse;
+}
+
+nsresult
+RetrieveOption(
+  const nsACString&   aAdapterName,
+  uint8_t            aOption,
+  std::vector<char>& aOptionValueBuf,
+  uint32_t*          aOptionSize,
+  WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper)
+{
+
+  nsresult rv;
+  nsAutoString wideNetworkAdapterName = NS_ConvertUTF8toUTF16(aAdapterName);
+
+  DHCPCAPI_PARAMS DhcpRequestedOptionParams = {
+    0,                //  Flags - Reserved, must be set to zero [2]
+    aOption, // OptionId
+    false,            // whether this is vendor specific - let's assume not
+    nullptr,             // data filled in on return
+    0                // nBytes used by return data
+  };
+
+  std::vector<char> tmpBuffer(MOZ_WORKING_BUFFER_SIZE_DHCP_PARAMS);  // a buffer for the DHCP response object
+  DWORD winAPIResponse = IterateDHCPInformRequestsUntilBufferLargeEnough(DhcpRequestedOptionParams,
+     wideNetworkAdapterName.get(),
+     tmpBuffer,
+     aWindowsNetworkFunctionsWrapper);
+
+  switch (winAPIResponse){
+    case NO_ERROR:
+      {
+        if (DhcpRequestedOptionParams.nBytesData == 0) {
+          *aOptionSize = 0;
+          rv = NS_ERROR_NOT_AVAILABLE;
+          break;
+        }
+
+        if (*aOptionSize >= DhcpRequestedOptionParams.nBytesData) {
+          rv = NS_OK;
+        } else {
+          rv = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
+        }
+
+        uint32_t actualSizeReturned =
+              *aOptionSize > DhcpRequestedOptionParams.nBytesData?
+              DhcpRequestedOptionParams.nBytesData: *aOptionSize;
+
+        memcpy(aOptionValueBuf.data(),
+              DhcpRequestedOptionParams.Data, actualSizeReturned);
+        *aOptionSize = DhcpRequestedOptionParams.nBytesData;
+        break;
+      }
+    case ERROR_INVALID_PARAMETER:
+      MOZ_LOG(gDhcpUtilsLog, mozilla::LogLevel::Warning,
+          ("RetrieveOption returned %d (ERROR_INVALID_PARAMETER) when option %d requested",
+           winAPIResponse, aOption));
+      rv = NS_ERROR_INVALID_ARG;
+      break;
+    default:
+      MOZ_LOG(gDhcpUtilsLog, mozilla::LogLevel::Warning,
+          ("RetrieveOption returned %d when option %d requested", winAPIResponse, aOption));
+      rv = NS_ERROR_FAILURE;
+  }
+  return rv;
+}
+
+} // namespace windowsDHCPClient
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/DHCPUtils.h
@@ -0,0 +1,34 @@
+/* -*- 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/. */
+
+#ifndef mozilla_toolkit_system_windowsDHCPClient_DHCPUtils_h
+#define mozilla_toolkit_system_windowsDHCPClient_DHCPUtils_h
+
+#include "WindowsNetworkFunctionsWrapper.h"
+#include <vector>
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+namespace windowsDHCPClient {
+
+nsresult GetActiveDHCPNetworkAdapterName(
+  nsACString& aNetworkAdapterName,
+  WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper);
+
+nsresult RetrieveOption(
+  const nsACString&  aAdapterName,
+  uint8_t           aOption,
+  std::vector<char>& aOptionValueBuf,
+  uint32_t*         aOptionSize,
+  WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper
+);
+
+} // namespace windowsDHCPClient
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
+#endif // mozilla_toolkit_system_windowsDHCPClient_DHCPUtils_h
new file mode 100644
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.cpp
@@ -0,0 +1,46 @@
+/* -*- 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 "WindowsNetworkFunctionsWrapper.h"
+
+
+#pragma comment(lib, "IPHLPAPI.lib")
+#pragma comment(lib, "dhcpcsvc.lib" )
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+namespace windowsDHCPClient {
+
+NS_IMPL_ISUPPORTS(WindowsNetworkFunctionsWrapper, nsISupports)
+
+ULONG WindowsNetworkFunctionsWrapper::GetAdaptersAddressesWrapped(
+      _In_    ULONG                 aFamily,
+      _In_    ULONG                 aFlags,
+      _In_    PVOID                 aReserved,
+      _Inout_ PIP_ADAPTER_ADDRESSES aAdapterAddresses,
+      _Inout_ PULONG                aSizePointer)
+{
+  return GetAdaptersAddresses(aFamily, aFlags, aReserved, aAdapterAddresses, aSizePointer);
+}
+
+DWORD WindowsNetworkFunctionsWrapper::DhcpRequestParamsWrapped(
+    _In_    DWORD                 aFlags,
+    _In_    LPVOID                aReserved,
+    _In_    LPWSTR                aAdapterName,
+    _In_    LPDHCPCAPI_CLASSID    aClassId,
+    _In_    DHCPCAPI_PARAMS_ARRAY aSendParams,
+    _Inout_ DHCPCAPI_PARAMS_ARRAY aRecdParams,
+    _In_    LPBYTE                aBuffer,
+    _Inout_ LPDWORD               apSize,
+    _In_    LPWSTR                aRequestIdStr)
+{
+  return DhcpRequestParams(aFlags, aReserved, aAdapterName, aClassId, aSendParams, aRecdParams, aBuffer, apSize, aRequestIdStr);
+}
+} // namespace windowsDHCPClient
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.h
@@ -0,0 +1,60 @@
+/* -*- 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/. */
+
+#ifndef mozilla_toolkit_system_windowsDHCPClient_windowsNetworkFunctionsWrapper_h
+#define mozilla_toolkit_system_windowsDHCPClient_windowsNetworkFunctionsWrapper_h
+
+#include <Winsock2.h> // there is a compilation error if Winsock.h is not
+                      // declared before dhcpcsdk.h
+#include <dhcpcsdk.h>
+#include <iphlpapi.h>
+
+#include "nsISupports.h"
+
+// Thin wrapper around low-level network functions needed for DHCP querying for web proxy
+namespace mozilla {
+namespace toolkit {
+namespace system {
+namespace windowsDHCPClient {
+
+class WindowsNetworkFunctionsWrapper : nsISupports
+{
+
+  public:
+
+    NS_DECL_THREADSAFE_ISUPPORTS
+    WindowsNetworkFunctionsWrapper(){};
+
+    virtual ULONG GetAdaptersAddressesWrapped(
+      _In_    ULONG                 aFamily,
+      _In_    ULONG                 aFlags,
+      _In_    PVOID                 aReserved,
+      _Inout_ PIP_ADAPTER_ADDRESSES aAdapterAddresses,
+      _Inout_ PULONG                aSizePointer
+    );
+
+    virtual DWORD DhcpRequestParamsWrapped(
+    _In_    DWORD                 aFlags,
+    _In_    LPVOID                aReserved,
+    _In_    LPWSTR                aAdapterName,
+    _In_    LPDHCPCAPI_CLASSID    aClassId,
+    _In_    DHCPCAPI_PARAMS_ARRAY aSendParams,
+    _Inout_ DHCPCAPI_PARAMS_ARRAY aRecdParams,
+    _In_    LPBYTE                aBuffer,
+    _Inout_ LPDWORD               apSize,
+    _In_    LPWSTR                aRequestIdStr
+  );
+
+  protected:
+    ~WindowsNetworkFunctionsWrapper(){};
+
+};
+
+} // namespace windowsDHCPClient
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
+#endif //mozilla_toolkit_system_windowsDHCPClient_windowsNetworkFunctionsWrapper_h
new file mode 100644
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/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: HTTP')
+
+TEST_DIRS += ['tests/gtest']
+
+SOURCES += [
+    'DHCPUtils.cpp',
+    'nsWindowsDHCPClient.cpp',
+    'WindowsNetworkFunctionsWrapper.cpp'
+]
+
+FINAL_LIBRARY = 'xul'
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.cpp
@@ -0,0 +1,103 @@
+/* -*- 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 "nsWindowsDHCPClient.h"
+
+#include <vector>
+
+#include "DHCPUtils.h"
+#include "nsNetCID.h"
+#include "nsString.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ModuleUtils.h"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+namespace windowsDHCPClient {
+
+LazyLogModule gDhcpLog("windowsDHCPClient");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gDhcpLog, LogLevel::Debug, args)
+
+#define MOZ_MAX_DHCP_OPTION_LENGTH 255 // this is the maximum option length in DHCP V4 and 6
+
+NS_IMPL_ISUPPORTS(nsWindowsDHCPClient, nsIDHCPClient)
+
+nsresult
+nsWindowsDHCPClient::Init()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsDHCPClient::GetOption(uint8_t aOption, nsACString& aRetVal)
+{
+  nsCString networkAdapterName;
+  nsresult rv;
+  rv = GetActiveDHCPNetworkAdapterName(networkAdapterName, mNetworkFunctions);
+  if (rv != NS_OK) {
+    LOG(("Failed to get network adapter name in nsWindowsDHCPClient::GetOption due to error %d", rv));
+    return rv;
+  }
+
+  uint32_t sizeOptionValue = MOZ_MAX_DHCP_OPTION_LENGTH;
+  std::vector<char> optionValue;
+
+  bool retryingAfterLossOfSignificantData = false;
+  do {
+    optionValue.resize(sizeOptionValue);
+    rv = RetrieveOption(networkAdapterName,
+            aOption,
+            optionValue,
+            &sizeOptionValue,
+            mNetworkFunctions);
+    if (rv == NS_ERROR_LOSS_OF_SIGNIFICANT_DATA) {
+      LOG(("In nsWindowsDHCPClient::GetOption, DHCP Option %d required %d bytes", aOption, sizeOptionValue));
+      if (retryingAfterLossOfSignificantData) {
+        break;
+      }
+      retryingAfterLossOfSignificantData = true;
+    }
+  } while (rv == NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
+  if (rv != NS_OK) {
+    LOG(("Failed to get DHCP Option %d nsWindowsDHCPClient::GetOption due to error %d", aOption, rv));
+    return rv;
+  }
+  aRetVal.Assign(optionValue.data(), sizeOptionValue);
+  return NS_OK;
+}
+
+#define NS_WINDOWSDHCPCLIENTSERVICE_CID  /* {FEBF1D69-4D7D-4891-9524-045AD18B5592} */\
+    { 0xFEBF1D69, 0x4D7D, 0x4891, \
+         {0x95, 0x24, 0x04, 0x5a, 0xd1, 0x8b, 0x55, 0x92 } }
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWindowsDHCPClient, Init)
+NS_DEFINE_NAMED_CID(NS_WINDOWSDHCPCLIENTSERVICE_CID);
+
+static const mozilla::Module::CIDEntry kSysDHCPClientCIDs[] = {
+  { &kNS_WINDOWSDHCPCLIENTSERVICE_CID, false, nullptr, nsWindowsDHCPClientConstructor },
+  { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kSysDHCPClientContracts[] = {
+  { NS_DHCPCLIENT_CONTRACTID, &kNS_WINDOWSDHCPCLIENTSERVICE_CID },
+  { nullptr }
+};
+
+static const mozilla::Module kSysDHCPClientModule = {
+  mozilla::Module::kVersion,
+  kSysDHCPClientCIDs,
+  kSysDHCPClientContracts
+};
+
+NSMODULE_DEFN(nsDHCPClientModule) = &kSysDHCPClientModule;
+
+} // namespace windowsDHCPClient
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.h
@@ -0,0 +1,38 @@
+/* -*- 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 "nsIDHCPClient.h"
+#include "nsIServiceManager.h"
+#include "nsNetCID.h"
+#include "WindowsNetworkFunctionsWrapper.h"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+namespace windowsDHCPClient {
+
+class nsWindowsDHCPClient final : public nsIDHCPClient
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIDHCPCLIENT
+
+  explicit nsWindowsDHCPClient(WindowsNetworkFunctionsWrapper *aNetworkFunctions = new WindowsNetworkFunctionsWrapper())
+    : mNetworkFunctions(aNetworkFunctions) {};
+  nsresult Init();
+
+private:
+
+   ~nsWindowsDHCPClient() {};
+   WindowsNetworkFunctionsWrapper* mNetworkFunctions;
+
+};
+
+
+} // namespace windowsDHCPClient
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/tests/gtest/TestDHCPUtils.cpp
@@ -0,0 +1,323 @@
+/* -*- 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 "DHCPUtils.h"
+#include "gtest/gtest.h"
+#include "nsString.h"
+#include "nsWindowsDHCPClient.h"
+
+using namespace mozilla::toolkit::system::windowsDHCPClient;
+
+
+
+class WindowsNetworkFunctionsMock : public WindowsNetworkFunctionsWrapper {
+
+  public:
+    WindowsNetworkFunctionsMock():mAddressesToReturn(nullptr) {
+      memset(mOptions, 0, sizeof(char*) * 256);
+    }
+
+
+    ULONG GetAdaptersAddressesWrapped(
+      _In_    ULONG                 Family,
+      _In_    ULONG                 Flags,
+      _In_    PVOID                 Reserved,
+      _Inout_ PIP_ADAPTER_ADDRESSES AdapterAddresses,
+      _Inout_ PULONG                SizePointer
+    ){
+      if (*SizePointer < sizeof(*mAddressesToReturn)){
+        *SizePointer = sizeof(*mAddressesToReturn);
+        return ERROR_BUFFER_OVERFLOW;
+      }
+
+      *SizePointer = sizeof(*mAddressesToReturn);
+      memcpy(AdapterAddresses, mAddressesToReturn,
+                    *SizePointer);
+      return 0;
+    }
+
+    DWORD DhcpRequestParamsWrapped(
+      _In_    DWORD                 Flags,
+      _In_    LPVOID                Reserved,
+      _In_    LPWSTR                AdapterName,
+      _In_    LPDHCPCAPI_CLASSID    ClassId,
+      _In_    DHCPCAPI_PARAMS_ARRAY SendParams,
+      _Inout_ DHCPCAPI_PARAMS_ARRAY RecdParams,
+      _In_    LPBYTE                Buffer,
+      _Inout_ LPDWORD               pSize,
+      _In_    LPWSTR                RequestIdStr
+    )
+    {
+      mLastRequestedNetworkAdapterName.Assign(AdapterName);
+
+      if (mOptions[RecdParams.Params[0].OptionId] == nullptr) {
+        RecdParams.Params[0].nBytesData = 0;
+      }
+      else {
+        RecdParams.Params[0].Data = Buffer;
+        size_t lengthOfValue = strlen(mOptions[RecdParams.Params[0].OptionId]);
+        if (*pSize > lengthOfValue) {
+          memcpy(Buffer, mOptions[RecdParams.Params[0].OptionId], lengthOfValue);
+          RecdParams.Params[0].nBytesData = lengthOfValue;
+        } else {
+          *pSize = lengthOfValue;
+          return ERROR_MORE_DATA;
+        }
+      }
+      return 0;
+    }
+
+    void
+    AddAdapterAddresses(IP_ADAPTER_ADDRESSES& aAddressesToAdd)
+    {
+      if (mAddressesToReturn == nullptr) {
+        mAddressesToReturn = &aAddressesToAdd;
+        return;
+      }
+      IP_ADAPTER_ADDRESSES* tail = mAddressesToReturn;
+
+      while (tail->Next != nullptr) {
+        tail = tail->Next;
+      }
+      tail->Next = &aAddressesToAdd;
+    }
+
+    void
+    SetDHCPOption(uint8_t option, char* value)
+    {
+      mOptions[option] = value;
+    }
+
+    nsString
+    GetLastRequestedNetworkAdapterName()
+    {
+      return mLastRequestedNetworkAdapterName;
+    }
+
+  private:
+    IP_ADAPTER_ADDRESSES* mAddressesToReturn = nullptr;
+    char* mOptions[256];
+    nsString mLastRequestedNetworkAdapterName;
+
+
+};
+
+class TestDHCPUtils : public ::testing::Test {
+  protected:
+    RefPtr<WindowsNetworkFunctionsMock> mMockWindowsFunctions;
+    nsCString mDefaultAdapterName;
+
+    virtual void
+    SetUp()
+    {
+      mMockWindowsFunctions = new WindowsNetworkFunctionsMock();
+      mDefaultAdapterName.AssignLiteral("my favourite network adapter");
+    }
+
+    void
+    Given_DHCP_Option_Is(uint8_t option, char* value)
+    {
+      mMockWindowsFunctions.get()->SetDHCPOption(option, value);
+    }
+
+    void
+    Given_Network_Adapter_Called(
+      IP_ADAPTER_ADDRESSES& adapterAddresses,
+      char* adapterName)
+    {
+      adapterAddresses.AdapterName = adapterName;
+      adapterAddresses.Next = nullptr;
+      adapterAddresses.Dhcpv4Server.iSockaddrLength = 0;
+      adapterAddresses.Dhcpv6Server.iSockaddrLength = 0;
+      AddAdapterAddresses(adapterAddresses);
+    }
+
+    void
+    Given_Network_Adapter_Supports_DHCP_V4(IP_ADAPTER_ADDRESSES& adapterAddresses)
+    {
+      adapterAddresses.Dhcpv4Server.iSockaddrLength = 4;
+    }
+
+    void
+    Given_Network_Adapter_Supports_DHCP_V6(IP_ADAPTER_ADDRESSES& adapterAddresses)
+    {
+      adapterAddresses.Dhcpv6Server.iSockaddrLength = 12;
+    }
+
+    void
+    Given_Network_Adapter_Has_Operational_Status(
+        IP_ADAPTER_ADDRESSES& adapterAddresses,
+        IF_OPER_STATUS operStatus)
+    {
+      adapterAddresses.OperStatus = operStatus;
+    }
+
+  private:
+    void
+    AddAdapterAddresses(IP_ADAPTER_ADDRESSES& aAddressToAdd)
+    {
+      mMockWindowsFunctions.get()->AddAdapterAddresses(aAddressToAdd);
+    }
+
+
+};
+
+// following class currently just distinguishes tests of nsWindowsDHCPClient from
+// tests of DHCPUtils.
+class TestNsWindowsDHCPClient : public TestDHCPUtils { };
+
+
+TEST_F(TestDHCPUtils, TestGetAdaptersAddresses)
+{
+  IP_ADAPTER_ADDRESSES adapterAddresses = {};
+  Given_Network_Adapter_Called(adapterAddresses, "my favourite network adapter");
+  Given_Network_Adapter_Supports_DHCP_V4(adapterAddresses);
+  Given_Network_Adapter_Has_Operational_Status(adapterAddresses, IfOperStatusUp);
+
+  nsCString networkAdapterName;
+
+  ASSERT_EQ(NS_OK, GetActiveDHCPNetworkAdapterName(networkAdapterName, mMockWindowsFunctions));
+
+  ASSERT_STREQ(networkAdapterName.Data(), "my favourite network adapter");
+}
+
+TEST_F(TestDHCPUtils, TestGetAdaptersAddressesNoAvailableNetworks)
+{
+  IP_ADAPTER_ADDRESSES adapterAddresses = {};
+  Given_Network_Adapter_Called(adapterAddresses, "my favourite network adapter");
+  Given_Network_Adapter_Supports_DHCP_V4(adapterAddresses);
+  Given_Network_Adapter_Has_Operational_Status(adapterAddresses, IfOperStatusDown);
+
+  nsCString networkAdapterName;
+  ASSERT_EQ(NS_ERROR_NOT_AVAILABLE, GetActiveDHCPNetworkAdapterName(networkAdapterName, mMockWindowsFunctions));
+
+  ASSERT_STREQ(networkAdapterName.Data(), "");
+}
+
+TEST_F(TestDHCPUtils, TestGetAdaptersAddressesNoNetworksWithDHCP)
+{
+  IP_ADAPTER_ADDRESSES adapterAddresses = {};
+  Given_Network_Adapter_Called(adapterAddresses, "my favourite network adapter");
+  Given_Network_Adapter_Has_Operational_Status(adapterAddresses, IfOperStatusUp);
+
+  nsCString networkAdapterName;
+  ASSERT_EQ(NS_ERROR_NOT_AVAILABLE, GetActiveDHCPNetworkAdapterName(networkAdapterName, mMockWindowsFunctions));
+
+  ASSERT_STREQ(networkAdapterName.Data(), "");
+}
+
+TEST_F(TestDHCPUtils, TestGetAdaptersAddressesSecondNetworkIsAvailable)
+{
+  IP_ADAPTER_ADDRESSES adapterAddresses = {};
+  Given_Network_Adapter_Called(adapterAddresses, "my favourite network adapter");
+  Given_Network_Adapter_Supports_DHCP_V4(adapterAddresses);
+  Given_Network_Adapter_Has_Operational_Status(adapterAddresses, IfOperStatusDown);
+
+
+  IP_ADAPTER_ADDRESSES secondAdapterAddresses = {};
+  Given_Network_Adapter_Called(secondAdapterAddresses, "my second favourite network adapter");
+  Given_Network_Adapter_Supports_DHCP_V6(secondAdapterAddresses);
+  Given_Network_Adapter_Has_Operational_Status(secondAdapterAddresses, IfOperStatusUp);
+
+  nsCString networkAdapterName;
+  ASSERT_EQ(NS_OK, GetActiveDHCPNetworkAdapterName(networkAdapterName, mMockWindowsFunctions));
+
+  ASSERT_STREQ(networkAdapterName.Data(), "my second favourite network adapter");
+}
+
+
+TEST_F(TestDHCPUtils, TestGetOption)
+{
+
+  char* pacURL = "http://pac.com";
+  Given_DHCP_Option_Is(1, "My network option");
+  Given_DHCP_Option_Is(252, pacURL);
+
+  std::vector<char> optionValue(255, *"originalValue originalValue");
+  memcpy(optionValue.data(), "originalValue originalValue", strlen("originalValue originalValue") + 1);
+
+  uint32_t size = 255;
+
+  nsresult retVal = RetrieveOption(mDefaultAdapterName, 252, optionValue, &size, mMockWindowsFunctions);
+
+  ASSERT_EQ(strlen(pacURL), size);
+  ASSERT_STREQ("http://pac.comoriginalValue", optionValue.data());
+  ASSERT_EQ(NS_OK, retVal);
+}
+
+TEST_F(TestDHCPUtils, TestGetAbsentOption)
+{
+  std::vector<char> optionValue(255);
+  uint32_t size = 256;
+  memcpy(optionValue.data(), "originalValue", strlen("originalValue") + 1);
+
+  nsresult retVal = RetrieveOption(mDefaultAdapterName, 252, optionValue, &size, mMockWindowsFunctions);
+
+  ASSERT_EQ(0, size);
+  ASSERT_EQ(NS_ERROR_NOT_AVAILABLE, retVal);
+}
+
+TEST_F(TestDHCPUtils, TestGetTooLongOption)
+{
+  Given_DHCP_Option_Is(252, "http://pac.com");
+
+  std::vector<char> optionValue(255);
+  memcpy(optionValue.data(), "originalValue", strlen("originalValue") + 1);
+  uint32_t size = 4;
+  nsresult retVal = RetrieveOption(mDefaultAdapterName, 252, optionValue, &size, mMockWindowsFunctions);
+
+  ASSERT_STREQ("httpinalValue", optionValue.data());
+  ASSERT_EQ(NS_ERROR_LOSS_OF_SIGNIFICANT_DATA, retVal);
+  ASSERT_EQ(strlen("http://pac.com"), size);
+}
+
+TEST_F(TestNsWindowsDHCPClient, TestGettingOptionThroughNSWindowsDHCPClient)
+{
+  IP_ADAPTER_ADDRESSES adapterAddresses = {};
+  Given_Network_Adapter_Called(adapterAddresses, "my favourite network adapter");
+  Given_Network_Adapter_Supports_DHCP_V4(adapterAddresses);
+  Given_Network_Adapter_Has_Operational_Status(adapterAddresses, IfOperStatusUp);
+  Given_DHCP_Option_Is(252, "http://pac.com");
+
+  nsCString optionValue;
+  nsCOMPtr<nsIDHCPClient> dhcpClient = new nsWindowsDHCPClient(mMockWindowsFunctions);
+  nsresult retVal = dhcpClient->GetOption(252, optionValue);
+
+  ASSERT_STREQ("http://pac.com", optionValue.Data());
+  ASSERT_STREQ(L"my favourite network adapter", mMockWindowsFunctions->GetLastRequestedNetworkAdapterName().Data());
+  ASSERT_EQ(NS_OK, retVal);
+}
+
+TEST_F(TestNsWindowsDHCPClient, TestGettingOptionThroughNSWindowsDHCPClientWhenNoAvailableNetworkAdapter)
+{
+  IP_ADAPTER_ADDRESSES adapterAddresses = {};
+  Given_Network_Adapter_Called(adapterAddresses, "my favourite network adapter");
+  Given_Network_Adapter_Has_Operational_Status(adapterAddresses, IfOperStatusDown);
+  Given_DHCP_Option_Is(252, "http://pac.com");
+
+  nsCString optionValue;
+  nsCOMPtr<nsIDHCPClient> dhcpClient = new nsWindowsDHCPClient(mMockWindowsFunctions);
+  nsresult retVal = dhcpClient->GetOption(252, optionValue);
+
+  ASSERT_STREQ("", optionValue.Data());
+  ASSERT_EQ(NS_ERROR_NOT_AVAILABLE, retVal);
+}
+
+TEST_F(TestNsWindowsDHCPClient, TestGettingAbsentOptionThroughNSWindowsDHCPClient)
+{
+  IP_ADAPTER_ADDRESSES adapterAddresses = {};
+  Given_Network_Adapter_Called(adapterAddresses, "my favourite network adapter");
+  Given_Network_Adapter_Supports_DHCP_V6(adapterAddresses);
+  Given_Network_Adapter_Has_Operational_Status(adapterAddresses, IfOperStatusUp);
+
+  nsCString optionValue;
+  nsCOMPtr<nsIDHCPClient> dhcpClient = new nsWindowsDHCPClient(mMockWindowsFunctions);
+  nsresult retVal = dhcpClient->GetOption(252, optionValue);
+
+  ASSERT_STREQ("", optionValue.Data());
+  ASSERT_EQ(NS_ERROR_NOT_AVAILABLE, retVal);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/tests/gtest/moz.build
@@ -0,0 +1,21 @@
+# -*- 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: HTTP')
+
+UNIFIED_SOURCES += [
+    'TestDHCPUtils.cpp',
+]
+
+LOCAL_INCLUDES += [
+    '/toolkit/system/windowsDHCPClient',
+]
+
+FINAL_LIBRARY = 'xul-gtest'
+
+if CONFIG['GNU_CXX']:
+    CXXFLAGS += ['-Wshadow']