Bug 1545420 - Allow extensions to set Proxy-Authorization and connection isolation key through proxy.onRequest, r=dragana,mixedpuppy+mixedpuppy
authorHonza Bambas <honzab.moz@firemni.cz>
Mon, 06 May 2019 07:22:18 +0000
changeset 531495 5cb05da8e3ab5fa40f12f8aa9c8c2b2c3d542e8f
parent 531494 b3417a0b019fa428a2edbf79683ed5b2260c14cd
child 531496 40f352e2d6b863c7b7040db9098d9421802709df
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdragana, mixedpuppy
bugs1545420
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1545420 - Allow extensions to set Proxy-Authorization and connection isolation key through proxy.onRequest, r=dragana,mixedpuppy+mixedpuppy Differential Revision: https://phabricator.services.mozilla.com/D29825
dom/chrome-webidl/ChannelWrapper.webidl
netwerk/base/nsIProtocolProxyService.idl
netwerk/base/nsIProxyInfo.idl
netwerk/base/nsNetUtil.cpp
netwerk/base/nsProtocolProxyService.cpp
netwerk/base/nsProtocolProxyService.h
netwerk/base/nsProxyInfo.cpp
netwerk/base/nsProxyInfo.h
netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
netwerk/protocol/http/nsHttpConnectionInfo.cpp
netwerk/protocol/http/nsHttpConnectionInfo.h
netwerk/test/httpserver/httpd.js
netwerk/test/unit/socks_client_subprocess.js
netwerk/test/unit/test_protocolproxyservice-async-filters.js
netwerk/test/unit/test_protocolproxyservice.js
netwerk/test/unit/test_proxy-authorization-via-proxyinfo.js
netwerk/test/unit/test_speculative_connect.js
netwerk/test/unit/xpcshell.ini
services/sync/tests/unit/test_addons_store.js
toolkit/components/extensions/ProxyScriptContext.jsm
toolkit/components/extensions/test/xpcshell/test_ext_proxy_authorization_via_proxyinfo.js
toolkit/components/extensions/test/xpcshell/test_ext_proxy_socks.js
toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
toolkit/components/extensions/webrequest/ChannelWrapper.cpp
toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
--- a/dom/chrome-webidl/ChannelWrapper.webidl
+++ b/dom/chrome-webidl/ChannelWrapper.webidl
@@ -412,16 +412,28 @@ dictionary MozProxyInfo {
    */
   ByteString? username = null;
 
   /**
    * The timeout, in seconds, before the network stack will failover to the
    * next candidate proxy server if it has not received a response.
    */
   unsigned long failoverTimeout;
+
+  /**
+   * Any non-empty value will be passed directly as Proxy-Authorization header
+   * value for the CONNECT request attempt.  However, this header set on the
+   * resource request itself takes precedence.
+   */
+  ByteString? proxyAuthorizationHeader = null;
+
+  /**
+   * An optional key used for additional isolation of this proxy connection.
+   */
+  ByteString? connectionIsolationKey = null;
 };
 
 /**
  * MozFrameAncestorInfo combines loadInfo::AncestorPrincipals with
  * loadInfo::AncestorOuterWindowIDs for easier access in the WebRequest API.
  *
  * url represents the parent of the loading window.
  * frameId is the outerWindowID for the parent of the loading window.
--- a/netwerk/base/nsIProtocolProxyService.idl
+++ b/netwerk/base/nsIProtocolProxyService.idl
@@ -94,17 +94,17 @@ interface nsIProtocolProxyService : nsIS
      * nsIProxiedProtocolHandler, then the nsIProxyInfo instance returned from
      * resolve may be passed to the newProxiedChannel method to create a
      * nsIChannel to the given URI that uses the specified proxy.
      *
      * NOTE: However, if the nsIProxyInfo type is "http", then it means that
      * the given URI should be loaded using the HTTP protocol handler, which
      * also supports nsIProxiedProtocolHandler.
      *
-     * @see nsIProxiedProtocolHandler::newProxiedChannel 
+     * @see nsIProxiedProtocolHandler::newProxiedChannel
      */
     nsICancelable asyncResolve(in nsISupports aChannelOrURI, in unsigned long aFlags,
                                in nsIProtocolProxyCallback aCallback,
                                [optional] in nsIEventTarget aMainThreadTarget);
 
     /**
      * This method may be called to construct a nsIProxyInfo instance from
      * the given parameters.  This method may be useful in conjunction with
@@ -134,17 +134,20 @@ interface nsIProtocolProxyService : nsIS
      *        this proxy fails.  Pass UINT32_MAX to specify the default
      *        timeout value, causing nsIProxyInfo::failoverTimeout to be
      *        assigned the default value.
      * @param aFailoverProxy
      *        Specifies the next proxy to try if this proxy fails.  This
      *        parameter may be null.
      */
     nsIProxyInfo newProxyInfo(in ACString aType, in AUTF8String aHost,
-                              in long aPort, in unsigned long aFlags,
+                              in long aPort,
+                              in ACString aProxyAuthorizationHeader,
+                              in ACString aConnectionIsolationKey,
+                              in unsigned long aFlags,
                               in unsigned long aFailoverTimeout,
                               in nsIProxyInfo aFailoverProxy);
 
     /**
      * This method may be called to construct a nsIProxyInfo instance for
      * with the specified username and password.
      * Currently implemented for SOCKS proxies only.
      * @param aType
@@ -173,16 +176,18 @@ interface nsIProtocolProxyService : nsIS
      *        assigned the default value.
      * @param aFailoverProxy
      *        Specifies the next proxy to try if this proxy fails.  This
      *        parameter may be null.
      */
     nsIProxyInfo newProxyInfoWithAuth(in ACString aType, in AUTF8String aHost,
                                       in long aPort,
                                       in ACString aUsername, in ACString aPassword,
+                                      in ACString aProxyAuthorizationHeader,
+                                      in ACString aConnectionIsolationKey,
                                       in unsigned long aFlags,
                                       in unsigned long aFailoverTimeout,
                                       in nsIProxyInfo aFailoverProxy);
 
     /**
      * If the proxy identified by aProxyInfo is unavailable for some reason,
      * this method may be called to access an alternate proxy that may be used
      * instead.  As a side-effect, this method may affect future result values
@@ -266,17 +271,17 @@ interface nsIProtocolProxyService : nsIS
      * @param aFilter
      *        The nsIProtocolProxyChannelFilter instance to be unregistered.
      */
     void unregisterChannelFilter(in nsIProtocolProxyChannelFilter aFilter);
 
      /**
       * These values correspond to the possible integer values for the
       * network.proxy.type preference.
-      */ 
+      */
      const unsigned long PROXYCONFIG_DIRECT   = 0;
      const unsigned long PROXYCONFIG_MANUAL   = 1;
      const unsigned long PROXYCONFIG_PAC      = 2;
      const unsigned long PROXYCONFIG_WPAD     = 4;
      const unsigned long PROXYCONFIG_SYSTEM   = 5;
 
      /**
       * This attribute specifies the current type of proxy configuration.
--- a/netwerk/base/nsIProxyInfo.idl
+++ b/netwerk/base/nsIProxyInfo.idl
@@ -30,28 +30,28 @@ interface nsIProxyInfo : nsISupports
    *   "https"    HTTP proxying over TLS connection to proxy
    *   "socks"    SOCKS v5 proxy
    *   "socks4"   SOCKS v4 proxy
    *   "direct"   no proxy
    *   "unknown"  unknown proxy (see nsIProtocolProxyService::resolve)
    *
    * A future version of this interface may define additional types.
    */
-  readonly attribute ACString type; 
+  readonly attribute ACString type;
 
   /**
    * This attribute specifies flags that modify the proxy type.  The value of
    * this attribute is the bit-wise combination of the Proxy Flags defined
    * below.  Any undefined bits are reserved for future use.
    */
   readonly attribute unsigned long flags;
 
   /**
    * This attribute specifies flags that were used by nsIProxyProtocolService when
-   * creating this ProxyInfo element. 
+   * creating this ProxyInfo element.
    */
   readonly attribute unsigned long resolveFlags;
 
   /**
    * Specifies a proxy username.
    */
   readonly attribute ACString username;
 
@@ -68,16 +68,27 @@ interface nsIProxyInfo : nsISupports
    */
   readonly attribute unsigned long failoverTimeout;
 
   /**
    * This attribute specifies the proxy to failover to when this proxy fails.
    */
   attribute nsIProxyInfo failoverProxy;
 
+  /**
+   * Any non-empty value will be passed directly as Proxy-Authorization header
+   * value for the CONNECT request attempt.  However, this header set on the
+   * resource request itself takes precedence.
+   */
+  readonly attribute ACString proxyAuthorizationHeader;
+
+  /**
+   * An optional key used for additional isolation of this proxy connection.
+   */
+  readonly attribute ACString connectionIsolationKey;
 
   /****************************************************************************
    * The following "Proxy Flags" may be bit-wise combined to construct the
    * flags attribute defined on this interface.  All unspecified bits are
    * reserved for future use.
    */
 
   /**
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -1048,18 +1048,18 @@ nsresult NS_CheckPortSafety(nsIURI* uri)
 }
 
 nsresult NS_NewProxyInfo(const nsACString& type, const nsACString& host,
                          int32_t port, uint32_t flags, nsIProxyInfo** result) {
   nsresult rv;
   nsCOMPtr<nsIProtocolProxyService> pps =
       do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
   if (NS_SUCCEEDED(rv))
-    rv =
-        pps->NewProxyInfo(type, host, port, flags, UINT32_MAX, nullptr, result);
+    rv = pps->NewProxyInfo(type, host, port, EmptyCString(), EmptyCString(),
+                           flags, UINT32_MAX, nullptr, result);
   return rv;
 }
 
 nsresult NS_GetFileProtocolHandler(nsIFileProtocolHandler** result,
                                    nsIIOService* ioService /* = nullptr */) {
   nsresult rv;
   nsCOMPtr<nsIIOService> grip;
   rv = net_EnsureIOService(&ioService, grip);
@@ -2935,30 +2935,30 @@ nsresult NS_CompareLoadInfoAndLoadContex
   MOZ_ASSERT(originAttrsLoadInfo.mPrivateBrowsingId ==
                  originAttrsLoadContext.mPrivateBrowsingId,
              "The value of mPrivateBrowsingId in the loadContext and in the "
              "loadInfo are not the same!");
 
   return NS_OK;
 }
 
-nsresult NS_SetRequestBlockingReason(nsIChannel *channel, uint32_t reason) {
+nsresult NS_SetRequestBlockingReason(nsIChannel* channel, uint32_t reason) {
   NS_ENSURE_ARG(channel);
 
   nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
   return NS_SetRequestBlockingReason(loadInfo, reason);
 }
 
-nsresult NS_SetRequestBlockingReason(nsILoadInfo *loadInfo, uint32_t reason) {
+nsresult NS_SetRequestBlockingReason(nsILoadInfo* loadInfo, uint32_t reason) {
   NS_ENSURE_ARG(loadInfo);
 
   return loadInfo->SetRequestBlockingReason(reason);
 }
 
-nsresult NS_SetRequestBlockingReasonIfNull(nsILoadInfo *loadInfo,
+nsresult NS_SetRequestBlockingReasonIfNull(nsILoadInfo* loadInfo,
                                            uint32_t reason) {
   NS_ENSURE_ARG(loadInfo);
 
   uint32_t existingReason;
   if (NS_SUCCEEDED(loadInfo->GetRequestBlockingReason(&existingReason)) &&
       existingReason != nsILoadInfo::BLOCKING_REASON_NONE) {
     return NS_OK;
   }
--- a/netwerk/base/nsProtocolProxyService.cpp
+++ b/netwerk/base/nsProtocolProxyService.cpp
@@ -1596,30 +1596,34 @@ nsProtocolProxyService::AsyncResolve(nsI
     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) {
+nsProtocolProxyService::NewProxyInfo(
+    const nsACString& aType, const nsACString& aHost, int32_t aPort,
+    const nsACString& aProxyAuthorizationHeader,
+    const nsACString& aConnectionIsolationKey, uint32_t aFlags,
+    uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy,
+    nsIProxyInfo** aResult) {
   return NewProxyInfoWithAuth(aType, aHost, aPort, EmptyCString(),
-                              EmptyCString(), aFlags, aFailoverTimeout,
+                              EmptyCString(), aProxyAuthorizationHeader,
+                              aConnectionIsolationKey, aFlags, aFailoverTimeout,
                               aFailoverProxy, aResult);
 }
 
 NS_IMETHODIMP
 nsProtocolProxyService::NewProxyInfoWithAuth(
     const nsACString& aType, const nsACString& aHost, int32_t aPort,
-    const nsACString& aUsername, const nsACString& aPassword, uint32_t aFlags,
+    const nsACString& aUsername, const nsACString& aPassword,
+    const nsACString& aProxyAuthorizationHeader,
+    const nsACString& aConnectionIsolationKey, uint32_t aFlags,
     uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy,
     nsIProxyInfo** aResult) {
   static const char* types[] = {kProxyType_HTTP, kProxyType_HTTPS,
                                 kProxyType_SOCKS, kProxyType_SOCKS4,
                                 kProxyType_DIRECT};
 
   // resolve type; this allows us to avoid copying the type string into each
   // proxy info instance.  we just reference the string literals directly :)
@@ -1634,17 +1638,19 @@ nsProtocolProxyService::NewProxyInfoWith
 
   // 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,
+  return NewProxyInfo_Internal(type, aHost, aPort, aUsername, aPassword,
+                               aProxyAuthorizationHeader,
+                               aConnectionIsolationKey, aFlags,
                                aFailoverTimeout, aFailoverProxy, 0, aResult);
 }
 
 NS_IMETHODIMP
 nsProtocolProxyService::GetFailoverForProxy(nsIProxyInfo* aProxy, nsIURI* aURI,
                                             nsresult aStatus,
                                             nsIProxyInfo** aResult) {
   // We only support failover when a PAC file is configured, either
@@ -1981,17 +1987,19 @@ nsresult nsProtocolProxyService::GetProt
   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,
+    const nsACString& aUsername, const nsACString& aPassword,
+    const nsACString& aProxyAuthorizationHeader,
+    const nsACString& aConnectionIsolationKey, uint32_t aFlags,
     uint32_t aFailoverTimeout, nsIProxyInfo* aFailoverProxy,
     uint32_t aResolveFlags, nsIProxyInfo** aResult) {
   if (aPort <= 0) aPort = -1;
 
   nsCOMPtr<nsProxyInfo> failover;
   if (aFailoverProxy) {
     failover = do_QueryInterface(aFailoverProxy);
     NS_ENSURE_ARG(failover);
@@ -2004,16 +2012,18 @@ nsresult nsProtocolProxyService::NewProx
   proxyInfo->mHost = aHost;
   proxyInfo->mPort = aPort;
   proxyInfo->mUsername = aUsername;
   proxyInfo->mPassword = aPassword;
   proxyInfo->mFlags = aFlags;
   proxyInfo->mResolveFlags = aResolveFlags;
   proxyInfo->mTimeout =
       aFailoverTimeout == UINT32_MAX ? mFailedProxyTimeout : aFailoverTimeout;
+  proxyInfo->mProxyAuthorizationHeader = aProxyAuthorizationHeader;
+  proxyInfo->mConnectionIsolationKey = aConnectionIsolationKey;
   failover.swap(proxyInfo->mNext);
 
   NS_ADDREF(*aResult = proxyInfo);
   return NS_OK;
 }
 
 nsresult nsProtocolProxyService::Resolve_Internal(nsIChannel* channel,
                                                   const nsProtocolInfo& info,
@@ -2170,19 +2180,19 @@ nsresult nsProtocolProxyService::Resolve
     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);
+    rv = NewProxyInfo_Internal(type, *host, port, EmptyCString(),
+                               EmptyCString(), 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.
--- a/netwerk/base/nsProtocolProxyService.h
+++ b/netwerk/base/nsProtocolProxyService.h
@@ -210,19 +210,22 @@ class nsProtocolProxyService final : pub
    *        The next proxy to try if this one fails.
    * @param aResolveFlags
    *        The flags passed to resolve (from nsIProtocolProxyService).
    * @param result
    *        The resulting nsIProxyInfo object.
    */
   nsresult NewProxyInfo_Internal(const char* type, const nsACString& host,
                                  int32_t port, const nsACString& username,
-                                 const nsACString& password, uint32_t flags,
-                                 uint32_t timeout, nsIProxyInfo* next,
-                                 uint32_t aResolveFlags, nsIProxyInfo** result);
+                                 const nsACString& password,
+                                 const nsACString& aProxyAuthorizationHeader,
+                                 const nsACString& aConnectionIsolationKey,
+                                 uint32_t flags, uint32_t timeout,
+                                 nsIProxyInfo* next, uint32_t aResolveFlags,
+                                 nsIProxyInfo** result);
 
   /**
    * This method is an internal version of Resolve that does not query PAC.
    * It performs all of the built-in processing, and reports back to the
    * caller with either the proxy info result or a flag to instruct the
    * caller to use PAC instead.
    *
    * @param channel
--- a/netwerk/base/nsProxyInfo.cpp
+++ b/netwerk/base/nsProxyInfo.cpp
@@ -51,16 +51,28 @@ nsProxyInfo::GetUsername(nsACString& res
 
 NS_IMETHODIMP
 nsProxyInfo::GetPassword(nsACString& result) {
   result = mPassword;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsProxyInfo::GetProxyAuthorizationHeader(nsACString& result) {
+  result = mProxyAuthorizationHeader;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsProxyInfo::GetConnectionIsolationKey(nsACString& result) {
+  result = mConnectionIsolationKey;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsProxyInfo::GetFailoverTimeout(uint32_t* result) {
   *result = mTimeout;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsProxyInfo::GetFailoverProxy(nsIProxyInfo** result) {
   NS_IF_ADDREF(*result = mNext);
--- a/netwerk/base/nsProxyInfo.h
+++ b/netwerk/base/nsProxyInfo.h
@@ -27,22 +27,28 @@ namespace net {
 class nsProxyInfo final : public nsIProxyInfo {
  public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROXYINFO_IID)
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIPROXYINFO
 
   // Cheap accessors for use within Necko
-  const nsCString& Host() { return mHost; }
-  int32_t Port() { return mPort; }
-  const char* Type() { return mType; }
-  uint32_t Flags() { return mFlags; }
-  const nsCString& Username() { return mUsername; }
-  const nsCString& Password() { return mPassword; }
+  const nsCString& Host() const { return mHost; }
+  int32_t Port() const { return mPort; }
+  const char* Type() const { return mType; }
+  uint32_t Flags() const { return mFlags; }
+  const nsCString& Username() const { return mUsername; }
+  const nsCString& Password() const { return mPassword; }
+  const nsCString& ProxyAuthorizationHeader() const {
+    return mProxyAuthorizationHeader;
+  }
+  const nsCString& ConnectionIsolationKey() const {
+    return mConnectionIsolationKey;
+  }
 
   bool IsDirect();
   bool IsHTTP();
   bool IsHTTPS();
   bool IsSOCKS();
 
  private:
   friend class nsProtocolProxyService;
@@ -56,16 +62,18 @@ class nsProxyInfo final : public nsIProx
         mNext(nullptr) {}
 
   ~nsProxyInfo() { NS_IF_RELEASE(mNext); }
 
   const char* mType;  // pointer to statically allocated value
   nsCString mHost;
   nsCString mUsername;
   nsCString mPassword;
+  nsCString mProxyAuthorizationHeader;
+  nsCString mConnectionIsolationKey;
   int32_t mPort;
   uint32_t mFlags;
   uint32_t mResolveFlags;
   uint32_t mTimeout;
   nsProxyInfo* mNext;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsProxyInfo, NS_PROXYINFO_IID)
--- a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
@@ -1542,16 +1542,25 @@ void nsHttpChannelAuthProvider::SetAutho
   // set informations that depend on whether
   // we're authenticating against a proxy
   // or a webserver
   nsISupports** continuationState;
 
   nsAutoCString suffix;
   if (header == nsHttp::Proxy_Authorization) {
     continuationState = &mProxyAuthContinuationState;
+
+    if (mProxyInfo) {
+      // Let this be overriden by anything from the cache.
+      auto const& pa = mProxyInfo->ProxyAuthorizationHeader();
+      if (!pa.IsEmpty()) {
+        rv = mAuthChannel->SetProxyCredentials(pa);
+        MOZ_ASSERT(NS_SUCCEEDED(rv));
+      }
+    }
   } else {
     continuationState = &mAuthContinuationState;
 
     nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
     GetOriginAttributesSuffix(chan, suffix);
   }
 
   rv = authCache->GetAuthEntryForPath(scheme, host, port, path, suffix, &entry);
--- a/netwerk/protocol/http/nsHttpConnectionInfo.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
@@ -247,16 +247,26 @@ void nsHttpConnectionInfo::BuildHashKey(
   if (mIsolated && !mTopWindowOrigin.IsEmpty()) {
     mHashKey.Append('{');
     mHashKey.Append('{');
     mHashKey.Append(mTopWindowOrigin);
     mHashKey.Append('}');
     mHashKey.Append('}');
   }
 
+  if (mProxyInfo) {
+    const nsCString& connectionIsolationKey =
+        mProxyInfo->ConnectionIsolationKey();
+    if (!connectionIsolationKey.IsEmpty()) {
+      mHashKey.AppendLiteral("{CIK ");
+      mHashKey.Append(connectionIsolationKey);
+      mHashKey.AppendLiteral("}");
+    }
+  }
+
   nsAutoCString originAttributes;
   mOriginAttributes.CreateSuffix(originAttributes);
   mHashKey.Append(originAttributes);
 }
 
 void nsHttpConnectionInfo::SetOriginServer(const nsACString& host,
                                            int32_t port) {
   mOrigin = host;
--- a/netwerk/protocol/http/nsHttpConnectionInfo.h
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -86,16 +86,23 @@ class nsHttpConnectionInfo final : publi
   }
   const char* ProxyUsername() const {
     return mProxyInfo ? mProxyInfo->Username().get() : nullptr;
   }
   const char* ProxyPassword() const {
     return mProxyInfo ? mProxyInfo->Password().get() : nullptr;
   }
 
+  const nsCString& ProxyAuthorizationHeader() const {
+    return mProxyInfo ? mProxyInfo->ProxyAuthorizationHeader() : EmptyCString();
+  }
+  const nsCString& ConnectionIsolationKey() const {
+    return mProxyInfo ? mProxyInfo->ConnectionIsolationKey() : EmptyCString();
+  }
+
   // Compare this connection info to another...
   // Two connections are 'equal' if they end up talking the same
   // protocol to the same server. This is needed to properly manage
   // persistent connections to proxies
   // Note that we don't care about transparent proxies -
   // it doesn't matter if we're talking via socks or not, since
   // a request will end up at the same host.
   bool Equals(const nsHttpConnectionInfo* info) {
--- a/netwerk/test/httpserver/httpd.js
+++ b/netwerk/test/httpserver/httpd.js
@@ -1596,18 +1596,25 @@ RequestReader.prototype =
         throw new Error("unsupported HTTP version");
     } catch (e) {
       // we support HTTP/1.0 and HTTP/1.1 only
       throw HTTP_501;
     }
 
 
     var fullPath = request[1];
+
+    if (metadata._method == "CONNECT") {
+      metadata._path = "CONNECT";
+      metadata._scheme = "https";
+      [metadata._host, metadata._port] = fullPath.split(":");
+      return;
+    }
+
     var serverIdentity = this._connection.server.identity;
-
     var scheme, host, port;
 
     if (fullPath.charAt(0) != "/") {
       // No absolute paths in the request line in HTTP prior to 1.1
       if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1)) {
         dumpn("*** Metadata version too low");
         throw HTTP_400;
       }
@@ -2263,17 +2270,17 @@ ServerHandler.prototype =
       };
   },
 
   //
   // see nsIHttpServer.registerPathHandler
   //
   registerPathHandler(path, handler) {
     // XXX true path validation!
-    if (path.charAt(0) != "/")
+    if (path.charAt(0) != "/" && path != "CONNECT")
       throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
 
     this._handlerToField(handler, this._overridePaths, path);
   },
 
   //
   // see nsIHttpServer.registerPrefixHandler
   //
--- a/netwerk/test/unit/socks_client_subprocess.js
+++ b/netwerk/test/unit/socks_client_subprocess.js
@@ -10,17 +10,17 @@ var sts = Cc["@mozilla.org/network/socke
 
 function launchConnection(socks_vers, socks_port, dest_host, dest_port, dns)
 {
   var pi_flags = 0;
   if (dns == 'remote')
     pi_flags = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
   
   var pps = new ProtocolProxyService();
-  var pi = pps.newProxyInfo(socks_vers, 'localhost', socks_port,
+  var pi = pps.newProxyInfo(socks_vers, 'localhost', socks_port, '', '',
           pi_flags, -1, null);
   var trans = sts.createTransport(null, 0, dest_host, dest_port, pi);
   var input = trans.openInputStream(Ci.nsITransport.OPEN_BLOCKING,0,0);
   var output = trans.openOutputStream(Ci.nsITransport.OPEN_BLOCKING,0,0);
   var bin = new BinaryInputStream(input);
   var data = bin.readBytes(5);
   if (data == 'PING!') {
     print('client: got ping, sending pong.');
--- a/netwerk/test/unit/test_protocolproxyservice-async-filters.js
+++ b/netwerk/test/unit/test_protocolproxyservice-async-filters.js
@@ -102,17 +102,17 @@ TestFilter.prototype = {
 
   QueryInterface: ChromeUtils.generateQI([Ci.nsIProtocolProxyFilter]),
 
   applyFilter: function(pps, uri, pi, cb) {
     if (this._result == THROW) {
       throw Cr.NS_ERROR_FAILURE;
     }
 
-    var pi_tail = pps.newProxyInfo(this._type, this._host, this._port,
+    var pi_tail = pps.newProxyInfo(this._type, this._host, this._port, "", "",
                                    this._flags, this._timeout, null);
     if (pi)
       pi.failoverProxy = pi_tail;
     else
       pi = pi_tail;
 
     if (this._result == ASYNC) {
       executeSoon(() => { cb.onProxyFilterResult(pi) });
--- a/netwerk/test/unit/test_protocolproxyservice.js
+++ b/netwerk/test/unit/test_protocolproxyservice.js
@@ -100,44 +100,44 @@ function TestFilter(type, host, port, fl
 TestFilter.prototype = {
   _type: "",
   _host: "",
   _port: -1,
   _flags: 0,
   _timeout: 0,
   QueryInterface: ChromeUtils.generateQI([Ci.nsIProtocolProxyFilter]),
   applyFilter: function(pps, uri, pi, cb) {
-    var pi_tail = pps.newProxyInfo(this._type, this._host, this._port,
+    var pi_tail = pps.newProxyInfo(this._type, this._host, this._port, "", "",
                                    this._flags, this._timeout, null);
     if (pi)
       pi.failoverProxy = pi_tail;
     else
       pi = pi_tail;
     cb.onProxyFilterResult(pi);
   }
 };
 
 function BasicFilter() {}
 BasicFilter.prototype = {
   QueryInterface: ChromeUtils.generateQI([Ci.nsIProtocolProxyFilter]),
   applyFilter: function(pps, uri, pi, cb) {
     cb.onProxyFilterResult(
-      pps.newProxyInfo("http", "localhost", 8080, 0, 10,
-      pps.newProxyInfo("direct", "", -1, 0, 0, null))
+      pps.newProxyInfo("http", "localhost", 8080, "", "", 0, 10,
+      pps.newProxyInfo("direct", "", -1, "", "", 0, 0, null))
     );
   }
 };
 
 function BasicChannelFilter() {}
 BasicChannelFilter.prototype = {
   QueryInterface: ChromeUtils.generateQI([Ci.nsIProtocolProxyChannelFilter]),
   applyFilter: function(pps, channel, pi, cb) {
     cb.onProxyFilterResult(
-      pps.newProxyInfo("http", channel.URI.host, 7777, 0, 10,
-      pps.newProxyInfo("direct", "", -1, 0, 0, null))
+      pps.newProxyInfo("http", channel.URI.host, 7777, "", "", 0, 10,
+      pps.newProxyInfo("direct", "", -1, "", "", 0, 0, null))
     );
   }
 };
 
 function resolveCallback() { }
 resolveCallback.prototype = {
   nextFunction: null,
 
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-authorization-via-proxyinfo.js
@@ -0,0 +1,72 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This test checks that the proxy-auth header is propagated to the CONNECT request when
+// set on a proxy-info object via a proxy filter
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+
+class TestFilter {
+  constructor(type, host, port, flags, auth) {
+    this._type = type;
+    this._host = host;
+    this._port = port;
+    this._flags = flags;
+    this._auth = auth;
+    this.QueryInterface = ChromeUtils.generateQI([Ci.nsIProtocolProxyFilter]);
+  }
+
+  applyFilter(pps, uri, pi, cb) {
+    cb.onProxyFilterResult(pps.newProxyInfo(
+      this._type, this._host, this._port,
+      this._auth, "", this._flags, 1000, null));
+  }
+};
+
+let httpServer = null;
+let port;
+let connectProcessesed = false;
+const proxyAuthHeader = 'proxy-auth-header-value';
+
+function make_channel(url) {
+  return NetUtil.newChannel({
+    uri: url,
+    loadUsingSystemPrincipal: true
+  });
+}
+
+function connect_handler(request, response)
+{
+  Assert.equal(request.method, "CONNECT");
+  Assert.ok(request.hasHeader("Proxy-Authorization"));
+  Assert.equal(request.getHeader("Proxy-Authorization"), proxyAuthHeader);
+
+  // Just refuse to connect, we have what we need now.
+  response.setStatusLine(request.httpVersion, 500, "STOP");
+  connectProcessesed = true;
+}
+
+function finish_test()
+{
+  Assert.ok(connectProcessesed);
+  httpServer.stop(do_test_finished);
+}
+
+function run_test()
+{
+  httpServer = new HttpServer();
+  httpServer.identity.add("https", "mozilla.org", 443);
+  httpServer.registerPathHandler("CONNECT", connect_handler);
+  httpServer.start(-1);
+  port = httpServer.identity.primaryPort;
+
+  pps.registerFilter(new TestFilter("http", "localhost", port, 0, proxyAuthHeader), 10);
+
+  let chan = make_channel("https://mozilla.org/");
+  chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+  do_test_pending();
+}
--- a/netwerk/test/unit/test_speculative_connect.js
+++ b/netwerk/test/unit/test_speculative_connect.js
@@ -240,17 +240,17 @@ function test_proxies(proxyHost, next) {
     info("Proxy: " + proxyHost);
     var sts = Cc["@mozilla.org/network/socket-transport-service;1"]
               .getService(Ci.nsISocketTransportService);
     Assert.notEqual(typeof(sts), undefined);
     var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"]
               .getService();
     Assert.notEqual(typeof(pps), undefined);
 
-    var proxyInfo = pps.newProxyInfo("http", proxyHost, 8080, 0, 1, null);
+    var proxyInfo = pps.newProxyInfo("http", proxyHost, 8080, "", "", 0, 1, null);
     Assert.notEqual(typeof(proxyInfo), undefined);
 
     var transport = sts.createTransport(null, 0, "dummyHost", 80, proxyInfo);
     Assert.notEqual(typeof(transport), undefined);
 
     transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918;
 
     transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1);
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -446,8 +446,9 @@ skip-if = os == "android"
 [test_network_connectivity_service.js]
 skip-if = os == "android" # DNSv6 issues on android
 [test_suspend_channel_on_authRetry.js]
 [test_suspend_channel_on_examine_merged_response.js]
 [test_bug1527293.js]
 [test_stale-while-revalidate_negative.js]
 [test_stale-while-revalidate_positive.js]
 [test_stale-while-revalidate_max-age-0.js]
+[test_proxy-authorization-via-proxyinfo.js]
--- a/services/sync/tests/unit/test_addons_store.js
+++ b/services/sync/tests/unit/test_addons_store.js
@@ -131,17 +131,17 @@ let engine;
 let tracker;
 let store;
 let reconciler;
 
 const proxyService = Cc["@mozilla.org/network/protocol-proxy-service;1"]
   .getService(Ci.nsIProtocolProxyService);
 
 const proxyFilter = {
-  proxyInfo: proxyService.newProxyInfo("http", "localhost", HTTP_PORT, 0, 4096, null),
+  proxyInfo: proxyService.newProxyInfo("http", "localhost", HTTP_PORT, "", "", 0, 4096, null),
 
   applyFilter(service, channel, defaultProxyInfo, callback) {
     if (channel.URI.host === "example.com") {
       callback.onProxyFilterResult(this.proxyInfo);
     } else {
       callback.onProxyFilterResult(defaultProxyInfo);
     }
   },
--- a/toolkit/components/extensions/ProxyScriptContext.jsm
+++ b/toolkit/components/extensions/ProxyScriptContext.jsm
@@ -60,17 +60,17 @@ const PROXY_TYPES = Object.freeze({
   SOCKS4: "socks4",
 });
 
 const ProxyInfoData = {
   validate(proxyData) {
     if (proxyData.type && proxyData.type.toLowerCase() === "direct") {
       return {type: proxyData.type};
     }
-    for (let prop of ["type", "host", "port", "username", "password", "proxyDNS", "failoverTimeout"]) {
+    for (let prop of ["type", "host", "port", "username", "password", "proxyDNS", "failoverTimeout", "proxyAuthorizationHeader", "connectionIsolationKey"]) {
       this[prop](proxyData);
     }
     return proxyData;
   },
 
   type(proxyData) {
     let {type} = proxyData;
     if (typeof type !== "string" || !PROXY_TYPES.hasOwnProperty(type.toUpperCase())) {
@@ -130,40 +130,54 @@ const ProxyInfoData = {
 
   failoverTimeout(proxyData) {
     let {failoverTimeout} = proxyData;
     if (failoverTimeout !== undefined && (!Number.isInteger(failoverTimeout) || failoverTimeout < 1)) {
       throw new ExtensionError(`ProxyInfoData: Invalid failover timeout: "${failoverTimeout}"`);
     }
   },
 
+  proxyAuthorizationHeader(proxyData) {
+    let {proxyauthorizationheader} = proxyData;
+    if (proxyauthorizationheader !== undefined && typeof username !== "string") {
+      throw new ExtensionError(`ProxyInfoData: Invalid proxy server authorization header: "${proxyauthorizationheader}"`);
+    }
+  },
+
+  connectionIsolationKey(proxyData) {
+    let {connectionisolationkey} = proxyData;
+    if (connectionisolationkey !== undefined && typeof username !== "string") {
+      throw new ExtensionError(`ProxyInfoData: Invalid proxy server authorization header: "${connectionisolationkey}"`);
+    }
+  },
+
   createProxyInfoFromData(proxyDataList, defaultProxyInfo, proxyDataListIndex = 0) {
     if (proxyDataListIndex >= proxyDataList.length) {
       return defaultProxyInfo;
     }
     let proxyData = proxyDataList[proxyDataListIndex];
     if (proxyData == null) {
       return null;
     }
-    let {type, host, port, username, password, proxyDNS, failoverTimeout} =
+    let {type, host, port, username, password, proxyDNS, failoverTimeout, proxyAuthorizationHeader, connectionIsolationKey} =
         ProxyInfoData.validate(proxyData);
     if (type === PROXY_TYPES.DIRECT) {
       return defaultProxyInfo;
     }
     let failoverProxy = this.createProxyInfoFromData(proxyDataList, defaultProxyInfo, proxyDataListIndex + 1);
 
     if (type === PROXY_TYPES.SOCKS || type === PROXY_TYPES.SOCKS4) {
       return ProxyService.newProxyInfoWithAuth(
-        type, host, port, username, password,
-        proxyDNS ? TRANSPARENT_PROXY_RESOLVES_HOST : 0,
-        failoverTimeout ? failoverTimeout : PROXY_TIMEOUT_SEC,
-        failoverProxy);
+        type, host, port, username, password, proxyAuthorizationHeader,
+        connectionIsolationKey, proxyDNS ? TRANSPARENT_PROXY_RESOLVES_HOST : 0,
+        failoverTimeout ? failoverTimeout : PROXY_TIMEOUT_SEC, failoverProxy);
     }
     return ProxyService.newProxyInfo(
       type, host, port,
+      proxyAuthorizationHeader, connectionIsolationKey,
       proxyDNS ? TRANSPARENT_PROXY_RESOLVES_HOST : 0,
       failoverTimeout ? failoverTimeout : PROXY_TIMEOUT_SEC,
       failoverProxy);
   },
 
   /**
    * Creates a new proxy info data object using the return value of FindProxyForURL.
    *
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_authorization_via_proxyinfo.js
@@ -0,0 +1,79 @@
+"use strict";
+
+XPCOMUtils.defineLazyServiceGetter(this, "authManager",
+                                   "@mozilla.org/network/http-auth-manager;1",
+                                   "nsIHttpAuthManager");
+
+PromiseTestUtils.whitelistRejectionsGlobally(/Message manager disconnected/);
+
+const proxy = createHttpServer();
+const proxyToken = "this_is_my_pass";
+
+// accept proxy connections for mozilla.org
+proxy.identity.add("http", "mozilla.org", 80);
+
+proxy.registerPathHandler("/", (request, response) => {
+  if (request.hasHeader("Proxy-Authorization")) {
+    response.setStatusLine(request.httpVersion, 200, "OK");
+    response.setHeader("Content-Type", "text/plain", false);
+    response.write(request.getHeader("Proxy-Authorization"));
+  } else {
+    response.setStatusLine(request.httpVersion, 407, "Proxy authentication required");
+    response.setHeader("Content-Type", "text/plain", false);
+    response.setHeader("Proxy-Authenticate", "UnknownMeantToFail", false);
+    response.write("auth required");
+  }
+});
+
+function getExtension(background) {
+  return ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: [
+        "proxy",
+        "webRequest",
+        "webRequestBlocking",
+        "<all_urls>",
+      ],
+    },
+    background: `(${background})(${proxy.identity.primaryPort}, "${proxyToken}")`,
+  });
+}
+
+add_task(async function test_webRequest_auth_proxy() {
+  function background(port, proxyToken) {
+    browser.webRequest.onCompleted.addListener(details => {
+      browser.test.log(`onCompleted ${JSON.stringify(details)}\n`);
+      browser.test.assertEq("localhost", details.proxyInfo.host, "proxy host");
+      browser.test.assertEq(port, details.proxyInfo.port, "proxy port");
+      browser.test.assertEq("http", details.proxyInfo.type, "proxy type");
+      browser.test.assertEq("", details.proxyInfo.username, "proxy username not set");
+      browser.test.assertEq(proxyToken, details.proxyInfo.proxyAuthorizationHeader, "proxy authorization header");
+      browser.test.assertEq(proxyToken, details.proxyInfo.connectionIsolationKey, "proxy connection isolation");
+
+      browser.test.notifyPass("requestCompleted");
+    }, {urls: ["<all_urls>"]});
+
+    browser.webRequest.onAuthRequired.addListener(details => {
+      // Using proxyAuthorizationHeader should prevent an auth request coming to us in the extension.
+      browser.test.fail("onAuthRequired");
+    }, {urls: ["<all_urls>"]}, ["blocking"]);
+
+    // Handle the proxy request.
+    browser.proxy.onRequest.addListener(details => {
+      browser.test.log(`onRequest ${JSON.stringify(details)}`);
+      return [{host: "localhost", port, type: "http", proxyAuthorizationHeader: proxyToken, connectionIsolationKey: proxyToken}];
+    }, {urls: ["<all_urls>"]}, ["requestHeaders"]);
+  }
+
+  let extension = getExtension(background);
+
+  await extension.startup();
+
+  authManager.clearAll();
+
+  let contentPage = await ExtensionTestUtils.loadContentPage(`http://mozilla.org/`);
+
+  await extension.awaitFinish("requestCompleted");
+  await contentPage.close();
+  await extension.unload();
+});
--- a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_socks.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_socks.js
@@ -363,17 +363,17 @@ class SocksTestClient {
               .getService(Ci.nsISocketTransportService);
 
     let pi_flags = 0;
     if (socks.dns == "remote") {
       pi_flags = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
     }
 
     let pi = pps.newProxyInfoWithAuth(socks.version, socks.host, socks.port,
-                                      socks.username, socks.password,
+                                      socks.username, socks.password, "", "",
                                       pi_flags, -1, null);
 
     this.trans = sts.createTransport(null, 0, dest.host, dest.port, pi);
     this.input = this.trans.openInputStream(Ci.nsITransport.OPEN_BLOCKING, 0, 0);
     this.output = this.trans.openOutputStream(Ci.nsITransport.OPEN_BLOCKING, 0, 0);
     this.outbuf = String();
     this.resolve = resolve;
     this.reject = reject;
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -78,16 +78,17 @@ skip-if = true # This test no longer tes
 [test_ext_permission_xhr.js]
 [test_ext_persistent_events.js]
 [test_ext_privacy.js]
 skip-if = appname == "thunderbird" || (os == "android" && debug)
 [test_ext_privacy_disable.js]
 skip-if = appname == "thunderbird"
 [test_ext_privacy_update.js]
 [test_ext_proxy_auth.js]
+[test_ext_proxy_authorization_via_proxyinfo.js]
 [test_ext_proxy_config.js]
 skip-if = appname == "thunderbird"
 [test_ext_proxy_onauthrequired.js]
 [test_ext_proxy_settings.js]
 skip-if = appname == "thunderbird" || os == "android" # proxy settings are not supported on android
 [test_ext_proxy_socks.js]
 [test_ext_proxy_speculative.js]
 [test_ext_proxy_startup.js]
--- a/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
+++ b/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
@@ -819,16 +819,19 @@ void ChannelWrapper::GetFinalURL(nsStrin
  * ...
  *****************************************************************************/
 
 nsresult FillProxyInfo(MozProxyInfo& aDict, nsIProxyInfo* aProxyInfo) {
   MOZ_TRY(aProxyInfo->GetHost(aDict.mHost));
   MOZ_TRY(aProxyInfo->GetPort(&aDict.mPort));
   MOZ_TRY(aProxyInfo->GetType(aDict.mType));
   MOZ_TRY(aProxyInfo->GetUsername(aDict.mUsername));
+  MOZ_TRY(
+      aProxyInfo->GetProxyAuthorizationHeader(aDict.mProxyAuthorizationHeader));
+  MOZ_TRY(aProxyInfo->GetConnectionIsolationKey(aDict.mConnectionIsolationKey));
   MOZ_TRY(aProxyInfo->GetFailoverTimeout(&aDict.mFailoverTimeout.Construct()));
 
   uint32_t flags;
   MOZ_TRY(aProxyInfo->GetFlags(&flags));
   aDict.mProxyDNS = flags & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;
 
   return NS_OK;
 }
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -484,17 +484,17 @@ var AddonTestUtils = {
       const serverHost = "localhost";
       const serverPort = server.identity.primaryPort;
 
       for (let host of hosts) {
         server.identity.add("http", host, 80);
       }
 
       const proxyFilter = {
-        proxyInfo: proxyService.newProxyInfo("http", serverHost, serverPort, 0, 4096, null),
+        proxyInfo: proxyService.newProxyInfo("http", serverHost, serverPort, "", "", 0, 4096, null),
 
         applyFilter(service, channel, defaultProxyInfo, callback) {
           if (hosts.has(channel.URI.host)) {
             callback.onProxyFilterResult(this.proxyInfo);
           } else {
             callback.onProxyFilterResult(defaultProxyInfo);
           }
         },