tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsPACMan.cpp (33477B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "nsPACMan.h"
      8 
      9 #include "mozilla/Preferences.h"
     10 #include "nsContentUtils.h"
     11 #include "nsComponentManagerUtils.h"
     12 #include "nsIAsyncVerifyRedirectCallback.h"
     13 #include "nsIAuthPrompt.h"
     14 #include "nsIDHCPClient.h"
     15 #include "nsIHttpChannel.h"
     16 #include "nsIPrefBranch.h"
     17 #include "nsIPromptFactory.h"
     18 #include "nsIProtocolProxyService.h"
     19 #include "nsISystemProxySettings.h"
     20 #include "nsIOService.h"
     21 #include "nsNetUtil.h"
     22 #include "nsThreadUtils.h"
     23 #include "mozilla/StaticPrefs_network.h"
     24 #include "mozilla/Try.h"
     25 
     26 //-----------------------------------------------------------------------------
     27 
     28 namespace mozilla {
     29 namespace net {
     30 
     31 LazyLogModule gProxyLog("proxy");
     32 
     33 #undef LOG
     34 #define LOG(args) MOZ_LOG(gProxyLog, LogLevel::Debug, args)
     35 #define MOZ_WPAD_URL "http://wpad/wpad.dat"
     36 #define MOZ_DHCP_WPAD_OPTION 252
     37 
     38 // If a GetOption call is in progress (which may block)
     39 // this will be set to true so we don't dispatch another task
     40 // until the pending one is complete.
     41 static Atomic<bool> sGetOptionInProgress(false);
     42 
     43 // These pointers are declared in nsProtocolProxyService.cpp
     44 extern const char kProxyType_HTTPS[];
     45 extern const char kProxyType_DIRECT[];
     46 
     47 // The PAC thread does evaluations of both PAC files and
     48 // nsISystemProxySettings because they can both block the calling thread and we
     49 // don't want that on the main thread
     50 
     51 // Check to see if the underlying request was not an error page in the case of
     52 // a HTTP request.  For other types of channels, just return true.
     53 static bool HttpRequestSucceeded(nsIStreamLoader* loader) {
     54  nsCOMPtr<nsIRequest> request;
     55  loader->GetRequest(getter_AddRefs(request));
     56 
     57  bool result = true;  // default to assuming success
     58 
     59  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
     60  if (httpChannel) {
     61    // failsafe
     62    (void)httpChannel->GetRequestSucceeded(&result);
     63  }
     64 
     65  return result;
     66 }
     67 
     68 // Read preference setting of extra JavaScript context heap size.
     69 // PrefService tends to be run on main thread, where ProxyAutoConfig runs on
     70 // ProxyResolution thread, so it's read here and passed to ProxyAutoConfig.
     71 static uint32_t GetExtraJSContextHeapSize() {
     72  MOZ_ASSERT(NS_IsMainThread());
     73 
     74  static int32_t extraSize = -1;
     75 
     76  if (extraSize < 0) {
     77    nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
     78    int32_t value;
     79 
     80    if (prefs &&
     81        NS_SUCCEEDED(prefs->GetIntPref(
     82            "network.proxy.autoconfig_extra_jscontext_heap_size", &value))) {
     83      LOG(("autoconfig_extra_jscontext_heap_size: %d\n", value));
     84 
     85      extraSize = value;
     86    }
     87  }
     88 
     89  return extraSize < 0 ? 0 : extraSize;
     90 }
     91 
     92 // Read network proxy type from preference
     93 // Used to verify that the preference is WPAD in nsPACMan::ConfigureWPAD
     94 nsresult GetNetworkProxyTypeFromPref(int32_t* type) {
     95  *type = 0;
     96  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
     97 
     98  if (!prefs) {
     99    LOG(("Failed to get a preference service object"));
    100    return NS_ERROR_FACTORY_NOT_REGISTERED;
    101  }
    102  nsresult rv = prefs->GetIntPref("network.proxy.type", type);
    103  if (NS_FAILED(rv)) {
    104    LOG(("Failed to retrieve network.proxy.type from prefs"));
    105    return rv;
    106  }
    107  LOG(("network.proxy.type pref retrieved: %d\n", *type));
    108  return NS_OK;
    109 }
    110 
    111 //-----------------------------------------------------------------------------
    112 
    113 // The ExecuteCallback runnable is triggered by
    114 // nsPACManCallback::OnQueryComplete on the Main thread when its completion is
    115 // discovered on the pac thread
    116 
    117 class ExecuteCallback final : public Runnable {
    118 public:
    119  ExecuteCallback(nsPACManCallback* aCallback, nsresult status)
    120      : Runnable("net::ExecuteCallback"),
    121        mCallback(aCallback),
    122        mStatus(status) {}
    123 
    124  void SetPACString(const nsACString& pacString) { mPACString = pacString; }
    125 
    126  void SetPACURL(const nsACString& pacURL) { mPACURL = pacURL; }
    127 
    128  NS_IMETHOD Run() override {
    129    mCallback->OnQueryComplete(mStatus, mPACString, mPACURL);
    130    mCallback = nullptr;
    131    return NS_OK;
    132  }
    133 
    134 private:
    135  RefPtr<nsPACManCallback> mCallback;
    136  nsresult mStatus;
    137  nsCString mPACString;
    138  nsCString mPACURL;
    139 };
    140 
    141 //-----------------------------------------------------------------------------
    142 
    143 // The PAC thread must be deleted from the main thread, this class
    144 // acts as a proxy to do that, as the PACMan is reference counted
    145 // and might be destroyed on either thread
    146 
    147 class ShutdownThread final : public Runnable {
    148 public:
    149  explicit ShutdownThread(nsIThread* thread)
    150      : Runnable("net::ShutdownThread"), mThread(thread) {}
    151 
    152  NS_IMETHOD Run() override {
    153    MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
    154    mThread->Shutdown();
    155    return NS_OK;
    156  }
    157 
    158 private:
    159  nsCOMPtr<nsIThread> mThread;
    160 };
    161 
    162 // Dispatch this to wait until the PAC thread shuts down.
    163 
    164 class WaitForThreadShutdown final : public Runnable {
    165 public:
    166  explicit WaitForThreadShutdown(nsPACMan* aPACMan)
    167      : Runnable("net::WaitForThreadShutdown"), mPACMan(aPACMan) {}
    168 
    169  NS_IMETHOD Run() override {
    170    MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
    171    if (mPACMan->mPACThread) {
    172      mPACMan->mPACThread->Shutdown();
    173      mPACMan->mPACThread = nullptr;
    174    }
    175    return NS_OK;
    176  }
    177 
    178 private:
    179  RefPtr<nsPACMan> mPACMan;
    180 };
    181 
    182 //-----------------------------------------------------------------------------
    183 
    184 // PACLoadComplete allows the PAC thread to tell the main thread that
    185 // the javascript PAC file has been installed (perhaps unsuccessfully)
    186 // and that there is no reason to queue executions anymore
    187 
    188 class PACLoadComplete final : public Runnable {
    189 public:
    190  explicit PACLoadComplete(nsPACMan* aPACMan)
    191      : Runnable("net::PACLoadComplete"), mPACMan(aPACMan) {}
    192 
    193  NS_IMETHOD Run() override {
    194    MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
    195    {
    196      auto loader = mPACMan->mLoader.Lock();
    197      loader.ref() = nullptr;
    198    }
    199    mPACMan->PostProcessPendingQ();
    200    return NS_OK;
    201  }
    202 
    203 private:
    204  RefPtr<nsPACMan> mPACMan;
    205 };
    206 
    207 //-----------------------------------------------------------------------------
    208 
    209 // ConfigureWPADComplete allows the PAC thread to tell the main thread that
    210 // the URL for the PAC file has been found
    211 class ConfigureWPADComplete final : public Runnable {
    212 public:
    213  ConfigureWPADComplete(nsPACMan* aPACMan, const nsACString& aPACURISpec)
    214      : Runnable("net::ConfigureWPADComplete"),
    215        mPACMan(aPACMan),
    216        mPACURISpec(aPACURISpec) {}
    217 
    218  NS_IMETHOD Run() override {
    219    MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
    220    mPACMan->AssignPACURISpec(mPACURISpec);
    221    mPACMan->ContinueLoadingAfterPACUriKnown();
    222    return NS_OK;
    223  }
    224 
    225 private:
    226  RefPtr<nsPACMan> mPACMan;
    227  nsCString mPACURISpec;
    228 };
    229 
    230 //-----------------------------------------------------------------------------
    231 
    232 // ExecutePACThreadAction is used to proxy actions from the main
    233 // thread onto the PAC thread. There are 4 options: process the queue,
    234 // cancel the queue, query DHCP for the PAC option
    235 // and setup the javascript context with a new PAC file
    236 
    237 class ExecutePACThreadAction final : public Runnable {
    238 public:
    239  // by default we just process the queue
    240  explicit ExecutePACThreadAction(nsPACMan* aPACMan)
    241      : Runnable("net::ExecutePACThreadAction"),
    242        mPACMan(aPACMan),
    243        mCancel(false),
    244        mCancelStatus(NS_OK),
    245        mSetupPAC(false),
    246        mExtraHeapSize(0),
    247        mConfigureWPAD(false),
    248        mShutdown(false) {}
    249 
    250  void CancelQueue(nsresult status, bool aShutdown) {
    251    mCancel = true;
    252    mCancelStatus = status;
    253    mShutdown = aShutdown;
    254  }
    255 
    256  void SetupPAC(const char* data, uint32_t dataLen, const nsACString& pacURI,
    257                uint32_t extraHeapSize) {
    258    mSetupPAC = true;
    259    mSetupPACData.Assign(data, dataLen);
    260    mSetupPACURI = pacURI;
    261    mExtraHeapSize = extraHeapSize;
    262  }
    263 
    264  void ConfigureWPAD() { mConfigureWPAD = true; }
    265 
    266  NS_IMETHOD Run() override {
    267    MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
    268    if (mCancel) {
    269      mPACMan->CancelPendingQ(mCancelStatus, mShutdown);
    270      mCancel = false;
    271      return NS_OK;
    272    }
    273 
    274    if (mSetupPAC) {
    275      mSetupPAC = false;
    276 
    277      nsCOMPtr<nsISerialEventTarget> target = mPACMan->GetNeckoTarget();
    278      mPACMan->mPAC->ConfigurePAC(mSetupPACURI, mSetupPACData,
    279                                  mPACMan->mIncludePath, mExtraHeapSize,
    280                                  target);
    281 
    282      RefPtr<PACLoadComplete> runnable = new PACLoadComplete(mPACMan);
    283      mPACMan->Dispatch(runnable.forget());
    284      return NS_OK;
    285    }
    286 
    287    if (mConfigureWPAD) {
    288      nsAutoCString spec;
    289      mConfigureWPAD = false;
    290      mPACMan->ConfigureWPAD(spec);
    291      RefPtr<ConfigureWPADComplete> runnable =
    292          new ConfigureWPADComplete(mPACMan, spec);
    293      mPACMan->Dispatch(runnable.forget());
    294      return NS_OK;
    295    }
    296 
    297    mPACMan->ProcessPendingQ();
    298    return NS_OK;
    299  }
    300 
    301 private:
    302  RefPtr<nsPACMan> mPACMan;
    303 
    304  bool mCancel;
    305  nsresult mCancelStatus;
    306 
    307  bool mSetupPAC;
    308  uint32_t mExtraHeapSize;
    309  nsCString mSetupPACData;
    310  nsCString mSetupPACURI;
    311  bool mConfigureWPAD;
    312  bool mShutdown;
    313 };
    314 
    315 //-----------------------------------------------------------------------------
    316 
    317 PendingPACQuery::PendingPACQuery(nsPACMan* pacMan, nsIURI* uri,
    318                                 nsPACManCallback* callback, uint32_t flags,
    319                                 bool mainThreadResponse)
    320    : Runnable("net::PendingPACQuery"),
    321      mPort(0),
    322      mFlags(flags),
    323      mPACMan(pacMan),
    324      mCallback(callback),
    325      mOnMainThreadOnly(mainThreadResponse) {
    326  uri->GetAsciiSpec(mSpec);
    327  uri->GetAsciiHost(mHost);
    328  uri->GetScheme(mScheme);
    329  uri->GetPort(&mPort);
    330 }
    331 
    332 void PendingPACQuery::Complete(nsresult status, const nsACString& pacString) {
    333  if (!mCallback) return;
    334  RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, status);
    335  runnable->SetPACString(pacString);
    336  if (mOnMainThreadOnly) {
    337    mPACMan->Dispatch(runnable.forget());
    338  } else {
    339    runnable->Run();
    340  }
    341 }
    342 
    343 void PendingPACQuery::UseAlternatePACFile(const nsACString& pacURL) {
    344  if (!mCallback) return;
    345 
    346  RefPtr<ExecuteCallback> runnable = new ExecuteCallback(mCallback, NS_OK);
    347  runnable->SetPACURL(pacURL);
    348  if (mOnMainThreadOnly) {
    349    mPACMan->Dispatch(runnable.forget());
    350  } else {
    351    runnable->Run();
    352  }
    353 }
    354 
    355 NS_IMETHODIMP
    356 PendingPACQuery::Run() {
    357  MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
    358  mPACMan->PostQuery(this);
    359  return NS_OK;
    360 }
    361 
    362 //-----------------------------------------------------------------------------
    363 
    364 static bool sThreadLocalSetup = false;
    365 static uint32_t sThreadLocalIndex = 0xdeadbeef;  // out of range
    366 
    367 static const char* kPACIncludePath =
    368    "network.proxy.autoconfig_url.include_path";
    369 
    370 nsPACMan::nsPACMan(nsISerialEventTarget* mainThreadEventTarget)
    371    : NeckoTargetHolder(mainThreadEventTarget),
    372      mLoader("nsPACMan::mLoader"),
    373      mLoadPending(false),
    374      mShutdown(false),
    375      mLoadFailureCount(0),
    376      mInProgress(false),
    377      mAutoDetect(false),
    378      mWPADOverDHCPEnabled(false),
    379      mProxyConfigType(0) {
    380  MOZ_ASSERT(NS_IsMainThread(), "pacman must be created on main thread");
    381  mIncludePath = Preferences::GetBool(kPACIncludePath, false);
    382  if (StaticPrefs::network_proxy_parse_pac_on_socket_process() &&
    383      gIOService->SocketProcessReady()) {
    384    mPAC = MakeUnique<RemoteProxyAutoConfig>();
    385  } else {
    386    mPAC = MakeUnique<ProxyAutoConfig>();
    387    if (!sThreadLocalSetup) {
    388      sThreadLocalSetup = true;
    389      PR_NewThreadPrivateIndex(&sThreadLocalIndex, nullptr);
    390    }
    391    mPAC->SetThreadLocalIndex(sThreadLocalIndex);
    392  }
    393 }
    394 
    395 nsPACMan::~nsPACMan() {
    396  MOZ_ASSERT(mShutdown, "Shutdown must be called before dtor.");
    397 
    398  if (mPACThread) {
    399    if (NS_IsMainThread()) {
    400      mPACThread->Shutdown();
    401      mPACThread = nullptr;
    402    } else {
    403      RefPtr<ShutdownThread> runnable = new ShutdownThread(mPACThread);
    404      Dispatch(runnable.forget());
    405    }
    406  }
    407 
    408 #ifdef DEBUG
    409  {
    410    auto loader = mLoader.Lock();
    411    NS_ASSERTION(loader.ref() == nullptr, "pac man not shutdown properly");
    412  }
    413 #endif
    414 
    415  NS_ASSERTION(mPendingQ.isEmpty(), "pac man not shutdown properly");
    416 }
    417 
    418 void nsPACMan::Shutdown() {
    419  MOZ_ASSERT(NS_IsMainThread(), "pacman must be shutdown on main thread");
    420  if (mShutdown) {
    421    return;
    422  }
    423 
    424  CancelExistingLoad();
    425 
    426  if (mPACThread) {
    427    PostCancelPendingQ(NS_ERROR_ABORT, /*aShutdown =*/true);
    428 
    429    // Shutdown is initiated from an observer. We don't want to block the
    430    // observer service on thread shutdown so we post a shutdown runnable that
    431    // will run after we return instead.
    432    RefPtr<WaitForThreadShutdown> runnable = new WaitForThreadShutdown(this);
    433    Dispatch(runnable.forget());
    434  }
    435 
    436  mShutdown = true;
    437 }
    438 
    439 nsresult nsPACMan::DispatchToPAC(already_AddRefed<nsIRunnable> aEvent,
    440                                 bool aSync) {
    441  LOG(("nsPACMan::DispatchToPAC"));
    442  MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
    443 
    444  nsCOMPtr<nsIRunnable> e(aEvent);
    445 
    446  if (mShutdown) {
    447    return NS_ERROR_NOT_AVAILABLE;
    448  }
    449 
    450  // Lazily create the PAC thread. This method is main-thread only so we don't
    451  // have to worry about threading issues here.
    452  if (!mPACThread) {
    453    MOZ_TRY(NS_NewNamedThread("ProxyResolution", getter_AddRefs(mPACThread)));
    454    nsresult rv = mPAC->Init(mPACThread);
    455    if (NS_FAILED(rv)) {
    456      return rv;
    457    }
    458  }
    459 
    460  if (aSync) {
    461    return NS_DispatchAndSpinEventLoopUntilComplete(
    462        "nsPACMan::DispatchToPAC"_ns, mPACThread, e.forget());
    463  } else {
    464    return mPACThread->Dispatch(e.forget());
    465  }
    466 }
    467 
    468 nsresult nsPACMan::AsyncGetProxyForURI(nsIURI* uri, nsPACManCallback* callback,
    469                                       uint32_t flags,
    470                                       bool mainThreadResponse) {
    471  LOG(("nsPACMan::AsyncGetProxyForURI"));
    472  MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
    473  if (mShutdown) return NS_ERROR_NOT_AVAILABLE;
    474 
    475  // Maybe Reload PAC
    476  if (!mPACURISpec.IsEmpty() && !mScheduledReload.IsNull() &&
    477      TimeStamp::Now() > mScheduledReload) {
    478    LOG(("nsPACMan::AsyncGetProxyForURI reload as scheduled\n"));
    479 
    480    LoadPACFromURI(mAutoDetect ? ""_ns : mPACURISpec, false);
    481  }
    482 
    483  RefPtr<PendingPACQuery> query =
    484      new PendingPACQuery(this, uri, callback, flags, mainThreadResponse);
    485 
    486  if (IsPACURI(uri)) {
    487    // deal with this directly instead of queueing it
    488    query->Complete(NS_OK, ""_ns);
    489    return NS_OK;
    490  }
    491 
    492  return DispatchToPAC(query.forget());
    493 }
    494 
    495 nsresult nsPACMan::PostQuery(PendingPACQuery* query) {
    496  MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
    497  LOG(("nsPACMan::PostQuery"));
    498 
    499  if (mShutdown) {
    500    query->Complete(NS_ERROR_NOT_AVAILABLE, ""_ns);
    501    return NS_OK;
    502  }
    503 
    504  // add a reference to the query while it is in the pending list
    505  RefPtr<PendingPACQuery> addref(query);
    506  mPendingQ.insertBack(addref.forget().take());
    507  ProcessPendingQ();
    508  return NS_OK;
    509 }
    510 
    511 // check if proxy settings are configured for WPAD
    512 bool IsProxyConfigValidForWPAD(int proxyConfigType, bool wpadSystemSettings) {
    513  return proxyConfigType == nsIProtocolProxyService::PROXYCONFIG_WPAD ||
    514         (proxyConfigType == nsIProtocolProxyService::PROXYCONFIG_SYSTEM &&
    515          wpadSystemSettings);
    516 }
    517 
    518 nsresult nsPACMan::LoadPACFromURI(const nsACString& aSpec) {
    519  return LoadPACFromURI(aSpec, true);
    520 }
    521 
    522 nsresult nsPACMan::LoadPACFromURI(const nsACString& aSpec,
    523                                  bool aResetLoadFailureCount) {
    524  NS_ENSURE_STATE(!mShutdown);
    525 
    526  nsCOMPtr<nsIStreamLoader> loader =
    527      do_CreateInstance(NS_STREAMLOADER_CONTRACTID);
    528  NS_ENSURE_STATE(loader);
    529 
    530  LOG(("nsPACMan::LoadPACFromURI aSpec: %s, aResetLoadFailureCount: %s\n",
    531       aSpec.BeginReading(), aResetLoadFailureCount ? "true" : "false"));
    532 
    533  CancelExistingLoad();
    534 
    535  {
    536    auto locked = mLoader.Lock();
    537    locked.ref() = loader.forget();
    538  }
    539  mPACURIRedirectSpec.Truncate();
    540  mNormalPACURISpec.Truncate();  // set at load time
    541  if (aResetLoadFailureCount) {
    542    mLoadFailureCount = 0;
    543  }
    544  mAutoDetect = aSpec.IsEmpty();
    545  mPACURISpec.Assign(aSpec);
    546 
    547  // reset to Null
    548  mScheduledReload = TimeStamp();
    549 
    550  // if we're on the main thread here so we can get hold of prefs,
    551  // we check that we have WPAD preffed on if we're auto-detecting
    552  if (mAutoDetect && NS_IsMainThread()) {
    553    nsresult rv = GetNetworkProxyTypeFromPref(&mProxyConfigType);
    554    if (NS_FAILED(rv)) {
    555      return rv;
    556    }
    557    if (!IsProxyConfigValidForWPAD(mProxyConfigType, mAutoDetect)) {
    558      LOG(
    559          ("LoadPACFromURI - Aborting WPAD autodetection because the pref "
    560           "doesn't match anymore"));
    561      return NS_BINDING_ABORTED;
    562    }
    563  }
    564  // Since we might get called from nsProtocolProxyService::Init, we need to
    565  // post an event back to the main thread before we try to use the IO service.
    566  //
    567  // But, we need to flag ourselves as loading, so that we queue up any PAC
    568  // queries the enter between now and when we actually load the PAC file.
    569 
    570  if (!mLoadPending) {
    571    nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
    572        "nsPACMan::StartLoading", this, &nsPACMan::StartLoading);
    573    nsresult rv =
    574        NS_IsMainThread()
    575            ? Dispatch(runnable.forget())
    576            : GetCurrentSerialEventTarget()->Dispatch(runnable.forget());
    577    if (NS_FAILED(rv)) return rv;
    578    mLoadPending = true;
    579  }
    580 
    581  return NS_OK;
    582 }
    583 
    584 nsresult nsPACMan::GetPACFromDHCP(nsACString& aSpec) {
    585  nsresult rv;
    586  MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
    587  if (!mDHCPClient) {
    588    LOG(
    589        ("nsPACMan::GetPACFromDHCP DHCP option %d query failed because there "
    590         "is no DHCP client available\n",
    591         MOZ_DHCP_WPAD_OPTION));
    592    return NS_ERROR_NOT_IMPLEMENTED;
    593  }
    594  if (sGetOptionInProgress &&
    595      StaticPrefs::network_proxy_dhcp_wpad_only_one_outstanding()) {
    596    LOG(("GetPACFromDHCP task already in progress"));
    597    return NS_ERROR_IN_PROGRESS;
    598  }
    599 
    600  MonitorAutoLock lock(mMonitor);
    601  mPACStringFromDHCP.Truncate();
    602 
    603  RefPtr<nsPACMan> self = this;
    604  sGetOptionInProgress = true;
    605  rv = NS_DispatchBackgroundTask(
    606      NS_NewRunnableFunction(
    607          "nsPACMan::GetPACFromDHCP",
    608          [dhcpClient = nsCOMPtr{mDHCPClient}, self] {
    609            nsAutoCString spec;
    610            nsresult rv;
    611            rv = dhcpClient->GetOption(MOZ_DHCP_WPAD_OPTION, spec);
    612            if (NS_FAILED(rv)) {
    613              LOG(
    614                  ("nsPACMan::GetPACFromDHCP DHCP option %d "
    615                   "query failed with result %d\n",
    616                   MOZ_DHCP_WPAD_OPTION, (uint32_t)rv));
    617            } else {
    618              LOG(
    619                  ("nsPACMan::GetPACFromDHCP DHCP option %d query succeeded,"
    620                   "finding PAC URL %s\n",
    621                   MOZ_DHCP_WPAD_OPTION, spec.BeginReading()));
    622            }
    623            MonitorAutoLock lock(self->mMonitor);
    624            self->mPACStringFromDHCP = spec;
    625            sGetOptionInProgress = false;
    626            self->mMonitor.NotifyAll();
    627          }),
    628      NS_DISPATCH_EVENT_MAY_BLOCK);
    629 
    630  if (NS_FAILED(rv)) {
    631    sGetOptionInProgress = false;
    632    return rv;
    633  }
    634 
    635  // If the call to GetOption does not complete in a reasonable amount of time,
    636  // we should continue to avoid blocking all network traffic.
    637  mMonitor.Wait(TimeDuration::FromSeconds(
    638      StaticPrefs::network_proxy_dhcp_wpad_timeout_sec()));
    639  aSpec = mPACStringFromDHCP;
    640  mPACStringFromDHCP.Truncate();
    641 
    642  return NS_OK;
    643 }
    644 
    645 nsresult nsPACMan::ConfigureWPAD(nsACString& aSpec) {
    646  LOG(("nsPACMan::ConfigureWPAD(%s)", nsCString(aSpec).get()));
    647  MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
    648 
    649  if (!IsProxyConfigValidForWPAD(mProxyConfigType, mAutoDetect)) {
    650    LOG(
    651        ("ConfigureWPAD - Aborting WPAD autodetection because the pref "
    652         "doesn't match anymore"));
    653    return NS_BINDING_ABORTED;
    654  }
    655 
    656  aSpec.Truncate();
    657  if (mWPADOverDHCPEnabled) {
    658    GetPACFromDHCP(aSpec);
    659  }
    660 
    661  if (aSpec.IsEmpty()) {
    662    // We diverge from the WPAD spec here in that we don't walk the
    663    // hosts's FQDN, stripping components until we hit a TLD.  Doing so
    664    // is dangerous in the face of an incomplete list of TLDs, and TLDs
    665    // get added over time.  We could consider doing only a single
    666    // substitution of the first component, if that proves to help
    667    // compatibility.
    668    aSpec.AssignLiteral(MOZ_WPAD_URL);
    669  }
    670  return NS_OK;
    671 }
    672 
    673 void nsPACMan::AssignPACURISpec(const nsACString& aSpec) {
    674  MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
    675  mPACURISpec.Assign(aSpec);
    676 }
    677 
    678 void nsPACMan::StartLoading() {
    679  MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
    680  mLoadPending = false;
    681  LOG(("nsPACMan::StartLoading"));
    682 
    683  {
    684    // CancelExistingLoad was called...
    685    nsCOMPtr<nsIStreamLoader> loader;
    686    {
    687      auto locked = mLoader.Lock();
    688      loader = locked.ref();
    689    }
    690    if (!loader) {
    691      PostCancelPendingQ(NS_ERROR_ABORT);
    692      return;
    693    }
    694  }
    695 
    696  if (mAutoDetect) {
    697    nsresult rv = GetNetworkProxyTypeFromPref(&mProxyConfigType);
    698    if (NS_FAILED(rv)) {
    699      NS_WARNING(
    700          "Could not retrieve Network Proxy Type pref when auto-detecting "
    701          "proxy. Halting.");
    702      return;
    703    }
    704    RefPtr<ExecutePACThreadAction> wpadConfigurer =
    705        new ExecutePACThreadAction(this);
    706    wpadConfigurer->ConfigureWPAD();
    707    DispatchToPAC(wpadConfigurer.forget());
    708  } else {
    709    ContinueLoadingAfterPACUriKnown();
    710  }
    711 }
    712 
    713 void nsPACMan::ContinueLoadingAfterPACUriKnown() {
    714  MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
    715  LOG(("nsPACMan::ContinueLoadingAfterPACUriKnown"));
    716 
    717  nsCOMPtr<nsIStreamLoader> loader;
    718  {
    719    auto locked = mLoader.Lock();
    720    loader = locked.ref();
    721  }
    722 
    723  // CancelExistingLoad was called...
    724  if (!loader) {
    725    PostCancelPendingQ(NS_ERROR_ABORT);
    726    return;
    727  }
    728  if (NS_SUCCEEDED(loader->Init(this, nullptr))) {
    729    // Always hit the origin server when loading PAC.
    730    nsCOMPtr<nsIIOService> ios = do_GetIOService();
    731    if (ios) {
    732      nsCOMPtr<nsIChannel> channel;
    733      nsCOMPtr<nsIURI> pacURI;
    734      NS_NewURI(getter_AddRefs(pacURI), mPACURISpec);
    735 
    736      // NOTE: This results in GetProxyForURI being called
    737      if (pacURI) {
    738        nsresult rv = pacURI->GetSpec(mNormalPACURISpec);
    739        MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
    740        NS_NewChannel(getter_AddRefs(channel), pacURI,
    741                      nsContentUtils::GetSystemPrincipal(),
    742                      nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
    743                      nsIContentPolicy::TYPE_OTHER,
    744                      nullptr,  // nsICookieJarSettings
    745                      nullptr,  // PerformanceStorage
    746                      nullptr,  // aLoadGroup
    747                      nullptr,  // aCallbacks
    748                      nsIRequest::LOAD_NORMAL, ios);
    749      } else {
    750        LOG(("nsPACMan::StartLoading Failed pacspec uri conversion %s\n",
    751             mPACURISpec.get()));
    752      }
    753 
    754      if (channel) {
    755        // allow deprecated HTTP request from SystemPrincipal
    756        nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
    757        loadInfo->SetAllowDeprecatedSystemRequests(true);
    758        loadInfo->SetHttpsOnlyStatus(nsILoadInfo::HTTPS_ONLY_EXEMPT);
    759 
    760        channel->SetLoadFlags(nsIRequest::LOAD_BYPASS_CACHE);
    761        channel->SetNotificationCallbacks(this);
    762        channel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE);
    763        if (NS_SUCCEEDED(channel->AsyncOpen(loader))) return;
    764      }
    765    }
    766  }
    767 
    768  CancelExistingLoad();
    769  PostCancelPendingQ(NS_ERROR_UNEXPECTED);
    770 }
    771 
    772 void nsPACMan::OnLoadFailure() {
    773  int32_t minInterval = 5;    // 5 seconds
    774  int32_t maxInterval = 300;  // 5 minutes
    775 
    776  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
    777  if (prefs) {
    778    prefs->GetIntPref("network.proxy.autoconfig_retry_interval_min",
    779                      &minInterval);
    780    prefs->GetIntPref("network.proxy.autoconfig_retry_interval_max",
    781                      &maxInterval);
    782  }
    783 
    784  int32_t interval = minInterval << mLoadFailureCount++;  // seconds
    785  if (!interval || interval > maxInterval) interval = maxInterval;
    786 
    787  mScheduledReload = TimeStamp::Now() + TimeDuration::FromSeconds(interval);
    788 
    789  LOG(("OnLoadFailure: retry in %d seconds (%d fails)\n", interval,
    790       (uint32_t)mLoadFailureCount));
    791 
    792  // while we wait for the retry queued members should try direct
    793  // even if that means fast failure.
    794  PostCancelPendingQ(NS_ERROR_NOT_AVAILABLE);
    795 }
    796 
    797 void nsPACMan::CancelExistingLoad() {
    798  nsCOMPtr<nsIStreamLoader> loader;
    799  {
    800    auto locked = mLoader.Lock();
    801    loader.swap(*locked);
    802  }
    803  if (loader) {
    804    nsCOMPtr<nsIRequest> request;
    805    loader->GetRequest(getter_AddRefs(request));
    806    if (request) {
    807      request->Cancel(NS_ERROR_ABORT);
    808    }
    809  }
    810 }
    811 
    812 void nsPACMan::PostProcessPendingQ() {
    813  MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
    814  RefPtr<ExecutePACThreadAction> pending = new ExecutePACThreadAction(this);
    815  DispatchToPAC(pending.forget());
    816 }
    817 
    818 void nsPACMan::PostCancelPendingQ(nsresult status, bool aShutdown) {
    819  MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
    820  RefPtr<ExecutePACThreadAction> pending = new ExecutePACThreadAction(this);
    821  pending->CancelQueue(status, aShutdown);
    822  DispatchToPAC(pending.forget());
    823 }
    824 
    825 void nsPACMan::CancelPendingQ(nsresult status, bool aShutdown) {
    826  MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
    827  RefPtr<PendingPACQuery> query;
    828 
    829  while (!mPendingQ.isEmpty()) {
    830    query = dont_AddRef(mPendingQ.popLast());
    831    query->Complete(status, ""_ns);
    832  }
    833 
    834  if (aShutdown) {
    835    mPAC->Shutdown();
    836  }
    837 }
    838 
    839 void nsPACMan::ProcessPendingQ() {
    840  MOZ_ASSERT(!NS_IsMainThread(), "wrong thread");
    841  while (ProcessPending()) {
    842    ;
    843  }
    844 
    845  if (mShutdown) {
    846    mPAC->Shutdown();
    847  } else {
    848    // do GC while the thread has nothing pending
    849    mPAC->GC();
    850  }
    851 }
    852 
    853 // returns true if progress was made by shortening the queue
    854 bool nsPACMan::ProcessPending() {
    855  LOG(("nsPACMan::AsyncGetProxyForURI"));
    856  if (mPendingQ.isEmpty()) return false;
    857 
    858  // queue during normal load, but if we are retrying a failed load then
    859  // fast fail the queries
    860  if (mInProgress || (IsLoading() && !mLoadFailureCount)) return false;
    861 
    862  RefPtr<PendingPACQuery> query(dont_AddRef(mPendingQ.popFirst()));
    863 
    864  // Having |mLoadFailureCount > 0| means we haven't had a sucessful PAC load
    865  // yet. We should use DIRECT instead.
    866  if (mShutdown || IsLoading() || mLoadFailureCount > 0) {
    867    query->Complete(NS_ERROR_NOT_AVAILABLE, ""_ns);
    868    return true;
    869  }
    870 
    871  nsAutoCString pacString;
    872  bool completed = false;
    873  mInProgress = true;
    874  nsAutoCString PACURI;
    875 
    876  // first we need to consider the system proxy changing the pac url
    877  if (mSystemProxySettings &&
    878      NS_SUCCEEDED(mSystemProxySettings->GetPACURI(PACURI)) &&
    879      !PACURI.IsEmpty() && !PACURI.Equals(mPACURISpec)) {
    880    query->UseAlternatePACFile(PACURI);
    881    LOG(("Use PAC from system settings: %s\n", PACURI.get()));
    882    completed = true;
    883  }
    884 
    885  // now try the system proxy settings for this particular url if
    886  // PAC was not specified
    887  if (!completed && mSystemProxySettings && PACURI.IsEmpty() &&
    888      NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI(
    889          query->mSpec, query->mScheme, query->mHost, query->mPort,
    890          pacString))) {
    891    if (query->mFlags & nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY &&
    892        query->mFlags & nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY) {
    893      if (StringBeginsWith(pacString, nsDependentCString(kProxyType_DIRECT),
    894                           nsCaseInsensitiveUTF8StringComparator)) {
    895        // DIRECT indicates that system proxy settings are not configured to use
    896        // SOCKS proxy. Try https proxy as a secondary preferrable proxy. This
    897        // is mainly for websocket whose precedence is SOCKS > HTTPS > DIRECT.
    898        NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI(
    899            query->mSpec, nsDependentCString(kProxyType_HTTPS), query->mHost,
    900            query->mPort, pacString));
    901      }
    902    }
    903    LOG(("Use proxy from system settings: %s\n", pacString.get()));
    904    query->Complete(NS_OK, pacString);
    905    completed = true;
    906  }
    907 
    908  // the systemproxysettings didn't complete the resolution. try via PAC
    909  if (!completed) {
    910    auto callback = [query(query)](nsresult aStatus,
    911                                   const nsACString& aResult) {
    912      LOG(("Use proxy from PAC: %s\n", PromiseFlatCString(aResult).get()));
    913      query->Complete(aStatus, aResult);
    914    };
    915    mPAC->GetProxyForURIWithCallback(query->mSpec, query->mHost,
    916                                     std::move(callback));
    917  }
    918 
    919  mInProgress = false;
    920  return true;
    921 }
    922 
    923 NS_IMPL_ISUPPORTS(nsPACMan, nsIStreamLoaderObserver, nsIInterfaceRequestor,
    924                  nsIChannelEventSink)
    925 
    926 NS_IMETHODIMP
    927 nsPACMan::OnStreamComplete(nsIStreamLoader* loader, nsISupports* context,
    928                           nsresult status, uint32_t dataLen,
    929                           const uint8_t* data) {
    930  MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
    931 
    932  bool loadSucceeded = NS_SUCCEEDED(status) && HttpRequestSucceeded(loader);
    933  {
    934    auto locked = mLoader.Lock();
    935    if (locked.ref() != loader) {
    936      // If this happens, then it means that LoadPACFromURI was called more
    937      // than once before the initial call completed.  In this case, status
    938      // should be NS_ERROR_ABORT, and if so, then we know that we can and
    939      // should delay any processing.
    940      LOG(("OnStreamComplete: called more than once\n"));
    941      if (status == NS_ERROR_ABORT) {
    942        return NS_OK;
    943      }
    944    } else if (!loadSucceeded) {
    945      // We have to clear the loader to indicate that we are not loading PAC
    946      // currently.
    947      // Note that we can only clear the loader when |loader| and |mLoader| are
    948      // the same one.
    949      locked.ref() = nullptr;
    950    }
    951  }
    952 
    953  LOG(("OnStreamComplete: entry\n"));
    954 
    955  if (loadSucceeded) {
    956    // Get the URI spec used to load this PAC script.
    957    nsAutoCString pacURI;
    958    {
    959      nsCOMPtr<nsIRequest> request;
    960      loader->GetRequest(getter_AddRefs(request));
    961      nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
    962      if (channel) {
    963        nsCOMPtr<nsIURI> uri;
    964        channel->GetURI(getter_AddRefs(uri));
    965        if (uri) uri->GetAsciiSpec(pacURI);
    966      }
    967    }
    968 
    969    nsCOMPtr<nsIProtocolProxyService> pps =
    970        do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
    971    MOZ_ASSERT(pps);
    972    if (pps) {
    973      pps->NotifyProxyConfigChangedInternal();
    974    }
    975 
    976    // We succeeded in loading the pac file using a bunch of interfaces that are
    977    // main thread only.  Unfortunately, we have to initialize the instance of
    978    // the PAC evaluator (NS_PROXYAUTOCONFIG_CONTRACTID) on the PAC thread,
    979    // because that's where it will be used.
    980    RefPtr<ExecutePACThreadAction> pending = new ExecutePACThreadAction(this);
    981    pending->SetupPAC(reinterpret_cast<const char*>(data), dataLen, pacURI,
    982                      GetExtraJSContextHeapSize());
    983    DispatchToPAC(pending.forget());
    984 
    985    LOG(("OnStreamComplete: process the PAC contents\n"));
    986 
    987    // Even if the PAC file could not be parsed, we did succeed in loading the
    988    // data for it.
    989    mLoadFailureCount = 0;
    990  } else {
    991    // We were unable to load the PAC file (presumably because of a network
    992    // failure).  Try again a little later.
    993    LOG(("OnStreamComplete: unable to load PAC, retry later\n"));
    994    OnLoadFailure();
    995  }
    996 
    997  if (NS_SUCCEEDED(status)) {
    998    PostProcessPendingQ();
    999  } else {
   1000    PostCancelPendingQ(status);
   1001  }
   1002 
   1003  return NS_OK;
   1004 }
   1005 
   1006 NS_IMETHODIMP
   1007 nsPACMan::GetInterface(const nsIID& iid, void** result) {
   1008  // In case loading the PAC file requires authentication.
   1009  if (iid.Equals(NS_GET_IID(nsIAuthPrompt))) {
   1010    nsCOMPtr<nsIPromptFactory> promptFac =
   1011        do_GetService("@mozilla.org/prompter;1");
   1012    NS_ENSURE_TRUE(promptFac, NS_ERROR_NO_INTERFACE);
   1013    nsresult rv =
   1014        promptFac->GetPrompt(nullptr, iid, reinterpret_cast<void**>(result));
   1015    if (NS_FAILED(rv)) {
   1016      return NS_ERROR_NO_INTERFACE;
   1017    }
   1018    return NS_OK;
   1019  }
   1020 
   1021  // In case loading the PAC file results in a redirect.
   1022  if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
   1023    NS_ADDREF_THIS();
   1024    *result = static_cast<nsIChannelEventSink*>(this);
   1025    return NS_OK;
   1026  }
   1027 
   1028  return NS_ERROR_NO_INTERFACE;
   1029 }
   1030 
   1031 NS_IMETHODIMP
   1032 nsPACMan::AsyncOnChannelRedirect(nsIChannel* oldChannel, nsIChannel* newChannel,
   1033                                 uint32_t flags,
   1034                                 nsIAsyncVerifyRedirectCallback* callback) {
   1035  MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
   1036 
   1037  nsresult rv = NS_OK;
   1038  nsCOMPtr<nsIURI> pacURI;
   1039  if (NS_FAILED((rv = newChannel->GetURI(getter_AddRefs(pacURI))))) return rv;
   1040 
   1041  rv = pacURI->GetSpec(mPACURIRedirectSpec);
   1042  if (NS_FAILED(rv)) return rv;
   1043 
   1044  LOG(("nsPACMan redirect from original %s to redirected %s\n",
   1045       mPACURISpec.get(), mPACURIRedirectSpec.get()));
   1046 
   1047  // do not update mPACURISpec - that needs to stay as the
   1048  // configured URI so that we can determine when the config changes.
   1049  // However do track the most recent URI in the redirect change
   1050  // as mPACURIRedirectSpec so that URI can be allowed to bypass
   1051  // the proxy and actually fetch the pac file.
   1052 
   1053  callback->OnRedirectVerifyCallback(NS_OK);
   1054  return NS_OK;
   1055 }
   1056 
   1057 nsresult nsPACMan::Init(nsISystemProxySettings* systemProxySettings) {
   1058  mSystemProxySettings = systemProxySettings;
   1059  mDHCPClient = do_GetService(NS_DHCPCLIENT_CONTRACTID);
   1060  return NS_OK;
   1061 }
   1062 
   1063 }  // namespace net
   1064 }  // namespace mozilla