Source code

Revision control

Other Tools

/* -*- 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 "nsIDNSService.h"
#include "nsPIDNSService.h"
#include "nsIPrefBranch.h"
#include "nsContentUtils.h"
#include "nsThreadUtils.h"
#include "nsQueryObject.h"
#include "nsSOCKSIOLayer.h"
#include "nsString.h"
#include "nsNetUtil.h"
#include "nsNetCID.h"
#include "plstr.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/Logging.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[];
#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;
int32_t defaultPort;
};
//----------------------------------------------------------------------------
// 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)
: mStatus(NS_OK),
mDispatched(false),
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
typedef std::function<nsresult(nsAsyncResolveRequest*, nsIProxyInfo*, bool)>
Callback;
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:
typedef nsProtocolProxyService::FilterLink 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<nsIEventTarget> mProcessingThread;
};
void EnsureResolveFlagsMatch() {
nsCOMPtr<nsIProxyInfo> proxyInfo = mProxyInfo;
while (proxyInfo) {
proxyInfo->SetResolveFlags(mResolveFlags);
proxyInfo->GetFailoverProxy(getter_AddRefs(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 = NS_LITERAL_CSTRING("DIRECT;");
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;
nsCString mPACString;
nsCString mPACURL;
bool mDispatched;
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_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()
: mFilterLocalHosts(false),
mProxyConfig(PROXYCONFIG_DIRECT),
mHTTPProxyPort(-1),
mFTPProxyPort(-1),
mHTTPSProxyPort(-1),
mSOCKSProxyPort(-1),
mSOCKSProxyVersion(4),
mSOCKSProxyRemoteDNS(false),
mProxyOverTLS(true),
mWPADOverDHCPEnabled(false),
mAllowHijackingLocalhost(false),
mPACMan(nullptr),
mSessionStart(PR_Now()),
mFailedProxyTimeout(30 * 60) // 30 minute default
,
mIsShutdown(false) {}
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() {
mProxySettingTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
// 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();
}
if (NS_WARN_IF(!mProxySettingTarget)) {
return NS_ERROR_NOT_INITIALIZED;
}
return mProxySettingTarget->Dispatch(req, 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 (mProxySettingTarget) {
mProxySettingTarget = 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)) {
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;
}
void nsProtocolProxyService::PrefsChanged(nsIPrefBranch* prefBranch,
const char* pref) {
nsresult rv = NS_OK;
bool reloadPAC = false;
nsAutoCString tempString;
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("ftp")))
proxy_GetStringPref(prefBranch, PROXY_PREF("ftp"), mFTPProxyHost);
if (!pref || !strcmp(pref, PROXY_PREF("ftp_port")))
proxy_GetIntPref(prefBranch, PROXY_PREF("ftp_port"), mFTPProxyPort);
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("allow_hijacking_localhost"))) {
proxy_GetBoolPref(prefBranch, PROXY_PREF("allow_hijacking_localhost"),
mAllowHijackingLocalhost);
}
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('.')) ||
(!mAllowHijackingLocalhost &&
(host.EqualsLiteral("127.0.0.1") || host.EqualsLiteral("::1") ||
host.EqualsLiteral("localhost")))) {
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 (!PL_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 (PL_strncasecmp(start, kProxyType_HTTP, 4) == 0) {
type = kProxyType_HTTP;
}
break;
case 5:
if (PL_strncasecmp(start, kProxyType_PROXY, 5) == 0) {
type = kProxyType_HTTP;
} else if (PL_strncasecmp(start, kProxyType_SOCKS, 5) == 0) {
type = kProxyType_SOCKS4; // assume v4 for 4x compat
} else if (PL_strncasecmp(start, kProxyType_HTTPS, 5) == 0) {
type = kProxyType_HTTPS;
}
break;
case 6:
if (PL_strncasecmp(start, kProxyType_DIRECT, 6) == 0)
type = kProxyType_DIRECT;
else if (PL_strncasecmp(start, kProxyType_SOCKS4, 6) == 0)
type = kProxyType_SOCKS4;
else if (PL_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.Put(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(
nsIEventTarget* 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"),
mStatus(NS_OK),
mCompleted(false) {}
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() { mMutex.Lock(); }
void Unlock() { mMutex.Unlock(); }
void Wait() { mCondVar.Wait(TimeDuration::FromSeconds(3)); }
private:
~nsAsyncBridgeRequest() = default;
friend class nsProtocolProxyService;
Mutex mMutex;
CondVar mCondVar;
nsresult mStatus;
nsCString mPACString;
nsCString mPACURL;
bool mCompleted;
};
NS_IMPL_ISUPPORTS0(nsAsyncBridgeRequest)
nsresult nsProtocolProxyService::AsyncResolveInternal(
nsIChannel* channel, uint32_t flags, nsIProtocolProxyCallback* callback,
nsICancelable** result, bool isSyncOK,
nsIEventTarget* 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,
nsIEventTarget* mainThreadEventTarget,
nsICancelable** result) {
return AsyncResolveInternal(channel, flags, callback, result, true,
mainThreadEventTarget);
}
NS_IMETHODIMP
nsProtocolProxyService::AsyncResolve(nsISupports* channelOrURI, uint32_t flags,
nsIProtocolProxyCallback* callback,
nsIEventTarget* 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_DATA_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, EmptyCString(),
EmptyCString(), 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) {
// We only support failover when a PAC file is configured, either
// directly or via system settings
if (mProxyConfig != PROXYCONFIG_PAC && mProxyConfig != PROXYCONFIG_WPAD &&
mProxyConfig != PROXYCONFIG_SYSTEM)
return NS_ERROR_NOT_AVAILABLE;
// 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.
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 {
typedef RefPtr<nsProtocolProxyService::FilterLink> FilterLinkRef;
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 {
typedef RefPtr<nsProtocolProxyService::FilterLink> FilterLinkRef;
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());
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));
return mFilters.RemoveElement(givenObject, ProxyFilterObjectComparator())
? NS_OK
: NS_ERROR_UNEXPECTED;
}
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