tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsHttpChannelAuthProvider.cpp (56341B)


      1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set expandtab ts=4 sw=2 sts=2 cin: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 // HttpLog.h should generally be included first
      8 #include "HttpLog.h"
      9 
     10 #include "mozilla/BasePrincipal.h"
     11 #include "mozilla/Components.h"
     12 #include "mozilla/StoragePrincipalHelper.h"
     13 #include "mozilla/Tokenizer.h"
     14 #include "MockHttpAuth.h"
     15 #include "nsHttpChannelAuthProvider.h"
     16 #include "nsCRT.h"
     17 #include "nsNetUtil.h"
     18 #include "nsHttpHandler.h"
     19 #include "nsIHttpAuthenticator.h"
     20 #include "nsIHttpChannelInternal.h"
     21 #include "nsIAuthPrompt2.h"
     22 #include "nsIAuthPromptProvider.h"
     23 #include "nsIInterfaceRequestor.h"
     24 #include "nsIInterfaceRequestorUtils.h"
     25 #include "nsEscape.h"
     26 #include "nsAuthInformationHolder.h"
     27 #include "nsIStringBundle.h"
     28 #include "nsIPromptService.h"
     29 #include "netCore.h"
     30 #include "nsIHttpAuthenticableChannel.h"
     31 #include "nsIURI.h"
     32 #include "nsContentUtils.h"
     33 #include "nsHttp.h"
     34 #include "nsHttpBasicAuth.h"
     35 #include "nsHttpDigestAuth.h"
     36 #ifdef MOZ_AUTH_EXTENSION
     37 #  include "nsHttpNegotiateAuth.h"
     38 #endif
     39 #include "nsHttpNTLMAuth.h"
     40 #include "nsServiceManagerUtils.h"
     41 #include "nsIURL.h"
     42 #include "mozilla/StaticPrefs_network.h"
     43 #include "mozilla/StaticPrefs_prompts.h"
     44 #include "nsIProxiedChannel.h"
     45 #include "nsIProxyInfo.h"
     46 
     47 namespace mozilla::net {
     48 
     49 #define SUBRESOURCE_AUTH_DIALOG_DISALLOW_ALL 0
     50 #define SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN 1
     51 #define SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL 2
     52 
     53 #define MAX_DISPLAYED_USER_LENGTH 64
     54 #define MAX_DISPLAYED_HOST_LENGTH 64
     55 
     56 static void GetOriginAttributesSuffix(nsIChannel* aChan, nsACString& aSuffix) {
     57  OriginAttributes oa;
     58 
     59  // Deliberately ignoring the result and going with defaults
     60  if (aChan) {
     61    StoragePrincipalHelper::GetOriginAttributesForNetworkState(aChan, oa);
     62  }
     63 
     64  oa.CreateSuffix(aSuffix);
     65 }
     66 
     67 nsHttpChannelAuthProvider::nsHttpChannelAuthProvider()
     68    : mProxyAuth(false),
     69      mTriedProxyAuth(false),
     70      mTriedHostAuth(false),
     71      mCrossOrigin(false),
     72      mConnectionBased(false),
     73      mHttpHandler(gHttpHandler) {}
     74 
     75 nsHttpChannelAuthProvider::~nsHttpChannelAuthProvider() {
     76  MOZ_RELEASE_ASSERT(NS_IsMainThread());
     77  MOZ_ASSERT(!mAuthChannel, "Disconnect wasn't called");
     78 }
     79 
     80 NS_IMETHODIMP
     81 nsHttpChannelAuthProvider::Init(nsIHttpAuthenticableChannel* channel) {
     82  MOZ_ASSERT(channel, "channel expected!");
     83 
     84  mAuthChannel = channel;
     85 
     86  nsresult rv = mAuthChannel->GetURI(getter_AddRefs(mURI));
     87  if (NS_FAILED(rv)) return rv;
     88 
     89  rv = mAuthChannel->GetIsSSL(&mUsingSSL);
     90  if (NS_FAILED(rv)) return rv;
     91 
     92  nsCOMPtr<nsIProxiedChannel> proxied(channel);
     93  if (proxied) {
     94    nsCOMPtr<nsIProxyInfo> pi;
     95    rv = proxied->GetProxyInfo(getter_AddRefs(pi));
     96    if (NS_FAILED(rv)) return rv;
     97 
     98    if (pi) {
     99      nsAutoCString proxyType;
    100      rv = pi->GetType(proxyType);
    101      if (NS_FAILED(rv)) return rv;
    102 
    103      mProxyUsingSSL = proxyType.EqualsLiteral("https");
    104    }
    105  }
    106 
    107  rv = mURI->GetAsciiHost(mHost);
    108  if (NS_FAILED(rv)) return rv;
    109 
    110  // reject the URL if it doesn't specify a host
    111  if (mHost.IsEmpty()) return NS_ERROR_MALFORMED_URI;
    112 
    113  rv = mURI->GetPort(&mPort);
    114  if (NS_FAILED(rv)) return rv;
    115 
    116  nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(channel);
    117  mIsPrivate = NS_UsePrivateBrowsing(bareChannel);
    118 
    119  return NS_OK;
    120 }
    121 
    122 NS_IMETHODIMP
    123 nsHttpChannelAuthProvider::ProcessAuthentication(uint32_t httpStatus,
    124                                                 bool SSLConnectFailed) {
    125  LOG(
    126      ("nsHttpChannelAuthProvider::ProcessAuthentication "
    127       "[this=%p channel=%p code=%u SSLConnectFailed=%d]\n",
    128       this, mAuthChannel, httpStatus, SSLConnectFailed));
    129 
    130  MOZ_ASSERT(mAuthChannel, "Channel not initialized");
    131 
    132  nsCOMPtr<nsIProxyInfo> proxyInfo;
    133  nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
    134  if (NS_FAILED(rv)) return rv;
    135  if (proxyInfo) {
    136    mProxyInfo = do_QueryInterface(proxyInfo);
    137    if (!mProxyInfo) return NS_ERROR_NO_INTERFACE;
    138  }
    139 
    140  nsAutoCString challenges;
    141  mProxyAuth = (httpStatus == 407);
    142 
    143  rv = PrepareForAuthentication(mProxyAuth);
    144  if (NS_FAILED(rv)) return rv;
    145 
    146  if (mProxyAuth) {
    147    // only allow a proxy challenge if we have a proxy server configured.
    148    // otherwise, we could inadvertently expose the user's proxy
    149    // credentials to an origin server.  We could attempt to proceed as
    150    // if we had received a 401 from the server, but why risk flirting
    151    // with trouble?  IE similarly rejects 407s when a proxy server is
    152    // not configured, so there's no reason not to do the same.
    153    if (!UsingHttpProxy()) {
    154      LOG(("rejecting 407 when proxy server not configured!\n"));
    155      return NS_ERROR_UNEXPECTED;
    156    }
    157    if (UsingSSL() && !SSLConnectFailed) {
    158      // we need to verify that this challenge came from the proxy
    159      // server itself, and not some server on the other side of the
    160      // SSL tunnel.
    161      LOG(("rejecting 407 from origin server!\n"));
    162      return NS_ERROR_UNEXPECTED;
    163    }
    164    rv = mAuthChannel->GetProxyChallenges(challenges);
    165  } else {
    166    rv = mAuthChannel->GetWWWChallenges(challenges);
    167  }
    168  if (NS_FAILED(rv)) return rv;
    169 
    170  nsAutoCString creds;
    171  rv = GetCredentials(challenges, mProxyAuth, creds);
    172  if (rv == NS_ERROR_IN_PROGRESS || rv == NS_ERROR_BASIC_HTTP_AUTH_DISABLED) {
    173    return rv;
    174  }
    175 
    176  if (NS_FAILED(rv)) {
    177    LOG(("unable to authenticate\n"));
    178  } else {
    179    // set the authentication credentials
    180    if (mProxyAuth) {
    181      rv = mAuthChannel->SetProxyCredentials(creds);
    182    } else {
    183      rv = mAuthChannel->SetWWWCredentials(creds);
    184    }
    185  }
    186  return rv;
    187 }
    188 
    189 NS_IMETHODIMP
    190 nsHttpChannelAuthProvider::AddAuthorizationHeaders(
    191    bool aDontUseCachedWWWCreds) {
    192  LOG(
    193      ("nsHttpChannelAuthProvider::AddAuthorizationHeaders? "
    194       "[this=%p channel=%p]\n",
    195       this, mAuthChannel));
    196 
    197  MOZ_ASSERT(mAuthChannel, "Channel not initialized");
    198 
    199  nsCOMPtr<nsIProxyInfo> proxyInfo;
    200  nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
    201  if (NS_FAILED(rv)) return rv;
    202  if (proxyInfo) {
    203    mProxyInfo = do_QueryInterface(proxyInfo);
    204    if (!mProxyInfo) return NS_ERROR_NO_INTERFACE;
    205  }
    206 
    207  uint32_t loadFlags;
    208  rv = mAuthChannel->GetLoadFlags(&loadFlags);
    209  if (NS_FAILED(rv)) return rv;
    210 
    211  // this getter never fails
    212  nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
    213 
    214  // check if proxy credentials should be sent
    215  if (!ProxyHost().IsEmpty() && UsingHttpProxy()) {
    216    SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization, "http"_ns,
    217                           ProxyHost(), ProxyPort(),
    218                           ""_ns,  // proxy has no path
    219                           mProxyIdent);
    220  }
    221 
    222  if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
    223    LOG(("Skipping Authorization header for anonymous load\n"));
    224    return NS_OK;
    225  }
    226 
    227  if (aDontUseCachedWWWCreds) {
    228    LOG(
    229        ("Authorization header already present:"
    230         " skipping adding auth header from cache\n"));
    231    return NS_OK;
    232  }
    233 
    234  // check if server credentials should be sent
    235  nsAutoCString path, scheme;
    236  if (NS_SUCCEEDED(GetCurrentPath(path)) &&
    237      NS_SUCCEEDED(mURI->GetScheme(scheme))) {
    238    SetAuthorizationHeader(authCache, nsHttp::Authorization, scheme, Host(),
    239                           Port(), path, mIdent);
    240  }
    241 
    242  return NS_OK;
    243 }
    244 
    245 NS_IMETHODIMP
    246 nsHttpChannelAuthProvider::Cancel(nsresult status) {
    247  MOZ_ASSERT(mAuthChannel, "Channel not initialized");
    248 
    249  if (mAsyncPromptAuthCancelable) {
    250    mAsyncPromptAuthCancelable->Cancel(status);
    251    mAsyncPromptAuthCancelable = nullptr;
    252  }
    253 
    254  if (mGenerateCredentialsCancelable) {
    255    mGenerateCredentialsCancelable->Cancel(status);
    256    mGenerateCredentialsCancelable = nullptr;
    257  }
    258  return NS_OK;
    259 }
    260 
    261 NS_IMETHODIMP
    262 nsHttpChannelAuthProvider::Disconnect(nsresult status) {
    263  mAuthChannel = nullptr;
    264 
    265  if (mAsyncPromptAuthCancelable) {
    266    mAsyncPromptAuthCancelable->Cancel(status);
    267    mAsyncPromptAuthCancelable = nullptr;
    268  }
    269 
    270  if (mGenerateCredentialsCancelable) {
    271    mGenerateCredentialsCancelable->Cancel(status);
    272    mGenerateCredentialsCancelable = nullptr;
    273  }
    274 
    275  NS_IF_RELEASE(mProxyAuthContinuationState);
    276  NS_IF_RELEASE(mAuthContinuationState);
    277 
    278  return NS_OK;
    279 }
    280 
    281 // helper function for getting an auth prompt from an interface requestor
    282 static void GetAuthPrompt(nsIInterfaceRequestor* ifreq, bool proxyAuth,
    283                          nsIAuthPrompt2** result) {
    284  if (!ifreq) return;
    285 
    286  uint32_t promptReason;
    287  if (proxyAuth) {
    288    promptReason = nsIAuthPromptProvider::PROMPT_PROXY;
    289  } else {
    290    promptReason = nsIAuthPromptProvider::PROMPT_NORMAL;
    291  }
    292 
    293  nsCOMPtr<nsIAuthPromptProvider> promptProvider = do_GetInterface(ifreq);
    294  if (promptProvider) {
    295    promptProvider->GetAuthPrompt(promptReason, NS_GET_IID(nsIAuthPrompt2),
    296                                  reinterpret_cast<void**>(result));
    297  } else {
    298    NS_QueryAuthPrompt2(ifreq, result);
    299  }
    300 }
    301 
    302 // generate credentials for the given challenge, and update the auth cache.
    303 nsresult nsHttpChannelAuthProvider::GenCredsAndSetEntry(
    304    nsIHttpAuthenticator* auth, bool proxyAuth, const nsACString& scheme,
    305    const nsACString& host, int32_t port, const nsACString& directory,
    306    const nsACString& realm, const nsACString& challenge,
    307    const nsHttpAuthIdentity& ident, nsCOMPtr<nsISupports>& sessionState,
    308    nsACString& result) {
    309  nsresult rv;
    310  nsISupports* ss = sessionState;
    311 
    312  // set informations that depend on whether
    313  // we're authenticating against a proxy
    314  // or a webserver
    315  nsISupports** continuationState;
    316 
    317  if (proxyAuth) {
    318    continuationState = &mProxyAuthContinuationState;
    319  } else {
    320    continuationState = &mAuthContinuationState;
    321  }
    322 
    323  rv = auth->GenerateCredentialsAsync(
    324      mAuthChannel, this, challenge, proxyAuth, ident.Domain(), ident.User(),
    325      ident.Password(), ss, *continuationState,
    326      getter_AddRefs(mGenerateCredentialsCancelable));
    327  if (NS_SUCCEEDED(rv)) {
    328    // Calling generate credentials async, results will be dispatched to the
    329    // main thread by calling OnCredsGenerated method
    330    return NS_ERROR_IN_PROGRESS;
    331  }
    332 
    333  uint32_t generateFlags;
    334  rv = auth->GenerateCredentials(
    335      mAuthChannel, challenge, proxyAuth, ident.Domain(), ident.User(),
    336      ident.Password(), &ss, &*continuationState, &generateFlags, result);
    337 
    338  sessionState.swap(ss);
    339  if (NS_FAILED(rv)) return rv;
    340 
    341  // don't log this in release build since it could contain sensitive info.
    342 #ifdef DEBUG
    343  LOG(("generated creds: %s\n", result.BeginReading()));
    344 #endif
    345 
    346  return UpdateCache(auth, scheme, host, port, directory, realm, challenge,
    347                     ident, result, generateFlags, sessionState, proxyAuth);
    348 }
    349 
    350 nsresult nsHttpChannelAuthProvider::UpdateCache(
    351    nsIHttpAuthenticator* auth, const nsACString& scheme,
    352    const nsACString& host, int32_t port, const nsACString& directory,
    353    const nsACString& realm, const nsACString& challenge,
    354    const nsHttpAuthIdentity& ident, const nsACString& creds,
    355    uint32_t generateFlags, nsISupports* sessionState, bool aProxyAuth) {
    356  nsresult rv;
    357 
    358  uint32_t authFlags;
    359  rv = auth->GetAuthFlags(&authFlags);
    360  if (NS_FAILED(rv)) return rv;
    361 
    362  // find out if this authenticator allows reuse of credentials and/or
    363  // challenge.
    364  bool saveCreds =
    365      0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CREDENTIALS);
    366  bool saveChallenge =
    367      0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CHALLENGE);
    368 
    369  bool saveIdentity =
    370      0 == (generateFlags & nsIHttpAuthenticator::USING_INTERNAL_IDENTITY);
    371 
    372  // this getter never fails
    373  nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
    374 
    375  nsAutoCString suffix;
    376  if (!aProxyAuth) {
    377    // We don't isolate proxy credentials cache entries with the origin suffix
    378    // as it would only annoy users with authentication dialogs popping up.
    379    nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
    380    GetOriginAttributesSuffix(chan, suffix);
    381  }
    382 
    383  // create a cache entry.  we do this even though we don't yet know that
    384  // these credentials are valid b/c we need to avoid prompting the user
    385  // more than once in case the credentials are valid.
    386  //
    387  // if the credentials are not reusable, then we don't bother sticking
    388  // them in the auth cache.
    389  rv = authCache->SetAuthEntry(scheme, host, port, directory, realm,
    390                               saveCreds ? creds : ""_ns,
    391                               saveChallenge ? challenge : ""_ns, suffix,
    392                               saveIdentity ? &ident : nullptr, sessionState);
    393  return rv;
    394 }
    395 
    396 NS_IMETHODIMP nsHttpChannelAuthProvider::ClearProxyIdent() {
    397  LOG(("nsHttpChannelAuthProvider::ClearProxyIdent [this=%p]\n", this));
    398 
    399  mProxyIdent.Clear();
    400  return NS_OK;
    401 }
    402 
    403 nsresult nsHttpChannelAuthProvider::PrepareForAuthentication(bool proxyAuth) {
    404  LOG(
    405      ("nsHttpChannelAuthProvider::PrepareForAuthentication "
    406       "[this=%p channel=%p]\n",
    407       this, mAuthChannel));
    408 
    409  if (!proxyAuth) {
    410    // reset the current proxy continuation state because our last
    411    // authentication attempt was completed successfully.
    412    NS_IF_RELEASE(mProxyAuthContinuationState);
    413    LOG(("  proxy continuation state has been reset"));
    414  }
    415 
    416  if (!UsingHttpProxy() || mProxyAuthType.IsEmpty()) return NS_OK;
    417 
    418  // We need to remove any Proxy_Authorization header left over from a
    419  // non-request based authentication handshake (e.g., for NTLM auth).
    420 
    421  nsresult rv;
    422  nsCOMPtr<nsIHttpAuthenticator> precedingAuth;
    423  nsCString proxyAuthType;
    424  rv = GetAuthenticator(mProxyAuthType, proxyAuthType,
    425                        getter_AddRefs(precedingAuth));
    426  if (NS_FAILED(rv)) return rv;
    427 
    428  uint32_t precedingAuthFlags;
    429  rv = precedingAuth->GetAuthFlags(&precedingAuthFlags);
    430  if (NS_FAILED(rv)) return rv;
    431 
    432  if (!(precedingAuthFlags & nsIHttpAuthenticator::REQUEST_BASED)) {
    433    nsAutoCString challenges;
    434    rv = mAuthChannel->GetProxyChallenges(challenges);
    435    if (NS_FAILED(rv)) {
    436      // delete the proxy authorization header because we weren't
    437      // asked to authenticate
    438      rv = mAuthChannel->SetProxyCredentials(""_ns);
    439      if (NS_FAILED(rv)) return rv;
    440      LOG(("  cleared proxy authorization header"));
    441    }
    442  }
    443 
    444  return NS_OK;
    445 }
    446 
    447 class MOZ_STACK_CLASS ChallengeParser final : Tokenizer {
    448 public:
    449  explicit ChallengeParser(const nsACString& aChallenges)
    450      : Tokenizer(aChallenges, nullptr, "") {
    451    Record();
    452  }
    453 
    454  Maybe<nsDependentCSubstring> GetNext() {
    455    Token t;
    456    nsDependentCSubstring result;
    457 
    458    bool inQuote = false;
    459 
    460    while (Next(t)) {
    461      if (t.Type() == TOKEN_EOL) {
    462        Claim(result, ClaimInclusion::EXCLUDE_LAST);
    463        SkipWhites(WhiteSkipping::INCLUDE_NEW_LINE);
    464        Record();
    465        inQuote = false;
    466        if (!result.IsEmpty()) {
    467          return Some(result);
    468        }
    469      } else if (t.Equals(Token::Char(',')) && !inQuote) {
    470        // Sometimes we get multiple challenges separated by a comma.
    471        // This is not great, as it's slightly ambiguous. We check if something
    472        // is a new challenge by matching agains <param_name> =
    473        // If the , isn't followed by a word and = then most likely
    474        // it is the name of an authType.
    475 
    476        const char* prevCursorPos = mCursor;
    477        const char* prevRollbackPos = mRollback;
    478 
    479        auto hasWordAndEqual = [&]() {
    480          SkipWhites();
    481          nsDependentCSubstring word;
    482          if (!ReadWord(word)) {
    483            return false;
    484          }
    485          SkipWhites();
    486          return Check(Token::Char('='));
    487        };
    488        if (!hasWordAndEqual()) {
    489          // This is not a parameter. It means the `,` character starts a
    490          // different challenge.
    491          // We'll revert the cursor and return the contents so far.
    492          mCursor = prevCursorPos;
    493          mRollback = prevRollbackPos;
    494          Claim(result, ClaimInclusion::EXCLUDE_LAST);
    495          SkipWhites();
    496          Record();
    497          if (!result.IsEmpty()) {
    498            return Some(result);
    499          }
    500        }
    501      } else if (t.Equals(Token::Char('"'))) {
    502        inQuote = !inQuote;
    503      }
    504    }
    505 
    506    Claim(result, Tokenizer::ClaimInclusion::INCLUDE_LAST);
    507    SkipWhites();
    508    Record();
    509    if (!result.IsEmpty()) {
    510      return Some(result);
    511    }
    512    return Nothing{};
    513  }
    514 };
    515 
    516 enum ChallengeRank {
    517  Unknown = 0,
    518  Basic = 1,
    519  Digest = 2,
    520  NTLM = 3,
    521  Negotiate = 4,
    522 };
    523 
    524 ChallengeRank Rank(const nsACString& aChallenge) {
    525  if (StringBeginsWith(aChallenge, "Negotiate"_ns,
    526                       nsCaseInsensitiveCStringComparator)) {
    527    return ChallengeRank::Negotiate;
    528  }
    529 
    530  if (StringBeginsWith(aChallenge, "NTLM"_ns,
    531                       nsCaseInsensitiveCStringComparator)) {
    532    return ChallengeRank::NTLM;
    533  }
    534 
    535  if (StringBeginsWith(aChallenge, "Digest"_ns,
    536                       nsCaseInsensitiveCStringComparator)) {
    537    return ChallengeRank::Digest;
    538  }
    539 
    540  if (StringBeginsWith(aChallenge, "Basic"_ns,
    541                       nsCaseInsensitiveCStringComparator)) {
    542    return ChallengeRank::Basic;
    543  }
    544 
    545  return ChallengeRank::Unknown;
    546 }
    547 
    548 nsresult nsHttpChannelAuthProvider::GetCredentials(
    549    const nsACString& aChallenges, bool proxyAuth, nsCString& creds) {
    550  LOG(("nsHttpChannelAuthProvider::GetCredentials"));
    551  nsAutoCString challenges(aChallenges);
    552 
    553  using AuthChallenge = struct AuthChallenge {
    554    nsDependentCSubstring challenge;
    555    uint16_t algorithm = 0;
    556    ChallengeRank rank = ChallengeRank::Unknown;
    557 
    558    void operator=(const AuthChallenge& aOther) {
    559      challenge.Rebind(aOther.challenge, 0);
    560      algorithm = aOther.algorithm;
    561      rank = aOther.rank;
    562    }
    563  };
    564 
    565  nsTArray<AuthChallenge> cc;
    566 
    567  ChallengeParser p(challenges);
    568  while (true) {
    569    auto next = p.GetNext();
    570    if (next.isNothing()) {
    571      break;
    572    }
    573    AuthChallenge ac{next.ref(), 0};
    574    nsAutoCString realm, domain, nonce, opaque;
    575    bool stale = false;
    576    uint16_t qop = 0;
    577    ac.rank = Rank(ac.challenge);
    578    if (StringBeginsWith(ac.challenge, "Digest"_ns,
    579                         nsCaseInsensitiveCStringComparator)) {
    580      (void)nsHttpDigestAuth::ParseChallenge(ac.challenge, realm, domain, nonce,
    581                                             opaque, &stale, &ac.algorithm,
    582                                             &qop);
    583    }
    584    cc.AppendElement(ac);
    585  }
    586 
    587  // Returns true if an authorization is in progress
    588  auto authInProgress = [&]() -> bool {
    589    return proxyAuth ? mProxyAuthContinuationState : mAuthContinuationState;
    590  };
    591 
    592  // We shouldn't sort if authorization is already in progress
    593  // otherwise we might end up picking the wrong one. See bug 1805666
    594  if (!authInProgress() ||
    595      StaticPrefs::network_auth_sort_challenge_in_progress()) {
    596    cc.StableSort([](const AuthChallenge& lhs, const AuthChallenge& rhs) {
    597      // Different auth types
    598      if (lhs.rank != rhs.rank) {
    599        return lhs.rank < rhs.rank ? 1 : -1;
    600      }
    601 
    602      // If they're the same auth type, and not a Digest, then we treat them
    603      // as equal (don't reorder them).
    604      if (lhs.rank != ChallengeRank::Digest) {
    605        return 0;
    606      }
    607 
    608      if (lhs.algorithm == rhs.algorithm) {
    609        return 0;
    610      }
    611 
    612      return lhs.algorithm < rhs.algorithm ? 1 : -1;
    613    });
    614  }
    615 
    616  nsAutoCString scheme;
    617 
    618  // If a preference to enable basic HTTP Auth is unset and the scheme is HTTP,
    619  // check if Basic is the sole available authentication challenge.
    620  if (NS_SUCCEEDED(mURI->GetScheme(scheme)) && scheme == "http"_ns &&
    621      !StaticPrefs::network_http_basic_http_auth_enabled() &&
    622      cc[0].rank == ChallengeRank::Basic) {
    623    // HTTP Auth and "Basic" is the sole available authentication.
    624    return NS_ERROR_BASIC_HTTP_AUTH_DISABLED;
    625  }
    626 
    627  nsCOMPtr<nsIHttpAuthenticator> auth;
    628  nsCString authType;  // force heap allocation to enable string sharing since
    629                       // we'll be assigning this value into mAuthType.
    630 
    631  // set informations that depend on whether we're authenticating against a
    632  // proxy or a webserver
    633  nsISupports** currentContinuationState;
    634  nsCString* currentAuthType;
    635 
    636  if (proxyAuth) {
    637    currentContinuationState = &mProxyAuthContinuationState;
    638    currentAuthType = &mProxyAuthType;
    639  } else {
    640    currentContinuationState = &mAuthContinuationState;
    641    currentAuthType = &mAuthType;
    642  }
    643 
    644  nsresult rv = NS_ERROR_NOT_AVAILABLE;
    645  bool gotCreds = false;
    646 
    647  // figure out which challenge we can handle and which authenticator to use.
    648  for (size_t i = 0; i < cc.Length(); i++) {
    649    rv = GetAuthenticator(cc[i].challenge, authType, getter_AddRefs(auth));
    650    LOG(("trying auth for %s", authType.get()));
    651    if (NS_SUCCEEDED(rv)) {
    652      //
    653      // if we've already selected an auth type from a previous challenge
    654      // received while processing this channel, then skip others until
    655      // we find a challenge corresponding to the previously tried auth
    656      // type.
    657      //
    658      if (!currentAuthType->IsEmpty() && authType != *currentAuthType) continue;
    659 
    660      //
    661      // we allow the routines to run all the way through before we
    662      // decide if they are valid.
    663      //
    664      // we don't worry about the auth cache being altered because that
    665      // would have been the last step, and if the error is from updating
    666      // the authcache it wasn't really altered anyway. -CTN
    667      //
    668      // at this point the code is really only useful for client side
    669      // errors (it will not automatically fail over to do a different
    670      // auth type if the server keeps rejecting what is being sent, even
    671      // if a particular auth method only knows 1 thing, like a
    672      // non-identity based authentication method)
    673      //
    674      rv = GetCredentialsForChallenge(cc[i].challenge, authType, proxyAuth,
    675                                      auth, creds);
    676      if (NS_SUCCEEDED(rv)) {
    677        gotCreds = true;
    678        *currentAuthType = authType;
    679 
    680        break;
    681      }
    682      if (rv == NS_ERROR_IN_PROGRESS) {
    683        // authentication prompt has been invoked and result is
    684        // expected asynchronously, save current challenge being
    685        // processed and all remaining challenges to use later in
    686        // OnAuthAvailable and now immediately return
    687        mCurrentChallenge = cc[i].challenge;
    688        // imperfect; does not save server-side preference ordering.
    689        // instead, continues with remaining string as provided by client
    690        mRemainingChallenges.Truncate();
    691        while (i + 1 < cc.Length()) {
    692          i++;
    693          mRemainingChallenges.Append(cc[i].challenge);
    694          mRemainingChallenges.Append("\n"_ns);
    695        }
    696        return rv;
    697      }
    698 
    699      // reset the auth type and continuation state
    700      NS_IF_RELEASE(*currentContinuationState);
    701      currentAuthType->Truncate();
    702    }
    703  }
    704 
    705  if (!gotCreds && !currentAuthType->IsEmpty()) {
    706    // looks like we never found the auth type we were looking for.
    707    // reset the auth type and continuation state, and try again.
    708    currentAuthType->Truncate();
    709    NS_IF_RELEASE(*currentContinuationState);
    710 
    711    rv = GetCredentials(challenges, proxyAuth, creds);
    712  }
    713 
    714  return rv;
    715 }
    716 
    717 nsresult nsHttpChannelAuthProvider::GetAuthorizationMembers(
    718    bool proxyAuth, nsACString& scheme, nsCString& host, int32_t& port,
    719    nsACString& path, nsHttpAuthIdentity*& ident,
    720    nsISupports**& continuationState) {
    721  if (proxyAuth) {
    722    MOZ_ASSERT(UsingHttpProxy(),
    723               "proxyAuth is true, but no HTTP proxy is configured!");
    724 
    725    host = ProxyHost();
    726    port = ProxyPort();
    727    ident = &mProxyIdent;
    728    scheme.AssignLiteral("http");
    729 
    730    continuationState = &mProxyAuthContinuationState;
    731  } else {
    732    host = Host();
    733    port = Port();
    734    ident = &mIdent;
    735 
    736    nsresult rv;
    737    rv = GetCurrentPath(path);
    738    if (NS_FAILED(rv)) return rv;
    739 
    740    rv = mURI->GetScheme(scheme);
    741    if (NS_FAILED(rv)) return rv;
    742 
    743    continuationState = &mAuthContinuationState;
    744  }
    745 
    746  return NS_OK;
    747 }
    748 
    749 nsresult nsHttpChannelAuthProvider::GetCredentialsForChallenge(
    750    const nsACString& aChallenge, const nsACString& aAuthType, bool proxyAuth,
    751    nsIHttpAuthenticator* auth, nsCString& creds) {
    752  LOG(
    753      ("nsHttpChannelAuthProvider::GetCredentialsForChallenge "
    754       "[this=%p channel=%p proxyAuth=%d challenges=%s]\n",
    755       this, mAuthChannel, proxyAuth, nsCString(aChallenge).get()));
    756 
    757  // this getter never fails
    758  nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
    759 
    760  uint32_t authFlags;
    761  nsresult rv = auth->GetAuthFlags(&authFlags);
    762  if (NS_FAILED(rv)) return rv;
    763 
    764  nsAutoCString realm;
    765  ParseRealm(aChallenge, realm);
    766 
    767  // if no realm, then use the auth type as the realm.  ToUpperCase so the
    768  // ficticious realm stands out a bit more.
    769  // XXX this will cause some single signon misses!
    770  // XXX this was meant to be used with NTLM, which supplies no realm.
    771  /*
    772  if (realm.IsEmpty()) {
    773      realm = authType;
    774      ToUpperCase(realm);
    775  }
    776  */
    777 
    778  // set informations that depend on whether
    779  // we're authenticating against a proxy
    780  // or a webserver
    781  nsAutoCString host;
    782  int32_t port;
    783  nsHttpAuthIdentity* ident;
    784  nsAutoCString path, scheme;
    785  bool identFromURI = false;
    786  nsISupports** continuationState;
    787 
    788  rv = GetAuthorizationMembers(proxyAuth, scheme, host, port, path, ident,
    789                               continuationState);
    790  if (NS_FAILED(rv)) return rv;
    791 
    792  uint32_t loadFlags;
    793  rv = mAuthChannel->GetLoadFlags(&loadFlags);
    794  if (NS_FAILED(rv)) return rv;
    795 
    796  // Fill only for non-proxy auth, proxy credentials are not OA-isolated.
    797  nsAutoCString suffix;
    798 
    799  if (!proxyAuth) {
    800    nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
    801    GetOriginAttributesSuffix(chan, suffix);
    802 
    803    // if this is the first challenge, then try using the identity
    804    // specified in the URL.
    805    if (mIdent.IsEmpty()) {
    806      GetIdentityFromURI(authFlags, mIdent);
    807      identFromURI = !mIdent.IsEmpty();
    808    }
    809 
    810    if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !identFromURI) {
    811      LOG(("Skipping authentication for anonymous non-proxy request\n"));
    812      return NS_ERROR_NOT_AVAILABLE;
    813    }
    814 
    815    // Let explicit URL credentials pass
    816    // regardless of the LOAD_ANONYMOUS flag
    817  } else if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !UsingHttpProxy()) {
    818    LOG(("Skipping authentication for anonymous non-proxy request\n"));
    819    return NS_ERROR_NOT_AVAILABLE;
    820  }
    821 
    822  //
    823  // if we already tried some credentials for this transaction, then
    824  // we need to possibly clear them from the cache, unless the credentials
    825  // in the cache have changed, in which case we'd want to give them a
    826  // try instead.
    827  //
    828  RefPtr<nsHttpAuthEntry> entry;
    829  (void)authCache->GetAuthEntryForDomain(scheme, host, port, realm, suffix,
    830                                         entry);
    831 
    832  // hold reference to the auth session state (in case we clear our
    833  // reference to the entry).
    834  nsCOMPtr<nsISupports> sessionStateGrip;
    835  if (entry) sessionStateGrip = entry->mMetaData;
    836 
    837  // remember if we already had the continuation state.  it means we are in
    838  // the middle of the authentication exchange and the connection must be
    839  // kept sticky then (and only then).
    840  bool authAtProgress = !!*continuationState;
    841 
    842  // for digest auth, maybe our cached nonce value simply timed out...
    843  bool identityInvalid;
    844  nsISupports* sessionState = sessionStateGrip;
    845  rv = auth->ChallengeReceived(mAuthChannel, aChallenge, proxyAuth,
    846                               &sessionState, &*continuationState,
    847                               &identityInvalid);
    848  sessionStateGrip.swap(sessionState);
    849  if (NS_FAILED(rv)) return rv;
    850 
    851  LOG(("  identity invalid = %d\n", identityInvalid));
    852 
    853  if (mConnectionBased && identityInvalid) {
    854    // If the flag is set and identity is invalid, it means we received the
    855    // first challange for a new negotiation round after negotiating a
    856    // connection based auth failed (invalid password). The mConnectionBased
    857    // flag is set later for the newly received challenge, so here it reflects
    858    // the previous 401/7 response schema.
    859    rv = mAuthChannel->CloseStickyConnection();
    860    MOZ_ASSERT(NS_SUCCEEDED(rv));
    861    if (!proxyAuth) {
    862      // We must clear proxy ident in the following scenario + explanation:
    863      // - we are authenticating to an NTLM proxy and an NTLM server
    864      // - we successfully authenticated to the proxy, mProxyIdent keeps
    865      //   the user name/domain and password, the identity has also been cached
    866      // - we just threw away the connection because we are now asking for
    867      //   creds for the server (WWW auth)
    868      // - hence, we will have to auth to the proxy again as well
    869      // - if we didn't clear the proxy identity, it would be considered
    870      //   as non-valid and we would ask the user again ; clearing it forces
    871      //   use of the cached identity and not asking the user again
    872      ClearProxyIdent();
    873    }
    874  }
    875 
    876  mConnectionBased = !!(authFlags & nsIHttpAuthenticator::CONNECTION_BASED);
    877 
    878  // It's legal if the peer closes the connection after the first 401/7.
    879  // Making the connection sticky will prevent its restart giving the user
    880  // a 'network reset' error every time.  Hence, we mark the connection
    881  // as restartable.
    882  mAuthChannel->ConnectionRestartable(!authAtProgress);
    883 
    884  if (identityInvalid) {
    885    if (entry) {
    886      if (ident->Equals(entry->Identity())) {
    887        if (!identFromURI) {
    888          LOG(("  clearing bad auth cache entry\n"));
    889          // ok, we've already tried this user identity, so clear the
    890          // corresponding entry from the auth cache.
    891          authCache->ClearAuthEntry(scheme, host, port, realm, suffix);
    892          entry = nullptr;
    893          ident->Clear();
    894        }
    895      } else if (!identFromURI ||
    896                 (ident->User() == entry->Identity().User() &&
    897                  !(loadFlags & (nsIChannel::LOAD_ANONYMOUS |
    898                                 nsIChannel::LOAD_EXPLICIT_CREDENTIALS)))) {
    899        LOG(("  taking identity from auth cache\n"));
    900        // the password from the auth cache is more likely to be
    901        // correct than the one in the URL.  at least, we know that it
    902        // works with the given username.  it is possible for a server
    903        // to distinguish logons based on the supplied password alone,
    904        // but that would be quite unusual... and i don't think we need
    905        // to worry about such unorthodox cases.
    906        *ident = entry->Identity();
    907        identFromURI = false;
    908        if (entry->Creds()[0] != '\0') {
    909          LOG(("    using cached credentials!\n"));
    910          creds.Assign(entry->Creds());
    911          return entry->AddPath(path);
    912        }
    913      }
    914    } else if (!identFromURI) {
    915      // hmm... identity invalid, but no auth entry!  the realm probably
    916      // changed (see bug 201986).
    917      ident->Clear();
    918    }
    919 
    920    if (!entry && ident->IsEmpty()) {
    921      uint32_t level = nsIAuthPrompt2::LEVEL_NONE;
    922      if ((!proxyAuth && mUsingSSL) || (proxyAuth && mProxyUsingSSL)) {
    923        level = nsIAuthPrompt2::LEVEL_SECURE;
    924      } else if (authFlags & nsIHttpAuthenticator::IDENTITY_ENCRYPTED) {
    925        level = nsIAuthPrompt2::LEVEL_PW_ENCRYPTED;
    926      }
    927 
    928      // Depending on the pref setting, the authentication dialog may be
    929      // blocked for all sub-resources, blocked for cross-origin
    930      // sub-resources, or always allowed for sub-resources.
    931      // For more details look at the bug 647010.
    932      // BlockPrompt will set mCrossOrigin parameter as well.
    933      if (BlockPrompt(proxyAuth)) {
    934        LOG((
    935            "nsHttpChannelAuthProvider::GetCredentialsForChallenge: "
    936            "Prompt is blocked [this=%p pref=%d img-pref=%d "
    937            "non-web-content-triggered-pref=%d]\n",
    938            this, StaticPrefs::network_auth_subresource_http_auth_allow(),
    939            StaticPrefs::
    940                network_auth_subresource_img_cross_origin_http_auth_allow(),
    941            StaticPrefs::
    942                network_auth_non_web_content_triggered_resources_http_auth_allow()));
    943        return NS_ERROR_ABORT;
    944      }
    945 
    946      // at this point we are forced to interact with the user to get
    947      // their username and password for this domain.
    948      rv = PromptForIdentity(level, proxyAuth, realm, aAuthType, authFlags,
    949                             *ident);
    950      if (NS_FAILED(rv)) return rv;
    951      identFromURI = false;
    952    }
    953  }
    954 
    955  // get credentials for the given user:pass
    956  //
    957  // always store the credentials we're trying now so that they will be used
    958  // on subsequent links.  This will potentially remove good credentials from
    959  // the cache.  This is ok as we don't want to use cached credentials if the
    960  // user specified something on the URI or in another manner.  This is so
    961  // that we don't transparently authenticate as someone they're not
    962  // expecting to authenticate as.
    963  //
    964  nsCString result;
    965  rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port, path, realm,
    966                           aChallenge, *ident, sessionStateGrip, creds);
    967  return rv;
    968 }
    969 
    970 bool nsHttpChannelAuthProvider::BlockPrompt(bool proxyAuth) {
    971  // Verify that it's ok to prompt for credentials here, per spec
    972  // http://xhr.spec.whatwg.org/#the-send%28%29-method
    973 
    974  nsCOMPtr<nsIHttpChannelInternal> chanInternal =
    975      do_QueryInterface(mAuthChannel);
    976  MOZ_ASSERT(chanInternal);
    977 
    978  if (chanInternal->GetBlockAuthPrompt()) {
    979    LOG(
    980        ("nsHttpChannelAuthProvider::BlockPrompt: Prompt is blocked "
    981         "[this=%p channel=%p]\n",
    982         this, mAuthChannel));
    983    return true;
    984  }
    985 
    986  if (proxyAuth) {
    987    // Do not block auth-dialog if this is a proxy authentication.
    988    return false;
    989  }
    990 
    991  nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
    992  nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
    993 
    994  // We will treat loads w/o loadInfo as a top level document.
    995  bool topDoc = true;
    996  bool xhr = false;
    997  bool nonWebContent = false;
    998 
    999  if (loadInfo->GetExternalContentPolicyType() !=
   1000      ExtContentPolicy::TYPE_DOCUMENT) {
   1001    topDoc = false;
   1002  }
   1003 
   1004  if (!topDoc) {
   1005    nsCOMPtr<nsIPrincipal> triggeringPrinc = loadInfo->TriggeringPrincipal();
   1006    if (triggeringPrinc->IsSystemPrincipal()) {
   1007      nonWebContent = true;
   1008    }
   1009  }
   1010 
   1011  if (loadInfo->GetExternalContentPolicyType() ==
   1012      ExtContentPolicy::TYPE_XMLHTTPREQUEST) {
   1013    xhr = true;
   1014  }
   1015 
   1016  if (!topDoc && !xhr) {
   1017    nsCOMPtr<nsIURI> topURI;
   1018    (void)chanInternal->GetTopWindowURI(getter_AddRefs(topURI));
   1019    if (topURI) {
   1020      mCrossOrigin = !NS_SecurityCompareURIs(topURI, mURI, true);
   1021    } else {
   1022      nsIPrincipal* loadingPrinc = loadInfo->GetLoadingPrincipal();
   1023      MOZ_ASSERT(loadingPrinc);
   1024      mCrossOrigin = !loadingPrinc->IsSameOrigin(mURI);
   1025    }
   1026  }
   1027 
   1028  if (!topDoc &&
   1029      !StaticPrefs::
   1030          network_auth_non_web_content_triggered_resources_http_auth_allow() &&
   1031      nonWebContent) {
   1032    return true;
   1033  }
   1034 
   1035  switch (StaticPrefs::network_auth_subresource_http_auth_allow()) {
   1036    case SUBRESOURCE_AUTH_DIALOG_DISALLOW_ALL:
   1037      // Do not open the http-authentication credentials dialog for
   1038      // the sub-resources.
   1039      return !topDoc && !xhr;
   1040    case SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN:
   1041      // Open the http-authentication credentials dialog for
   1042      // the sub-resources only if they are not cross-origin.
   1043      return !topDoc && !xhr && mCrossOrigin;
   1044    case SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL:
   1045      // Allow the http-authentication dialog for subresources.
   1046      // If pref network.auth.subresource-img-cross-origin-http-auth-allow
   1047      // is set, http-authentication dialog for image subresources is
   1048      // blocked.
   1049      if (mCrossOrigin &&
   1050          !StaticPrefs::
   1051              network_auth_subresource_img_cross_origin_http_auth_allow() &&
   1052          loadInfo &&
   1053          ((loadInfo->GetExternalContentPolicyType() ==
   1054            ExtContentPolicy::TYPE_IMAGE) ||
   1055           (loadInfo->GetExternalContentPolicyType() ==
   1056            ExtContentPolicy::TYPE_IMAGESET))) {
   1057        return true;
   1058      }
   1059      return false;
   1060    default:
   1061      // This is an invalid value.
   1062      MOZ_ASSERT(false, "A non valid value!");
   1063  }
   1064  return false;
   1065 }
   1066 
   1067 inline void GetAuthType(const nsACString& aChallenge, nsCString& authType) {
   1068  auto spaceIndex = aChallenge.FindChar(' ');
   1069  authType = Substring(aChallenge, 0, spaceIndex);
   1070  // normalize to lowercase
   1071  ToLowerCase(authType);
   1072 }
   1073 
   1074 nsresult nsHttpChannelAuthProvider::GetAuthenticator(
   1075    const nsACString& aChallenge, nsCString& authType,
   1076    nsIHttpAuthenticator** auth) {
   1077  LOG(("nsHttpChannelAuthProvider::GetAuthenticator [this=%p channel=%p]\n",
   1078       this, mAuthChannel));
   1079 
   1080  GetAuthType(aChallenge, authType);
   1081 
   1082  nsCOMPtr<nsIHttpAuthenticator> authenticator;
   1083 #ifdef MOZ_AUTH_EXTENSION
   1084  if (authType.EqualsLiteral("negotiate")) {
   1085    authenticator = nsHttpNegotiateAuth::GetOrCreate();
   1086  } else
   1087 #endif
   1088      if (authType.EqualsLiteral("basic")) {
   1089    authenticator = nsHttpBasicAuth::GetOrCreate();
   1090  } else if (authType.EqualsLiteral("digest")) {
   1091    authenticator = nsHttpDigestAuth::GetOrCreate();
   1092  } else if (authType.EqualsLiteral("ntlm")) {
   1093    authenticator = nsHttpNTLMAuth::GetOrCreate();
   1094  } else if (authType.EqualsLiteral("mock_auth") &&
   1095             PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
   1096    authenticator = MockHttpAuth::Create();
   1097  } else {
   1098    return NS_ERROR_FACTORY_NOT_REGISTERED;
   1099  }
   1100 
   1101  if (!authenticator) {
   1102    // If called during shutdown it's possible that the singleton authenticator
   1103    // was already cleared so we have a null one here.
   1104    return NS_ERROR_NOT_AVAILABLE;
   1105  }
   1106 
   1107  MOZ_ASSERT(authenticator);
   1108  authenticator.forget(auth);
   1109 
   1110  return NS_OK;
   1111 }
   1112 
   1113 // buf contains "domain\user"
   1114 static void ParseUserDomain(const nsAString& buf, nsDependentSubstring& user,
   1115                            nsDependentSubstring& domain) {
   1116  auto backslashPos = buf.FindChar(u'\\');
   1117  if (backslashPos != kNotFound) {
   1118    domain.Rebind(buf, 0, backslashPos);
   1119    user.Rebind(buf, backslashPos + 1);
   1120  }
   1121 }
   1122 
   1123 void nsHttpChannelAuthProvider::GetIdentityFromURI(uint32_t authFlags,
   1124                                                   nsHttpAuthIdentity& ident) {
   1125  LOG(("nsHttpChannelAuthProvider::GetIdentityFromURI [this=%p channel=%p]\n",
   1126       this, mAuthChannel));
   1127 
   1128  bool hasUserPass;
   1129  if (NS_FAILED(mURI->GetHasUserPass(&hasUserPass)) || !hasUserPass) {
   1130    return;
   1131  }
   1132 
   1133  nsAutoString userBuf;
   1134  nsAutoString passBuf;
   1135 
   1136  // XXX i18n
   1137  nsAutoCString buf;
   1138  nsresult rv = mURI->GetUsername(buf);
   1139  if (NS_FAILED(rv)) {
   1140    return;
   1141  }
   1142  NS_UnescapeURL(buf);
   1143  CopyUTF8toUTF16(buf, userBuf);
   1144 
   1145  rv = mURI->GetPassword(buf);
   1146  if (NS_FAILED(rv)) {
   1147    return;
   1148  }
   1149  NS_UnescapeURL(buf);
   1150  CopyUTF8toUTF16(buf, passBuf);
   1151 
   1152  nsDependentSubstring user(userBuf, 0);
   1153  nsDependentSubstring domain(EmptyString(), 0);
   1154 
   1155  if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN) {
   1156    ParseUserDomain(userBuf, user, domain);
   1157  }
   1158 
   1159  ident = nsHttpAuthIdentity(domain, user, passBuf);
   1160 }
   1161 
   1162 void nsHttpChannelAuthProvider::ParseRealm(const nsACString& aChallenge,
   1163                                           nsACString& realm) {
   1164  //
   1165  // From RFC2617 section 1.2, the realm value is defined as such:
   1166  //
   1167  //    realm       = "realm" "=" realm-value
   1168  //    realm-value = quoted-string
   1169  //
   1170  // but, we'll accept anything after the the "=" up to the first space, or
   1171  // end-of-line, if the string is not quoted.
   1172  //
   1173 
   1174  Tokenizer t(aChallenge);
   1175 
   1176  // The challenge begins with the authType.
   1177  // If we can't find that something has probably gone wrong.
   1178  t.SkipWhites();
   1179  nsDependentCSubstring authType;
   1180  if (!t.ReadWord(authType)) {
   1181    return;
   1182  }
   1183 
   1184  // Will return true if the tokenizer advanced the cursor - false otherwise.
   1185  auto readParam = [&](nsDependentCSubstring& key, nsAutoCString& value) {
   1186    key.Rebind(EmptyCString(), 0);
   1187    value.Truncate();
   1188 
   1189    t.SkipWhites();
   1190    if (!t.ReadWord(key)) {
   1191      return false;
   1192    }
   1193    t.SkipWhites();
   1194    if (!t.CheckChar('=')) {
   1195      return true;
   1196    }
   1197    t.SkipWhites();
   1198 
   1199    Tokenizer::Token token1;
   1200 
   1201    t.Record();
   1202    if (!t.Next(token1)) {
   1203      return true;
   1204    }
   1205    nsDependentCSubstring sub;
   1206    bool hasQuote = false;
   1207    if (token1.Equals(Tokenizer::Token::Char('"'))) {
   1208      hasQuote = true;
   1209    } else {
   1210      t.Claim(sub, Tokenizer::ClaimInclusion::INCLUDE_LAST);
   1211      value.Append(sub);
   1212    }
   1213    t.Record();
   1214    Tokenizer::Token token2;
   1215    while (t.Next(token2)) {
   1216      if (hasQuote && token2.Equals(Tokenizer::Token::Char('"')) &&
   1217          !token1.Equals(Tokenizer::Token::Char('\\'))) {
   1218        break;
   1219      }
   1220      if (!hasQuote && (token2.Type() == Tokenizer::TokenType::TOKEN_WS ||
   1221                        token2.Type() == Tokenizer::TokenType::TOKEN_EOL)) {
   1222        break;
   1223      }
   1224 
   1225      t.Claim(sub, Tokenizer::ClaimInclusion::INCLUDE_LAST);
   1226      if (!sub.Equals(R"(\)")) {
   1227        value.Append(sub);
   1228      }
   1229      t.Record();
   1230      token1 = token2;
   1231    }
   1232    return true;
   1233  };
   1234 
   1235  while (!t.CheckEOF()) {
   1236    nsDependentCSubstring key;
   1237    nsAutoCString value;
   1238    // If we couldn't read anything, and the input isn't followed by a ,
   1239    // then we exit.
   1240    if (!readParam(key, value) && !t.Check(Tokenizer::Token::Char(','))) {
   1241      break;
   1242    }
   1243    // When we find the first instance of realm we exit.
   1244    // Theoretically there should be only one instance and we should fail
   1245    // if there are more, but we're trying to preserve existing behaviour.
   1246    if (key.Equals("realm"_ns, nsCaseInsensitiveCStringComparator)) {
   1247      realm = value;
   1248      break;
   1249    }
   1250  }
   1251 }
   1252 
   1253 class nsHTTPAuthInformation : public nsAuthInformationHolder {
   1254 public:
   1255  nsHTTPAuthInformation(uint32_t aFlags, const nsString& aRealm,
   1256                        const nsACString& aAuthType)
   1257      : nsAuthInformationHolder(aFlags, aRealm, aAuthType) {}
   1258 
   1259  void SetToHttpAuthIdentity(uint32_t authFlags, nsHttpAuthIdentity& identity);
   1260 };
   1261 
   1262 void nsHTTPAuthInformation::SetToHttpAuthIdentity(
   1263    uint32_t authFlags, nsHttpAuthIdentity& identity) {
   1264  identity = nsHttpAuthIdentity(Domain(), User(), Password());
   1265 }
   1266 
   1267 nsresult nsHttpChannelAuthProvider::PromptForIdentity(
   1268    uint32_t level, bool proxyAuth, const nsACString& realm,
   1269    const nsACString& authType, uint32_t authFlags, nsHttpAuthIdentity& ident) {
   1270  LOG(("nsHttpChannelAuthProvider::PromptForIdentity [this=%p channel=%p]\n",
   1271       this, mAuthChannel));
   1272 
   1273  nsresult rv;
   1274 
   1275  nsCOMPtr<nsIInterfaceRequestor> callbacks;
   1276  rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
   1277  if (NS_FAILED(rv)) return rv;
   1278 
   1279  nsCOMPtr<nsILoadGroup> loadGroup;
   1280  rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup));
   1281  if (NS_FAILED(rv)) return rv;
   1282 
   1283  nsCOMPtr<nsIAuthPrompt2> authPrompt;
   1284  GetAuthPrompt(callbacks, proxyAuth, getter_AddRefs(authPrompt));
   1285  if (!authPrompt && loadGroup) {
   1286    nsCOMPtr<nsIInterfaceRequestor> cbs;
   1287    loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
   1288    GetAuthPrompt(cbs, proxyAuth, getter_AddRefs(authPrompt));
   1289  }
   1290  if (!authPrompt) return NS_ERROR_NO_INTERFACE;
   1291 
   1292  // XXX i18n: need to support non-ASCII realm strings (see bug 41489)
   1293  NS_ConvertASCIItoUTF16 realmU(realm);
   1294 
   1295  // prompt the user...
   1296  uint32_t promptFlags = 0;
   1297  if (proxyAuth) {
   1298    promptFlags |= nsIAuthInformation::AUTH_PROXY;
   1299    if (mTriedProxyAuth) promptFlags |= nsIAuthInformation::PREVIOUS_FAILED;
   1300    mTriedProxyAuth = true;
   1301  } else {
   1302    promptFlags |= nsIAuthInformation::AUTH_HOST;
   1303    if (mTriedHostAuth) promptFlags |= nsIAuthInformation::PREVIOUS_FAILED;
   1304    mTriedHostAuth = true;
   1305  }
   1306 
   1307  if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN) {
   1308    promptFlags |= nsIAuthInformation::NEED_DOMAIN;
   1309  }
   1310 
   1311  if (mCrossOrigin) {
   1312    promptFlags |= nsIAuthInformation::CROSS_ORIGIN_SUB_RESOURCE;
   1313  }
   1314 
   1315  RefPtr<nsHTTPAuthInformation> holder =
   1316      new nsHTTPAuthInformation(promptFlags, realmU, authType);
   1317  if (!holder) return NS_ERROR_OUT_OF_MEMORY;
   1318 
   1319  nsCOMPtr<nsIChannel> channel(do_QueryInterface(mAuthChannel, &rv));
   1320  if (NS_FAILED(rv)) return rv;
   1321 
   1322  rv = authPrompt->AsyncPromptAuth(channel, this, nullptr, level, holder,
   1323                                   getter_AddRefs(mAsyncPromptAuthCancelable));
   1324 
   1325  if (NS_SUCCEEDED(rv)) {
   1326    // indicate using this error code that authentication prompt
   1327    // result is expected asynchronously
   1328    rv = NS_ERROR_IN_PROGRESS;
   1329  } else {
   1330    // Fall back to synchronous prompt
   1331    bool retval = false;
   1332    rv = authPrompt->PromptAuth(channel, level, holder, &retval);
   1333    if (NS_FAILED(rv)) return rv;
   1334 
   1335    if (!retval) {
   1336      rv = NS_ERROR_ABORT;
   1337    } else {
   1338      holder->SetToHttpAuthIdentity(authFlags, ident);
   1339    }
   1340  }
   1341 
   1342  if (mConnectionBased) {
   1343    // Connection can be reset by the server in the meantime user is entering
   1344    // the credentials.  Result would be just a "Connection was reset" error.
   1345    // Hence, we drop the current regardless if the user would make it on time
   1346    // to provide credentials.
   1347    // It's OK to send the NTLM type 1 message (response to the plain "NTLM"
   1348    // challenge) on a new connection.
   1349    {
   1350      DebugOnly<nsresult> rv = mAuthChannel->CloseStickyConnection();
   1351      MOZ_ASSERT(NS_SUCCEEDED(rv));
   1352    }
   1353  }
   1354 
   1355  return rv;
   1356 }
   1357 
   1358 NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthAvailable(
   1359    nsISupports* aContext, nsIAuthInformation* aAuthInfo) {
   1360  LOG(("nsHttpChannelAuthProvider::OnAuthAvailable [this=%p channel=%p]", this,
   1361       mAuthChannel));
   1362 
   1363  mAsyncPromptAuthCancelable = nullptr;
   1364  if (!mAuthChannel) return NS_OK;
   1365 
   1366  nsresult rv;
   1367 
   1368  nsAutoCString host;
   1369  int32_t port;
   1370  nsHttpAuthIdentity* ident;
   1371  nsAutoCString path, scheme;
   1372  nsISupports** continuationState;
   1373  rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, path, ident,
   1374                               continuationState);
   1375  if (NS_FAILED(rv)) OnAuthCancelled(aContext, false);
   1376 
   1377  nsAutoCString realm;
   1378  ParseRealm(mCurrentChallenge, realm);
   1379 
   1380  nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
   1381  nsAutoCString suffix;
   1382  if (!mProxyAuth) {
   1383    // Fill only for non-proxy auth, proxy credentials are not OA-isolated.
   1384    GetOriginAttributesSuffix(chan, suffix);
   1385  }
   1386 
   1387  nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
   1388  RefPtr<nsHttpAuthEntry> entry;
   1389  (void)authCache->GetAuthEntryForDomain(scheme, host, port, realm, suffix,
   1390                                         entry);
   1391 
   1392  nsCOMPtr<nsISupports> sessionStateGrip;
   1393  if (entry) sessionStateGrip = entry->mMetaData;
   1394 
   1395  nsAuthInformationHolder* holder =
   1396      static_cast<nsAuthInformationHolder*>(aAuthInfo);
   1397  *ident =
   1398      nsHttpAuthIdentity(holder->Domain(), holder->User(), holder->Password());
   1399 
   1400  nsAutoCString unused;
   1401  nsCOMPtr<nsIHttpAuthenticator> auth;
   1402  rv = GetAuthenticator(mCurrentChallenge, unused, getter_AddRefs(auth));
   1403  if (NS_FAILED(rv)) {
   1404    MOZ_ASSERT(false, "GetAuthenticator failed");
   1405    OnAuthCancelled(aContext, true);
   1406    return NS_OK;
   1407  }
   1408 
   1409  nsCString creds;
   1410  rv = GenCredsAndSetEntry(auth, mProxyAuth, scheme, host, port, path, realm,
   1411                           mCurrentChallenge, *ident, sessionStateGrip, creds);
   1412 
   1413  mCurrentChallenge.Truncate();
   1414  if (NS_FAILED(rv)) {
   1415    OnAuthCancelled(aContext, true);
   1416    return NS_OK;
   1417  }
   1418 
   1419  return ContinueOnAuthAvailable(creds);
   1420 }
   1421 
   1422 NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthCancelled(nsISupports* aContext,
   1423                                                         bool userCancel) {
   1424  LOG(("nsHttpChannelAuthProvider::OnAuthCancelled [this=%p channel=%p]", this,
   1425       mAuthChannel));
   1426 
   1427  mAsyncPromptAuthCancelable = nullptr;
   1428  if (!mAuthChannel) return NS_OK;
   1429 
   1430  // When user cancels or auth fails we want to close the connection for
   1431  // connection based schemes like NTLM.  Some servers don't like re-negotiation
   1432  // on the same connection.
   1433  nsresult rv;
   1434  if (mConnectionBased) {
   1435    rv = mAuthChannel->CloseStickyConnection();
   1436    MOZ_ASSERT(NS_SUCCEEDED(rv));
   1437    mConnectionBased = false;
   1438  }
   1439 
   1440  nsCOMPtr<nsIChannel> channel = do_QueryInterface(mAuthChannel);
   1441  if (channel) {
   1442    nsresult status;
   1443    (void)channel->GetStatus(&status);
   1444    if (NS_FAILED(status)) {
   1445      // If the channel is already cancelled, there is no need to deal with the
   1446      // rest challenges.
   1447      LOG(("  Clear mRemainingChallenges, since mAuthChannel is cancelled"));
   1448      mRemainingChallenges.Truncate();
   1449    }
   1450  }
   1451 
   1452  if (userCancel) {
   1453    if (!mRemainingChallenges.IsEmpty()) {
   1454      // there are still some challenges to process, do so
   1455 
   1456      // Get rid of current continuationState to avoid reusing it in
   1457      // next challenges since it is no longer relevant.
   1458      if (mProxyAuth) {
   1459        NS_IF_RELEASE(mProxyAuthContinuationState);
   1460      } else {
   1461        NS_IF_RELEASE(mAuthContinuationState);
   1462      }
   1463      nsAutoCString creds;
   1464      rv = GetCredentials(mRemainingChallenges, mProxyAuth, creds);
   1465      if (NS_SUCCEEDED(rv)) {
   1466        // GetCredentials loaded the credentials from the cache or
   1467        // some other way in a synchronous manner, process those
   1468        // credentials now
   1469        mRemainingChallenges.Truncate();
   1470        return ContinueOnAuthAvailable(creds);
   1471      }
   1472      if (rv == NS_ERROR_IN_PROGRESS) {
   1473        // GetCredentials successfully queued another authprompt for
   1474        // a challenge from the list, we are now waiting for the user
   1475        // to provide the credentials
   1476        return NS_OK;
   1477      }
   1478 
   1479      // otherwise, we failed...
   1480    }
   1481 
   1482    mRemainingChallenges.Truncate();
   1483  }
   1484 
   1485  rv = mAuthChannel->OnAuthCancelled(userCancel);
   1486  MOZ_ASSERT(NS_SUCCEEDED(rv));
   1487 
   1488  return NS_OK;
   1489 }
   1490 
   1491 NS_IMETHODIMP nsHttpChannelAuthProvider::OnCredsGenerated(
   1492    const nsACString& aGeneratedCreds, uint32_t aFlags, nsresult aResult,
   1493    nsISupports* aSessionState, nsISupports* aContinuationState) {
   1494  nsresult rv;
   1495 
   1496  MOZ_ASSERT(NS_IsMainThread());
   1497 
   1498  // When channel is closed, do not proceed
   1499  if (!mAuthChannel) {
   1500    return NS_OK;
   1501  }
   1502 
   1503  mGenerateCredentialsCancelable = nullptr;
   1504 
   1505  if (NS_FAILED(aResult)) {
   1506    return OnAuthCancelled(nullptr, true);
   1507  }
   1508 
   1509  // We want to update m(Proxy)AuthContinuationState in case it was changed by
   1510  // nsHttpNegotiateAuth::GenerateCredentials
   1511  nsCOMPtr<nsISupports> contState(aContinuationState);
   1512  if (mProxyAuth) {
   1513    contState.swap(mProxyAuthContinuationState);
   1514  } else {
   1515    contState.swap(mAuthContinuationState);
   1516  }
   1517 
   1518  nsCOMPtr<nsIHttpAuthenticator> auth;
   1519  nsAutoCString unused;
   1520  rv = GetAuthenticator(mCurrentChallenge, unused, getter_AddRefs(auth));
   1521  NS_ENSURE_SUCCESS(rv, rv);
   1522 
   1523  nsAutoCString host;
   1524  int32_t port;
   1525  nsHttpAuthIdentity* ident;
   1526  nsAutoCString directory, scheme;
   1527  nsISupports** unusedContinuationState;
   1528 
   1529  // Get realm from challenge
   1530  nsAutoCString realm;
   1531  ParseRealm(mCurrentChallenge, realm);
   1532 
   1533  rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, directory, ident,
   1534                               unusedContinuationState);
   1535  if (NS_FAILED(rv)) return rv;
   1536 
   1537  rv =
   1538      UpdateCache(auth, scheme, host, port, directory, realm, mCurrentChallenge,
   1539                  *ident, aGeneratedCreds, aFlags, aSessionState, mProxyAuth);
   1540  MOZ_ASSERT(NS_SUCCEEDED(rv));
   1541  mCurrentChallenge.Truncate();
   1542 
   1543  rv = ContinueOnAuthAvailable(aGeneratedCreds);
   1544  MOZ_ASSERT(NS_SUCCEEDED(rv));
   1545  return NS_OK;
   1546 }
   1547 
   1548 nsresult nsHttpChannelAuthProvider::ContinueOnAuthAvailable(
   1549    const nsACString& creds) {
   1550  nsresult rv;
   1551  if (mProxyAuth) {
   1552    rv = mAuthChannel->SetProxyCredentials(creds);
   1553  } else {
   1554    rv = mAuthChannel->SetWWWCredentials(creds);
   1555  }
   1556  if (NS_FAILED(rv)) return rv;
   1557 
   1558  // drop our remaining list of challenges.  We don't need them, because we
   1559  // have now authenticated against a challenge and will be sending that
   1560  // information to the server (or proxy).  If it doesn't accept our
   1561  // authentication it'll respond with failure and resend the challenge list
   1562  mRemainingChallenges.Truncate();
   1563 
   1564  (void)mAuthChannel->OnAuthAvailable();
   1565 
   1566  return NS_OK;
   1567 }
   1568 
   1569 void nsHttpChannelAuthProvider::SetAuthorizationHeader(
   1570    nsHttpAuthCache* authCache, const nsHttpAtom& header,
   1571    const nsACString& scheme, const nsACString& host, int32_t port,
   1572    const nsACString& path, nsHttpAuthIdentity& ident) {
   1573  RefPtr<nsHttpAuthEntry> entry;
   1574  nsresult rv;
   1575 
   1576  // set informations that depend on whether
   1577  // we're authenticating against a proxy
   1578  // or a webserver
   1579  nsISupports** continuationState;
   1580 
   1581  nsAutoCString suffix;
   1582  if (header == nsHttp::Proxy_Authorization) {
   1583    continuationState = &mProxyAuthContinuationState;
   1584 
   1585    if (mProxyInfo) {
   1586      nsAutoCString type;
   1587      mProxyInfo->GetType(type);
   1588      if (type.EqualsLiteral("https") || type.EqualsLiteral("masque")) {
   1589        // Let this be overriden by anything from the cache.
   1590        auto const& pa = mProxyInfo->ProxyAuthorizationHeader();
   1591        if (!pa.IsEmpty()) {
   1592          rv = mAuthChannel->SetProxyCredentials(pa);
   1593          MOZ_ASSERT(NS_SUCCEEDED(rv));
   1594        }
   1595      }
   1596    }
   1597  } else {
   1598    continuationState = &mAuthContinuationState;
   1599 
   1600    nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
   1601    GetOriginAttributesSuffix(chan, suffix);
   1602  }
   1603 
   1604  rv = authCache->GetAuthEntryForPath(scheme, host, port, path, suffix, entry);
   1605  if (NS_SUCCEEDED(rv)) {
   1606    // if we are trying to add a header for origin server auth and if the
   1607    // URL contains an explicit username, then try the given username first.
   1608    // we only want to do this, however, if we know the URL requires auth
   1609    // based on the presence of an auth cache entry for this URL (which is
   1610    // true since we are here).  but, if the username from the URL matches
   1611    // the username from the cache, then we should prefer the password
   1612    // stored in the cache since that is most likely to be valid.
   1613    if (header == nsHttp::Authorization && entry->Domain()[0] == '\0') {
   1614      GetIdentityFromURI(0, ident);
   1615      // if the usernames match, then clear the ident so we will pick
   1616      // up the one from the auth cache instead.
   1617      // when this is undesired, specify LOAD_EXPLICIT_CREDENTIALS load
   1618      // flag.
   1619      if (ident.User() == entry->User()) {
   1620        uint32_t loadFlags;
   1621        if (NS_SUCCEEDED(mAuthChannel->GetLoadFlags(&loadFlags)) &&
   1622            !(loadFlags & nsIChannel::LOAD_EXPLICIT_CREDENTIALS)) {
   1623          ident.Clear();
   1624        }
   1625      }
   1626    }
   1627    bool identFromURI;
   1628    if (ident.IsEmpty()) {
   1629      ident = entry->Identity();
   1630      identFromURI = false;
   1631    } else {
   1632      identFromURI = true;
   1633    }
   1634 
   1635    nsCString temp;  // this must have the same lifetime as creds
   1636    nsAutoCString creds(entry->Creds());
   1637    // we can only send a preemptive Authorization header if we have either
   1638    // stored credentials or a stored challenge from which to derive
   1639    // credentials.  if the identity is from the URI, then we cannot use
   1640    // the stored credentials.
   1641    if ((creds.IsEmpty() || identFromURI) && !entry->Challenge().IsEmpty()) {
   1642      nsCOMPtr<nsIHttpAuthenticator> auth;
   1643      nsAutoCString unused;
   1644      rv = GetAuthenticator(entry->Challenge(), unused, getter_AddRefs(auth));
   1645      if (NS_SUCCEEDED(rv)) {
   1646        bool proxyAuth = (header == nsHttp::Proxy_Authorization);
   1647        rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port, path,
   1648                                 entry->Realm(), entry->Challenge(), ident,
   1649                                 entry->mMetaData, temp);
   1650        if (NS_SUCCEEDED(rv)) creds = temp;
   1651 
   1652        // make sure the continuation state is null since we do not
   1653        // support mixing preemptive and 'multirequest' authentication.
   1654        NS_IF_RELEASE(*continuationState);
   1655      }
   1656    }
   1657    if (!creds.IsEmpty()) {
   1658      LOG(("   adding \"%s\" request header\n", header.get()));
   1659      if (header == nsHttp::Proxy_Authorization) {
   1660        rv = mAuthChannel->SetProxyCredentials(creds);
   1661        MOZ_ASSERT(NS_SUCCEEDED(rv));
   1662      } else {
   1663        rv = mAuthChannel->SetWWWCredentials(creds);
   1664        MOZ_ASSERT(NS_SUCCEEDED(rv));
   1665      }
   1666    } else {
   1667      ident.Clear();  // don't remember the identity
   1668    }
   1669  }
   1670 }
   1671 
   1672 nsresult nsHttpChannelAuthProvider::GetCurrentPath(nsACString& path) {
   1673  nsresult rv;
   1674  nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
   1675  if (url) {
   1676    rv = url->GetDirectory(path);
   1677  } else {
   1678    rv = mURI->GetPathQueryRef(path);
   1679  }
   1680  return rv;
   1681 }
   1682 
   1683 NS_IMPL_ISUPPORTS(nsHttpChannelAuthProvider, nsICancelable,
   1684                  nsIHttpChannelAuthProvider, nsIAuthPromptCallback,
   1685                  nsIHttpAuthenticatorCallback)
   1686 
   1687 }  // namespace mozilla::net