netwerk/base/nsProtocolProxyService.cpp
author Kershaw Chang <kershaw@mozilla.com>
Mon, 22 Oct 2018 09:07:51 +0000
changeset 490666 28403444666ca113c31b269dd55d31159ac2fe6b
parent 475234 bdba0cfc639e6bf4a2a8a7a9297d0cfa80fa04a6
permissions -rw-r--r--
Bug 1219935 - Skip OCSP request if PAC download is in progress r=keeler,bagder This is a straightforward patch. Just add a new attribute in nsIProtocolProxyService to indicate whether PAC is still loading. If yes, fail the OCSP request. Differential Revision: https://phabricator.services.mozilla.com/D9154

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set ts=4 sw=4 sts=4 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 "nsIScriptSecurityManager.h"
#include "nsIPrefService.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_ReleaseOnMainThreadSystemGroup(
                  "nsAsyncResolveRequest::mChannel", mChannel.forget());
            }

            if (mCallback) {
                NS_ReleaseOnMainThreadSystemGroup(
                  "nsAsyncResolveRequest::mCallback", mCallback.forget());
            }

            if (mProxyInfo) {
                NS_ReleaseOnMainThreadSystemGroup(
                  "nsAsyncResolveRequest::mProxyInfo", mProxyInfo.forget());
            }

            if (mXPComPPS) {
                NS_ReleaseOnMainThreadSystemGroup(
                  "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;
    };

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->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,
                                                        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);
            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;
  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 (!mRequest) {
    // We got canceled
    LOG(("  canceled"));
    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_ReleaseOnMainThreadSystemGroup(
          "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]) & (~0L << (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]) & (~0L << (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]) & (~0L << (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]) & (~0L << (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)
    , 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("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)
{
    if (mHostFiltersArray.Length() == 0 && !mFilterLocalHosts)
        return true;

    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('.')) ||
        host.EqualsLiteral("127.0.0.1") ||
        host.EqualsLiteral("::1")) {
        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())) {
        HostInfo *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, 5) == 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) {
        const char *host = nullptr, *hostEnd = nullptr;
        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;
        }

        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;
        if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(pacURI), maybeURL)) &&
            NS_SUCCEEDED(pacURI->GetAsciiHost(urlHost)) &&
            !urlHost.IsEmpty()) {
            // http://www.example.com:8080

            pi->mHost = urlHost;

            int32_t tPort;
            if (NS_SUCCEEDED(pacURI->GetPort(&tPort)) && tPort != -1) {
                port = tPort;
            }
            pi->mPort = port;
        }
        else {
            // www.example.com:8080
            if (start < end) {
                host = start;
                hostEnd = strchr(host, ':');
                if (!hostEnd || hostEnd > end) {
                    hostEnd = end;
                    // no port, so assume default
                }
                else {
                    port = atoi(hostEnd + 1);
                }
            }
            // YES, it is ok to specify a null proxy host.
            if (host) {
                pi->mHost.Assign(host, hostEnd - host);
                pi->mPort = port;
            }
        }
        NS_ADDREF(*result = pi);
    }

    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, 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,
                                     uint32_t aFlags,
                                     uint32_t aFailoverTimeout,
                                     nsIProxyInfo *aFailoverProxy,
                                     nsIProxyInfo **aResult)
{
    return NewProxyInfoWithAuth(aType, aHost, aPort,
                                EmptyCString(), EmptyCString(),
                                aFlags, aFailoverTimeout,
                                aFailoverProxy, aResult);
}

NS_IMETHODIMP
nsProtocolProxyService::NewProxyInfoWithAuth(const nsACString &aType,
                                             const nsACString &aHost,
                                             int32_t aPort,
                                             const nsACString &aUsername,
                                             const nsACString &aPassword,
                                             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,
                                 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));

    NS_ADDREF(*aResult = pi->mNext);
    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));
  }
};

} // anon

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
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();
    }

    if (aFilters.IsEmpty()) {
        return;
    }

    //
    // filter  = ( host | domain | ipaddr ["/" mask] ) [":" port]
    // filters = filter *( "," LWS filter)
    //
    // Reset mFilterLocalHosts - will be set to true if "<local>" is in pref string
    mFilterLocalHosts = false;

    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;
            } else if (token.Equals(mozilla::Tokenizer::Token::Char('/'))) {
                t.Claim(hostStr);
                t.Record();
                parsingMask = true;
                continue;
            } else 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);

            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)
{
    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;

    nsCOMPtr<nsIProtocolHandler> handler;
    rv = ios->GetProtocolHandler(info->scheme.get(), getter_AddRefs(handler));
    if (NS_FAILED(rv))
        return rv;

    rv = handler->DoGetProtocolFlags(uri, &info->flags);
    if (NS_FAILED(rv))
        return rv;

    rv = handler->GetDefaultPort(&info->defaultPort);
    return rv;
}

nsresult
nsProtocolProxyService::NewProxyInfo_Internal(const char *aType,
                                              const nsACString &aHost,
                                              int32_t aPort,
                                              const nsACString &aUsername,
                                              const nsACString &aPassword,
                                              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);
    }

    nsProxyInfo *proxyInfo = new nsProxyInfo();
    if (!proxyInfo)
        return NS_ERROR_OUT_OF_MEMORY;

    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;
    failover.swap(proxyInfo->mNext);

    NS_ADDREF(*aResult = proxyInfo);
    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;

    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))) {
            ProcessPACString(pacString, 0, 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 (!mFTPProxyHost.IsEmpty() && mFTPProxyPort > 0 &&
             !(flags & RESOLVE_IGNORE_URI_SCHEME) &&
             info.scheme.EqualsLiteral("ftp")) {
        host = &mFTPProxyHost;
        type = kProxyType_HTTP;
        port = mFTPProxyPort;
    }
    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,
                                   EmptyCString(), EmptyCString(),
                                   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;

    nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
    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(this, uri, list, callback);
      return NS_SUCCEEDED(rv);
    }

    if (filterLink->channelFilter) {
      rv = filterLink->channelFilter->ApplyFilter(this, 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;
        }
    }

    // Now, scan to see if all remaining proxies are disabled.  If so, then
    // we'll just bail and return them all.  Otherwise, we'll go and prune the
    // disabled ones.

    bool allDisabled = true;

    nsProxyInfo *iter;
    for (iter = head; iter; iter = iter->mNext) {
        if (!IsProxyDisabled(iter)) {
            allDisabled = false;
            break;
        }
    }

    if (allDisabled) {
        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));
}

NS_IMETHODIMP
nsProtocolProxyService::GetIsPACLoading(bool *aResult)
{
    NS_ENSURE_ARG_POINTER(aResult);

    *aResult = false;
    if (mPACMan && mPACMan->IsLoading()) {
        *aResult = true;
    }
    return NS_OK;
}

} // namespace net
} // namespace mozilla