diff options
Diffstat (limited to 'netwerk/base/nsProtocolProxyService.cpp')
-rw-r--r-- | netwerk/base/nsProtocolProxyService.cpp | 2458 |
1 files changed, 2458 insertions, 0 deletions
diff --git a/netwerk/base/nsProtocolProxyService.cpp b/netwerk/base/nsProtocolProxyService.cpp new file mode 100644 index 0000000000..07a752a94f --- /dev/null +++ b/netwerk/base/nsProtocolProxyService.cpp @@ -0,0 +1,2458 @@ +/* -*- 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<nsIURI> proxyURI; + nsCOMPtr<nsIHttpChannelInternal> 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<nsresult(nsAsyncResolveRequest*, nsIProxyInfo*, bool)>; + + 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<nsAsyncResolveRequest> 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<RefPtr<FilterLink>> mFiltersCopy; + + nsTArray<RefPtr<FilterLink>>::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<nsIProxyInfo> mProxyInfo; + + // The logic is written as non-thread safe, assert single-thread usage. + nsCOMPtr<nsISerialEventTarget> mProcessingThread; + }; + + void EnsureResolveFlagsMatch() { + nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(mProxyInfo); + if (!pi || pi->ResolveFlags() == mResolveFlags) { + return; + } + + nsCOMPtr<nsIProxyInfo> 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<nsIURI> 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<nsIURI> 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<nsAsyncResolveRequest> 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<uint32_t>(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<nsIProtocolProxyService> mXPComPPS; + nsCOMPtr<nsIChannel> mChannel; + nsCOMPtr<nsIProtocolProxyCallback> mCallback; + nsCOMPtr<nsIProxyInfo> mProxyInfo; + + RefPtr<AsyncApplyFilters> 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<bool> 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<FilterLink> 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<nsAsyncResolveRequest> request; + request.swap(mRequest); + + nsCOMPtr<nsIProxyInfo> 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 off-main-thread since it may hang on Windows +// platform +class AsyncGetPACURIRequest final : public nsIRunnable { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + using CallbackFunc = nsresult (nsProtocolProxyService::*)(bool, bool, + nsresult, + const nsACString&); + + AsyncGetPACURIRequest(nsProtocolProxyService* aService, + CallbackFunc aCallback, + nsISystemProxySettings* aSystemProxySettings, + bool aMainThreadOnly, bool aForceReload, + bool aResetPACThread) + : mIsMainThreadOnly(aMainThreadOnly), + mService(aService), + mServiceHolder(do_QueryObject(aService)), + mCallback(aCallback), + mSystemProxySettings(aSystemProxySettings), + mForceReload(aForceReload), + mResetPACThread(aResetPACThread) { + MOZ_ASSERT(NS_IsMainThread()); + Unused << mIsMainThreadOnly; + } + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread() == mIsMainThreadOnly); + + nsCString pacUri; + nsresult rv = mSystemProxySettings->GetPACURI(pacUri); + + nsCOMPtr<nsIRunnable> event = + NewNonOwningCancelableRunnableMethod<bool, bool, nsresult, nsCString>( + "AsyncGetPACURIRequestCallback", mService, mCallback, mForceReload, + mResetPACThread, rv, pacUri); + + return NS_DispatchToMainThread(event); + } + + private: + ~AsyncGetPACURIRequest() { + NS_ReleaseOnMainThread("AsyncGetPACURIRequest::mServiceHolder", + mServiceHolder.forget()); + } + + bool mIsMainThreadOnly; + + nsProtocolProxyService* mService; // ref-count is hold by mServiceHolder + nsCOMPtr<nsIProtocolProxyService2> mServiceHolder; + CallbackFunc mCallback; + nsCOMPtr<nsISystemProxySettings> mSystemProxySettings; + + bool mForceReload; + bool mResetPACThread; +}; + +NS_IMPL_ISUPPORTS(AsyncGetPACURIRequest, 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<nsIPrefBranch> 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<nsIObserverService> 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<nsIPrefBranch> 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<nsIURI> 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::AsyncConfigureFromPAC(bool aForceReload, + bool aResetPACThread) { + MOZ_ASSERT(NS_IsMainThread()); + + bool mainThreadOnly; + nsresult rv = mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIRunnable> req = new AsyncGetPACURIRequest( + this, &nsProtocolProxyService::OnAsyncGetPACURI, mSystemProxySettings, + mainThreadOnly, aForceReload, aResetPACThread); + + if (mainThreadOnly) { + return req->Run(); + } + + return NS_DispatchBackgroundTask(req.forget(), + nsIEventTarget::DISPATCH_NORMAL); +} + +nsresult nsProtocolProxyService::OnAsyncGetPACURI(bool aForceReload, + bool aResetPACThread, + nsresult aResult, + const nsACString& aUri) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aResetPACThread) { + ResetPACThread(); + } + + 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<nsIObserverService> 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<nsIPrefBranch> 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"))) { + 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 == 5) { + mSOCKSProxyVersion = 5; + } else { + mSOCKSProxyVersion = 4; + } + } + + if (!pref || !strcmp(pref, PROXY_PREF("socks_remote_dns"))) { + proxy_GetBoolPref(prefBranch, PROXY_PREF("socks_remote_dns"), + mSOCKSProxyRemoteDNS); + } + + if (!pref || !strcmp(pref, PROXY_PREF("proxy_over_tls"))) { + proxy_GetBoolPref(prefBranch, PROXY_PREF("proxy_over_tls"), mProxyOverTLS); + } + + if (!pref || !strcmp(pref, PROXY_PREF("enable_wpad_over_dhcp"))) { + proxy_GetBoolPref(prefBranch, PROXY_PREF("enable_wpad_over_dhcp"), + mWPADOverDHCPEnabled); + reloadPAC = 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) { + // Get System Proxy settings if available + AsyncConfigureFromPAC(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 || mSOCKSProxyRemoteDNS) { + 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<nsProxyInfo> 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<nsIURI> 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<nsIPrefBranch> 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) { + AsyncConfigureFromPAC(true, true); + } 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<nsIURI> uri; + nsresult rv = GetProxyURI(channel, getter_AddRefs(uri)); + if (NS_FAILED(rv)) return rv; + + *result = nullptr; + RefPtr<nsAsyncResolveRequest> ctx = + new nsAsyncResolveRequest(this, channel, flags, callback); + + nsProtocolInfo info; + rv = GetProtocolInfo(uri, &info); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIProxyInfo> pi; + bool usePACThread; + + // adapt to realtime changes in the system proxy service + if (mProxyConfig == PROXYCONFIG_SYSTEM) { + nsCOMPtr<nsISystemProxySettings> 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<nsIChannel> channel = do_QueryInterface(channelOrURI); + if (!channel) { + nsCOMPtr<nsIURI> 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<nsProxyInfo> 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<nsProtocolProxyService::FilterLink>; + + 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<nsProtocolProxyService::FilterLink>; + + public: + bool Equals(const FilterLinkRef& link, const nsISupports* obj) const { + return obj == nsCOMPtr<nsISupports>(do_QueryInterface(link->filter)) || + obj == nsCOMPtr<nsISupports>(do_QueryInterface(link->channelFilter)); + } +}; + +} // namespace + +nsresult nsProtocolProxyService::InsertFilterLink(RefPtr<FilterLink>&& link) { + LOG(("nsProtocolProxyService::InsertFilterLink filter=%p", link.get())); + + if (mIsShutdown) { + return NS_ERROR_FAILURE; + } + + mFilters.AppendElement(link); + mFilters.Sort(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<FilterLink> 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<FilterLink> 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<nsISupports> givenObject = do_QueryInterface(filter); + return RemoveFilterLink(givenObject); +} + +NS_IMETHODIMP +nsProtocolProxyService::UnregisterChannelFilter( + nsIProtocolProxyChannelFilter* channelFilter) { + // QI to nsISupports so we can safely test object identity. + nsCOMPtr<nsISupports> 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 "<local>" 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 "<local>", then all local (i.e. + // no dots in the hostname) hosts should bypass the proxy + if (hostStr.EqualsIgnoreCase("<local>")) { + 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<nsIIOService> 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<nsProxyInfo> failover; + if (aFailoverProxy) { + failover = do_QueryInterface(aFailoverProxy); + NS_ENSURE_ARG(failover); + } + + RefPtr<nsProxyInfo> 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; +} + +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<nsIURI> 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<nsIProxyInfo> 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) { + // 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; + if (mSOCKSProxyVersion == 4) { + type = kProxyType_SOCKS4; + } else { + type = kProxyType_SOCKS; + } + port = mSOCKSProxyPort; + if (mSOCKSProxyRemoteDNS) { + proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST; + } + } 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; + if (mSOCKSProxyVersion == 4) { + type = kProxyType_SOCKS4; + } else { + type = kProxyType_SOCKS; + } + port = mSOCKSProxyPort; + if (mSOCKSProxyRemoteDNS) { + proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST; + } + } + + 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<nsProxyInfo> pi = do_QueryInterface(aProxy); + if (!pi || !pi->mType || pi->mType == kProxyType_DIRECT) return; + + // To avoid getting DNS service recursively, we directly use + // GetXPCOMSingleton(). + nsCOMPtr<nsIDNSService> dns = nsDNSService::GetXPCOMSingleton(); + if (!dns) return; + nsCOMPtr<nsPIDNSService> 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<RefPtr<FilterLink>>& aCopy) { + MOZ_ASSERT(aCopy.Length() == 0); + aCopy.AppendElements(mFilters); +} + +bool nsProtocolProxyService::ApplyFilter( + FilterLink const* filterLink, nsIChannel* channel, + const nsProtocolInfo& info, nsCOMPtr<nsIProxyInfo> 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<nsIURI> 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 |