summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
parentInitial commit. (diff)
downloadthunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.tar.xz
thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/protocol/http/nsHttpChannelAuthProvider.cpp')
-rw-r--r--netwerk/protocol/http/nsHttpChannelAuthProvider.cpp1964
1 files changed, 1964 insertions, 0 deletions
diff --git a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
new file mode 100644
index 0000000000..8c4f834239
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
@@ -0,0 +1,1964 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set expandtab ts=4 sw=2 sts=2 cin: */
+/* 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/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/Tokenizer.h"
+#include "MockHttpAuth.h"
+#include "nsHttpChannelAuthProvider.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsHttpHandler.h"
+#include "nsIHttpAuthenticator.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsEscape.h"
+#include "nsAuthInformationHolder.h"
+#include "nsIStringBundle.h"
+#include "nsIPromptService.h"
+#include "netCore.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsIURI.h"
+#include "nsContentUtils.h"
+#include "nsHttp.h"
+#include "nsHttpBasicAuth.h"
+#include "nsHttpDigestAuth.h"
+#ifdef MOZ_AUTH_EXTENSION
+# include "nsHttpNegotiateAuth.h"
+#endif
+#include "nsHttpNTLMAuth.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIURL.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_prompts.h"
+#include "mozilla/Telemetry.h"
+#include "nsIProxiedChannel.h"
+#include "nsIProxyInfo.h"
+
+namespace mozilla::net {
+
+#define SUBRESOURCE_AUTH_DIALOG_DISALLOW_ALL 0
+#define SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN 1
+#define SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL 2
+
+#define HTTP_AUTH_DIALOG_TOP_LEVEL_DOC 29
+#define HTTP_AUTH_DIALOG_SAME_ORIGIN_SUBRESOURCE 30
+#define HTTP_AUTH_DIALOG_SAME_ORIGIN_XHR 31
+#define HTTP_AUTH_DIALOG_NON_WEB_CONTENT 32
+
+#define HTTP_AUTH_BASIC_INSECURE 0
+#define HTTP_AUTH_BASIC_SECURE 1
+#define HTTP_AUTH_DIGEST_INSECURE 2
+#define HTTP_AUTH_DIGEST_SECURE 3
+#define HTTP_AUTH_NTLM_INSECURE 4
+#define HTTP_AUTH_NTLM_SECURE 5
+#define HTTP_AUTH_NEGOTIATE_INSECURE 6
+#define HTTP_AUTH_NEGOTIATE_SECURE 7
+
+#define MAX_DISPLAYED_USER_LENGTH 64
+#define MAX_DISPLAYED_HOST_LENGTH 64
+
+static void GetOriginAttributesSuffix(nsIChannel* aChan, nsACString& aSuffix) {
+ OriginAttributes oa;
+
+ // Deliberately ignoring the result and going with defaults
+ if (aChan) {
+ StoragePrincipalHelper::GetOriginAttributesForNetworkState(aChan, oa);
+ }
+
+ oa.CreateSuffix(aSuffix);
+}
+
+nsHttpChannelAuthProvider::nsHttpChannelAuthProvider()
+ : mProxyAuth(false),
+ mTriedProxyAuth(false),
+ mTriedHostAuth(false),
+ mSuppressDefensiveAuth(false),
+ mCrossOrigin(false),
+ mConnectionBased(false),
+ mHttpHandler(gHttpHandler) {}
+
+nsHttpChannelAuthProvider::~nsHttpChannelAuthProvider() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mAuthChannel, "Disconnect wasn't called");
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::Init(nsIHttpAuthenticableChannel* channel) {
+ MOZ_ASSERT(channel, "channel expected!");
+
+ mAuthChannel = channel;
+
+ nsresult rv = mAuthChannel->GetURI(getter_AddRefs(mURI));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mAuthChannel->GetIsSSL(&mUsingSSL);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProxiedChannel> proxied(channel);
+ if (proxied) {
+ nsCOMPtr<nsIProxyInfo> pi;
+ rv = proxied->GetProxyInfo(getter_AddRefs(pi));
+ if (NS_FAILED(rv)) return rv;
+
+ if (pi) {
+ nsAutoCString proxyType;
+ rv = pi->GetType(proxyType);
+ if (NS_FAILED(rv)) return rv;
+
+ mProxyUsingSSL = proxyType.EqualsLiteral("https");
+ }
+ }
+
+ rv = mURI->GetAsciiHost(mHost);
+ if (NS_FAILED(rv)) return rv;
+
+ // reject the URL if it doesn't specify a host
+ if (mHost.IsEmpty()) return NS_ERROR_MALFORMED_URI;
+
+ rv = mURI->GetPort(&mPort);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> bareChannel = do_QueryInterface(channel);
+ mIsPrivate = NS_UsePrivateBrowsing(bareChannel);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::ProcessAuthentication(uint32_t httpStatus,
+ bool SSLConnectFailed) {
+ LOG(
+ ("nsHttpChannelAuthProvider::ProcessAuthentication "
+ "[this=%p channel=%p code=%u SSLConnectFailed=%d]\n",
+ this, mAuthChannel, httpStatus, SSLConnectFailed));
+
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv)) return rv;
+ if (proxyInfo) {
+ mProxyInfo = do_QueryInterface(proxyInfo);
+ if (!mProxyInfo) return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsAutoCString challenges;
+ mProxyAuth = (httpStatus == 407);
+
+ rv = PrepareForAuthentication(mProxyAuth);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mProxyAuth) {
+ // only allow a proxy challenge if we have a proxy server configured.
+ // otherwise, we could inadvertently expose the user's proxy
+ // credentials to an origin server. We could attempt to proceed as
+ // if we had received a 401 from the server, but why risk flirting
+ // with trouble? IE similarly rejects 407s when a proxy server is
+ // not configured, so there's no reason not to do the same.
+ if (!UsingHttpProxy()) {
+ LOG(("rejecting 407 when proxy server not configured!\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (UsingSSL() && !SSLConnectFailed) {
+ // we need to verify that this challenge came from the proxy
+ // server itself, and not some server on the other side of the
+ // SSL tunnel.
+ LOG(("rejecting 407 from origin server!\n"));
+ return NS_ERROR_UNEXPECTED;
+ }
+ rv = mAuthChannel->GetProxyChallenges(challenges);
+ } else {
+ rv = mAuthChannel->GetWWWChallenges(challenges);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString creds;
+ rv = GetCredentials(challenges, mProxyAuth, creds);
+ if (rv == NS_ERROR_IN_PROGRESS) return rv;
+ if (NS_FAILED(rv)) {
+ LOG(("unable to authenticate\n"));
+ } else {
+ // set the authentication credentials
+ if (mProxyAuth) {
+ rv = mAuthChannel->SetProxyCredentials(creds);
+ } else {
+ rv = mAuthChannel->SetWWWCredentials(creds);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::AddAuthorizationHeaders(
+ bool aDontUseCachedWWWCreds) {
+ LOG(
+ ("nsHttpChannelAuthProvider::AddAuthorizationHeaders? "
+ "[this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv)) return rv;
+ if (proxyInfo) {
+ mProxyInfo = do_QueryInterface(proxyInfo);
+ if (!mProxyInfo) return NS_ERROR_NO_INTERFACE;
+ }
+
+ uint32_t loadFlags;
+ rv = mAuthChannel->GetLoadFlags(&loadFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // this getter never fails
+ nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
+
+ // check if proxy credentials should be sent
+ if (!ProxyHost().IsEmpty() && UsingHttpProxy()) {
+ SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization, "http"_ns,
+ ProxyHost(), ProxyPort(),
+ ""_ns, // proxy has no path
+ mProxyIdent);
+ }
+
+ if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
+ LOG(("Skipping Authorization header for anonymous load\n"));
+ return NS_OK;
+ }
+
+ if (aDontUseCachedWWWCreds) {
+ LOG(
+ ("Authorization header already present:"
+ " skipping adding auth header from cache\n"));
+ return NS_OK;
+ }
+
+ // check if server credentials should be sent
+ nsAutoCString path, scheme;
+ if (NS_SUCCEEDED(GetCurrentPath(path)) &&
+ NS_SUCCEEDED(mURI->GetScheme(scheme))) {
+ SetAuthorizationHeader(authCache, nsHttp::Authorization, scheme, Host(),
+ Port(), path, mIdent);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::CheckForSuperfluousAuth() {
+ LOG(
+ ("nsHttpChannelAuthProvider::CheckForSuperfluousAuth? "
+ "[this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ // we've been called because it has been determined that this channel is
+ // getting loaded without taking the userpass from the URL. if the URL
+ // contained a userpass, then (provided some other conditions are true),
+ // we'll give the user an opportunity to abort the channel as this might be
+ // an attempt to spoof a different site (see bug 232567).
+ if (!ConfirmAuth("SuperfluousAuth", true)) {
+ // calling cancel here sets our mStatus and aborts the HTTP
+ // transaction, which prevents OnDataAvailable events.
+ Unused << mAuthChannel->Cancel(NS_ERROR_ABORT);
+ return NS_ERROR_ABORT;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::Cancel(nsresult status) {
+ MOZ_ASSERT(mAuthChannel, "Channel not initialized");
+
+ if (mAsyncPromptAuthCancelable) {
+ mAsyncPromptAuthCancelable->Cancel(status);
+ mAsyncPromptAuthCancelable = nullptr;
+ }
+
+ if (mGenerateCredentialsCancelable) {
+ mGenerateCredentialsCancelable->Cancel(status);
+ mGenerateCredentialsCancelable = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannelAuthProvider::Disconnect(nsresult status) {
+ mAuthChannel = nullptr;
+
+ if (mAsyncPromptAuthCancelable) {
+ mAsyncPromptAuthCancelable->Cancel(status);
+ mAsyncPromptAuthCancelable = nullptr;
+ }
+
+ if (mGenerateCredentialsCancelable) {
+ mGenerateCredentialsCancelable->Cancel(status);
+ mGenerateCredentialsCancelable = nullptr;
+ }
+
+ NS_IF_RELEASE(mProxyAuthContinuationState);
+ NS_IF_RELEASE(mAuthContinuationState);
+
+ return NS_OK;
+}
+
+// helper function for getting an auth prompt from an interface requestor
+static void GetAuthPrompt(nsIInterfaceRequestor* ifreq, bool proxyAuth,
+ nsIAuthPrompt2** result) {
+ if (!ifreq) return;
+
+ uint32_t promptReason;
+ if (proxyAuth) {
+ promptReason = nsIAuthPromptProvider::PROMPT_PROXY;
+ } else {
+ promptReason = nsIAuthPromptProvider::PROMPT_NORMAL;
+ }
+
+ nsCOMPtr<nsIAuthPromptProvider> promptProvider = do_GetInterface(ifreq);
+ if (promptProvider) {
+ promptProvider->GetAuthPrompt(promptReason, NS_GET_IID(nsIAuthPrompt2),
+ reinterpret_cast<void**>(result));
+ } else {
+ NS_QueryAuthPrompt2(ifreq, result);
+ }
+}
+
+// generate credentials for the given challenge, and update the auth cache.
+nsresult nsHttpChannelAuthProvider::GenCredsAndSetEntry(
+ nsIHttpAuthenticator* auth, bool proxyAuth, const nsACString& scheme,
+ const nsACString& host, int32_t port, const nsACString& directory,
+ const nsACString& realm, const nsACString& challenge,
+ const nsHttpAuthIdentity& ident, nsCOMPtr<nsISupports>& sessionState,
+ nsACString& result) {
+ nsresult rv;
+ nsISupports* ss = sessionState;
+
+ // set informations that depend on whether
+ // we're authenticating against a proxy
+ // or a webserver
+ nsISupports** continuationState;
+
+ if (proxyAuth) {
+ continuationState = &mProxyAuthContinuationState;
+ } else {
+ continuationState = &mAuthContinuationState;
+ }
+
+ rv = auth->GenerateCredentialsAsync(
+ mAuthChannel, this, challenge, proxyAuth, ident.Domain(), ident.User(),
+ ident.Password(), ss, *continuationState,
+ getter_AddRefs(mGenerateCredentialsCancelable));
+ if (NS_SUCCEEDED(rv)) {
+ // Calling generate credentials async, results will be dispatched to the
+ // main thread by calling OnCredsGenerated method
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ uint32_t generateFlags;
+ rv = auth->GenerateCredentials(
+ mAuthChannel, challenge, proxyAuth, ident.Domain(), ident.User(),
+ ident.Password(), &ss, &*continuationState, &generateFlags, result);
+
+ sessionState.swap(ss);
+ if (NS_FAILED(rv)) return rv;
+
+ // don't log this in release build since it could contain sensitive info.
+#ifdef DEBUG
+ LOG(("generated creds: %s\n", result.BeginReading()));
+#endif
+
+ return UpdateCache(auth, scheme, host, port, directory, realm, challenge,
+ ident, result, generateFlags, sessionState, proxyAuth);
+}
+
+nsresult nsHttpChannelAuthProvider::UpdateCache(
+ nsIHttpAuthenticator* auth, const nsACString& scheme,
+ const nsACString& host, int32_t port, const nsACString& directory,
+ const nsACString& realm, const nsACString& challenge,
+ const nsHttpAuthIdentity& ident, const nsACString& creds,
+ uint32_t generateFlags, nsISupports* sessionState, bool aProxyAuth) {
+ nsresult rv;
+
+ uint32_t authFlags;
+ rv = auth->GetAuthFlags(&authFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // find out if this authenticator allows reuse of credentials and/or
+ // challenge.
+ bool saveCreds =
+ 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CREDENTIALS);
+ bool saveChallenge =
+ 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CHALLENGE);
+
+ bool saveIdentity =
+ 0 == (generateFlags & nsIHttpAuthenticator::USING_INTERNAL_IDENTITY);
+
+ // this getter never fails
+ nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
+
+ nsAutoCString suffix;
+ if (!aProxyAuth) {
+ // We don't isolate proxy credentials cache entries with the origin suffix
+ // as it would only annoy users with authentication dialogs popping up.
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ GetOriginAttributesSuffix(chan, suffix);
+ }
+
+ // create a cache entry. we do this even though we don't yet know that
+ // these credentials are valid b/c we need to avoid prompting the user
+ // more than once in case the credentials are valid.
+ //
+ // if the credentials are not reusable, then we don't bother sticking
+ // them in the auth cache.
+ rv = authCache->SetAuthEntry(scheme, host, port, directory, realm,
+ saveCreds ? creds : ""_ns,
+ saveChallenge ? challenge : ""_ns, suffix,
+ saveIdentity ? &ident : nullptr, sessionState);
+ return rv;
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::ClearProxyIdent() {
+ LOG(("nsHttpChannelAuthProvider::ClearProxyIdent [this=%p]\n", this));
+
+ mProxyIdent.Clear();
+ return NS_OK;
+}
+
+nsresult nsHttpChannelAuthProvider::PrepareForAuthentication(bool proxyAuth) {
+ LOG(
+ ("nsHttpChannelAuthProvider::PrepareForAuthentication "
+ "[this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ if (!proxyAuth) {
+ // reset the current proxy continuation state because our last
+ // authentication attempt was completed successfully.
+ NS_IF_RELEASE(mProxyAuthContinuationState);
+ LOG((" proxy continuation state has been reset"));
+ }
+
+ if (!UsingHttpProxy() || mProxyAuthType.IsEmpty()) return NS_OK;
+
+ // We need to remove any Proxy_Authorization header left over from a
+ // non-request based authentication handshake (e.g., for NTLM auth).
+
+ nsresult rv;
+ nsCOMPtr<nsIHttpAuthenticator> precedingAuth;
+ nsCString proxyAuthType;
+ rv = GetAuthenticator(mProxyAuthType, proxyAuthType,
+ getter_AddRefs(precedingAuth));
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t precedingAuthFlags;
+ rv = precedingAuth->GetAuthFlags(&precedingAuthFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!(precedingAuthFlags & nsIHttpAuthenticator::REQUEST_BASED)) {
+ nsAutoCString challenges;
+ rv = mAuthChannel->GetProxyChallenges(challenges);
+ if (NS_FAILED(rv)) {
+ // delete the proxy authorization header because we weren't
+ // asked to authenticate
+ rv = mAuthChannel->SetProxyCredentials(""_ns);
+ if (NS_FAILED(rv)) return rv;
+ LOG((" cleared proxy authorization header"));
+ }
+ }
+
+ return NS_OK;
+}
+
+class MOZ_STACK_CLASS ChallengeParser final : Tokenizer {
+ public:
+ explicit ChallengeParser(const nsACString& aChallenges)
+ : Tokenizer(aChallenges, nullptr, "") {
+ Record();
+ }
+
+ Maybe<nsDependentCSubstring> GetNext() {
+ Token t;
+ nsDependentCSubstring result;
+
+ bool inQuote = false;
+
+ while (Next(t)) {
+ if (t.Type() == TOKEN_EOL) {
+ Claim(result, ClaimInclusion::EXCLUDE_LAST);
+ SkipWhites(WhiteSkipping::INCLUDE_NEW_LINE);
+ Record();
+ inQuote = false;
+ if (!result.IsEmpty()) {
+ return Some(result);
+ }
+ } else if (t.Equals(Token::Char(',')) && !inQuote &&
+ StaticPrefs::
+ network_auth_allow_multiple_challenges_same_line()) {
+ // Sometimes we get multiple challenges separated by a comma.
+ // This is not great, as it's slightly ambiguous. We check if something
+ // is a new challenge by matching agains <param_name> =
+ // If the , isn't followed by a word and = then most likely
+ // it is the name of an authType.
+
+ const char* prevCursorPos = mCursor;
+ const char* prevRollbackPos = mRollback;
+
+ auto hasWordAndEqual = [&]() {
+ SkipWhites();
+ nsDependentCSubstring word;
+ if (!ReadWord(word)) {
+ return false;
+ }
+ SkipWhites();
+ return Check(Token::Char('='));
+ };
+ if (!hasWordAndEqual()) {
+ // This is not a parameter. It means the `,` character starts a
+ // different challenge.
+ // We'll revert the cursor and return the contents so far.
+ mCursor = prevCursorPos;
+ mRollback = prevRollbackPos;
+ Claim(result, ClaimInclusion::EXCLUDE_LAST);
+ SkipWhites();
+ Record();
+ if (!result.IsEmpty()) {
+ return Some(result);
+ }
+ }
+ } else if (t.Equals(Token::Char('"'))) {
+ inQuote = !inQuote;
+ }
+ }
+
+ Claim(result, Tokenizer::ClaimInclusion::INCLUDE_LAST);
+ SkipWhites();
+ Record();
+ if (!result.IsEmpty()) {
+ return Some(result);
+ }
+ return Nothing{};
+ }
+};
+
+enum ChallengeRank {
+ Unknown = 0,
+ Basic = 1,
+ Digest = 2,
+ NTLM = 3,
+ Negotiate = 4,
+};
+
+ChallengeRank Rank(const nsACString& aChallenge) {
+ if (StringBeginsWith(aChallenge, "Negotiate"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ return ChallengeRank::Negotiate;
+ }
+
+ if (StringBeginsWith(aChallenge, "NTLM"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ return ChallengeRank::NTLM;
+ }
+
+ if (StringBeginsWith(aChallenge, "Digest"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ return ChallengeRank::Digest;
+ }
+
+ if (StringBeginsWith(aChallenge, "Basic"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ return ChallengeRank::Basic;
+ }
+
+ return ChallengeRank::Unknown;
+}
+
+nsresult nsHttpChannelAuthProvider::GetCredentials(
+ const nsACString& aChallenges, bool proxyAuth, nsCString& creds) {
+ LOG(("nsHttpChannelAuthProvider::GetCredentials"));
+ nsAutoCString challenges(aChallenges);
+
+ using AuthChallenge = struct AuthChallenge {
+ nsDependentCSubstring challenge;
+ uint16_t algorithm = 0;
+ ChallengeRank rank = ChallengeRank::Unknown;
+
+ void operator=(const AuthChallenge& aOther) {
+ challenge.Rebind(aOther.challenge, 0);
+ algorithm = aOther.algorithm;
+ rank = aOther.rank;
+ }
+ };
+
+ nsTArray<AuthChallenge> cc;
+
+ ChallengeParser p(challenges);
+ while (true) {
+ auto next = p.GetNext();
+ if (next.isNothing()) {
+ break;
+ }
+ AuthChallenge ac{next.ref(), 0};
+ nsAutoCString realm, domain, nonce, opaque;
+ bool stale = false;
+ uint16_t qop = 0;
+ ac.rank = Rank(ac.challenge);
+ if (StringBeginsWith(ac.challenge, "Digest"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ Unused << nsHttpDigestAuth::ParseChallenge(ac.challenge, realm, domain,
+ nonce, opaque, &stale,
+ &ac.algorithm, &qop);
+ }
+ cc.AppendElement(ac);
+ }
+
+ cc.StableSort([](const AuthChallenge& lhs, const AuthChallenge& rhs) {
+ if (StaticPrefs::network_auth_choose_most_secure_challenge()) {
+ // Different auth types
+ if (lhs.rank != rhs.rank) {
+ return lhs.rank < rhs.rank ? 1 : -1;
+ }
+
+ // If they're the same auth type, and not a Digest, then we treat them
+ // as equal (don't reorder them).
+ if (lhs.rank != ChallengeRank::Digest) {
+ return 0;
+ }
+ } else {
+ // Non-digest challenges should not be reordered when the pref is off.
+ if (lhs.algorithm == 0 || rhs.algorithm == 0) {
+ return 0;
+ }
+ }
+
+ // Same algorithm.
+ if (lhs.algorithm == rhs.algorithm) {
+ return 0;
+ }
+ return lhs.algorithm < rhs.algorithm ? 1 : -1;
+ });
+
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ nsCString authType; // force heap allocation to enable string sharing since
+ // we'll be assigning this value into mAuthType.
+
+ // set informations that depend on whether we're authenticating against a
+ // proxy or a webserver
+ nsISupports** currentContinuationState;
+ nsCString* currentAuthType;
+
+ if (proxyAuth) {
+ currentContinuationState = &mProxyAuthContinuationState;
+ currentAuthType = &mProxyAuthType;
+ } else {
+ currentContinuationState = &mAuthContinuationState;
+ currentAuthType = &mAuthType;
+ }
+
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ bool gotCreds = false;
+
+ // figure out which challenge we can handle and which authenticator to use.
+ for (size_t i = 0; i < cc.Length(); i++) {
+ rv = GetAuthenticator(cc[i].challenge, authType, getter_AddRefs(auth));
+ LOG(("trying auth for %s", authType.get()));
+ if (NS_SUCCEEDED(rv)) {
+ //
+ // if we've already selected an auth type from a previous challenge
+ // received while processing this channel, then skip others until
+ // we find a challenge corresponding to the previously tried auth
+ // type.
+ //
+ if (!currentAuthType->IsEmpty() && authType != *currentAuthType) continue;
+
+ //
+ // we allow the routines to run all the way through before we
+ // decide if they are valid.
+ //
+ // we don't worry about the auth cache being altered because that
+ // would have been the last step, and if the error is from updating
+ // the authcache it wasn't really altered anyway. -CTN
+ //
+ // at this point the code is really only useful for client side
+ // errors (it will not automatically fail over to do a different
+ // auth type if the server keeps rejecting what is being sent, even
+ // if a particular auth method only knows 1 thing, like a
+ // non-identity based authentication method)
+ //
+ rv = GetCredentialsForChallenge(cc[i].challenge, authType, proxyAuth,
+ auth, creds);
+ if (NS_SUCCEEDED(rv)) {
+ gotCreds = true;
+ *currentAuthType = authType;
+
+ break;
+ }
+ if (rv == NS_ERROR_IN_PROGRESS) {
+ // authentication prompt has been invoked and result is
+ // expected asynchronously, save current challenge being
+ // processed and all remaining challenges to use later in
+ // OnAuthAvailable and now immediately return
+ mCurrentChallenge = cc[i].challenge;
+ // imperfect; does not save server-side preference ordering.
+ // instead, continues with remaining string as provided by client
+ mRemainingChallenges.Truncate();
+ while (i + 1 < cc.Length()) {
+ i++;
+ mRemainingChallenges.Append(cc[i].challenge);
+ mRemainingChallenges.Append("\n"_ns);
+ }
+ return rv;
+ }
+
+ // reset the auth type and continuation state
+ NS_IF_RELEASE(*currentContinuationState);
+ currentAuthType->Truncate();
+ }
+ }
+
+ if (!gotCreds && !currentAuthType->IsEmpty()) {
+ // looks like we never found the auth type we were looking for.
+ // reset the auth type and continuation state, and try again.
+ currentAuthType->Truncate();
+ NS_IF_RELEASE(*currentContinuationState);
+
+ rv = GetCredentials(challenges, proxyAuth, creds);
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannelAuthProvider::GetAuthorizationMembers(
+ bool proxyAuth, nsACString& scheme, nsCString& host, int32_t& port,
+ nsACString& path, nsHttpAuthIdentity*& ident,
+ nsISupports**& continuationState) {
+ if (proxyAuth) {
+ MOZ_ASSERT(UsingHttpProxy(),
+ "proxyAuth is true, but no HTTP proxy is configured!");
+
+ host = ProxyHost();
+ port = ProxyPort();
+ ident = &mProxyIdent;
+ scheme.AssignLiteral("http");
+
+ continuationState = &mProxyAuthContinuationState;
+ } else {
+ host = Host();
+ port = Port();
+ ident = &mIdent;
+
+ nsresult rv;
+ rv = GetCurrentPath(path);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ continuationState = &mAuthContinuationState;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannelAuthProvider::GetCredentialsForChallenge(
+ const nsACString& aChallenge, const nsACString& aAuthType, bool proxyAuth,
+ nsIHttpAuthenticator* auth, nsCString& creds) {
+ LOG(
+ ("nsHttpChannelAuthProvider::GetCredentialsForChallenge "
+ "[this=%p channel=%p proxyAuth=%d challenges=%s]\n",
+ this, mAuthChannel, proxyAuth, nsCString(aChallenge).get()));
+
+ // this getter never fails
+ nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
+
+ uint32_t authFlags;
+ nsresult rv = auth->GetAuthFlags(&authFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString realm;
+ ParseRealm(aChallenge, realm);
+
+ // if no realm, then use the auth type as the realm. ToUpperCase so the
+ // ficticious realm stands out a bit more.
+ // XXX this will cause some single signon misses!
+ // XXX this was meant to be used with NTLM, which supplies no realm.
+ /*
+ if (realm.IsEmpty()) {
+ realm = authType;
+ ToUpperCase(realm);
+ }
+ */
+
+ // set informations that depend on whether
+ // we're authenticating against a proxy
+ // or a webserver
+ nsAutoCString host;
+ int32_t port;
+ nsHttpAuthIdentity* ident;
+ nsAutoCString path, scheme;
+ bool identFromURI = false;
+ nsISupports** continuationState;
+
+ rv = GetAuthorizationMembers(proxyAuth, scheme, host, port, path, ident,
+ continuationState);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t loadFlags;
+ rv = mAuthChannel->GetLoadFlags(&loadFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ // Fill only for non-proxy auth, proxy credentials are not OA-isolated.
+ nsAutoCString suffix;
+
+ if (!proxyAuth) {
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ GetOriginAttributesSuffix(chan, suffix);
+
+ // if this is the first challenge, then try using the identity
+ // specified in the URL.
+ if (mIdent.IsEmpty()) {
+ GetIdentityFromURI(authFlags, mIdent);
+ identFromURI = !mIdent.IsEmpty();
+ }
+
+ if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !identFromURI) {
+ LOG(("Skipping authentication for anonymous non-proxy request\n"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Let explicit URL credentials pass
+ // regardless of the LOAD_ANONYMOUS flag
+ } else if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !UsingHttpProxy()) {
+ LOG(("Skipping authentication for anonymous non-proxy request\n"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ //
+ // if we already tried some credentials for this transaction, then
+ // we need to possibly clear them from the cache, unless the credentials
+ // in the cache have changed, in which case we'd want to give them a
+ // try instead.
+ //
+ nsHttpAuthEntry* entry = nullptr;
+ Unused << authCache->GetAuthEntryForDomain(scheme, host, port, realm, suffix,
+ &entry);
+
+ // hold reference to the auth session state (in case we clear our
+ // reference to the entry).
+ nsCOMPtr<nsISupports> sessionStateGrip;
+ if (entry) sessionStateGrip = entry->mMetaData;
+
+ // remember if we already had the continuation state. it means we are in
+ // the middle of the authentication exchange and the connection must be
+ // kept sticky then (and only then).
+ bool authAtProgress = !!*continuationState;
+
+ // for digest auth, maybe our cached nonce value simply timed out...
+ bool identityInvalid;
+ nsISupports* sessionState = sessionStateGrip;
+ rv = auth->ChallengeReceived(mAuthChannel, aChallenge, proxyAuth,
+ &sessionState, &*continuationState,
+ &identityInvalid);
+ sessionStateGrip.swap(sessionState);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG((" identity invalid = %d\n", identityInvalid));
+
+ if (mConnectionBased && identityInvalid) {
+ // If the flag is set and identity is invalid, it means we received the
+ // first challange for a new negotiation round after negotiating a
+ // connection based auth failed (invalid password). The mConnectionBased
+ // flag is set later for the newly received challenge, so here it reflects
+ // the previous 401/7 response schema.
+ rv = mAuthChannel->CloseStickyConnection();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (!proxyAuth) {
+ // We must clear proxy ident in the following scenario + explanation:
+ // - we are authenticating to an NTLM proxy and an NTLM server
+ // - we successfully authenticated to the proxy, mProxyIdent keeps
+ // the user name/domain and password, the identity has also been cached
+ // - we just threw away the connection because we are now asking for
+ // creds for the server (WWW auth)
+ // - hence, we will have to auth to the proxy again as well
+ // - if we didn't clear the proxy identity, it would be considered
+ // as non-valid and we would ask the user again ; clearing it forces
+ // use of the cached identity and not asking the user again
+ ClearProxyIdent();
+ }
+ }
+
+ mConnectionBased = !!(authFlags & nsIHttpAuthenticator::CONNECTION_BASED);
+
+ // It's legal if the peer closes the connection after the first 401/7.
+ // Making the connection sticky will prevent its restart giving the user
+ // a 'network reset' error every time. Hence, we mark the connection
+ // as restartable.
+ mAuthChannel->ConnectionRestartable(!authAtProgress);
+
+ if (identityInvalid) {
+ if (entry) {
+ if (ident->Equals(entry->Identity())) {
+ if (!identFromURI) {
+ LOG((" clearing bad auth cache entry\n"));
+ // ok, we've already tried this user identity, so clear the
+ // corresponding entry from the auth cache.
+ authCache->ClearAuthEntry(scheme, host, port, realm, suffix);
+ entry = nullptr;
+ ident->Clear();
+ }
+ } else if (!identFromURI ||
+ (ident->User() == entry->Identity().User() &&
+ !(loadFlags & (nsIChannel::LOAD_ANONYMOUS |
+ nsIChannel::LOAD_EXPLICIT_CREDENTIALS)))) {
+ LOG((" taking identity from auth cache\n"));
+ // the password from the auth cache is more likely to be
+ // correct than the one in the URL. at least, we know that it
+ // works with the given username. it is possible for a server
+ // to distinguish logons based on the supplied password alone,
+ // but that would be quite unusual... and i don't think we need
+ // to worry about such unorthodox cases.
+ *ident = entry->Identity();
+ identFromURI = false;
+ if (entry->Creds()[0] != '\0') {
+ LOG((" using cached credentials!\n"));
+ creds.Assign(entry->Creds());
+ return entry->AddPath(path);
+ }
+ }
+ } else if (!identFromURI) {
+ // hmm... identity invalid, but no auth entry! the realm probably
+ // changed (see bug 201986).
+ ident->Clear();
+ }
+
+ if (!entry && ident->IsEmpty()) {
+ uint32_t level = nsIAuthPrompt2::LEVEL_NONE;
+ if ((!proxyAuth && mUsingSSL) || (proxyAuth && mProxyUsingSSL)) {
+ level = nsIAuthPrompt2::LEVEL_SECURE;
+ } else if (authFlags & nsIHttpAuthenticator::IDENTITY_ENCRYPTED) {
+ level = nsIAuthPrompt2::LEVEL_PW_ENCRYPTED;
+ }
+
+ // Collect statistics on how frequently the various types of HTTP
+ // authentication are used over SSL and non-SSL connections.
+ if (Telemetry::CanRecordPrereleaseData()) {
+ if ("basic"_ns.Equals(aAuthType, nsCaseInsensitiveCStringComparator)) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_BASIC_SECURE : HTTP_AUTH_BASIC_INSECURE);
+ } else if ("digest"_ns.Equals(aAuthType,
+ nsCaseInsensitiveCStringComparator)) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_DIGEST_SECURE : HTTP_AUTH_DIGEST_INSECURE);
+ } else if ("ntlm"_ns.Equals(aAuthType,
+ nsCaseInsensitiveCStringComparator)) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_NTLM_SECURE : HTTP_AUTH_NTLM_INSECURE);
+ } else if ("negotiate"_ns.Equals(aAuthType,
+ nsCaseInsensitiveCStringComparator)) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_NEGOTIATE_SECURE
+ : HTTP_AUTH_NEGOTIATE_INSECURE);
+ }
+ }
+
+ // Depending on the pref setting, the authentication dialog may be
+ // blocked for all sub-resources, blocked for cross-origin
+ // sub-resources, or always allowed for sub-resources.
+ // For more details look at the bug 647010.
+ // BlockPrompt will set mCrossOrigin parameter as well.
+ if (BlockPrompt(proxyAuth)) {
+ LOG((
+ "nsHttpChannelAuthProvider::GetCredentialsForChallenge: "
+ "Prompt is blocked [this=%p pref=%d img-pref=%d "
+ "non-web-content-triggered-pref=%d]\n",
+ this, StaticPrefs::network_auth_subresource_http_auth_allow(),
+ StaticPrefs::
+ network_auth_subresource_img_cross_origin_http_auth_allow(),
+ StaticPrefs::
+ network_auth_non_web_content_triggered_resources_http_auth_allow()));
+ return NS_ERROR_ABORT;
+ }
+
+ // at this point we are forced to interact with the user to get
+ // their username and password for this domain.
+ rv = PromptForIdentity(level, proxyAuth, realm, aAuthType, authFlags,
+ *ident);
+ if (NS_FAILED(rv)) return rv;
+ identFromURI = false;
+ }
+ }
+
+ if (identFromURI) {
+ // Warn the user before automatically using the identity from the URL
+ // to automatically log them into a site (see bug 232567).
+ if (!ConfirmAuth("AutomaticAuth", false)) {
+ // calling cancel here sets our mStatus and aborts the HTTP
+ // transaction, which prevents OnDataAvailable events.
+ rv = mAuthChannel->Cancel(NS_ERROR_ABORT);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ // this return code alone is not equivalent to Cancel, since
+ // it only instructs our caller that authentication failed.
+ // without an explicit call to Cancel, our caller would just
+ // load the page that accompanies the HTTP auth challenge.
+ return NS_ERROR_ABORT;
+ }
+ }
+
+ //
+ // get credentials for the given user:pass
+ //
+ // always store the credentials we're trying now so that they will be used
+ // on subsequent links. This will potentially remove good credentials from
+ // the cache. This is ok as we don't want to use cached credentials if the
+ // user specified something on the URI or in another manner. This is so
+ // that we don't transparently authenticate as someone they're not
+ // expecting to authenticate as.
+ //
+ nsCString result;
+ rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port, path, realm,
+ aChallenge, *ident, sessionStateGrip, creds);
+ return rv;
+}
+
+bool nsHttpChannelAuthProvider::BlockPrompt(bool proxyAuth) {
+ // Verify that it's ok to prompt for credentials here, per spec
+ // http://xhr.spec.whatwg.org/#the-send%28%29-method
+
+ nsCOMPtr<nsIHttpChannelInternal> chanInternal =
+ do_QueryInterface(mAuthChannel);
+ MOZ_ASSERT(chanInternal);
+
+ if (chanInternal->GetBlockAuthPrompt()) {
+ LOG(
+ ("nsHttpChannelAuthProvider::BlockPrompt: Prompt is blocked "
+ "[this=%p channel=%p]\n",
+ this, mAuthChannel));
+ return true;
+ }
+
+ if (proxyAuth) {
+ // Do not block auth-dialog if this is a proxy authentication.
+ return false;
+ }
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+
+ // We will treat loads w/o loadInfo as a top level document.
+ bool topDoc = true;
+ bool xhr = false;
+ bool nonWebContent = false;
+
+ if (loadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ topDoc = false;
+ }
+
+ if (!topDoc) {
+ nsCOMPtr<nsIPrincipal> triggeringPrinc = loadInfo->TriggeringPrincipal();
+ if (triggeringPrinc->IsSystemPrincipal()) {
+ nonWebContent = true;
+ }
+ }
+
+ if (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_XMLHTTPREQUEST) {
+ xhr = true;
+ }
+
+ if (!topDoc && !xhr) {
+ nsCOMPtr<nsIURI> topURI;
+ Unused << chanInternal->GetTopWindowURI(getter_AddRefs(topURI));
+ if (topURI) {
+ mCrossOrigin = !NS_SecurityCompareURIs(topURI, mURI, true);
+ } else {
+ nsIPrincipal* loadingPrinc = loadInfo->GetLoadingPrincipal();
+ MOZ_ASSERT(loadingPrinc);
+ mCrossOrigin = !loadingPrinc->IsSameOrigin(mURI);
+ }
+ }
+
+ if (Telemetry::CanRecordPrereleaseData()) {
+ if (topDoc) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ HTTP_AUTH_DIALOG_TOP_LEVEL_DOC);
+ } else if (nonWebContent) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ HTTP_AUTH_DIALOG_NON_WEB_CONTENT);
+ } else if (!mCrossOrigin) {
+ if (xhr) {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ HTTP_AUTH_DIALOG_SAME_ORIGIN_XHR);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ HTTP_AUTH_DIALOG_SAME_ORIGIN_SUBRESOURCE);
+ }
+ } else {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_DIALOG_STATS_3,
+ static_cast<uint32_t>(loadInfo->GetExternalContentPolicyType()));
+ }
+ }
+
+ if (!topDoc &&
+ !StaticPrefs::
+ network_auth_non_web_content_triggered_resources_http_auth_allow() &&
+ nonWebContent) {
+ return true;
+ }
+
+ switch (StaticPrefs::network_auth_subresource_http_auth_allow()) {
+ case SUBRESOURCE_AUTH_DIALOG_DISALLOW_ALL:
+ // Do not open the http-authentication credentials dialog for
+ // the sub-resources.
+ return !topDoc && !xhr;
+ case SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN:
+ // Open the http-authentication credentials dialog for
+ // the sub-resources only if they are not cross-origin.
+ return !topDoc && !xhr && mCrossOrigin;
+ case SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL:
+ // Allow the http-authentication dialog for subresources.
+ // If pref network.auth.subresource-img-cross-origin-http-auth-allow
+ // is set, http-authentication dialog for image subresources is
+ // blocked.
+ if (mCrossOrigin &&
+ !StaticPrefs::
+ network_auth_subresource_img_cross_origin_http_auth_allow() &&
+ loadInfo &&
+ ((loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_IMAGE) ||
+ (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_IMAGESET))) {
+ return true;
+ }
+ return false;
+ default:
+ // This is an invalid value.
+ MOZ_ASSERT(false, "A non valid value!");
+ }
+ return false;
+}
+
+inline void GetAuthType(const nsACString& aChallenge, nsCString& authType) {
+ auto spaceIndex = aChallenge.FindChar(' ');
+ authType = Substring(aChallenge, 0, spaceIndex);
+ // normalize to lowercase
+ ToLowerCase(authType);
+}
+
+nsresult nsHttpChannelAuthProvider::GetAuthenticator(
+ const nsACString& aChallenge, nsCString& authType,
+ nsIHttpAuthenticator** auth) {
+ LOG(("nsHttpChannelAuthProvider::GetAuthenticator [this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ GetAuthType(aChallenge, authType);
+
+ nsCOMPtr<nsIHttpAuthenticator> authenticator;
+#ifdef MOZ_AUTH_EXTENSION
+ if (authType.EqualsLiteral("negotiate")) {
+ authenticator = nsHttpNegotiateAuth::GetOrCreate();
+ } else
+#endif
+ if (authType.EqualsLiteral("basic")) {
+ authenticator = nsHttpBasicAuth::GetOrCreate();
+ } else if (authType.EqualsLiteral("digest")) {
+ authenticator = nsHttpDigestAuth::GetOrCreate();
+ } else if (authType.EqualsLiteral("ntlm")) {
+ authenticator = nsHttpNTLMAuth::GetOrCreate();
+ } else if (authType.EqualsLiteral("mock_auth") &&
+ PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
+ authenticator = MockHttpAuth::Create();
+ } else {
+ return NS_ERROR_FACTORY_NOT_REGISTERED;
+ }
+
+ if (!authenticator) {
+ // If called during shutdown it's possible that the singleton authenticator
+ // was already cleared so we have a null one here.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ MOZ_ASSERT(authenticator);
+ authenticator.forget(auth);
+
+ return NS_OK;
+}
+
+// buf contains "domain\user"
+static void ParseUserDomain(const nsAString& buf, nsDependentSubstring& user,
+ nsDependentSubstring& domain) {
+ auto backslashPos = buf.FindChar(u'\\');
+ if (backslashPos != kNotFound) {
+ domain.Rebind(buf, 0, backslashPos);
+ user.Rebind(buf, backslashPos + 1);
+ }
+}
+
+void nsHttpChannelAuthProvider::GetIdentityFromURI(uint32_t authFlags,
+ nsHttpAuthIdentity& ident) {
+ LOG(("nsHttpChannelAuthProvider::GetIdentityFromURI [this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ nsAutoString userBuf;
+ nsAutoString passBuf;
+
+ // XXX i18n
+ nsAutoCString buf;
+ mURI->GetUsername(buf);
+ if (!buf.IsEmpty()) {
+ NS_UnescapeURL(buf);
+ CopyUTF8toUTF16(buf, userBuf);
+ mURI->GetPassword(buf);
+ if (!buf.IsEmpty()) {
+ NS_UnescapeURL(buf);
+ CopyUTF8toUTF16(buf, passBuf);
+ }
+ }
+
+ if (!userBuf.IsEmpty()) {
+ nsDependentSubstring user(userBuf, 0);
+ nsDependentSubstring domain(u""_ns, 0);
+
+ if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN) {
+ ParseUserDomain(userBuf, user, domain);
+ }
+
+ ident = nsHttpAuthIdentity(domain, user, passBuf);
+ }
+}
+
+static void OldParseRealm(const nsACString& aChallenge, nsACString& realm) {
+ //
+ // From RFC2617 section 1.2, the realm value is defined as such:
+ //
+ // realm = "realm" "=" realm-value
+ // realm-value = quoted-string
+ //
+ // but, we'll accept anything after the the "=" up to the first space, or
+ // end-of-line, if the string is not quoted.
+ //
+
+ const nsCString& flat = PromiseFlatCString(aChallenge);
+ const char* challenge = flat.get();
+
+ const char* p = nsCRT::strcasestr(challenge, "realm=");
+ if (p) {
+ bool has_quote = false;
+ p += 6;
+ if (*p == '"') {
+ has_quote = true;
+ p++;
+ }
+
+ const char* end;
+ if (has_quote) {
+ end = p;
+ while (*end) {
+ if (*end == '\\') {
+ // escaped character, store that one instead if not zero
+ if (!*++end) break;
+ } else if (*end == '\"') {
+ // end of string
+ break;
+ }
+
+ realm.Append(*end);
+ ++end;
+ }
+ } else {
+ // realm given without quotes
+ end = strchr(p, ' ');
+ if (end) {
+ realm.Assign(p, end - p);
+ } else {
+ realm.Assign(p);
+ }
+ }
+ }
+}
+
+void nsHttpChannelAuthProvider::ParseRealm(const nsACString& aChallenge,
+ nsACString& realm) {
+ //
+ // From RFC2617 section 1.2, the realm value is defined as such:
+ //
+ // realm = "realm" "=" realm-value
+ // realm-value = quoted-string
+ //
+ // but, we'll accept anything after the the "=" up to the first space, or
+ // end-of-line, if the string is not quoted.
+ //
+
+ if (!StaticPrefs::network_auth_use_new_parse_realm()) {
+ OldParseRealm(aChallenge, realm);
+ return;
+ }
+
+ Tokenizer t(aChallenge);
+
+ // The challenge begins with the authType.
+ // If we can't find that something has probably gone wrong.
+ t.SkipWhites();
+ nsDependentCSubstring authType;
+ if (!t.ReadWord(authType)) {
+ return;
+ }
+
+ // Will return true if the tokenizer advanced the cursor - false otherwise.
+ auto readParam = [&](nsDependentCSubstring& key, nsAutoCString& value) {
+ key.Rebind(EmptyCString(), 0);
+ value.Truncate();
+
+ t.SkipWhites();
+ if (!t.ReadWord(key)) {
+ return false;
+ }
+ t.SkipWhites();
+ if (!t.CheckChar('=')) {
+ return true;
+ }
+ t.SkipWhites();
+
+ Tokenizer::Token token1;
+
+ t.Record();
+ if (!t.Next(token1)) {
+ return true;
+ }
+ nsDependentCSubstring sub;
+ bool hasQuote = false;
+ if (token1.Equals(Tokenizer::Token::Char('"'))) {
+ hasQuote = true;
+ } else {
+ t.Claim(sub, Tokenizer::ClaimInclusion::INCLUDE_LAST);
+ value.Append(sub);
+ }
+ t.Record();
+ Tokenizer::Token token2;
+ while (t.Next(token2)) {
+ if (hasQuote && token2.Equals(Tokenizer::Token::Char('"')) &&
+ !token1.Equals(Tokenizer::Token::Char('\\'))) {
+ break;
+ }
+ if (!hasQuote && (token2.Type() == Tokenizer::TokenType::TOKEN_WS ||
+ token2.Type() == Tokenizer::TokenType::TOKEN_EOL)) {
+ break;
+ }
+
+ t.Claim(sub, Tokenizer::ClaimInclusion::INCLUDE_LAST);
+ if (!sub.Equals(R"(\)")) {
+ value.Append(sub);
+ }
+ t.Record();
+ token1 = token2;
+ }
+ return true;
+ };
+
+ while (!t.CheckEOF()) {
+ nsDependentCSubstring key;
+ nsAutoCString value;
+ // If we couldn't read anything, and the input isn't followed by a ,
+ // then we exit.
+ if (!readParam(key, value) && !t.Check(Tokenizer::Token::Char(','))) {
+ break;
+ }
+ // When we find the first instance of realm we exit.
+ // Theoretically there should be only one instance and we should fail
+ // if there are more, but we're trying to preserve existing behaviour.
+ if (key.Equals("realm"_ns, nsCaseInsensitiveCStringComparator)) {
+ realm = value;
+ break;
+ }
+ }
+}
+
+class nsHTTPAuthInformation : public nsAuthInformationHolder {
+ public:
+ nsHTTPAuthInformation(uint32_t aFlags, const nsString& aRealm,
+ const nsACString& aAuthType)
+ : nsAuthInformationHolder(aFlags, aRealm, aAuthType) {}
+
+ void SetToHttpAuthIdentity(uint32_t authFlags, nsHttpAuthIdentity& identity);
+};
+
+void nsHTTPAuthInformation::SetToHttpAuthIdentity(
+ uint32_t authFlags, nsHttpAuthIdentity& identity) {
+ identity = nsHttpAuthIdentity(Domain(), User(), Password());
+}
+
+nsresult nsHttpChannelAuthProvider::PromptForIdentity(
+ uint32_t level, bool proxyAuth, const nsACString& realm,
+ const nsACString& authType, uint32_t authFlags, nsHttpAuthIdentity& ident) {
+ LOG(("nsHttpChannelAuthProvider::PromptForIdentity [this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIAuthPrompt2> authPrompt;
+ GetAuthPrompt(callbacks, proxyAuth, getter_AddRefs(authPrompt));
+ if (!authPrompt && loadGroup) {
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ GetAuthPrompt(cbs, proxyAuth, getter_AddRefs(authPrompt));
+ }
+ if (!authPrompt) return NS_ERROR_NO_INTERFACE;
+
+ // XXX i18n: need to support non-ASCII realm strings (see bug 41489)
+ NS_ConvertASCIItoUTF16 realmU(realm);
+
+ // prompt the user...
+ uint32_t promptFlags = 0;
+ if (proxyAuth) {
+ promptFlags |= nsIAuthInformation::AUTH_PROXY;
+ if (mTriedProxyAuth) promptFlags |= nsIAuthInformation::PREVIOUS_FAILED;
+ mTriedProxyAuth = true;
+ } else {
+ promptFlags |= nsIAuthInformation::AUTH_HOST;
+ if (mTriedHostAuth) promptFlags |= nsIAuthInformation::PREVIOUS_FAILED;
+ mTriedHostAuth = true;
+ }
+
+ if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN) {
+ promptFlags |= nsIAuthInformation::NEED_DOMAIN;
+ }
+
+ if (mCrossOrigin) {
+ promptFlags |= nsIAuthInformation::CROSS_ORIGIN_SUB_RESOURCE;
+ }
+
+ RefPtr<nsHTTPAuthInformation> holder =
+ new nsHTTPAuthInformation(promptFlags, realmU, authType);
+ if (!holder) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(mAuthChannel, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = authPrompt->AsyncPromptAuth(channel, this, nullptr, level, holder,
+ getter_AddRefs(mAsyncPromptAuthCancelable));
+
+ if (NS_SUCCEEDED(rv)) {
+ // indicate using this error code that authentication prompt
+ // result is expected asynchronously
+ rv = NS_ERROR_IN_PROGRESS;
+ } else {
+ // Fall back to synchronous prompt
+ bool retval = false;
+ rv = authPrompt->PromptAuth(channel, level, holder, &retval);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!retval) {
+ rv = NS_ERROR_ABORT;
+ } else {
+ holder->SetToHttpAuthIdentity(authFlags, ident);
+ }
+ }
+
+ // remember that we successfully showed the user an auth dialog
+ if (!proxyAuth) mSuppressDefensiveAuth = true;
+
+ if (mConnectionBased) {
+ // Connection can be reset by the server in the meantime user is entering
+ // the credentials. Result would be just a "Connection was reset" error.
+ // Hence, we drop the current regardless if the user would make it on time
+ // to provide credentials.
+ // It's OK to send the NTLM type 1 message (response to the plain "NTLM"
+ // challenge) on a new connection.
+ {
+ DebugOnly<nsresult> rv = mAuthChannel->CloseStickyConnection();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthAvailable(
+ nsISupports* aContext, nsIAuthInformation* aAuthInfo) {
+ LOG(("nsHttpChannelAuthProvider::OnAuthAvailable [this=%p channel=%p]", this,
+ mAuthChannel));
+
+ mAsyncPromptAuthCancelable = nullptr;
+ if (!mAuthChannel) return NS_OK;
+
+ nsresult rv;
+
+ nsAutoCString host;
+ int32_t port;
+ nsHttpAuthIdentity* ident;
+ nsAutoCString path, scheme;
+ nsISupports** continuationState;
+ rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, path, ident,
+ continuationState);
+ if (NS_FAILED(rv)) OnAuthCancelled(aContext, false);
+
+ nsAutoCString realm;
+ ParseRealm(mCurrentChallenge, realm);
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ nsAutoCString suffix;
+ if (!mProxyAuth) {
+ // Fill only for non-proxy auth, proxy credentials are not OA-isolated.
+ GetOriginAttributesSuffix(chan, suffix);
+ }
+
+ nsHttpAuthCache* authCache = gHttpHandler->AuthCache(mIsPrivate);
+ nsHttpAuthEntry* entry = nullptr;
+ Unused << authCache->GetAuthEntryForDomain(scheme, host, port, realm, suffix,
+ &entry);
+
+ nsCOMPtr<nsISupports> sessionStateGrip;
+ if (entry) sessionStateGrip = entry->mMetaData;
+
+ nsAuthInformationHolder* holder =
+ static_cast<nsAuthInformationHolder*>(aAuthInfo);
+ *ident =
+ nsHttpAuthIdentity(holder->Domain(), holder->User(), holder->Password());
+
+ nsAutoCString unused;
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ rv = GetAuthenticator(mCurrentChallenge, unused, getter_AddRefs(auth));
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false, "GetAuthenticator failed");
+ OnAuthCancelled(aContext, true);
+ return NS_OK;
+ }
+
+ nsCString creds;
+ rv = GenCredsAndSetEntry(auth, mProxyAuth, scheme, host, port, path, realm,
+ mCurrentChallenge, *ident, sessionStateGrip, creds);
+
+ mCurrentChallenge.Truncate();
+ if (NS_FAILED(rv)) {
+ OnAuthCancelled(aContext, true);
+ return NS_OK;
+ }
+
+ return ContinueOnAuthAvailable(creds);
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthCancelled(nsISupports* aContext,
+ bool userCancel) {
+ LOG(("nsHttpChannelAuthProvider::OnAuthCancelled [this=%p channel=%p]", this,
+ mAuthChannel));
+
+ mAsyncPromptAuthCancelable = nullptr;
+ if (!mAuthChannel) return NS_OK;
+
+ // When user cancels or auth fails we want to close the connection for
+ // connection based schemes like NTLM. Some servers don't like re-negotiation
+ // on the same connection.
+ nsresult rv;
+ if (mConnectionBased) {
+ rv = mAuthChannel->CloseStickyConnection();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mConnectionBased = false;
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(mAuthChannel);
+ if (channel) {
+ nsresult status;
+ Unused << channel->GetStatus(&status);
+ if (NS_FAILED(status)) {
+ // If the channel is already cancelled, there is no need to deal with the
+ // rest challenges.
+ LOG((" Clear mRemainingChallenges, since mAuthChannel is cancelled"));
+ mRemainingChallenges.Truncate();
+ }
+ }
+
+ if (userCancel) {
+ if (!mRemainingChallenges.IsEmpty()) {
+ // there are still some challenges to process, do so
+
+ // Get rid of current continuationState to avoid reusing it in
+ // next challenges since it is no longer relevant.
+ if (mProxyAuth) {
+ NS_IF_RELEASE(mProxyAuthContinuationState);
+ } else {
+ NS_IF_RELEASE(mAuthContinuationState);
+ }
+ nsAutoCString creds;
+ rv = GetCredentials(mRemainingChallenges, mProxyAuth, creds);
+ if (NS_SUCCEEDED(rv)) {
+ // GetCredentials loaded the credentials from the cache or
+ // some other way in a synchronous manner, process those
+ // credentials now
+ mRemainingChallenges.Truncate();
+ return ContinueOnAuthAvailable(creds);
+ }
+ if (rv == NS_ERROR_IN_PROGRESS) {
+ // GetCredentials successfully queued another authprompt for
+ // a challenge from the list, we are now waiting for the user
+ // to provide the credentials
+ return NS_OK;
+ }
+
+ // otherwise, we failed...
+ }
+
+ mRemainingChallenges.Truncate();
+ }
+
+ rv = mAuthChannel->OnAuthCancelled(userCancel);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannelAuthProvider::OnCredsGenerated(
+ const nsACString& aGeneratedCreds, uint32_t aFlags, nsresult aResult,
+ nsISupports* aSessionState, nsISupports* aContinuationState) {
+ nsresult rv;
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // When channel is closed, do not proceed
+ if (!mAuthChannel) {
+ return NS_OK;
+ }
+
+ mGenerateCredentialsCancelable = nullptr;
+
+ if (NS_FAILED(aResult)) {
+ return OnAuthCancelled(nullptr, true);
+ }
+
+ // We want to update m(Proxy)AuthContinuationState in case it was changed by
+ // nsHttpNegotiateAuth::GenerateCredentials
+ nsCOMPtr<nsISupports> contState(aContinuationState);
+ if (mProxyAuth) {
+ contState.swap(mProxyAuthContinuationState);
+ } else {
+ contState.swap(mAuthContinuationState);
+ }
+
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ nsAutoCString unused;
+ rv = GetAuthenticator(mCurrentChallenge, unused, getter_AddRefs(auth));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString host;
+ int32_t port;
+ nsHttpAuthIdentity* ident;
+ nsAutoCString directory, scheme;
+ nsISupports** unusedContinuationState;
+
+ // Get realm from challenge
+ nsAutoCString realm;
+ ParseRealm(mCurrentChallenge, realm);
+
+ rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, directory, ident,
+ unusedContinuationState);
+ if (NS_FAILED(rv)) return rv;
+
+ rv =
+ UpdateCache(auth, scheme, host, port, directory, realm, mCurrentChallenge,
+ *ident, aGeneratedCreds, aFlags, aSessionState, mProxyAuth);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mCurrentChallenge.Truncate();
+
+ rv = ContinueOnAuthAvailable(aGeneratedCreds);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_OK;
+}
+
+nsresult nsHttpChannelAuthProvider::ContinueOnAuthAvailable(
+ const nsACString& creds) {
+ nsresult rv;
+ if (mProxyAuth) {
+ rv = mAuthChannel->SetProxyCredentials(creds);
+ } else {
+ rv = mAuthChannel->SetWWWCredentials(creds);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ // drop our remaining list of challenges. We don't need them, because we
+ // have now authenticated against a challenge and will be sending that
+ // information to the server (or proxy). If it doesn't accept our
+ // authentication it'll respond with failure and resend the challenge list
+ mRemainingChallenges.Truncate();
+
+ Unused << mAuthChannel->OnAuthAvailable();
+
+ return NS_OK;
+}
+
+bool nsHttpChannelAuthProvider::ConfirmAuth(const char* bundleKey,
+ bool doYesNoPrompt) {
+ // skip prompting the user if
+ // 1) prompts are disabled by pref
+ // 2) we've already prompted the user
+ // 3) we're not a toplevel channel
+ // 4) the userpass length is less than the "phishy" threshold
+
+ if (!StaticPrefs::network_auth_confirmAuth_enabled()) {
+ return true;
+ }
+
+ uint32_t loadFlags;
+ nsresult rv = mAuthChannel->GetLoadFlags(&loadFlags);
+ if (NS_FAILED(rv)) return true;
+
+ if (mSuppressDefensiveAuth ||
+ !(loadFlags & nsIChannel::LOAD_INITIAL_DOCUMENT_URI)) {
+ return true;
+ }
+
+ nsAutoCString userPass;
+ rv = mURI->GetUserPass(userPass);
+ if (NS_FAILED(rv) ||
+ (userPass.Length() < gHttpHandler->PhishyUserPassLength())) {
+ return true;
+ }
+
+ // we try to confirm by prompting the user. if we cannot do so, then
+ // assume the user said ok. this is done to keep things working in
+ // embedded builds, where the string bundle might not be present, etc.
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ if (!bundleService) return true;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
+ if (!bundle) return true;
+
+ nsAutoCString host;
+ rv = mURI->GetHost(host);
+ if (NS_FAILED(rv)) return true;
+
+ nsAutoCString user;
+ rv = mURI->GetUsername(user);
+ if (NS_FAILED(rv)) return true;
+
+ NS_ConvertUTF8toUTF16 ucsHost(host), ucsUser(user);
+
+ size_t userLength = ucsUser.Length();
+ if (userLength > MAX_DISPLAYED_USER_LENGTH) {
+ size_t desiredLength = MAX_DISPLAYED_USER_LENGTH;
+ // Don't cut off right before a low surrogate. Just include it.
+ if (NS_IS_LOW_SURROGATE(ucsUser[desiredLength])) {
+ desiredLength++;
+ }
+ ucsUser.Replace(desiredLength, userLength - desiredLength,
+ nsContentUtils::GetLocalizedEllipsis());
+ }
+
+ size_t hostLen = ucsHost.Length();
+ if (hostLen > MAX_DISPLAYED_HOST_LENGTH) {
+ size_t cutPoint = hostLen - MAX_DISPLAYED_HOST_LENGTH;
+ // Likewise, don't cut off right before a low surrogate here.
+ // Keep the low surrogate
+ if (NS_IS_LOW_SURROGATE(ucsHost[cutPoint])) {
+ cutPoint--;
+ }
+ // It's possible cutPoint was 1 and is now 0. Only insert the ellipsis
+ // if we're actually removing anything.
+ if (cutPoint > 0) {
+ ucsHost.Replace(0, cutPoint, nsContentUtils::GetLocalizedEllipsis());
+ }
+ }
+
+ AutoTArray<nsString, 2> strs = {ucsHost, ucsUser};
+
+ nsAutoString msg;
+ rv = bundle->FormatStringFromName(bundleKey, strs, msg);
+ if (NS_FAILED(rv)) return true;
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (NS_FAILED(rv)) return true;
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (NS_FAILED(rv)) return true;
+
+ nsCOMPtr<nsIPromptService> promptSvc =
+ do_GetService("@mozilla.org/prompter;1", &rv);
+ if (NS_FAILED(rv) || !promptSvc) {
+ return true;
+ }
+
+ // do not prompt again
+ mSuppressDefensiveAuth = true;
+
+ // Get current browsing context to use as prompt parent
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
+ if (!chan) {
+ return true;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+ RefPtr<mozilla::dom::BrowsingContext> browsingContext;
+ loadInfo->GetBrowsingContext(getter_AddRefs(browsingContext));
+
+ bool confirmed;
+ if (doYesNoPrompt) {
+ int32_t choice;
+ bool checkState = false;
+ rv = promptSvc->ConfirmExBC(
+ browsingContext, StaticPrefs::prompts_modalType_confirmAuth(), nullptr,
+ msg.get(),
+ nsIPromptService::BUTTON_POS_1_DEFAULT +
+ nsIPromptService::STD_YES_NO_BUTTONS,
+ nullptr, nullptr, nullptr, nullptr, &checkState, &choice);
+ if (NS_FAILED(rv)) return true;
+
+ confirmed = choice == 0;
+ } else {
+ rv = promptSvc->ConfirmBC(browsingContext,
+ StaticPrefs::prompts_modalType_confirmAuth(),
+ nullptr, msg.get(), &confirmed);
+ if (NS_FAILED(rv)) return true;
+ }
+
+ return confirmed;
+}
+
+void nsHttpChannelAuthProvider::SetAuthorizationHeader(
+ nsHttpAuthCache* authCache, const nsHttpAtom& header,
+ const nsACString& scheme, const nsACString& host, int32_t port,
+ const nsACString& path, nsHttpAuthIdentity& ident) {
+ nsHttpAuthEntry* entry = nullptr;
+ nsresult rv;
+
+ // 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) {
+ nsAutoCString type;
+ mProxyInfo->GetType(type);
+ if (type.EqualsLiteral("https")) {
+ // 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);
+ if (NS_SUCCEEDED(rv)) {
+ // if we are trying to add a header for origin server auth and if the
+ // URL contains an explicit username, then try the given username first.
+ // we only want to do this, however, if we know the URL requires auth
+ // based on the presence of an auth cache entry for this URL (which is
+ // true since we are here). but, if the username from the URL matches
+ // the username from the cache, then we should prefer the password
+ // stored in the cache since that is most likely to be valid.
+ if (header == nsHttp::Authorization && entry->Domain()[0] == '\0') {
+ GetIdentityFromURI(0, ident);
+ // if the usernames match, then clear the ident so we will pick
+ // up the one from the auth cache instead.
+ // when this is undesired, specify LOAD_EXPLICIT_CREDENTIALS load
+ // flag.
+ if (ident.User() == entry->User()) {
+ uint32_t loadFlags;
+ if (NS_SUCCEEDED(mAuthChannel->GetLoadFlags(&loadFlags)) &&
+ !(loadFlags & nsIChannel::LOAD_EXPLICIT_CREDENTIALS)) {
+ ident.Clear();
+ }
+ }
+ }
+ bool identFromURI;
+ if (ident.IsEmpty()) {
+ ident = entry->Identity();
+ identFromURI = false;
+ } else {
+ identFromURI = true;
+ }
+
+ nsCString temp; // this must have the same lifetime as creds
+ nsAutoCString creds(entry->Creds());
+ // we can only send a preemptive Authorization header if we have either
+ // stored credentials or a stored challenge from which to derive
+ // credentials. if the identity is from the URI, then we cannot use
+ // the stored credentials.
+ if ((creds.IsEmpty() || identFromURI) && !entry->Challenge().IsEmpty()) {
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ nsAutoCString unused;
+ rv = GetAuthenticator(entry->Challenge(), unused, getter_AddRefs(auth));
+ if (NS_SUCCEEDED(rv)) {
+ bool proxyAuth = (header == nsHttp::Proxy_Authorization);
+ rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port, path,
+ entry->Realm(), entry->Challenge(), ident,
+ entry->mMetaData, temp);
+ if (NS_SUCCEEDED(rv)) creds = temp;
+
+ // make sure the continuation state is null since we do not
+ // support mixing preemptive and 'multirequest' authentication.
+ NS_IF_RELEASE(*continuationState);
+ }
+ }
+ if (!creds.IsEmpty()) {
+ LOG((" adding \"%s\" request header\n", header.get()));
+ if (header == nsHttp::Proxy_Authorization) {
+ rv = mAuthChannel->SetProxyCredentials(creds);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ } else {
+ rv = mAuthChannel->SetWWWCredentials(creds);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // suppress defensive auth prompting for this channel since we know
+ // that we already prompted at least once this session. we only do
+ // this for non-proxy auth since the URL's userpass is not used for
+ // proxy auth.
+ if (header == nsHttp::Authorization) mSuppressDefensiveAuth = true;
+ } else {
+ ident.Clear(); // don't remember the identity
+ }
+ }
+}
+
+nsresult nsHttpChannelAuthProvider::GetCurrentPath(nsACString& path) {
+ nsresult rv;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
+ if (url) {
+ rv = url->GetDirectory(path);
+ } else {
+ rv = mURI->GetPathQueryRef(path);
+ }
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsHttpChannelAuthProvider, nsICancelable,
+ nsIHttpChannelAuthProvider, nsIAuthPromptCallback,
+ nsIHttpAuthenticatorCallback)
+
+} // namespace mozilla::net