/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=4 sw=2 sts=2 et: */ /* 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 "mozilla/ArrayUtils.h" #include "mozilla/Attributes.h" #include "mozilla/AutoRestore.h" #include "nsProtocolProxyService.h" #include "nsProxyInfo.h" #include "nsIClassInfoImpl.h" #include "nsIIOService.h" #include "nsIObserverService.h" #include "nsIProtocolHandler.h" #include "nsIProtocolProxyCallback.h" #include "nsIChannel.h" #include "nsICancelable.h" #include "nsDNSService2.h" #include "nsPIDNSService.h" #include "nsIPrefBranch.h" #include "nsIPrefService.h" #include "nsContentUtils.h" #include "nsCRT.h" #include "nsThreadUtils.h" #include "nsQueryObject.h" #include "nsSOCKSIOLayer.h" #include "nsString.h" #include "nsNetUtil.h" #include "nsNetCID.h" #include "prnetdb.h" #include "nsPACMan.h" #include "nsProxyRelease.h" #include "mozilla/Mutex.h" #include "mozilla/CondVar.h" #include "nsISystemProxySettings.h" #include "nsINetworkLinkService.h" #include "nsIHttpChannelInternal.h" #include "mozilla/dom/nsMixedContentBlocker.h" #include "mozilla/Logging.h" #include "mozilla/ScopeExit.h" #include "mozilla/StaticPrefs_network.h" #include "mozilla/Tokenizer.h" #include "mozilla/Unused.h" //---------------------------------------------------------------------------- namespace mozilla { namespace net { extern const char kProxyType_HTTP[]; extern const char kProxyType_HTTPS[]; extern const char kProxyType_SOCKS[]; extern const char kProxyType_SOCKS4[]; extern const char kProxyType_SOCKS5[]; extern const char kProxyType_DIRECT[]; extern const char kProxyType_PROXY[]; #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 //---------------------------------------------------------------------------- // This structure is intended to be allocated on the stack struct nsProtocolInfo { nsAutoCString scheme; uint32_t flags = 0; int32_t defaultPort = 0; }; //---------------------------------------------------------------------------- // Return the channel's proxy URI, or if it doesn't exist, the // channel's main URI. static nsresult GetProxyURI(nsIChannel* channel, nsIURI** aOut) { nsresult rv = NS_OK; nsCOMPtr proxyURI; nsCOMPtr httpChannel(do_QueryInterface(channel)); if (httpChannel) { rv = httpChannel->GetProxyURI(getter_AddRefs(proxyURI)); } if (!proxyURI) { rv = channel->GetURI(getter_AddRefs(proxyURI)); } if (NS_FAILED(rv)) { return rv; } proxyURI.forget(aOut); return NS_OK; } //----------------------------------------------------------------------------- nsProtocolProxyService::FilterLink::FilterLink(uint32_t p, nsIProtocolProxyFilter* f) : position(p), filter(f), channelFilter(nullptr) { LOG(("nsProtocolProxyService::FilterLink::FilterLink %p, filter=%p", this, f)); } nsProtocolProxyService::FilterLink::FilterLink( uint32_t p, nsIProtocolProxyChannelFilter* cf) : position(p), filter(nullptr), channelFilter(cf) { LOG(("nsProtocolProxyService::FilterLink::FilterLink %p, channel-filter=%p", this, cf)); } nsProtocolProxyService::FilterLink::~FilterLink() { LOG(("nsProtocolProxyService::FilterLink::~FilterLink %p", this)); } //----------------------------------------------------------------------------- // The nsPACManCallback portion of this implementation should be run // on the main thread - so call nsPACMan::AsyncGetProxyForURI() with // a true mainThreadResponse parameter. class nsAsyncResolveRequest final : public nsIRunnable, public nsPACManCallback, public nsICancelable { public: NS_DECL_THREADSAFE_ISUPPORTS nsAsyncResolveRequest(nsProtocolProxyService* pps, nsIChannel* channel, uint32_t aResolveFlags, nsIProtocolProxyCallback* callback) : mResolveFlags(aResolveFlags), mPPS(pps), mXPComPPS(pps), mChannel(channel), mCallback(callback) { NS_ASSERTION(mCallback, "null callback"); } private: ~nsAsyncResolveRequest() { if (!NS_IsMainThread()) { // these xpcom pointers might need to be proxied back to the // main thread to delete safely, but if this request had its // callbacks called normally they will all be null and this is a nop if (mChannel) { NS_ReleaseOnMainThread("nsAsyncResolveRequest::mChannel", mChannel.forget()); } if (mCallback) { NS_ReleaseOnMainThread("nsAsyncResolveRequest::mCallback", mCallback.forget()); } if (mProxyInfo) { NS_ReleaseOnMainThread("nsAsyncResolveRequest::mProxyInfo", mProxyInfo.forget()); } if (mXPComPPS) { NS_ReleaseOnMainThread("nsAsyncResolveRequest::mXPComPPS", mXPComPPS.forget()); } } } // Helper class to loop over all registered asynchronous filters. // There is a cycle between nsAsyncResolveRequest and this class that // is broken after the last filter has called back on this object. class AsyncApplyFilters final : public nsIProxyProtocolFilterResult, public nsIRunnable, public nsICancelable { // The reference counter is thread-safe, but the processing logic is // considered single thread only. We want the counter be thread safe, // since this class can be released on a background thread. NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIPROXYPROTOCOLFILTERRESULT NS_DECL_NSIRUNNABLE NS_DECL_NSICANCELABLE using Callback = std::function; explicit AsyncApplyFilters(nsProtocolInfo& aInfo, Callback const& aCallback); // This method starts the processing or filters. If all of them // answer synchronously (call back from within applyFilters) this method // will return immediately and the returning result will carry return // result of the callback given in constructor. // This method is looping the registered filters (that have been copied // locally) as long as an answer from a filter is obtained synchronously. // Note that filters are processed serially to let them build a list // of proxy info. nsresult AsyncProcess(nsAsyncResolveRequest* aRequest); private: using FilterLink = nsProtocolProxyService::FilterLink; virtual ~AsyncApplyFilters(); // Processes the next filter and loops until a filter is successfully // called on or it has called back to us. nsresult ProcessNextFilter(); // Called after the last filter has been processed (=called back or failed // to be called on) nsresult Finish(); nsProtocolInfo mInfo; // This is nullified before we call back on the request or when // Cancel() on this object has been called to break the cycle // and signal to stop. RefPtr mRequest; Callback mCallback; // A shallow snapshot of filters as they were registered at the moment // we started to process filters for the given resolve request. nsTArray> mFiltersCopy; nsTArray>::index_type mNextFilterIndex; // true when we are calling ProcessNextFilter() from inside AsyncProcess(), // false otherwise. bool mProcessingInLoop; // true after a filter called back to us with a result, dropped to false // just before we call a filter. bool mFilterCalledBack; // This keeps the initial value we pass to the first filter in line and also // collects the result from each filter call. nsCOMPtr mProxyInfo; // The logic is written as non-thread safe, assert single-thread usage. nsCOMPtr mProcessingThread; }; void EnsureResolveFlagsMatch() { nsCOMPtr pi = do_QueryInterface(mProxyInfo); if (!pi || pi->ResolveFlags() == mResolveFlags) { return; } nsCOMPtr proxyInfo = pi->CloneProxyInfoWithNewResolveFlags(mResolveFlags); mProxyInfo.swap(proxyInfo); } public: nsresult ProcessLocally(nsProtocolInfo& info, nsIProxyInfo* pi, bool isSyncOK) { SetResult(NS_OK, pi); auto consumeFiltersResult = [isSyncOK](nsAsyncResolveRequest* ctx, nsIProxyInfo* pi, bool aCalledAsync) -> nsresult { ctx->SetResult(NS_OK, pi); if (isSyncOK || aCalledAsync) { ctx->Run(); return NS_OK; } return ctx->DispatchCallback(); }; mAsyncFilterApplier = new AsyncApplyFilters(info, consumeFiltersResult); // may call consumeFiltersResult() directly return mAsyncFilterApplier->AsyncProcess(this); } void SetResult(nsresult status, nsIProxyInfo* pi) { mStatus = status; mProxyInfo = pi; } NS_IMETHOD Run() override { if (mCallback) DoCallback(); return NS_OK; } NS_IMETHOD Cancel(nsresult reason) override { NS_ENSURE_ARG(NS_FAILED(reason)); if (mAsyncFilterApplier) { mAsyncFilterApplier->Cancel(reason); } // If we've already called DoCallback then, nothing more to do. if (!mCallback) return NS_OK; SetResult(reason, nullptr); return DispatchCallback(); } nsresult DispatchCallback() { if (mDispatched) { // Only need to dispatch once return NS_OK; } nsresult rv = NS_DispatchToCurrentThread(this); if (NS_FAILED(rv)) { NS_WARNING("unable to dispatch callback event"); } else { mDispatched = true; return NS_OK; } mCallback = nullptr; // break possible reference cycle return rv; } private: // Called asynchronously, so we do not need to post another PLEvent // before calling DoCallback. void OnQueryComplete(nsresult status, 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; mPACString = pacString; mPACURL = newPACURL; } // In the cancelation case, we may still have another PLEvent in // the queue that wants to call DoCallback. No need to wait for // it, just run the callback now. DoCallback(); } void DoCallback() { bool pacAvailable = true; if (mStatus == NS_ERROR_NOT_AVAILABLE && !mProxyInfo) { // If the PAC service is not avail (e.g. failed pac load // or shutdown) then we will be going direct. Make that // mapping now so that any filters are still applied. mPACString = "DIRECT;"_ns; mStatus = NS_OK; LOG(("pac not available, use DIRECT\n")); pacAvailable = false; } // Generate proxy info from the PAC string if appropriate if (NS_SUCCEEDED(mStatus) && !mProxyInfo && !mPACString.IsEmpty()) { mPPS->ProcessPACString(mPACString, mResolveFlags, getter_AddRefs(mProxyInfo)); nsCOMPtr proxyURI; GetProxyURI(mChannel, getter_AddRefs(proxyURI)); // Now apply proxy filters nsProtocolInfo info; mStatus = mPPS->GetProtocolInfo(proxyURI, &info); auto consumeFiltersResult = [pacAvailable](nsAsyncResolveRequest* self, nsIProxyInfo* pi, bool async) -> nsresult { LOG(("DoCallback::consumeFiltersResult this=%p, pi=%p, async=%d", self, pi, async)); self->mProxyInfo = pi; if (pacAvailable) { // if !pacAvailable, it was already logged above LOG(("pac thread callback %s\n", self->mPACString.get())); } if (NS_SUCCEEDED(self->mStatus)) { self->mPPS->MaybeDisableDNSPrefetch(self->mProxyInfo); } self->EnsureResolveFlagsMatch(); self->mCallback->OnProxyAvailable(self, self->mChannel, self->mProxyInfo, self->mStatus); return NS_OK; }; if (NS_SUCCEEDED(mStatus)) { mAsyncFilterApplier = new AsyncApplyFilters(info, consumeFiltersResult); // This may call consumeFiltersResult() directly. mAsyncFilterApplier->AsyncProcess(this); return; } consumeFiltersResult(this, nullptr, false); } else if (NS_SUCCEEDED(mStatus) && !mPACURL.IsEmpty()) { LOG(("pac thread callback indicates new pac file load\n")); nsCOMPtr proxyURI; GetProxyURI(mChannel, getter_AddRefs(proxyURI)); // trigger load of new pac url nsresult rv = mPPS->ConfigureFromPAC(mPACURL, false); if (NS_SUCCEEDED(rv)) { // now that the load is triggered, we can resubmit the query RefPtr newRequest = new nsAsyncResolveRequest(mPPS, mChannel, mResolveFlags, mCallback); rv = mPPS->mPACMan->AsyncGetProxyForURI(proxyURI, newRequest, mResolveFlags, true); } if (NS_FAILED(rv)) { mCallback->OnProxyAvailable(this, mChannel, nullptr, rv); } // do not call onproxyavailable() in SUCCESS case - the newRequest will // take care of that } else { LOG(("pac thread callback did not provide information %" PRIX32 "\n", static_cast(mStatus))); if (NS_SUCCEEDED(mStatus)) mPPS->MaybeDisableDNSPrefetch(mProxyInfo); EnsureResolveFlagsMatch(); mCallback->OnProxyAvailable(this, mChannel, mProxyInfo, mStatus); } // We are on the main thread now and don't need these any more so // release them to avoid having to proxy them back to the main thread // in the dtor mCallback = nullptr; // in case the callback holds an owning ref to us mPPS = nullptr; mXPComPPS = nullptr; mChannel = nullptr; mProxyInfo = nullptr; } private: nsresult mStatus{NS_OK}; nsCString mPACString; nsCString mPACURL; bool mDispatched{false}; uint32_t mResolveFlags; nsProtocolProxyService* mPPS; nsCOMPtr mXPComPPS; nsCOMPtr mChannel; nsCOMPtr mCallback; nsCOMPtr mProxyInfo; RefPtr mAsyncFilterApplier; }; NS_IMPL_ISUPPORTS(nsAsyncResolveRequest, nsICancelable, nsIRunnable) NS_IMPL_ISUPPORTS(nsAsyncResolveRequest::AsyncApplyFilters, nsIProxyProtocolFilterResult, nsICancelable, nsIRunnable) nsAsyncResolveRequest::AsyncApplyFilters::AsyncApplyFilters( nsProtocolInfo& aInfo, Callback const& aCallback) : mInfo(aInfo), mCallback(aCallback), mNextFilterIndex(0), mProcessingInLoop(false), mFilterCalledBack(false) { LOG(("AsyncApplyFilters %p", this)); } nsAsyncResolveRequest::AsyncApplyFilters::~AsyncApplyFilters() { LOG(("~AsyncApplyFilters %p", this)); MOZ_ASSERT(!mRequest); MOZ_ASSERT(!mProxyInfo); MOZ_ASSERT(!mFiltersCopy.Length()); } nsresult nsAsyncResolveRequest::AsyncApplyFilters::AsyncProcess( nsAsyncResolveRequest* aRequest) { LOG(("AsyncApplyFilters::AsyncProcess %p for req %p", this, aRequest)); MOZ_ASSERT(!mRequest, "AsyncApplyFilters started more than once!"); if (!(mInfo.flags & nsIProtocolHandler::ALLOWS_PROXY)) { // Calling the callback directly (not via Finish()) since we // don't want to prune. return mCallback(aRequest, aRequest->mProxyInfo, false); } mProcessingThread = NS_GetCurrentThread(); mRequest = aRequest; mProxyInfo = aRequest->mProxyInfo; aRequest->mPPS->CopyFilters(mFiltersCopy); // We want to give filters a chance to process in a single loop to prevent // any current-thread dispatch delays when those are not needed. // This code is rather "loopy" than "recursive" to prevent long stack traces. do { MOZ_ASSERT(!mProcessingInLoop); mozilla::AutoRestore restore(mProcessingInLoop); mProcessingInLoop = true; nsresult rv = ProcessNextFilter(); if (NS_FAILED(rv)) { return rv; } } while (mFilterCalledBack); return NS_OK; } nsresult nsAsyncResolveRequest::AsyncApplyFilters::ProcessNextFilter() { LOG(("AsyncApplyFilters::ProcessNextFilter %p ENTER pi=%p", this, mProxyInfo.get())); RefPtr filter; do { mFilterCalledBack = false; if (!mRequest) { // We got canceled LOG((" canceled")); return NS_OK; // should we let the consumer know? } if (mNextFilterIndex == mFiltersCopy.Length()) { return Finish(); } filter = mFiltersCopy[mNextFilterIndex++]; // Loop until a call to a filter succeeded. Other option is to recurse // but that would waste stack trace when a number of filters gets registered // and all from some reason tend to fail. // The !mFilterCalledBack part of the condition is there to protect us from // calling on another filter when the current one managed to call back and // then threw. We already have the result so take it and use it since // the next filter will be processed by the root loop or a call to // ProcessNextFilter has already been dispatched to this thread. LOG((" calling filter %p pi=%p", filter.get(), mProxyInfo.get())); } while (!mRequest->mPPS->ApplyFilter(filter, mRequest->mChannel, mInfo, mProxyInfo, this) && !mFilterCalledBack); LOG(("AsyncApplyFilters::ProcessNextFilter %p LEAVE pi=%p", this, mProxyInfo.get())); return NS_OK; } NS_IMETHODIMP nsAsyncResolveRequest::AsyncApplyFilters::OnProxyFilterResult( nsIProxyInfo* aProxyInfo) { LOG(("AsyncApplyFilters::OnProxyFilterResult %p pi=%p", this, aProxyInfo)); MOZ_ASSERT(mProcessingThread && mProcessingThread->IsOnCurrentThread()); MOZ_ASSERT(!mFilterCalledBack); if (mFilterCalledBack) { LOG((" duplicate notification?")); return NS_OK; } mFilterCalledBack = true; if (!mRequest) { // We got canceled LOG((" canceled")); return NS_OK; } mProxyInfo = aProxyInfo; if (mProcessingInLoop) { // No need to call/dispatch ProcessNextFilter(), we are in a control // loop that will do this for us and save recursion/dispatching. LOG((" in a root loop")); return NS_OK; } if (mNextFilterIndex == mFiltersCopy.Length()) { // We are done, all filters have been called on! Finish(); return NS_OK; } // Redispatch, since we don't want long stacks when filters respond // synchronously. LOG((" redispatching")); NS_DispatchToCurrentThread(this); return NS_OK; } NS_IMETHODIMP nsAsyncResolveRequest::AsyncApplyFilters::Run() { LOG(("AsyncApplyFilters::Run %p", this)); MOZ_ASSERT(mProcessingThread && mProcessingThread->IsOnCurrentThread()); ProcessNextFilter(); return NS_OK; } nsresult nsAsyncResolveRequest::AsyncApplyFilters::Finish() { LOG(("AsyncApplyFilters::Finish %p pi=%p", this, mProxyInfo.get())); MOZ_ASSERT(mRequest); mFiltersCopy.Clear(); RefPtr request; request.swap(mRequest); nsCOMPtr pi; pi.swap(mProxyInfo); request->mPPS->PruneProxyInfo(mInfo, pi); return mCallback(request, pi, !mProcessingInLoop); } NS_IMETHODIMP nsAsyncResolveRequest::AsyncApplyFilters::Cancel(nsresult reason) { LOG(("AsyncApplyFilters::Cancel %p", this)); MOZ_ASSERT(mProcessingThread && mProcessingThread->IsOnCurrentThread()); // This will be called only from inside the request, so don't call // its's callback. Dropping the members means we simply break the cycle. mFiltersCopy.Clear(); mProxyInfo = nullptr; mRequest = nullptr; return NS_OK; } // Bug 1366133: make GetPACURI and GetSystemWPADSetting off-main-thread since it // may hang on Windows platform class AsyncGetPACURIRequestOrSystemWPADSetting final : public nsIRunnable { public: NS_DECL_THREADSAFE_ISUPPORTS using CallbackFunc = nsresult (nsProtocolProxyService::*)(bool, bool, nsresult, const nsACString&, bool); AsyncGetPACURIRequestOrSystemWPADSetting( nsProtocolProxyService* aService, CallbackFunc aCallback, nsISystemProxySettings* aSystemProxySettings, bool aMainThreadOnly, bool aForceReload, bool aResetPACThread, bool aSystemWPADAllowed) : mIsMainThreadOnly(aMainThreadOnly), mService(aService), mServiceHolder(do_QueryObject(aService)), mCallback(aCallback), mSystemProxySettings(aSystemProxySettings), mForceReload(aForceReload), mResetPACThread(aResetPACThread), mSystemWPADAllowed(aSystemWPADAllowed) { MOZ_ASSERT(NS_IsMainThread()); Unused << mIsMainThreadOnly; } NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread() == mIsMainThreadOnly); nsresult rv; nsCString pacUri; bool systemWPADSetting = false; if (mSystemWPADAllowed) { mSystemProxySettings->GetSystemWPADSetting(&systemWPADSetting); } rv = mSystemProxySettings->GetPACURI(pacUri); nsCOMPtr event = NewNonOwningCancelableRunnableMethod( "AsyncGetPACURIRequestOrSystemWPADSettingCallback", mService, mCallback, mForceReload, mResetPACThread, rv, pacUri, systemWPADSetting); return NS_DispatchToMainThread(event); } private: ~AsyncGetPACURIRequestOrSystemWPADSetting() { NS_ReleaseOnMainThread( "AsyncGetPACURIRequestOrSystemWPADSetting::mServiceHolder", mServiceHolder.forget()); } bool mIsMainThreadOnly; nsProtocolProxyService* mService; // ref-count is hold by mServiceHolder nsCOMPtr mServiceHolder; CallbackFunc mCallback; nsCOMPtr mSystemProxySettings; bool mForceReload; bool mResetPACThread; bool mSystemWPADAllowed; }; NS_IMPL_ISUPPORTS(AsyncGetPACURIRequestOrSystemWPADSetting, nsIRunnable) //---------------------------------------------------------------------------- // // apply mask to address (zeros out excluded bits). // // NOTE: we do the byte swapping here to minimize overall swapping. // static void proxy_MaskIPv6Addr(PRIPv6Addr& addr, uint16_t mask_len) { if (mask_len == 128) return; if (mask_len > 96) { addr.pr_s6_addr32[3] = PR_htonl(PR_ntohl(addr.pr_s6_addr32[3]) & (~0uL << (128 - mask_len))); } else if (mask_len > 64) { addr.pr_s6_addr32[3] = 0; addr.pr_s6_addr32[2] = PR_htonl(PR_ntohl(addr.pr_s6_addr32[2]) & (~0uL << (96 - mask_len))); } else if (mask_len > 32) { addr.pr_s6_addr32[3] = 0; addr.pr_s6_addr32[2] = 0; addr.pr_s6_addr32[1] = PR_htonl(PR_ntohl(addr.pr_s6_addr32[1]) & (~0uL << (64 - mask_len))); } else { addr.pr_s6_addr32[3] = 0; addr.pr_s6_addr32[2] = 0; addr.pr_s6_addr32[1] = 0; addr.pr_s6_addr32[0] = PR_htonl(PR_ntohl(addr.pr_s6_addr32[0]) & (~0uL << (32 - mask_len))); } } static void proxy_GetStringPref(nsIPrefBranch* aPrefBranch, const char* aPref, nsCString& aResult) { nsAutoCString temp; nsresult rv = aPrefBranch->GetCharPref(aPref, temp); if (NS_FAILED(rv)) { aResult.Truncate(); } else { aResult.Assign(temp); // all of our string prefs are hostnames, so we should remove any // whitespace characters that the user might have unknowingly entered. aResult.StripWhitespace(); } } static void proxy_GetIntPref(nsIPrefBranch* aPrefBranch, const char* aPref, int32_t& aResult) { int32_t temp; nsresult rv = aPrefBranch->GetIntPref(aPref, &temp); if (NS_FAILED(rv)) { aResult = -1; } else { aResult = temp; } } static void proxy_GetBoolPref(nsIPrefBranch* aPrefBranch, const char* aPref, bool& aResult) { bool temp; nsresult rv = aPrefBranch->GetBoolPref(aPref, &temp); if (NS_FAILED(rv)) { aResult = false; } else { aResult = temp; } } //---------------------------------------------------------------------------- static const int32_t PROXYCONFIG_DIRECT4X = 3; static const int32_t PROXYCONFIG_COUNT = 6; NS_IMPL_ADDREF(nsProtocolProxyService) NS_IMPL_RELEASE(nsProtocolProxyService) NS_IMPL_CLASSINFO(nsProtocolProxyService, nullptr, nsIClassInfo::SINGLETON, NS_PROTOCOLPROXYSERVICE_CID) // NS_IMPL_QUERY_INTERFACE_CI with the nsProtocolProxyService QI change NS_INTERFACE_MAP_BEGIN(nsProtocolProxyService) NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyService) NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyService2) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_ENTRY(nsITimerCallback) NS_INTERFACE_MAP_ENTRY(nsINamed) NS_INTERFACE_MAP_ENTRY_CONCRETE(nsProtocolProxyService) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIProtocolProxyService) NS_IMPL_QUERY_CLASSINFO(nsProtocolProxyService) NS_INTERFACE_MAP_END NS_IMPL_CI_INTERFACE_GETTER(nsProtocolProxyService, nsIProtocolProxyService, nsIProtocolProxyService2) nsProtocolProxyService::nsProtocolProxyService() : mSessionStart(PR_Now()) {} nsProtocolProxyService::~nsProtocolProxyService() { // These should have been cleaned up in our Observe method. NS_ASSERTION(mHostFiltersArray.Length() == 0 && mFilters.Length() == 0 && mPACMan == nullptr, "what happened to xpcom-shutdown?"); } // nsProtocolProxyService methods nsresult nsProtocolProxyService::Init() { // failure to access prefs is non-fatal nsCOMPtr prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); if (prefBranch) { // monitor proxy prefs prefBranch->AddObserver(PROXY_PREF_BRANCH, this, false); // read all prefs PrefsChanged(prefBranch, nullptr); } nsCOMPtr obs = services::GetObserverService(); if (obs) { // register for shutdown notification so we can clean ourselves up // properly. obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); obs->AddObserver(this, NS_NETWORK_LINK_TOPIC, false); } return NS_OK; } // ReloadNetworkPAC() checks if there's a non-networked PAC in use then avoids // to call ReloadPAC() nsresult nsProtocolProxyService::ReloadNetworkPAC() { nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); if (!prefs) { return NS_OK; } int32_t type; nsresult rv = prefs->GetIntPref(PROXY_PREF("type"), &type); if (NS_FAILED(rv)) { return NS_OK; } if (type == PROXYCONFIG_PAC) { nsAutoCString pacSpec; prefs->GetCharPref(PROXY_PREF("autoconfig_url"), pacSpec); if (!pacSpec.IsEmpty()) { nsCOMPtr pacURI; rv = NS_NewURI(getter_AddRefs(pacURI), pacSpec); if (!NS_SUCCEEDED(rv)) { return rv; } nsProtocolInfo pac; rv = GetProtocolInfo(pacURI, &pac); if (!NS_SUCCEEDED(rv)) { return rv; } if (!pac.scheme.EqualsLiteral("file") && !pac.scheme.EqualsLiteral("data")) { LOG((": received network changed event, reload PAC")); ReloadPAC(); } } } else if ((type == PROXYCONFIG_WPAD) || (type == PROXYCONFIG_SYSTEM)) { ReloadPAC(); } return NS_OK; } nsresult nsProtocolProxyService::AsyncConfigureWPADOrFromPAC( bool aForceReload, bool aResetPACThread, bool aSystemWPADAllowed) { MOZ_ASSERT(NS_IsMainThread()); bool mainThreadOnly; nsresult rv = mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr req = new AsyncGetPACURIRequestOrSystemWPADSetting( this, &nsProtocolProxyService::OnAsyncGetPACURIOrSystemWPADSetting, mSystemProxySettings, mainThreadOnly, aForceReload, aResetPACThread, aSystemWPADAllowed); if (mainThreadOnly) { return req->Run(); } return NS_DispatchBackgroundTask(req.forget(), nsIEventTarget::DISPATCH_NORMAL); } nsresult nsProtocolProxyService::OnAsyncGetPACURIOrSystemWPADSetting( bool aForceReload, bool aResetPACThread, nsresult aResult, const nsACString& aUri, bool aSystemWPADSetting) { MOZ_ASSERT(NS_IsMainThread()); if (aResetPACThread) { ResetPACThread(); } if (aSystemWPADSetting) { if (mSystemProxySettings || !mPACMan) { mSystemProxySettings = nullptr; ResetPACThread(); } nsAutoCString tempString; ConfigureFromPAC(EmptyCString(), false); } else if (NS_SUCCEEDED(aResult) && !aUri.IsEmpty()) { ConfigureFromPAC(PromiseFlatCString(aUri), aForceReload); } return NS_OK; } NS_IMETHODIMP nsProtocolProxyService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { mIsShutdown = true; // cleanup mHostFiltersArray.Clear(); mFilters.Clear(); if (mPACMan) { mPACMan->Shutdown(); mPACMan = nullptr; } if (mReloadPACTimer) { mReloadPACTimer->Cancel(); mReloadPACTimer = nullptr; } nsCOMPtr obs = services::GetObserverService(); if (obs) { obs->RemoveObserver(this, NS_NETWORK_LINK_TOPIC); obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); } } else if (strcmp(aTopic, NS_NETWORK_LINK_TOPIC) == 0) { nsCString converted = NS_ConvertUTF16toUTF8(aData); const char* state = converted.get(); if (!strcmp(state, NS_NETWORK_LINK_DATA_CHANGED)) { uint32_t delay = StaticPrefs::network_proxy_reload_pac_delay(); LOG(("nsProtocolProxyService::Observe call ReloadNetworkPAC() delay=%u", delay)); if (delay) { if (mReloadPACTimer) { mReloadPACTimer->Cancel(); mReloadPACTimer = nullptr; } NS_NewTimerWithCallback(getter_AddRefs(mReloadPACTimer), this, delay, nsITimer::TYPE_ONE_SHOT); } else { ReloadNetworkPAC(); } } } else { NS_ASSERTION(strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0, "what is this random observer event?"); nsCOMPtr prefs = do_QueryInterface(aSubject); if (prefs) PrefsChanged(prefs, NS_LossyConvertUTF16toASCII(aData).get()); } return NS_OK; } NS_IMETHODIMP nsProtocolProxyService::Notify(nsITimer* aTimer) { MOZ_ASSERT(aTimer == mReloadPACTimer); ReloadNetworkPAC(); return NS_OK; } NS_IMETHODIMP nsProtocolProxyService::GetName(nsACString& aName) { aName.AssignLiteral("nsProtocolProxyService"); return NS_OK; } void nsProtocolProxyService::PrefsChanged(nsIPrefBranch* prefBranch, const char* pref) { nsresult rv = NS_OK; bool reloadPAC = false; nsAutoCString tempString; auto invokeCallback = MakeScopeExit([&] { NotifyProxyConfigChangedInternal(); }); if (!pref || !strcmp(pref, PROXY_PREF("type")) || !strcmp(pref, PROXY_PREF("system_wpad"))) { int32_t type = -1; rv = prefBranch->GetIntPref(PROXY_PREF("type"), &type); if (NS_SUCCEEDED(rv)) { // bug 115720 - for ns4.x backwards compatibility if (type == PROXYCONFIG_DIRECT4X) { type = PROXYCONFIG_DIRECT; // Reset the type so that the dialog looks correct, and we // don't have to handle this case everywhere else // I'm paranoid about a loop of some sort - only do this // if we're enumerating all prefs, and ignore any error if (!pref) prefBranch->SetIntPref(PROXY_PREF("type"), type); } else if (type >= PROXYCONFIG_COUNT) { LOG(("unknown proxy type: %" PRId32 "; assuming direct\n", type)); type = PROXYCONFIG_DIRECT; } mProxyConfig = type; reloadPAC = true; } if (mProxyConfig == PROXYCONFIG_SYSTEM) { mSystemProxySettings = do_GetService(NS_SYSTEMPROXYSETTINGS_CONTRACTID); if (!mSystemProxySettings) mProxyConfig = PROXYCONFIG_DIRECT; ResetPACThread(); } else { if (mSystemProxySettings) { mSystemProxySettings = nullptr; ResetPACThread(); } } } if (!pref || !strcmp(pref, PROXY_PREF("http"))) { proxy_GetStringPref(prefBranch, PROXY_PREF("http"), mHTTPProxyHost); } if (!pref || !strcmp(pref, PROXY_PREF("http_port"))) { proxy_GetIntPref(prefBranch, PROXY_PREF("http_port"), mHTTPProxyPort); } if (!pref || !strcmp(pref, PROXY_PREF("ssl"))) { proxy_GetStringPref(prefBranch, PROXY_PREF("ssl"), mHTTPSProxyHost); } if (!pref || !strcmp(pref, PROXY_PREF("ssl_port"))) { proxy_GetIntPref(prefBranch, PROXY_PREF("ssl_port"), mHTTPSProxyPort); } if (!pref || !strcmp(pref, PROXY_PREF("socks"))) { proxy_GetStringPref(prefBranch, PROXY_PREF("socks"), mSOCKSProxyTarget); } if (!pref || !strcmp(pref, PROXY_PREF("socks_port"))) { proxy_GetIntPref(prefBranch, PROXY_PREF("socks_port"), mSOCKSProxyPort); } if (!pref || !strcmp(pref, PROXY_PREF("socks_version"))) { int32_t version; proxy_GetIntPref(prefBranch, PROXY_PREF("socks_version"), version); // make sure this preference value remains sane if (version == nsIProxyInfo::SOCKS_V5) { mSOCKSProxyVersion = nsIProxyInfo::SOCKS_V5; } else { mSOCKSProxyVersion = nsIProxyInfo::SOCKS_V4; } } if (!pref || !strcmp(pref, PROXY_PREF("socks_remote_dns"))) { proxy_GetBoolPref(prefBranch, PROXY_PREF("socks_remote_dns"), mSOCKS4ProxyRemoteDNS); } if (!pref || !strcmp(pref, PROXY_PREF("socks5_remote_dns"))) { proxy_GetBoolPref(prefBranch, PROXY_PREF("socks5_remote_dns"), mSOCKS5ProxyRemoteDNS); } 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 = reloadPAC || mProxyConfig == PROXYCONFIG_WPAD; } 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); } // We're done if not using something that could give us a PAC URL // (PAC, WPAD or System) if (mProxyConfig != PROXYCONFIG_PAC && mProxyConfig != PROXYCONFIG_WPAD && mProxyConfig != PROXYCONFIG_SYSTEM) { return; } // OK, we need to reload the PAC file if: // 1) network.proxy.type changed, or // 2) network.proxy.autoconfig_url changed and PAC is configured if (!pref || !strcmp(pref, PROXY_PREF("autoconfig_url"))) reloadPAC = true; if (reloadPAC) { 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) { LOG(("Auto-detecting proxy - Reset Pac Thread")); ResetPACThread(); } else if (mSystemProxySettings && mProxyConfig == PROXYCONFIG_SYSTEM && StaticPrefs::network_proxy_system_wpad()) { AsyncConfigureWPADOrFromPAC(false, false, true); } else if (mSystemProxySettings) { // Get System Proxy settings if available AsyncConfigureWPADOrFromPAC(false, false, false); } if (!tempString.IsEmpty() || mProxyConfig == PROXYCONFIG_WPAD) { ConfigureFromPAC(tempString, false); } } } bool nsProtocolProxyService::CanUseProxy(nsIURI* aURI, int32_t defaultPort) { int32_t port; nsAutoCString host; nsresult rv = aURI->GetAsciiHost(host); if (NS_FAILED(rv) || host.IsEmpty()) return false; rv = aURI->GetPort(&port); if (NS_FAILED(rv)) return false; if (port == -1) port = defaultPort; PRNetAddr addr; bool is_ipaddr = (PR_StringToNetAddr(host.get(), &addr) == PR_SUCCESS); PRIPv6Addr ipv6; if (is_ipaddr) { // convert parsed address to IPv6 if (addr.raw.family == PR_AF_INET) { // convert to IPv4-mapped address PR_ConvertIPv4AddrToIPv6(addr.inet.ip, &ipv6); } else if (addr.raw.family == PR_AF_INET6) { // copy the address memcpy(&ipv6, &addr.ipv6.ip, sizeof(PRIPv6Addr)); } else { NS_WARNING("unknown address family"); return true; // allow proxying } } // Don't use proxy for local hosts (plain hostname, no dots) if ((!is_ipaddr && mFilterLocalHosts && !host.Contains('.')) || // This method detects if we have network.proxy.allow_hijacking_localhost // pref enabled. If it's true then this method will always return false // otherwise it returns true if the host matches an address that's // hardcoded to the loopback address. (!StaticPrefs::network_proxy_allow_hijacking_localhost() && nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(host))) { LOG(("Not using proxy for this local host [%s]!\n", host.get())); return false; // don't allow proxying } int32_t index = -1; while (++index < int32_t(mHostFiltersArray.Length())) { const auto& hinfo = mHostFiltersArray[index]; if (is_ipaddr != hinfo->is_ipaddr) continue; if (hinfo->port && hinfo->port != port) continue; if (is_ipaddr) { // generate masked version of target IPv6 address PRIPv6Addr masked; memcpy(&masked, &ipv6, sizeof(PRIPv6Addr)); proxy_MaskIPv6Addr(masked, hinfo->ip.mask_len); // check for a match if (memcmp(&masked, &hinfo->ip.addr, sizeof(PRIPv6Addr)) == 0) { return false; // proxy disallowed } } else { uint32_t host_len = host.Length(); uint32_t filter_host_len = hinfo->name.host_len; if (host_len >= filter_host_len) { // // compare last |filter_host_len| bytes of target hostname. // const char* host_tail = host.get() + host_len - filter_host_len; if (!nsCRT::strncasecmp(host_tail, hinfo->name.host, filter_host_len)) { // If the tail of the host string matches the filter if (filter_host_len > 0 && hinfo->name.host[0] == '.') { // If the filter was of the form .foo.bar.tld, all such // matches are correct return false; // proxy disallowed } // abc-def.example.org should not match def.example.org // however, *.def.example.org should match .def.example.org // We check that the filter doesn't start with a `.`. If it does, // then the strncasecmp above should suffice. If it doesn't, // then we should only consider it a match if the strncasecmp happened // at a subdomain boundary if (host_len > filter_host_len && *(host_tail - 1) == '.') { // If the host was something.foo.bar.tld and the filter // was foo.bar.tld, it's still a match. // the character right before the tail must be a // `.` for this to work return false; // proxy disallowed } if (host_len == filter_host_len) { // If the host and filter are of the same length, // they should match return false; // proxy disallowed } } } } } return true; } // kProxyType\* may be referred to externally in // nsProxyInfo in order to compare by string pointer const char kProxyType_HTTP[] = "http"; const char kProxyType_HTTPS[] = "https"; const char kProxyType_PROXY[] = "proxy"; const char kProxyType_SOCKS[] = "socks"; const char kProxyType_SOCKS4[] = "socks4"; const char kProxyType_SOCKS5[] = "socks5"; const char kProxyType_DIRECT[] = "direct"; const char* nsProtocolProxyService::ExtractProxyInfo(const char* start, uint32_t aResolveFlags, nsProxyInfo** result) { *result = nullptr; uint32_t flags = 0; // see BNF in ProxyAutoConfig.h and notes in nsISystemProxySettings.idl // find end of proxy info delimiter const char* end = start; while (*end && *end != ';') ++end; // find end of proxy type delimiter const char* sp = start; while (sp < end && *sp != ' ' && *sp != '\t') ++sp; uint32_t len = sp - start; const char* type = nullptr; switch (len) { case 4: if (nsCRT::strncasecmp(start, kProxyType_HTTP, 4) == 0) { type = kProxyType_HTTP; } break; case 5: if (nsCRT::strncasecmp(start, kProxyType_PROXY, 5) == 0) { type = kProxyType_HTTP; } else if (nsCRT::strncasecmp(start, kProxyType_SOCKS, 5) == 0) { type = kProxyType_SOCKS4; // assume v4 for 4x compat if (StaticPrefs::network_proxy_default_pac_script_socks_version() == 5) { type = kProxyType_SOCKS; } } else if (nsCRT::strncasecmp(start, kProxyType_HTTPS, 5) == 0) { type = kProxyType_HTTPS; } break; case 6: if (nsCRT::strncasecmp(start, kProxyType_DIRECT, 6) == 0) { type = kProxyType_DIRECT; } else if (nsCRT::strncasecmp(start, kProxyType_SOCKS4, 6) == 0) { type = kProxyType_SOCKS4; } else if (nsCRT::strncasecmp(start, kProxyType_SOCKS5, 6) == 0) { // map "SOCKS5" to "socks" to match contract-id of registered // SOCKS-v5 socket provider. type = kProxyType_SOCKS; } break; } if (type) { int32_t port = -1; // If it's a SOCKS5 proxy, do name resolution on the server side. // We could use this with SOCKS4a servers too, but they might not // support it. if (type == kProxyType_SOCKS || mSOCKS5ProxyRemoteDNS) { flags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST; } // extract host:port start = sp; while ((*start == ' ' || *start == '\t') && start < end) start++; // port defaults if (type == kProxyType_HTTP) { port = 80; } else if (type == kProxyType_HTTPS) { port = 443; } else { port = 1080; } RefPtr pi = new nsProxyInfo(); pi->mType = type; pi->mFlags = flags; pi->mResolveFlags = aResolveFlags; pi->mTimeout = mFailedProxyTimeout; // www.foo.com:8080 and http://www.foo.com:8080 nsDependentCSubstring maybeURL(start, end - start); nsCOMPtr pacURI; nsAutoCString urlHost; // First assume the scheme is present, e.g. http://www.example.com:8080 if (NS_FAILED(NS_NewURI(getter_AddRefs(pacURI), maybeURL)) || NS_FAILED(pacURI->GetAsciiHost(urlHost)) || urlHost.IsEmpty()) { // It isn't, assume www.example.com:8080 maybeURL.Insert("http://", 0); if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(pacURI), maybeURL))) { pacURI->GetAsciiHost(urlHost); } } if (!urlHost.IsEmpty()) { pi->mHost = urlHost; int32_t tPort; if (NS_SUCCEEDED(pacURI->GetPort(&tPort)) && tPort != -1) { port = tPort; } pi->mPort = port; } pi.forget(result); } while (*end == ';' || *end == ' ' || *end == '\t') ++end; return end; } void nsProtocolProxyService::GetProxyKey(nsProxyInfo* pi, nsCString& key) { key.AssignASCII(pi->mType); if (!pi->mHost.IsEmpty()) { key.Append(' '); key.Append(pi->mHost); key.Append(':'); key.AppendInt(pi->mPort); } } uint32_t nsProtocolProxyService::SecondsSinceSessionStart() { PRTime now = PR_Now(); // get time elapsed since session start int64_t diff = now - mSessionStart; // convert microseconds to seconds diff /= PR_USEC_PER_SEC; // return converted 32 bit value return uint32_t(diff); } void nsProtocolProxyService::EnableProxy(nsProxyInfo* pi) { nsAutoCString key; GetProxyKey(pi, key); mFailedProxies.Remove(key); } void nsProtocolProxyService::DisableProxy(nsProxyInfo* pi) { nsAutoCString key; GetProxyKey(pi, key); uint32_t dsec = SecondsSinceSessionStart(); // Add timeout to interval (this is the time when the proxy can // be tried again). dsec += pi->mTimeout; // NOTE: The classic codebase would increase the timeout value // incrementally each time a subsequent failure occurred. // We could do the same, but it would require that we not // remove proxy entries in IsProxyDisabled or otherwise // change the way we are recording disabled proxies. // Simpler is probably better for now, and at least the // user can tune the timeout setting via preferences. LOG(("DisableProxy %s %d\n", key.get(), dsec)); // If this fails, oh well... means we don't have enough memory // to remember the failed proxy. mFailedProxies.InsertOrUpdate(key, dsec); } bool nsProtocolProxyService::IsProxyDisabled(nsProxyInfo* pi) { nsAutoCString key; GetProxyKey(pi, key); uint32_t val; if (!mFailedProxies.Get(key, &val)) return false; uint32_t dsec = SecondsSinceSessionStart(); // if time passed has exceeded interval, then try proxy again. if (dsec > val) { mFailedProxies.Remove(key); return false; } return true; } nsresult nsProtocolProxyService::SetupPACThread( nsISerialEventTarget* mainThreadEventTarget) { if (mIsShutdown) { return NS_ERROR_FAILURE; } if (mPACMan) return NS_OK; mPACMan = new nsPACMan(mainThreadEventTarget); bool mainThreadOnly; nsresult rv; 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() { if (!mPACMan) return NS_OK; mPACMan->Shutdown(); mPACMan = nullptr; return SetupPACThread(); } nsresult nsProtocolProxyService::ConfigureFromPAC(const nsCString& spec, bool forceReload) { nsresult rv = SetupPACThread(); NS_ENSURE_SUCCESS(rv, rv); 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) { if (pacString.IsEmpty()) { *result = nullptr; return; } const char* proxies = pacString.get(); nsProxyInfo *pi = nullptr, *first = nullptr, *last = nullptr; while (*proxies) { proxies = ExtractProxyInfo(proxies, aResolveFlags, &pi); if (pi && (pi->mType == kProxyType_HTTPS) && !mProxyOverTLS) { delete pi; pi = nullptr; } if (pi) { if (last) { NS_ASSERTION(last->mNext == nullptr, "leaking nsProxyInfo"); last->mNext = pi; } else { first = pi; } last = pi; } } *result = first; } // nsIProtocolProxyService2 NS_IMETHODIMP nsProtocolProxyService::ReloadPAC() { nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); if (!prefs) return NS_OK; 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_SYSTEM) { if (mSystemProxySettings) { AsyncConfigureWPADOrFromPAC(true, true, StaticPrefs::network_proxy_system_wpad()); } else { ResetPACThread(); } } 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 - // so call nsPACMan::AsyncGetProxyForURI() with // a false mainThreadResponse parameter. class nsAsyncBridgeRequest final : public nsPACManCallback { NS_DECL_THREADSAFE_ISUPPORTS nsAsyncBridgeRequest() : mMutex("nsDeprecatedCallback"), mCondVar(mMutex, "nsDeprecatedCallback") {} void OnQueryComplete(nsresult status, const nsACString& pacString, const nsACString& newPACURL) override { MutexAutoLock lock(mMutex); mCompleted = true; mStatus = status; mPACString = pacString; mPACURL = newPACURL; mCondVar.Notify(); } void Lock() MOZ_CAPABILITY_ACQUIRE(mMutex) { mMutex.Lock(); } void Unlock() MOZ_CAPABILITY_RELEASE(mMutex) { mMutex.Unlock(); } void Wait() { mCondVar.Wait(TimeDuration::FromSeconds(3)); } private: ~nsAsyncBridgeRequest() = default; friend class nsProtocolProxyService; Mutex mMutex; CondVar mCondVar; nsresult mStatus MOZ_GUARDED_BY(mMutex){NS_OK}; nsCString mPACString MOZ_GUARDED_BY(mMutex); nsCString mPACURL MOZ_GUARDED_BY(mMutex); bool mCompleted MOZ_GUARDED_BY(mMutex){false}; }; NS_IMPL_ISUPPORTS0(nsAsyncBridgeRequest) nsresult nsProtocolProxyService::AsyncResolveInternal( nsIChannel* channel, uint32_t flags, nsIProtocolProxyCallback* callback, nsICancelable** result, bool isSyncOK, nsISerialEventTarget* mainThreadEventTarget) { NS_ENSURE_ARG_POINTER(channel); NS_ENSURE_ARG_POINTER(callback); nsCOMPtr uri; nsresult rv = GetProxyURI(channel, getter_AddRefs(uri)); if (NS_FAILED(rv)) return rv; *result = nullptr; RefPtr ctx = new nsAsyncResolveRequest(this, channel, flags, callback); nsProtocolInfo info; rv = GetProtocolInfo(uri, &info); if (NS_FAILED(rv)) return rv; nsCOMPtr pi; bool usePACThread; // adapt to realtime changes in the system proxy service if (mProxyConfig == PROXYCONFIG_SYSTEM && !StaticPrefs::network_proxy_system_wpad()) { nsCOMPtr sp2 = do_GetService(NS_SYSTEMPROXYSETTINGS_CONTRACTID); if (sp2 != mSystemProxySettings) { mSystemProxySettings = sp2; ResetPACThread(); } } rv = SetupPACThread(mainThreadEventTarget); if (NS_FAILED(rv)) { return rv; } // SystemProxySettings and PAC files can block the main thread // but if neither of them are in use, we can just do the work // right here and directly invoke the callback rv = Resolve_Internal(channel, info, flags, &usePACThread, getter_AddRefs(pi)); if (NS_FAILED(rv)) return rv; if (!usePACThread || !mPACMan) { // we can do it locally rv = ctx->ProcessLocally(info, pi, isSyncOK); if (NS_SUCCEEDED(rv) && !isSyncOK) { ctx.forget(result); } return rv; } // else kick off a PAC thread query rv = mPACMan->AsyncGetProxyForURI(uri, ctx, flags, true); if (NS_SUCCEEDED(rv)) ctx.forget(result); return rv; } // nsIProtocolProxyService NS_IMETHODIMP nsProtocolProxyService::AsyncResolve2( nsIChannel* channel, uint32_t flags, nsIProtocolProxyCallback* callback, nsISerialEventTarget* mainThreadEventTarget, nsICancelable** result) { return AsyncResolveInternal(channel, flags, callback, result, true, mainThreadEventTarget); } NS_IMETHODIMP nsProtocolProxyService::AsyncResolve( nsISupports* channelOrURI, uint32_t flags, nsIProtocolProxyCallback* callback, nsISerialEventTarget* mainThreadEventTarget, nsICancelable** result) { nsresult rv; // Check if we got a channel: nsCOMPtr channel = do_QueryInterface(channelOrURI); if (!channel) { nsCOMPtr uri = do_QueryInterface(channelOrURI); if (!uri) { return NS_ERROR_NO_INTERFACE; } // creating a temporary channel from the URI which is not // used to perform any network loads, hence its safe to // use systemPrincipal as the loadingPrincipal. rv = NS_NewChannel(getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(), nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, nsIContentPolicy::TYPE_OTHER); NS_ENSURE_SUCCESS(rv, rv); } return AsyncResolveInternal(channel, flags, callback, result, false, mainThreadEventTarget); } NS_IMETHODIMP nsProtocolProxyService::NewProxyInfo( const nsACString& aType, const nsACString& aHost, int32_t aPort, const nsACString& aProxyAuthorizationHeader, const nsACString& aConnectionIsolationKey, uint32_t aFlags, uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy, nsIProxyInfo** aResult) { return NewProxyInfoWithAuth(aType, aHost, aPort, ""_ns, ""_ns, aProxyAuthorizationHeader, aConnectionIsolationKey, aFlags, aFailoverTimeout, aFailoverProxy, aResult); } NS_IMETHODIMP nsProtocolProxyService::NewProxyInfoWithAuth( const nsACString& aType, const nsACString& aHost, int32_t aPort, const nsACString& aUsername, const nsACString& aPassword, const nsACString& aProxyAuthorizationHeader, const nsACString& aConnectionIsolationKey, uint32_t aFlags, uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy, nsIProxyInfo** aResult) { static const char* types[] = {kProxyType_HTTP, kProxyType_HTTPS, kProxyType_SOCKS, kProxyType_SOCKS4, kProxyType_DIRECT}; // resolve type; this allows us to avoid copying the type string into each // proxy info instance. we just reference the string literals directly :) const char* type = nullptr; for (auto& t : types) { if (aType.LowerCaseEqualsASCII(t)) { type = t; break; } } NS_ENSURE_TRUE(type, NS_ERROR_INVALID_ARG); // We have only implemented username/password for SOCKS proxies. if ((!aUsername.IsEmpty() || !aPassword.IsEmpty()) && !aType.LowerCaseEqualsASCII(kProxyType_SOCKS) && !aType.LowerCaseEqualsASCII(kProxyType_SOCKS4)) { return NS_ERROR_NOT_IMPLEMENTED; } return NewProxyInfo_Internal(type, aHost, aPort, aUsername, aPassword, aProxyAuthorizationHeader, aConnectionIsolationKey, aFlags, aFailoverTimeout, aFailoverProxy, 0, aResult); } NS_IMETHODIMP nsProtocolProxyService::GetFailoverForProxy(nsIProxyInfo* aProxy, nsIURI* aURI, nsresult aStatus, nsIProxyInfo** aResult) { // Failover is supported through a variety of methods including: // * PAC scripts (PROXYCONFIG_PAC and PROXYCONFIG_WPAD) // * System proxy // * Extensions // With extensions the mProxyConfig can be any type and the extension // is still involved in the proxy filtering. It may have also supplied // any number of failover proxies. We cannot determine what the mix is // here, so we will attempt to get a failover regardless of the config // type. MANUAL configuration will not disable a proxy. // Verify that |aProxy| is one of our nsProxyInfo objects. nsCOMPtr pi = do_QueryInterface(aProxy); NS_ENSURE_ARG(pi); // OK, the QI checked out. We can proceed. // Remember that this proxy is down. If the user has manually configured some // proxies we do not want to disable them. if (mProxyConfig != PROXYCONFIG_MANUAL) { DisableProxy(pi); } // NOTE: At this point, we might want to prompt the user if we have // not already tried going DIRECT. This is something that the // classic codebase supported; however, IE6 does not prompt. if (!pi->mNext) return NS_ERROR_NOT_AVAILABLE; LOG(("PAC failover from %s %s:%d to %s %s:%d\n", pi->mType, pi->mHost.get(), pi->mPort, pi->mNext->mType, pi->mNext->mHost.get(), pi->mNext->mPort)); *aResult = do_AddRef(pi->mNext).take(); return NS_OK; } namespace { // anon class ProxyFilterPositionComparator { using FilterLinkRef = RefPtr; public: bool Equals(const FilterLinkRef& a, const FilterLinkRef& b) const { return a->position == b->position; } bool LessThan(const FilterLinkRef& a, const FilterLinkRef& b) const { return a->position < b->position; } }; class ProxyFilterObjectComparator { using FilterLinkRef = RefPtr; public: bool Equals(const FilterLinkRef& link, const nsISupports* obj) const { return obj == nsCOMPtr(do_QueryInterface(link->filter)) || obj == nsCOMPtr(do_QueryInterface(link->channelFilter)); } }; } // namespace nsresult nsProtocolProxyService::InsertFilterLink(RefPtr&& link) { LOG(("nsProtocolProxyService::InsertFilterLink filter=%p", link.get())); if (mIsShutdown) { return NS_ERROR_FAILURE; } // If we add a new element with the same position as an existing one, we want // to preserve the insertion order to avoid surprises. mFilters.InsertElementSorted(link, ProxyFilterPositionComparator()); NotifyProxyConfigChangedInternal(); return NS_OK; } NS_IMETHODIMP nsProtocolProxyService::RegisterFilter(nsIProtocolProxyFilter* filter, uint32_t position) { UnregisterFilter(filter); // remove this filter if we already have it RefPtr link = new FilterLink(position, filter); return InsertFilterLink(std::move(link)); } NS_IMETHODIMP nsProtocolProxyService::RegisterChannelFilter( nsIProtocolProxyChannelFilter* channelFilter, uint32_t position) { UnregisterChannelFilter( channelFilter); // remove this filter if we already have it RefPtr link = new FilterLink(position, channelFilter); return InsertFilterLink(std::move(link)); } nsresult nsProtocolProxyService::RemoveFilterLink(nsISupports* givenObject) { LOG(("nsProtocolProxyService::RemoveFilterLink target=%p", givenObject)); nsresult rv = mFilters.RemoveElement(givenObject, ProxyFilterObjectComparator()) ? NS_OK : NS_ERROR_UNEXPECTED; if (NS_SUCCEEDED(rv)) { NotifyProxyConfigChangedInternal(); } return rv; } NS_IMETHODIMP nsProtocolProxyService::UnregisterFilter(nsIProtocolProxyFilter* filter) { // QI to nsISupports so we can safely test object identity. nsCOMPtr givenObject = do_QueryInterface(filter); return RemoveFilterLink(givenObject); } NS_IMETHODIMP nsProtocolProxyService::UnregisterChannelFilter( nsIProtocolProxyChannelFilter* channelFilter) { // QI to nsISupports so we can safely test object identity. nsCOMPtr givenObject = do_QueryInterface(channelFilter); return RemoveFilterLink(givenObject); } NS_IMETHODIMP nsProtocolProxyService::GetProxyConfigType(uint32_t* aProxyConfigType) { *aProxyConfigType = mProxyConfig; return NS_OK; } void nsProtocolProxyService::LoadHostFilters(const nsACString& aFilters) { if (mIsShutdown) { return; } // check to see the owners flag? /!?/ TODO if (mHostFiltersArray.Length() > 0) { mHostFiltersArray.Clear(); } // Reset mFilterLocalHosts - will be set to true if "" is in pref // string mFilterLocalHosts = false; if (aFilters.IsEmpty()) { return; } // // filter = ( host | domain | ipaddr ["/" mask] ) [":" port] // filters = filter *( "," LWS filter) // mozilla::Tokenizer t(aFilters); mozilla::Tokenizer::Token token; bool eof = false; // while (*filters) { while (!eof) { // skip over spaces and , t.SkipWhites(); while (t.CheckChar(',')) { t.SkipWhites(); } nsAutoCString portStr; nsAutoCString hostStr; nsAutoCString maskStr; t.Record(); bool parsingIPv6 = false; bool parsingPort = false; bool parsingMask = false; while (t.Next(token)) { if (token.Equals(mozilla::Tokenizer::Token::EndOfFile())) { eof = true; break; } if (token.Equals(mozilla::Tokenizer::Token::Char(',')) || token.Type() == mozilla::Tokenizer::TOKEN_WS) { break; } if (token.Equals(mozilla::Tokenizer::Token::Char('['))) { parsingIPv6 = true; continue; } if (!parsingIPv6 && token.Equals(mozilla::Tokenizer::Token::Char(':'))) { // Port is starting. Claim the previous as host. if (parsingMask) { t.Claim(maskStr); } else { t.Claim(hostStr); } t.Record(); parsingPort = true; continue; } if (token.Equals(mozilla::Tokenizer::Token::Char('/'))) { t.Claim(hostStr); t.Record(); parsingMask = true; continue; } if (token.Equals(mozilla::Tokenizer::Token::Char(']'))) { parsingIPv6 = false; continue; } } if (!parsingPort && !parsingMask) { t.Claim(hostStr); } else if (parsingPort) { t.Claim(portStr); } else if (parsingMask) { t.Claim(maskStr); } else { NS_WARNING("Could not parse this rule"); continue; } if (hostStr.IsEmpty()) { continue; } // If the current host filter is "", then all local (i.e. // no dots in the hostname) hosts should bypass the proxy if (hostStr.EqualsIgnoreCase("")) { mFilterLocalHosts = true; LOG( ("loaded filter for local hosts " "(plain host names, no dots)\n")); // Continue to next host filter; continue; } // For all other host filters, create HostInfo object and add to list HostInfo* hinfo = new HostInfo(); nsresult rv = NS_OK; int32_t port = portStr.ToInteger(&rv); if (NS_FAILED(rv)) { port = 0; } hinfo->port = port; int32_t maskLen = maskStr.ToInteger(&rv); if (NS_FAILED(rv)) { maskLen = 128; } // PR_StringToNetAddr can't parse brackets enclosed IPv6 nsAutoCString addrString = hostStr; if (hostStr.First() == '[' && hostStr.Last() == ']') { addrString = Substring(hostStr, 1, hostStr.Length() - 2); } PRNetAddr addr; if (PR_StringToNetAddr(addrString.get(), &addr) == PR_SUCCESS) { hinfo->is_ipaddr = true; hinfo->ip.family = PR_AF_INET6; // we always store address as IPv6 hinfo->ip.mask_len = maskLen; if (hinfo->ip.mask_len == 0) { NS_WARNING("invalid mask"); goto loser; } if (addr.raw.family == PR_AF_INET) { // convert to IPv4-mapped address PR_ConvertIPv4AddrToIPv6(addr.inet.ip, &hinfo->ip.addr); // adjust mask_len accordingly if (hinfo->ip.mask_len <= 32) hinfo->ip.mask_len += 96; } else if (addr.raw.family == PR_AF_INET6) { // copy the address memcpy(&hinfo->ip.addr, &addr.ipv6.ip, sizeof(PRIPv6Addr)); } else { NS_WARNING("unknown address family"); goto loser; } // apply mask to IPv6 address proxy_MaskIPv6Addr(hinfo->ip.addr, hinfo->ip.mask_len); } else { nsAutoCString host; if (hostStr.First() == '*') { host = Substring(hostStr, 1); } else { host = hostStr; } if (host.IsEmpty()) { hinfo->name.host = nullptr; goto loser; } hinfo->name.host_len = host.Length(); hinfo->is_ipaddr = false; hinfo->name.host = ToNewCString(host, mozilla::fallible); if (!hinfo->name.host) goto loser; } // #define DEBUG_DUMP_FILTERS #ifdef DEBUG_DUMP_FILTERS printf("loaded filter[%zu]:\n", mHostFiltersArray.Length()); printf(" is_ipaddr = %u\n", hinfo->is_ipaddr); printf(" port = %u\n", hinfo->port); printf(" host = %s\n", hostStr.get()); if (hinfo->is_ipaddr) { printf(" ip.family = %x\n", hinfo->ip.family); printf(" ip.mask_len = %u\n", hinfo->ip.mask_len); PRNetAddr netAddr; PR_SetNetAddr(PR_IpAddrNull, PR_AF_INET6, 0, &netAddr); memcpy(&netAddr.ipv6.ip, &hinfo->ip.addr, sizeof(hinfo->ip.addr)); char buf[256]; PR_NetAddrToString(&netAddr, buf, sizeof(buf)); printf(" ip.addr = %s\n", buf); } else { printf(" name.host = %s\n", hinfo->name.host); } #endif mHostFiltersArray.AppendElement(hinfo); hinfo = nullptr; loser: delete hinfo; } } nsresult nsProtocolProxyService::GetProtocolInfo(nsIURI* uri, nsProtocolInfo* info) { AssertIsOnMainThread(); MOZ_ASSERT(uri, "URI is null"); MOZ_ASSERT(info, "info is null"); nsresult rv; rv = uri->GetScheme(info->scheme); if (NS_FAILED(rv)) return rv; nsCOMPtr ios = do_GetIOService(&rv); if (NS_FAILED(rv)) return rv; rv = ios->GetDynamicProtocolFlags(uri, &info->flags); if (NS_FAILED(rv)) return rv; rv = ios->GetDefaultPort(info->scheme.get(), &info->defaultPort); return rv; } nsresult nsProtocolProxyService::NewProxyInfo_Internal( const char* aType, const nsACString& aHost, int32_t aPort, const nsACString& aUsername, const nsACString& aPassword, const nsACString& aProxyAuthorizationHeader, const nsACString& aConnectionIsolationKey, uint32_t aFlags, uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy, uint32_t aResolveFlags, nsIProxyInfo** aResult) { if (aPort <= 0) aPort = -1; nsCOMPtr failover; if (aFailoverProxy) { failover = do_QueryInterface(aFailoverProxy); NS_ENSURE_ARG(failover); } RefPtr proxyInfo = new nsProxyInfo(); proxyInfo->mType = aType; proxyInfo->mHost = aHost; proxyInfo->mPort = aPort; proxyInfo->mUsername = aUsername; proxyInfo->mPassword = aPassword; proxyInfo->mFlags = aFlags; proxyInfo->mResolveFlags = aResolveFlags; proxyInfo->mTimeout = aFailoverTimeout == UINT32_MAX ? mFailedProxyTimeout : aFailoverTimeout; proxyInfo->mProxyAuthorizationHeader = aProxyAuthorizationHeader; proxyInfo->mConnectionIsolationKey = aConnectionIsolationKey; failover.swap(proxyInfo->mNext); proxyInfo.forget(aResult); return NS_OK; } const char* nsProtocolProxyService::SOCKSProxyType() { if (mSOCKSProxyVersion == nsIProxyInfo::SOCKS_V4) { return kProxyType_SOCKS4; } return kProxyType_SOCKS; } bool nsProtocolProxyService::SOCKSRemoteDNS() { return (mSOCKSProxyVersion == nsIProxyInfo::SOCKS_V4 && mSOCKS4ProxyRemoteDNS) || (mSOCKSProxyVersion == nsIProxyInfo::SOCKS_V5 && mSOCKS5ProxyRemoteDNS); } nsresult nsProtocolProxyService::Resolve_Internal(nsIChannel* channel, const nsProtocolInfo& info, uint32_t flags, bool* usePACThread, nsIProxyInfo** result) { NS_ENSURE_ARG_POINTER(channel); *usePACThread = false; *result = nullptr; if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY)) { return NS_OK; // Can't proxy this (filters may not override) } nsCOMPtr uri; nsresult rv = GetProxyURI(channel, getter_AddRefs(uri)); if (NS_FAILED(rv)) return rv; // See bug #586908. // Avoid endless loop if |uri| is the current PAC-URI. Returning OK // here means that we will not use a proxy for this connection. if (mPACMan && mPACMan->IsPACURI(uri)) return NS_OK; // if proxies are enabled and this host:port combo is supposed to use a // proxy, check for a proxy. if ((mProxyConfig == PROXYCONFIG_DIRECT) || !CanUseProxy(uri, info.defaultPort)) { return NS_OK; } bool mainThreadOnly; if (mSystemProxySettings && mProxyConfig == PROXYCONFIG_SYSTEM && NS_SUCCEEDED(mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly)) && !mainThreadOnly) { *usePACThread = true; return NS_OK; } if (mSystemProxySettings && mProxyConfig == PROXYCONFIG_SYSTEM) { // If the system proxy setting implementation is not threadsafe (e.g // linux gconf), we'll do it inline here. Such implementations promise // not to block // bug 1366133: this block uses GetPACURI & GetProxyForURI, which may // hang on Windows platform. Fortunately, current implementation on // Windows is not main thread only, so we are safe here. nsAutoCString PACURI; nsAutoCString pacString; if (NS_SUCCEEDED(mSystemProxySettings->GetPACURI(PACURI)) && !PACURI.IsEmpty()) { // There is a PAC URI configured. If it is unchanged, then // just execute the PAC thread. If it is changed then load // the new value if (mPACMan && mPACMan->IsPACURI(PACURI)) { // unchanged *usePACThread = true; return NS_OK; } ConfigureFromPAC(PACURI, false); return NS_OK; } nsAutoCString spec; nsAutoCString host; nsAutoCString scheme; int32_t port = -1; uri->GetAsciiSpec(spec); uri->GetAsciiHost(host); uri->GetScheme(scheme); uri->GetPort(&port); if (flags & RESOLVE_PREFER_SOCKS_PROXY) { LOG(("Ignoring RESOLVE_PREFER_SOCKS_PROXY for system proxy setting\n")); } else if (flags & RESOLVE_PREFER_HTTPS_PROXY) { scheme.AssignLiteral("https"); } else if (flags & RESOLVE_IGNORE_URI_SCHEME) { scheme.AssignLiteral("http"); } // now try the system proxy settings for this particular url if (NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI(spec, scheme, host, port, pacString))) { nsCOMPtr pi; ProcessPACString(pacString, 0, getter_AddRefs(pi)); if (flags & RESOLVE_PREFER_SOCKS_PROXY && flags & RESOLVE_PREFER_HTTPS_PROXY) { nsAutoCString type; pi->GetType(type); // DIRECT from ProcessPACString indicates that system proxy settings // are not configured to use SOCKS proxy. Try https proxy as a // secondary preferrable proxy. This is mainly for websocket whose // proxy precedence is SOCKS > HTTPS > DIRECT. if (type.EqualsLiteral(kProxyType_DIRECT)) { scheme.AssignLiteral(kProxyType_HTTPS); if (NS_SUCCEEDED(mSystemProxySettings->GetProxyForURI( spec, scheme, host, port, pacString))) { ProcessPACString(pacString, 0, getter_AddRefs(pi)); } } } pi.forget(result); return NS_OK; } } // if proxies are enabled and this host:port combo is supposed to use a // proxy, check for a proxy. if (mProxyConfig == PROXYCONFIG_DIRECT || (mProxyConfig == PROXYCONFIG_MANUAL && !CanUseProxy(uri, info.defaultPort))) { return NS_OK; } // Proxy auto config magic... if (mProxyConfig == PROXYCONFIG_PAC || mProxyConfig == PROXYCONFIG_WPAD || StaticPrefs::network_proxy_system_wpad()) { // Do not query PAC now. *usePACThread = true; return NS_OK; } // If we aren't in manual proxy configuration mode then we don't // want to honor any manual specific prefs that might be still set if (mProxyConfig != PROXYCONFIG_MANUAL) return NS_OK; // proxy info values for manual configuration mode const char* type = nullptr; const nsACString* host = nullptr; int32_t port = -1; uint32_t proxyFlags = 0; if ((flags & RESOLVE_PREFER_SOCKS_PROXY) && !mSOCKSProxyTarget.IsEmpty() && (IsHostLocalTarget(mSOCKSProxyTarget) || mSOCKSProxyPort > 0)) { host = &mSOCKSProxyTarget; type = SOCKSProxyType(); if (SOCKSRemoteDNS()) { proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST; } port = mSOCKSProxyPort; } else if ((flags & RESOLVE_PREFER_HTTPS_PROXY) && !mHTTPSProxyHost.IsEmpty() && mHTTPSProxyPort > 0) { host = &mHTTPSProxyHost; type = kProxyType_HTTP; port = mHTTPSProxyPort; } else if (!mHTTPProxyHost.IsEmpty() && mHTTPProxyPort > 0 && ((flags & RESOLVE_IGNORE_URI_SCHEME) || info.scheme.EqualsLiteral("http"))) { host = &mHTTPProxyHost; type = kProxyType_HTTP; port = mHTTPProxyPort; } else if (!mHTTPSProxyHost.IsEmpty() && mHTTPSProxyPort > 0 && !(flags & RESOLVE_IGNORE_URI_SCHEME) && info.scheme.EqualsLiteral("https")) { host = &mHTTPSProxyHost; type = kProxyType_HTTP; port = mHTTPSProxyPort; } else if (!mSOCKSProxyTarget.IsEmpty() && (IsHostLocalTarget(mSOCKSProxyTarget) || mSOCKSProxyPort > 0)) { host = &mSOCKSProxyTarget; type = SOCKSProxyType(); if (SOCKSRemoteDNS()) { proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST; } port = mSOCKSProxyPort; } if (type) { rv = NewProxyInfo_Internal(type, *host, port, ""_ns, ""_ns, ""_ns, ""_ns, proxyFlags, UINT32_MAX, nullptr, flags, result); if (NS_FAILED(rv)) return rv; } return NS_OK; } void nsProtocolProxyService::MaybeDisableDNSPrefetch(nsIProxyInfo* aProxy) { // Disable Prefetch in the DNS service if a proxy is in use. if (!aProxy) return; nsCOMPtr pi = do_QueryInterface(aProxy); if (!pi || !pi->mType || pi->mType == kProxyType_DIRECT) return; if (StaticPrefs::network_dns_prefetch_via_proxy()) { return; } // To avoid getting DNS service recursively, we directly use // GetXPCOMSingleton(). nsCOMPtr dns = nsDNSService::GetXPCOMSingleton(); if (!dns) return; nsCOMPtr pdns = do_QueryInterface(dns); if (!pdns) return; // We lose the prefetch optimization for the life of the dns service. pdns->SetPrefetchEnabled(false); } void nsProtocolProxyService::CopyFilters(nsTArray>& aCopy) { MOZ_ASSERT(aCopy.Length() == 0); aCopy.AppendElements(mFilters); } bool nsProtocolProxyService::ApplyFilter( FilterLink const* filterLink, nsIChannel* channel, const nsProtocolInfo& info, nsCOMPtr list, nsIProxyProtocolFilterResult* callback) { nsresult rv; // We prune the proxy list prior to invoking each filter. This may be // somewhat inefficient, but it seems like a good idea since we want each // filter to "see" a valid proxy list. PruneProxyInfo(info, list); if (filterLink->filter) { nsCOMPtr uri; Unused << GetProxyURI(channel, getter_AddRefs(uri)); if (!uri) { return false; } rv = filterLink->filter->ApplyFilter(uri, list, callback); return NS_SUCCEEDED(rv); } if (filterLink->channelFilter) { rv = filterLink->channelFilter->ApplyFilter(channel, list, callback); return NS_SUCCEEDED(rv); } return false; } void nsProtocolProxyService::PruneProxyInfo(const nsProtocolInfo& info, nsIProxyInfo** list) { if (!*list) return; LOG(("nsProtocolProxyService::PruneProxyInfo ENTER list=%p", *list)); nsProxyInfo* head = nullptr; CallQueryInterface(*list, &head); if (!head) { MOZ_ASSERT_UNREACHABLE("nsIProxyInfo must QI to nsProxyInfo"); return; } NS_RELEASE(*list); // Pruning of disabled proxies works like this: // - If all proxies are disabled, return the full list // - Otherwise, remove the disabled proxies. // // Pruning of disallowed proxies works like this: // - If the protocol handler disallows the proxy, then we disallow it. // Start by removing all disallowed proxies if required: if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY_HTTP)) { nsProxyInfo *last = nullptr, *iter = head; while (iter) { if ((iter->Type() == kProxyType_HTTP) || (iter->Type() == kProxyType_HTTPS)) { // reject! if (last) { last->mNext = iter->mNext; } else { head = iter->mNext; } nsProxyInfo* next = iter->mNext; iter->mNext = nullptr; iter->Release(); iter = next; } else { last = iter; iter = iter->mNext; } } if (!head) { return; } } // Scan to see if all remaining non-direct proxies are disabled. If so, then // we'll just bail and return them all. Otherwise, we'll go and prune the // disabled ones. bool allNonDirectProxiesDisabled = true; nsProxyInfo* iter; for (iter = head; iter; iter = iter->mNext) { if (!IsProxyDisabled(iter) && iter->mType != kProxyType_DIRECT) { allNonDirectProxiesDisabled = false; break; } } if (allNonDirectProxiesDisabled && StaticPrefs::network_proxy_retry_failed_proxies()) { LOG(("All proxies are disabled, so trying all again")); } else { // remove any disabled proxies. nsProxyInfo* last = nullptr; for (iter = head; iter;) { if (IsProxyDisabled(iter)) { // reject! nsProxyInfo* reject = iter; iter = iter->mNext; if (last) { last->mNext = iter; } else { head = iter; } reject->mNext = nullptr; NS_RELEASE(reject); continue; } // since we are about to use this proxy, make sure it is not on // the disabled proxy list. we'll add it back to that list if // we have to (in GetFailoverForProxy). // // XXX(darin): It might be better to do this as a final pass. // EnableProxy(iter); last = iter; iter = iter->mNext; } } // if only DIRECT was specified then return no proxy info, and we're done. if (head && !head->mNext && head->mType == kProxyType_DIRECT) { NS_RELEASE(head); } *list = head; // Transfer ownership LOG(("nsProtocolProxyService::PruneProxyInfo LEAVE list=%p", *list)); } bool nsProtocolProxyService::GetIsPACLoading() { return mPACMan && mPACMan->IsLoading(); } NS_IMETHODIMP nsProtocolProxyService::AddProxyConfigCallback( nsIProxyConfigChangedCallback* aCallback) { MOZ_ASSERT(NS_IsMainThread()); if (!aCallback) { return NS_ERROR_INVALID_ARG; } mProxyConfigChangedCallbacks.AppendElement(aCallback); return NS_OK; } NS_IMETHODIMP nsProtocolProxyService::RemoveProxyConfigCallback( nsIProxyConfigChangedCallback* aCallback) { MOZ_ASSERT(NS_IsMainThread()); mProxyConfigChangedCallbacks.RemoveElement(aCallback); return NS_OK; } NS_IMETHODIMP nsProtocolProxyService::NotifyProxyConfigChangedInternal() { LOG(("nsProtocolProxyService::NotifyProxyConfigChangedInternal")); MOZ_ASSERT(NS_IsMainThread()); for (const auto& callback : mProxyConfigChangedCallbacks) { callback->OnProxyConfigChanged(); } return NS_OK; } } // namespace net } // namespace mozilla