/* -*- 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 "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 "nsThreadUtils.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"

//----------------------------------------------------------------------------

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

#define WPAD_URL "http://wpad/wpad.dat"

//----------------------------------------------------------------------------

// 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;
}

//-----------------------------------------------------------------------------

// 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(mChannel.forget());
            }

            if (mCallback) {
                NS_ReleaseOnMainThread(mCallback.forget());
            }

            if (mProxyInfo) {
                NS_ReleaseOnMainThread(mProxyInfo.forget());
            }

            if (mXPComPPS) {
                NS_ReleaseOnMainThread(mXPComPPS.forget());
            }
        }
    }

public:
    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 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 nsCString &pacString,
                         const nsCString &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);
            if (NS_SUCCEEDED(mStatus))
                mPPS->ApplyFilters(mChannel, info, mProxyInfo);
            else
                mProxyInfo = nullptr;

            if(pacAvailable) {
                // if !pacAvailable, it was already logged above
                LOG(("pac thread callback %s\n", mPACString.get()));
            }
            if (NS_SUCCEEDED(mStatus))
                mPPS->MaybeDisableDNSPrefetch(mProxyInfo);
            mCallback->OnProxyAvailable(this, mChannel, mProxyInfo, mStatus);
        }
        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 %X\n", 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;
};

NS_IMPL_ISUPPORTS(nsAsyncResolveRequest, nsICancelable, nsIRunnable)

//----------------------------------------------------------------------------

#define IS_ASCII_SPACE(_c) ((_c) == ' ' || (_c) == '\t')

//
// 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)
{
    nsXPIDLCString temp;
    nsresult rv = aPrefBranch->GetCharPref(aPref, getter_Copies(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)
if ( aIID.Equals(NS_GET_IID(nsProtocolProxyService)) )  foundInterface = static_cast<nsIProtocolProxyService2*>(this); else
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)
    , mFilters(nullptr)
    , mProxyConfig(PROXYCONFIG_DIRECT)
    , mHTTPProxyPort(-1)
    , mFTPProxyPort(-1)
    , mHTTPSProxyPort(-1)
    , mSOCKSProxyPort(-1)
    , mSOCKSProxyVersion(4)
    , mSOCKSProxyRemoteDNS(false)
    , mProxyOverTLS(true)
    , mPACMan(nullptr)
    , mSessionStart(PR_Now())
    , mFailedProxyTimeout(30 * 60) // 30 minute default
{
}

nsProtocolProxyService::~nsProtocolProxyService()
{
    // These should have been cleaned up in our Observe method.
    NS_ASSERTION(mHostFiltersArray.Length() == 0 && mFilters == nullptr &&
                 mPACMan == nullptr, "what happened to xpcom-shutdown?");
}

// nsProtocolProxyService methods
nsresult
nsProtocolProxyService::Init()
{
    // failure to access prefs is non-fatal
    nsCOMPtr<nsIPrefBranch> prefBranch =
            do_GetService(NS_PREFSERVICE_CONTRACTID);
    if (prefBranch) {
        // monitor proxy prefs
        prefBranch->AddObserver(PROXY_PREF_BRANCH, this, false);

        // read all prefs
        PrefsChanged(prefBranch, nullptr);
    }

    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
    if (obs) {
        // register for shutdown notification so we can clean ourselves up
        // properly.
        obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
        obs->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
    }

    return NS_OK;
}

// ReloadNetworkPAC() checks if there's a non-networked PAC in use then avoids
// to call ReloadPAC()
nsresult
nsProtocolProxyService::ReloadNetworkPAC()
{
    nsCOMPtr<nsIPrefBranch> prefs =
        do_GetService(NS_PREFSERVICE_CONTRACTID);
    if (!prefs) {
        return NS_OK;
    }

    int32_t type;
    nsresult rv = prefs->GetIntPref(PROXY_PREF("type"), &type);
    if (NS_FAILED(rv)) {
        return NS_OK;
    }

    if (type == PROXYCONFIG_PAC) {
        nsXPIDLCString pacSpec;
        prefs->GetCharPref(PROXY_PREF("autoconfig_url"),
                           getter_Copies(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;
}


NS_IMETHODIMP
nsProtocolProxyService::Observe(nsISupports     *aSubject,
                                const char      *aTopic,
                                const char16_t *aData)
{
    if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
        // cleanup
        if (mHostFiltersArray.Length() > 0) {
            mHostFiltersArray.Clear();
        }
        if (mFilters) {
            delete mFilters;
            mFilters = nullptr;
        }
        if (mPACMan) {
            mPACMan->Shutdown();
            mPACMan = 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;
    nsXPIDLCString 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: %lu; 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("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"),
                                     getter_Copies(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"),
                                    getter_Copies(tempString));
            if (mPACMan && !mPACMan->IsPACURI(tempString)) {
                LOG(("PAC Thread URI Changed - Reset Pac Thread"));
                ResetPACThread();
            }
        } else if (mProxyConfig == PROXYCONFIG_WPAD) {
            // We diverge from the WPAD spec here in that we don't walk the
            // hosts's FQDN, stripping components until we hit a TLD.  Doing so
            // is dangerous in the face of an incomplete list of TLDs, and TLDs
            // get added over time.  We could consider doing only a single
            // substitution of the first component, if that proves to help
            // compatibility.
            tempString.AssignLiteral(WPAD_URL);
        } else if (mSystemProxySettings) {
            // Get System Proxy settings if available
            mSystemProxySettings->GetPACURI(tempString);
        }
        if (!tempString.IsEmpty())
            ConfigureFromPAC(tempString, false);
    }
}

bool
nsProtocolProxyService::CanUseProxy(nsIURI *aURI, int32_t defaultPort)
{
    if (mHostFiltersArray.Length() == 0)
        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()
{
    if (mPACMan)
        return NS_OK;

    mPACMan = new nsPACMan();

    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 = 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)
{
    SetupPACThread();

    if (mPACMan->IsPACURI(spec) && !forceReload)
        return NS_OK;

    mFailedProxies.Clear();

    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;

    nsXPIDLCString pacSpec;
    if (type == PROXYCONFIG_PAC)
        prefs->GetCharPref(PROXY_PREF("autoconfig_url"), getter_Copies(pacSpec));
    else if (type == PROXYCONFIG_WPAD)
        pacSpec.AssignLiteral(WPAD_URL);

    if (!pacSpec.IsEmpty())
        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 nsCString &pacString,
                         const nsCString &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(PR_SecondsToInterval(3)); }

private:
    ~nsAsyncBridgeRequest()
    {
    }

    friend class nsProtocolProxyService;

    Mutex    mMutex;
    CondVar  mCondVar;

    nsresult  mStatus;
    nsCString mPACString;
    nsCString mPACURL;
    bool      mCompleted;
};
NS_IMPL_ISUPPORTS0(nsAsyncBridgeRequest)

// nsProtocolProxyService
nsresult
nsProtocolProxyService::DeprecatedBlockingResolve(nsIChannel *aChannel,
                                                  uint32_t aFlags,
                                                  nsIProxyInfo **retval)
{
    NS_ENSURE_ARG_POINTER(aChannel);

    nsCOMPtr<nsIURI> uri;
    nsresult rv = GetProxyURI(aChannel, getter_AddRefs(uri));
    if (NS_FAILED(rv)) return rv;

    nsProtocolInfo info;
    rv = GetProtocolInfo(uri, &info);
    if (NS_FAILED(rv))
        return rv;

    nsCOMPtr<nsIProxyInfo> pi;
    bool usePACThread;

    // 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(aChannel, info, aFlags,
                          &usePACThread, getter_AddRefs(pi));
    if (NS_FAILED(rv))
        return rv;

    if (!usePACThread || !mPACMan) {
        ApplyFilters(aChannel, info, pi);
        pi.forget(retval);
        return NS_OK;
    }

    // Use the PAC thread to do the work, so we don't have to reimplement that
    // code, but block this thread on that completion.
    RefPtr<nsAsyncBridgeRequest> ctx = new nsAsyncBridgeRequest();
    ctx->Lock();
    if (NS_SUCCEEDED(mPACMan->AsyncGetProxyForURI(uri, ctx, false))) {
        // this can really block the main thread, so cap it at 3 seconds
       ctx->Wait();
    }
    ctx->Unlock();
    if (!ctx->mCompleted)
        return NS_ERROR_FAILURE;
    if (NS_FAILED(ctx->mStatus))
        return ctx->mStatus;

    // pretty much duplicate real DoCallback logic

    // Generate proxy info from the PAC string if appropriate
    if (!ctx->mPACString.IsEmpty()) {
        LOG(("sync pac thread callback %s\n", ctx->mPACString.get()));
        ProcessPACString(ctx->mPACString, 0, getter_AddRefs(pi));
        ApplyFilters(aChannel, info, pi);
        pi.forget(retval);
        return NS_OK;
    }

    if (!ctx->mPACURL.IsEmpty()) {
        NS_WARNING("sync pac thread callback indicates new pac file load\n");
        // This is a problem and is one of the reasons this blocking interface
        // is deprecated. The main loop needs to spin to make this reload happen. So
        // we are going to kick off the reload and return an error - it will work
        // next time. Because this sync interface is only used in the java plugin it
        // is extremely likely that the pac file has already been loaded anyhow.

        rv = ConfigureFromPAC(ctx->mPACURL, false);
        if (NS_FAILED(rv))
            return rv;
        return NS_ERROR_NOT_AVAILABLE;
    }

    *retval = nullptr;
    return NS_OK;
}

nsresult
nsProtocolProxyService::AsyncResolveInternal(nsIChannel *channel, uint32_t flags,
                                             nsIProtocolProxyCallback *callback,
                                             nsICancelable **result,
                                             bool isSyncOK)
{
    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;

    // 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
        ApplyFilters(channel, info, pi);
        ctx->SetResult(NS_OK, pi);
        if (isSyncOK) {
            ctx->Run();
            return NS_OK;
        }

        rv = ctx->DispatchCallback();
        if (NS_SUCCEEDED(rv))
            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,
                                      nsICancelable **result)
{
    return AsyncResolveInternal(channel, flags, callback, result, true);
}

NS_IMETHODIMP
nsProtocolProxyService::AsyncResolve(nsISupports *channelOrURI, uint32_t flags,
                                     nsIProtocolProxyCallback *callback,
                                     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;
        }

        nsCOMPtr<nsIScriptSecurityManager> secMan(
            do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv));
        NS_ENSURE_SUCCESS(rv, rv);
        nsCOMPtr<nsIPrincipal> systemPrincipal;
        rv = secMan->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
        NS_ENSURE_SUCCESS(rv, rv);

        // 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,
                           systemPrincipal,
                           nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                           nsIContentPolicy::TYPE_OTHER);
        NS_ENSURE_SUCCESS(rv, rv);
    }

    return AsyncResolveInternal(channel, flags, callback, result, false);
}

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 (uint32_t i = 0; i < ArrayLength(types); ++i) {
        if (aType.LowerCaseEqualsASCII(types[i])) {
            type = types[i];
            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;
}

nsresult
nsProtocolProxyService::InsertFilterLink(FilterLink *link, uint32_t position)
{
    if (!mFilters) {
        mFilters = link;
        return NS_OK;
    }

    // insert into mFilters in sorted order
    FilterLink *last = nullptr;
    for (FilterLink *iter = mFilters; iter; iter = iter->next) {
        if (position < iter->position) {
            if (last) {
                link->next = last->next;
                last->next = link;
            }
            else {
                link->next = mFilters;
                mFilters = link;
            }
            return NS_OK;
        }
        last = iter;
    }
    // our position is equal to or greater than the last link in the list
    last->next = link;
    return NS_OK;
}

NS_IMETHODIMP
nsProtocolProxyService::RegisterFilter(nsIProtocolProxyFilter *filter,
                                       uint32_t position)
{
    UnregisterFilter(filter); // remove this filter if we already have it

    FilterLink *link = new FilterLink(position, filter);
    if (!link) {
        return NS_ERROR_OUT_OF_MEMORY;
    }
    return InsertFilterLink(link, position);
}

NS_IMETHODIMP
nsProtocolProxyService::RegisterChannelFilter(nsIProtocolProxyChannelFilter *channelFilter,
                                              uint32_t position)
{
    UnregisterChannelFilter(channelFilter);  // remove this filter if we already have it

    FilterLink *link = new FilterLink(position, channelFilter);
    if (!link) {
        return NS_ERROR_OUT_OF_MEMORY;
    }
    return InsertFilterLink(link, position);
}

nsresult
nsProtocolProxyService::RemoveFilterLink(nsISupports* givenObject)
{
    FilterLink *last = nullptr;
    for (FilterLink *iter = mFilters; iter; iter = iter->next) {
        nsCOMPtr<nsISupports> object = do_QueryInterface(iter->filter);
        nsCOMPtr<nsISupports> object2 = do_QueryInterface(iter->channelFilter);
        if (object == givenObject || object2 == givenObject) {
            if (last)
                last->next = iter->next;
            else
                mFilters = iter->next;
            iter->next = nullptr;
            delete iter;
            return NS_OK;
        }
        last = iter;
    }

    // No need to throw an exception in this case.
    return NS_OK;
}

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)
{
    // 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)
{
    NS_PRECONDITION(uri, "URI is null");
    NS_PRECONDITION(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);
    nsresult rv = SetupPACThread();
    if (NS_FAILED(rv))
        return rv;

    *usePACThread = false;
    *result = nullptr;

    if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY))
        return NS_OK;  // Can't proxy this (filters may not override)

    nsCOMPtr<nsIURI> uri;
    rv = GetProxyURI(channel, getter_AddRefs(uri));
    if (NS_FAILED(rv)) return rv;

    // See bug #586908.
    // Avoid endless loop if |uri| is the current PAC-URI. Returning OK
    // here means that we will not use a proxy for this connection.
    if (mPACMan && mPACMan->IsPACURI(uri))
        return NS_OK;

    // If proxies are enabled and this host:port combo is supposed to use a
    // proxy, check for a proxy.
    if ((mProxyConfig == PROXYCONFIG_DIRECT) ||
        !CanUseProxy(uri, info.defaultPort)) {
        return NS_OK;
    }

    bool mainThreadOnly;
    if (mSystemProxySettings &&
        mProxyConfig == PROXYCONFIG_SYSTEM &&
        NS_SUCCEEDED(mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly)) &&
        !mainThreadOnly) {
        *usePACThread = true;
        return NS_OK;
    }

    if (mSystemProxySettings && mProxyConfig == PROXYCONFIG_SYSTEM) {
        // If the system proxy setting implementation is not threadsafe (e.g
        // linux gconf), we'll do it inline here. Such implementations promise
        // not to block

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

        // 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::ApplyFilters(nsIChannel *channel,
                                     const nsProtocolInfo &info,
                                     nsIProxyInfo **list)
{
    if (!(info.flags & nsIProtocolHandler::ALLOWS_PROXY))
        return;

    // 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.

    nsCOMPtr<nsIProxyInfo> result;

    for (FilterLink *iter = mFilters; iter; iter = iter->next) {
        PruneProxyInfo(info, list);
        nsresult rv = NS_OK;
        if (iter->filter) {
          nsCOMPtr<nsIURI> uri;
          rv = GetProxyURI(channel, getter_AddRefs(uri));
          if (uri) {
            rv = iter->filter->ApplyFilter(this, uri, *list,
                                           getter_AddRefs(result));
          }
        } else if (iter->channelFilter) {
          rv = iter->channelFilter->ApplyFilter(this, channel, *list,
                                                getter_AddRefs(result));
        }
        if (NS_FAILED(rv))
            continue;
        result.swap(*list);
    }

    PruneProxyInfo(info, list);
}

void
nsProtocolProxyService::PruneProxyInfo(const nsProtocolInfo &info,
                                       nsIProxyInfo **list)
{
    if (!*list)
        return;
    nsProxyInfo *head = nullptr;
    CallQueryInterface(*list, &head);
    if (!head) {
        NS_NOTREACHED("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
}

} // namespace net
} // namespace mozilla