michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim:set expandtab ts=4 sw=4 sts=4 cin: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // HttpLog.h should generally be included first michael@0: #include "HttpLog.h" michael@0: michael@0: #include "nsHttpChannelAuthProvider.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsHttpHandler.h" michael@0: #include "nsIHttpAuthenticator.h" michael@0: #include "nsIAuthPrompt2.h" michael@0: #include "nsIAuthPromptProvider.h" michael@0: #include "nsIInterfaceRequestor.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsEscape.h" michael@0: #include "nsAuthInformationHolder.h" michael@0: #include "nsIStringBundle.h" michael@0: #include "nsIPrompt.h" michael@0: #include "netCore.h" michael@0: #include "nsIHttpAuthenticableChannel.h" michael@0: #include "nsIURI.h" michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: static void michael@0: GetAppIdAndBrowserStatus(nsIChannel* aChan, uint32_t* aAppId, bool* aInBrowserElem) michael@0: { michael@0: nsCOMPtr loadContext; michael@0: if (aChan) { michael@0: NS_QueryNotificationCallbacks(aChan, loadContext); michael@0: } michael@0: if (!loadContext) { michael@0: *aAppId = NECKO_NO_APP_ID; michael@0: *aInBrowserElem = false; michael@0: } else { michael@0: loadContext->GetAppId(aAppId); michael@0: loadContext->GetIsInBrowserElement(aInBrowserElem); michael@0: } michael@0: } michael@0: michael@0: nsHttpChannelAuthProvider::nsHttpChannelAuthProvider() michael@0: : mAuthChannel(nullptr) michael@0: , mIsPrivate(false) michael@0: , mProxyAuthContinuationState(nullptr) michael@0: , mAuthContinuationState(nullptr) michael@0: , mProxyAuth(false) michael@0: , mTriedProxyAuth(false) michael@0: , mTriedHostAuth(false) michael@0: , mSuppressDefensiveAuth(false) michael@0: , mHttpHandler(gHttpHandler) michael@0: { michael@0: } michael@0: michael@0: nsHttpChannelAuthProvider::~nsHttpChannelAuthProvider() michael@0: { michael@0: MOZ_ASSERT(!mAuthChannel, "Disconnect wasn't called"); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannelAuthProvider::Init(nsIHttpAuthenticableChannel *channel) michael@0: { michael@0: MOZ_ASSERT(channel, "channel expected!"); michael@0: michael@0: mAuthChannel = channel; michael@0: michael@0: nsresult rv = mAuthChannel->GetURI(getter_AddRefs(mURI)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mAuthChannel->GetIsSSL(&mUsingSSL); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = mURI->GetAsciiHost(mHost); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // reject the URL if it doesn't specify a host michael@0: if (mHost.IsEmpty()) michael@0: return NS_ERROR_MALFORMED_URI; michael@0: michael@0: rv = mURI->GetPort(&mPort); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr bareChannel = do_QueryInterface(channel); michael@0: mIsPrivate = NS_UsePrivateBrowsing(bareChannel); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannelAuthProvider::ProcessAuthentication(uint32_t httpStatus, michael@0: bool SSLConnectFailed) michael@0: { michael@0: LOG(("nsHttpChannelAuthProvider::ProcessAuthentication " michael@0: "[this=%p channel=%p code=%u SSLConnectFailed=%d]\n", michael@0: this, mAuthChannel, httpStatus, SSLConnectFailed)); michael@0: michael@0: MOZ_ASSERT(mAuthChannel, "Channel not initialized"); michael@0: michael@0: nsCOMPtr proxyInfo; michael@0: nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (proxyInfo) { michael@0: mProxyInfo = do_QueryInterface(proxyInfo); michael@0: if (!mProxyInfo) return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: nsAutoCString challenges; michael@0: mProxyAuth = (httpStatus == 407); michael@0: michael@0: rv = PrepareForAuthentication(mProxyAuth); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (mProxyAuth) { michael@0: // only allow a proxy challenge if we have a proxy server configured. michael@0: // otherwise, we could inadvertently expose the user's proxy michael@0: // credentials to an origin server. We could attempt to proceed as michael@0: // if we had received a 401 from the server, but why risk flirting michael@0: // with trouble? IE similarly rejects 407s when a proxy server is michael@0: // not configured, so there's no reason not to do the same. michael@0: if (!UsingHttpProxy()) { michael@0: LOG(("rejecting 407 when proxy server not configured!\n")); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: if (UsingSSL() && !SSLConnectFailed) { michael@0: // we need to verify that this challenge came from the proxy michael@0: // server itself, and not some server on the other side of the michael@0: // SSL tunnel. michael@0: LOG(("rejecting 407 from origin server!\n")); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: rv = mAuthChannel->GetProxyChallenges(challenges); michael@0: } michael@0: else michael@0: rv = mAuthChannel->GetWWWChallenges(challenges); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsAutoCString creds; michael@0: rv = GetCredentials(challenges.get(), mProxyAuth, creds); michael@0: if (rv == NS_ERROR_IN_PROGRESS) michael@0: return rv; michael@0: if (NS_FAILED(rv)) michael@0: LOG(("unable to authenticate\n")); michael@0: else { michael@0: // set the authentication credentials michael@0: if (mProxyAuth) michael@0: rv = mAuthChannel->SetProxyCredentials(creds); michael@0: else michael@0: rv = mAuthChannel->SetWWWCredentials(creds); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannelAuthProvider::AddAuthorizationHeaders() michael@0: { michael@0: LOG(("nsHttpChannelAuthProvider::AddAuthorizationHeaders? " michael@0: "[this=%p channel=%p]\n", this, mAuthChannel)); michael@0: michael@0: MOZ_ASSERT(mAuthChannel, "Channel not initialized"); michael@0: michael@0: nsCOMPtr proxyInfo; michael@0: nsresult rv = mAuthChannel->GetProxyInfo(getter_AddRefs(proxyInfo)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (proxyInfo) { michael@0: mProxyInfo = do_QueryInterface(proxyInfo); michael@0: if (!mProxyInfo) return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: uint32_t loadFlags; michael@0: rv = mAuthChannel->GetLoadFlags(&loadFlags); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // this getter never fails michael@0: nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate); michael@0: michael@0: // check if proxy credentials should be sent michael@0: const char *proxyHost = ProxyHost(); michael@0: if (proxyHost && UsingHttpProxy()) michael@0: SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization, michael@0: "http", proxyHost, ProxyPort(), michael@0: nullptr, // proxy has no path michael@0: mProxyIdent); michael@0: michael@0: if (loadFlags & nsIRequest::LOAD_ANONYMOUS) { michael@0: LOG(("Skipping Authorization header for anonymous load\n")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // check if server credentials should be sent michael@0: nsAutoCString path, scheme; michael@0: if (NS_SUCCEEDED(GetCurrentPath(path)) && michael@0: NS_SUCCEEDED(mURI->GetScheme(scheme))) { michael@0: SetAuthorizationHeader(authCache, nsHttp::Authorization, michael@0: scheme.get(), michael@0: Host(), michael@0: Port(), michael@0: path.get(), michael@0: mIdent); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannelAuthProvider::CheckForSuperfluousAuth() michael@0: { michael@0: LOG(("nsHttpChannelAuthProvider::CheckForSuperfluousAuth? " michael@0: "[this=%p channel=%p]\n", this, mAuthChannel)); michael@0: michael@0: MOZ_ASSERT(mAuthChannel, "Channel not initialized"); michael@0: michael@0: // we've been called because it has been determined that this channel is michael@0: // getting loaded without taking the userpass from the URL. if the URL michael@0: // contained a userpass, then (provided some other conditions are true), michael@0: // we'll give the user an opportunity to abort the channel as this might be michael@0: // an attempt to spoof a different site (see bug 232567). michael@0: if (!ConfirmAuth(NS_LITERAL_STRING("SuperfluousAuth"), true)) { michael@0: // calling cancel here sets our mStatus and aborts the HTTP michael@0: // transaction, which prevents OnDataAvailable events. michael@0: mAuthChannel->Cancel(NS_ERROR_ABORT); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannelAuthProvider::Cancel(nsresult status) michael@0: { michael@0: MOZ_ASSERT(mAuthChannel, "Channel not initialized"); michael@0: michael@0: if (mAsyncPromptAuthCancelable) { michael@0: mAsyncPromptAuthCancelable->Cancel(status); michael@0: mAsyncPromptAuthCancelable = nullptr; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannelAuthProvider::Disconnect(nsresult status) michael@0: { michael@0: mAuthChannel = nullptr; michael@0: michael@0: if (mAsyncPromptAuthCancelable) { michael@0: mAsyncPromptAuthCancelable->Cancel(status); michael@0: mAsyncPromptAuthCancelable = nullptr; michael@0: } michael@0: michael@0: NS_IF_RELEASE(mProxyAuthContinuationState); michael@0: NS_IF_RELEASE(mAuthContinuationState); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // buf contains "domain\user" michael@0: static void michael@0: ParseUserDomain(char16_t *buf, michael@0: const char16_t **user, michael@0: const char16_t **domain) michael@0: { michael@0: char16_t *p = buf; michael@0: while (*p && *p != '\\') ++p; michael@0: if (!*p) michael@0: return; michael@0: *p = '\0'; michael@0: *domain = buf; michael@0: *user = p + 1; michael@0: } michael@0: michael@0: // helper function for setting identity from raw user:pass michael@0: static void michael@0: SetIdent(nsHttpAuthIdentity &ident, michael@0: uint32_t authFlags, michael@0: char16_t *userBuf, michael@0: char16_t *passBuf) michael@0: { michael@0: const char16_t *user = userBuf; michael@0: const char16_t *domain = nullptr; michael@0: michael@0: if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN) michael@0: ParseUserDomain(userBuf, &user, &domain); michael@0: michael@0: ident.Set(domain, user, passBuf); michael@0: } michael@0: michael@0: // helper function for getting an auth prompt from an interface requestor michael@0: static void michael@0: GetAuthPrompt(nsIInterfaceRequestor *ifreq, bool proxyAuth, michael@0: nsIAuthPrompt2 **result) michael@0: { michael@0: if (!ifreq) michael@0: return; michael@0: michael@0: uint32_t promptReason; michael@0: if (proxyAuth) michael@0: promptReason = nsIAuthPromptProvider::PROMPT_PROXY; michael@0: else michael@0: promptReason = nsIAuthPromptProvider::PROMPT_NORMAL; michael@0: michael@0: nsCOMPtr promptProvider = do_GetInterface(ifreq); michael@0: if (promptProvider) michael@0: promptProvider->GetAuthPrompt(promptReason, michael@0: NS_GET_IID(nsIAuthPrompt2), michael@0: reinterpret_cast(result)); michael@0: else michael@0: NS_QueryAuthPrompt2(ifreq, result); michael@0: } michael@0: michael@0: // generate credentials for the given challenge, and update the auth cache. michael@0: nsresult michael@0: nsHttpChannelAuthProvider::GenCredsAndSetEntry(nsIHttpAuthenticator *auth, michael@0: bool proxyAuth, michael@0: const char *scheme, michael@0: const char *host, michael@0: int32_t port, michael@0: const char *directory, michael@0: const char *realm, michael@0: const char *challenge, michael@0: const nsHttpAuthIdentity &ident, michael@0: nsCOMPtr &sessionState, michael@0: char **result) michael@0: { michael@0: nsresult rv; michael@0: uint32_t authFlags; michael@0: michael@0: rv = auth->GetAuthFlags(&authFlags); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsISupports *ss = sessionState; michael@0: michael@0: // set informations that depend on whether michael@0: // we're authenticating against a proxy michael@0: // or a webserver michael@0: nsISupports **continuationState; michael@0: michael@0: if (proxyAuth) { michael@0: continuationState = &mProxyAuthContinuationState; michael@0: } else { michael@0: continuationState = &mAuthContinuationState; michael@0: } michael@0: michael@0: uint32_t generateFlags; michael@0: rv = auth->GenerateCredentials(mAuthChannel, michael@0: challenge, michael@0: proxyAuth, michael@0: ident.Domain(), michael@0: ident.User(), michael@0: ident.Password(), michael@0: &ss, michael@0: &*continuationState, michael@0: &generateFlags, michael@0: result); michael@0: michael@0: sessionState.swap(ss); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // don't log this in release build since it could contain sensitive info. michael@0: #ifdef DEBUG michael@0: LOG(("generated creds: %s\n", *result)); michael@0: #endif michael@0: michael@0: // find out if this authenticator allows reuse of credentials and/or michael@0: // challenge. michael@0: bool saveCreds = michael@0: 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CREDENTIALS); michael@0: bool saveChallenge = michael@0: 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CHALLENGE); michael@0: michael@0: bool saveIdentity = michael@0: 0 == (generateFlags & nsIHttpAuthenticator::USING_INTERNAL_IDENTITY); michael@0: michael@0: // this getter never fails michael@0: nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate); michael@0: michael@0: nsCOMPtr chan = do_QueryInterface(mAuthChannel); michael@0: uint32_t appId; michael@0: bool isInBrowserElement; michael@0: GetAppIdAndBrowserStatus(chan, &appId, &isInBrowserElement); michael@0: michael@0: // create a cache entry. we do this even though we don't yet know that michael@0: // these credentials are valid b/c we need to avoid prompting the user michael@0: // more than once in case the credentials are valid. michael@0: // michael@0: // if the credentials are not reusable, then we don't bother sticking michael@0: // them in the auth cache. michael@0: rv = authCache->SetAuthEntry(scheme, host, port, directory, realm, michael@0: saveCreds ? *result : nullptr, michael@0: saveChallenge ? challenge : nullptr, michael@0: appId, isInBrowserElement, michael@0: saveIdentity ? &ident : nullptr, michael@0: sessionState); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannelAuthProvider::PrepareForAuthentication(bool proxyAuth) michael@0: { michael@0: LOG(("nsHttpChannelAuthProvider::PrepareForAuthentication " michael@0: "[this=%p channel=%p]\n", this, mAuthChannel)); michael@0: michael@0: if (!proxyAuth) { michael@0: // reset the current proxy continuation state because our last michael@0: // authentication attempt was completed successfully. michael@0: NS_IF_RELEASE(mProxyAuthContinuationState); michael@0: LOG((" proxy continuation state has been reset")); michael@0: } michael@0: michael@0: if (!UsingHttpProxy() || mProxyAuthType.IsEmpty()) michael@0: return NS_OK; michael@0: michael@0: // We need to remove any Proxy_Authorization header left over from a michael@0: // non-request based authentication handshake (e.g., for NTLM auth). michael@0: michael@0: nsAutoCString contractId; michael@0: contractId.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX); michael@0: contractId.Append(mProxyAuthType); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr precedingAuth = michael@0: do_GetService(contractId.get(), &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: uint32_t precedingAuthFlags; michael@0: rv = precedingAuth->GetAuthFlags(&precedingAuthFlags); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (!(precedingAuthFlags & nsIHttpAuthenticator::REQUEST_BASED)) { michael@0: nsAutoCString challenges; michael@0: rv = mAuthChannel->GetProxyChallenges(challenges); michael@0: if (NS_FAILED(rv)) { michael@0: // delete the proxy authorization header because we weren't michael@0: // asked to authenticate michael@0: rv = mAuthChannel->SetProxyCredentials(EmptyCString()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: LOG((" cleared proxy authorization header")); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannelAuthProvider::GetCredentials(const char *challenges, michael@0: bool proxyAuth, michael@0: nsAFlatCString &creds) michael@0: { michael@0: nsCOMPtr auth; michael@0: nsAutoCString challenge; michael@0: michael@0: nsCString authType; // force heap allocation to enable string sharing since michael@0: // we'll be assigning this value into mAuthType. michael@0: michael@0: // set informations that depend on whether we're authenticating against a michael@0: // proxy or a webserver michael@0: nsISupports **currentContinuationState; michael@0: nsCString *currentAuthType; michael@0: michael@0: if (proxyAuth) { michael@0: currentContinuationState = &mProxyAuthContinuationState; michael@0: currentAuthType = &mProxyAuthType; michael@0: } else { michael@0: currentContinuationState = &mAuthContinuationState; michael@0: currentAuthType = &mAuthType; michael@0: } michael@0: michael@0: nsresult rv = NS_ERROR_NOT_AVAILABLE; michael@0: bool gotCreds = false; michael@0: michael@0: // figure out which challenge we can handle and which authenticator to use. michael@0: for (const char *eol = challenges - 1; eol; ) { michael@0: const char *p = eol + 1; michael@0: michael@0: // get the challenge string (LF separated -- see nsHttpHeaderArray) michael@0: if ((eol = strchr(p, '\n')) != nullptr) michael@0: challenge.Assign(p, eol - p); michael@0: else michael@0: challenge.Assign(p); michael@0: michael@0: rv = GetAuthenticator(challenge.get(), authType, getter_AddRefs(auth)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // michael@0: // if we've already selected an auth type from a previous challenge michael@0: // received while processing this channel, then skip others until michael@0: // we find a challenge corresponding to the previously tried auth michael@0: // type. michael@0: // michael@0: if (!currentAuthType->IsEmpty() && authType != *currentAuthType) michael@0: continue; michael@0: michael@0: // michael@0: // we allow the routines to run all the way through before we michael@0: // decide if they are valid. michael@0: // michael@0: // we don't worry about the auth cache being altered because that michael@0: // would have been the last step, and if the error is from updating michael@0: // the authcache it wasn't really altered anyway. -CTN michael@0: // michael@0: // at this point the code is really only useful for client side michael@0: // errors (it will not automatically fail over to do a different michael@0: // auth type if the server keeps rejecting what is being sent, even michael@0: // if a particular auth method only knows 1 thing, like a michael@0: // non-identity based authentication method) michael@0: // michael@0: rv = GetCredentialsForChallenge(challenge.get(), authType.get(), michael@0: proxyAuth, auth, creds); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: gotCreds = true; michael@0: *currentAuthType = authType; michael@0: michael@0: break; michael@0: } michael@0: else if (rv == NS_ERROR_IN_PROGRESS) { michael@0: // authentication prompt has been invoked and result is michael@0: // expected asynchronously, save current challenge being michael@0: // processed and all remaining challenges to use later in michael@0: // OnAuthAvailable and now immediately return michael@0: mCurrentChallenge = challenge; michael@0: mRemainingChallenges = eol ? eol+1 : nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: // reset the auth type and continuation state michael@0: NS_IF_RELEASE(*currentContinuationState); michael@0: currentAuthType->Truncate(); michael@0: } michael@0: } michael@0: michael@0: if (!gotCreds && !currentAuthType->IsEmpty()) { michael@0: // looks like we never found the auth type we were looking for. michael@0: // reset the auth type and continuation state, and try again. michael@0: currentAuthType->Truncate(); michael@0: NS_IF_RELEASE(*currentContinuationState); michael@0: michael@0: rv = GetCredentials(challenges, proxyAuth, creds); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannelAuthProvider::GetAuthorizationMembers(bool proxyAuth, michael@0: nsCSubstring& scheme, michael@0: const char*& host, michael@0: int32_t& port, michael@0: nsCSubstring& path, michael@0: nsHttpAuthIdentity*& ident, michael@0: nsISupports**& continuationState) michael@0: { michael@0: if (proxyAuth) { michael@0: MOZ_ASSERT (UsingHttpProxy(), michael@0: "proxyAuth is true, but no HTTP proxy is configured!"); michael@0: michael@0: host = ProxyHost(); michael@0: port = ProxyPort(); michael@0: ident = &mProxyIdent; michael@0: scheme.AssignLiteral("http"); michael@0: michael@0: continuationState = &mProxyAuthContinuationState; michael@0: } michael@0: else { michael@0: host = Host(); michael@0: port = Port(); michael@0: ident = &mIdent; michael@0: michael@0: nsresult rv; michael@0: rv = GetCurrentPath(path); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = mURI->GetScheme(scheme); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: continuationState = &mAuthContinuationState; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannelAuthProvider::GetCredentialsForChallenge(const char *challenge, michael@0: const char *authType, michael@0: bool proxyAuth, michael@0: nsIHttpAuthenticator *auth, michael@0: nsAFlatCString &creds) michael@0: { michael@0: LOG(("nsHttpChannelAuthProvider::GetCredentialsForChallenge " michael@0: "[this=%p channel=%p proxyAuth=%d challenges=%s]\n", michael@0: this, mAuthChannel, proxyAuth, challenge)); michael@0: michael@0: // this getter never fails michael@0: nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate); michael@0: michael@0: uint32_t authFlags; michael@0: nsresult rv = auth->GetAuthFlags(&authFlags); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsAutoCString realm; michael@0: ParseRealm(challenge, realm); michael@0: michael@0: // if no realm, then use the auth type as the realm. ToUpperCase so the michael@0: // ficticious realm stands out a bit more. michael@0: // XXX this will cause some single signon misses! michael@0: // XXX this was meant to be used with NTLM, which supplies no realm. michael@0: /* michael@0: if (realm.IsEmpty()) { michael@0: realm = authType; michael@0: ToUpperCase(realm); michael@0: } michael@0: */ michael@0: michael@0: // set informations that depend on whether michael@0: // we're authenticating against a proxy michael@0: // or a webserver michael@0: const char *host; michael@0: int32_t port; michael@0: nsHttpAuthIdentity *ident; michael@0: nsAutoCString path, scheme; michael@0: bool identFromURI = false; michael@0: nsISupports **continuationState; michael@0: michael@0: rv = GetAuthorizationMembers(proxyAuth, scheme, host, port, michael@0: path, ident, continuationState); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: uint32_t loadFlags; michael@0: rv = mAuthChannel->GetLoadFlags(&loadFlags); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (!proxyAuth) { michael@0: // if this is the first challenge, then try using the identity michael@0: // specified in the URL. michael@0: if (mIdent.IsEmpty()) { michael@0: GetIdentityFromURI(authFlags, mIdent); michael@0: identFromURI = !mIdent.IsEmpty(); michael@0: } michael@0: michael@0: if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !identFromURI) { michael@0: LOG(("Skipping authentication for anonymous non-proxy request\n")); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // Let explicit URL credentials pass michael@0: // regardless of the LOAD_ANONYMOUS flag michael@0: } michael@0: else if ((loadFlags & nsIRequest::LOAD_ANONYMOUS) && !UsingHttpProxy()) { michael@0: LOG(("Skipping authentication for anonymous non-proxy request\n")); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsCOMPtr chan = do_QueryInterface(mAuthChannel); michael@0: uint32_t appId; michael@0: bool isInBrowserElement; michael@0: GetAppIdAndBrowserStatus(chan, &appId, &isInBrowserElement); michael@0: michael@0: // michael@0: // if we already tried some credentials for this transaction, then michael@0: // we need to possibly clear them from the cache, unless the credentials michael@0: // in the cache have changed, in which case we'd want to give them a michael@0: // try instead. michael@0: // michael@0: nsHttpAuthEntry *entry = nullptr; michael@0: authCache->GetAuthEntryForDomain(scheme.get(), host, port, michael@0: realm.get(), appId, michael@0: isInBrowserElement, &entry); michael@0: michael@0: // hold reference to the auth session state (in case we clear our michael@0: // reference to the entry). michael@0: nsCOMPtr sessionStateGrip; michael@0: if (entry) michael@0: sessionStateGrip = entry->mMetaData; michael@0: michael@0: // for digest auth, maybe our cached nonce value simply timed out... michael@0: bool identityInvalid; michael@0: nsISupports *sessionState = sessionStateGrip; michael@0: rv = auth->ChallengeReceived(mAuthChannel, michael@0: challenge, michael@0: proxyAuth, michael@0: &sessionState, michael@0: &*continuationState, michael@0: &identityInvalid); michael@0: sessionStateGrip.swap(sessionState); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: LOG((" identity invalid = %d\n", identityInvalid)); michael@0: michael@0: if (identityInvalid) { michael@0: if (entry) { michael@0: if (ident->Equals(entry->Identity())) { michael@0: if (!identFromURI) { michael@0: LOG((" clearing bad auth cache entry\n")); michael@0: // ok, we've already tried this user identity, so clear the michael@0: // corresponding entry from the auth cache. michael@0: authCache->ClearAuthEntry(scheme.get(), host, michael@0: port, realm.get(), michael@0: appId, isInBrowserElement); michael@0: entry = nullptr; michael@0: ident->Clear(); michael@0: } michael@0: } michael@0: else if (!identFromURI || michael@0: (nsCRT::strcmp(ident->User(), michael@0: entry->Identity().User()) == 0 && michael@0: !(loadFlags & michael@0: (nsIChannel::LOAD_ANONYMOUS | michael@0: nsIChannel::LOAD_EXPLICIT_CREDENTIALS)))) { michael@0: LOG((" taking identity from auth cache\n")); michael@0: // the password from the auth cache is more likely to be michael@0: // correct than the one in the URL. at least, we know that it michael@0: // works with the given username. it is possible for a server michael@0: // to distinguish logons based on the supplied password alone, michael@0: // but that would be quite unusual... and i don't think we need michael@0: // to worry about such unorthodox cases. michael@0: ident->Set(entry->Identity()); michael@0: identFromURI = false; michael@0: if (entry->Creds()[0] != '\0') { michael@0: LOG((" using cached credentials!\n")); michael@0: creds.Assign(entry->Creds()); michael@0: return entry->AddPath(path.get()); michael@0: } michael@0: } michael@0: } michael@0: else if (!identFromURI) { michael@0: // hmm... identity invalid, but no auth entry! the realm probably michael@0: // changed (see bug 201986). michael@0: ident->Clear(); michael@0: } michael@0: michael@0: if (!entry && ident->IsEmpty()) { michael@0: uint32_t level = nsIAuthPrompt2::LEVEL_NONE; michael@0: if (mUsingSSL) michael@0: level = nsIAuthPrompt2::LEVEL_SECURE; michael@0: else if (authFlags & nsIHttpAuthenticator::IDENTITY_ENCRYPTED) michael@0: level = nsIAuthPrompt2::LEVEL_PW_ENCRYPTED; michael@0: michael@0: // at this point we are forced to interact with the user to get michael@0: // their username and password for this domain. michael@0: rv = PromptForIdentity(level, proxyAuth, realm.get(), michael@0: authType, authFlags, *ident); michael@0: if (NS_FAILED(rv)) return rv; michael@0: identFromURI = false; michael@0: } michael@0: } michael@0: michael@0: if (identFromURI) { michael@0: // Warn the user before automatically using the identity from the URL michael@0: // to automatically log them into a site (see bug 232567). michael@0: if (!ConfirmAuth(NS_LITERAL_STRING("AutomaticAuth"), false)) { michael@0: // calling cancel here sets our mStatus and aborts the HTTP michael@0: // transaction, which prevents OnDataAvailable events. michael@0: mAuthChannel->Cancel(NS_ERROR_ABORT); michael@0: // this return code alone is not equivalent to Cancel, since michael@0: // it only instructs our caller that authentication failed. michael@0: // without an explicit call to Cancel, our caller would just michael@0: // load the page that accompanies the HTTP auth challenge. michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: } michael@0: michael@0: // michael@0: // get credentials for the given user:pass michael@0: // michael@0: // always store the credentials we're trying now so that they will be used michael@0: // on subsequent links. This will potentially remove good credentials from michael@0: // the cache. This is ok as we don't want to use cached credentials if the michael@0: // user specified something on the URI or in another manner. This is so michael@0: // that we don't transparently authenticate as someone they're not michael@0: // expecting to authenticate as. michael@0: // michael@0: nsXPIDLCString result; michael@0: rv = GenCredsAndSetEntry(auth, proxyAuth, scheme.get(), host, port, michael@0: path.get(), realm.get(), challenge, *ident, michael@0: sessionStateGrip, getter_Copies(result)); michael@0: if (NS_SUCCEEDED(rv)) michael@0: creds = result; michael@0: return rv; michael@0: } michael@0: michael@0: inline void michael@0: GetAuthType(const char *challenge, nsCString &authType) michael@0: { michael@0: const char *p; michael@0: michael@0: // get the challenge type michael@0: if ((p = strchr(challenge, ' ')) != nullptr) michael@0: authType.Assign(challenge, p - challenge); michael@0: else michael@0: authType.Assign(challenge); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannelAuthProvider::GetAuthenticator(const char *challenge, michael@0: nsCString &authType, michael@0: nsIHttpAuthenticator **auth) michael@0: { michael@0: LOG(("nsHttpChannelAuthProvider::GetAuthenticator [this=%p channel=%p]\n", michael@0: this, mAuthChannel)); michael@0: michael@0: GetAuthType(challenge, authType); michael@0: michael@0: // normalize to lowercase michael@0: ToLowerCase(authType); michael@0: michael@0: nsAutoCString contractid; michael@0: contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX); michael@0: contractid.Append(authType); michael@0: michael@0: return CallGetService(contractid.get(), auth); michael@0: } michael@0: michael@0: void michael@0: nsHttpChannelAuthProvider::GetIdentityFromURI(uint32_t authFlags, michael@0: nsHttpAuthIdentity &ident) michael@0: { michael@0: LOG(("nsHttpChannelAuthProvider::GetIdentityFromURI [this=%p channel=%p]\n", michael@0: this, mAuthChannel)); michael@0: michael@0: nsAutoString userBuf; michael@0: nsAutoString passBuf; michael@0: michael@0: // XXX i18n michael@0: nsAutoCString buf; michael@0: mURI->GetUsername(buf); michael@0: if (!buf.IsEmpty()) { michael@0: NS_UnescapeURL(buf); michael@0: CopyASCIItoUTF16(buf, userBuf); michael@0: mURI->GetPassword(buf); michael@0: if (!buf.IsEmpty()) { michael@0: NS_UnescapeURL(buf); michael@0: CopyASCIItoUTF16(buf, passBuf); michael@0: } michael@0: } michael@0: michael@0: if (!userBuf.IsEmpty()) { michael@0: SetIdent(ident, authFlags, (char16_t *) userBuf.get(), michael@0: (char16_t *) passBuf.get()); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHttpChannelAuthProvider::ParseRealm(const char *challenge, michael@0: nsACString &realm) michael@0: { michael@0: // michael@0: // From RFC2617 section 1.2, the realm value is defined as such: michael@0: // michael@0: // realm = "realm" "=" realm-value michael@0: // realm-value = quoted-string michael@0: // michael@0: // but, we'll accept anything after the the "=" up to the first space, or michael@0: // end-of-line, if the string is not quoted. michael@0: // michael@0: michael@0: const char *p = PL_strcasestr(challenge, "realm="); michael@0: if (p) { michael@0: bool has_quote = false; michael@0: p += 6; michael@0: if (*p == '"') { michael@0: has_quote = true; michael@0: p++; michael@0: } michael@0: michael@0: const char *end; michael@0: if (has_quote) { michael@0: end = p; michael@0: while (*end) { michael@0: if (*end == '\\') { michael@0: // escaped character, store that one instead if not zero michael@0: if (!*++end) michael@0: break; michael@0: } michael@0: else if (*end == '\"') michael@0: // end of string michael@0: break; michael@0: michael@0: realm.Append(*end); michael@0: ++end; michael@0: } michael@0: } michael@0: else { michael@0: // realm given without quotes michael@0: end = strchr(p, ' '); michael@0: if (end) michael@0: realm.Assign(p, end - p); michael@0: else michael@0: realm.Assign(p); michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: class nsHTTPAuthInformation : public nsAuthInformationHolder { michael@0: public: michael@0: nsHTTPAuthInformation(uint32_t aFlags, const nsString& aRealm, michael@0: const nsCString& aAuthType) michael@0: : nsAuthInformationHolder(aFlags, aRealm, aAuthType) {} michael@0: michael@0: void SetToHttpAuthIdentity(uint32_t authFlags, michael@0: nsHttpAuthIdentity& identity); michael@0: }; michael@0: michael@0: void michael@0: nsHTTPAuthInformation::SetToHttpAuthIdentity(uint32_t authFlags, michael@0: nsHttpAuthIdentity& identity) michael@0: { michael@0: identity.Set(Domain().get(), User().get(), Password().get()); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannelAuthProvider::PromptForIdentity(uint32_t level, michael@0: bool proxyAuth, michael@0: const char *realm, michael@0: const char *authType, michael@0: uint32_t authFlags, michael@0: nsHttpAuthIdentity &ident) michael@0: { michael@0: LOG(("nsHttpChannelAuthProvider::PromptForIdentity [this=%p channel=%p]\n", michael@0: this, mAuthChannel)); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr callbacks; michael@0: rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr loadGroup; michael@0: rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr authPrompt; michael@0: GetAuthPrompt(callbacks, proxyAuth, getter_AddRefs(authPrompt)); michael@0: if (!authPrompt && loadGroup) { michael@0: nsCOMPtr cbs; michael@0: loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs)); michael@0: GetAuthPrompt(cbs, proxyAuth, getter_AddRefs(authPrompt)); michael@0: } michael@0: if (!authPrompt) michael@0: return NS_ERROR_NO_INTERFACE; michael@0: michael@0: // XXX i18n: need to support non-ASCII realm strings (see bug 41489) michael@0: NS_ConvertASCIItoUTF16 realmU(realm); michael@0: michael@0: // prompt the user... michael@0: uint32_t promptFlags = 0; michael@0: if (proxyAuth) michael@0: { michael@0: promptFlags |= nsIAuthInformation::AUTH_PROXY; michael@0: if (mTriedProxyAuth) michael@0: promptFlags |= nsIAuthInformation::PREVIOUS_FAILED; michael@0: mTriedProxyAuth = true; michael@0: } michael@0: else { michael@0: promptFlags |= nsIAuthInformation::AUTH_HOST; michael@0: if (mTriedHostAuth) michael@0: promptFlags |= nsIAuthInformation::PREVIOUS_FAILED; michael@0: mTriedHostAuth = true; michael@0: } michael@0: michael@0: if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN) michael@0: promptFlags |= nsIAuthInformation::NEED_DOMAIN; michael@0: michael@0: nsRefPtr holder = michael@0: new nsHTTPAuthInformation(promptFlags, realmU, michael@0: nsDependentCString(authType)); michael@0: if (!holder) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: nsCOMPtr channel(do_QueryInterface(mAuthChannel, &rv)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = michael@0: authPrompt->AsyncPromptAuth(channel, this, nullptr, level, holder, michael@0: getter_AddRefs(mAsyncPromptAuthCancelable)); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // indicate using this error code that authentication prompt michael@0: // result is expected asynchronously michael@0: rv = NS_ERROR_IN_PROGRESS; michael@0: } michael@0: else { michael@0: // Fall back to synchronous prompt michael@0: bool retval = false; michael@0: rv = authPrompt->PromptAuth(channel, level, holder, &retval); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (!retval) michael@0: rv = NS_ERROR_ABORT; michael@0: else michael@0: holder->SetToHttpAuthIdentity(authFlags, ident); michael@0: } michael@0: michael@0: // remember that we successfully showed the user an auth dialog michael@0: if (!proxyAuth) michael@0: mSuppressDefensiveAuth = true; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthAvailable(nsISupports *aContext, michael@0: nsIAuthInformation *aAuthInfo) michael@0: { michael@0: LOG(("nsHttpChannelAuthProvider::OnAuthAvailable [this=%p channel=%p]", michael@0: this, mAuthChannel)); michael@0: michael@0: mAsyncPromptAuthCancelable = nullptr; michael@0: if (!mAuthChannel) michael@0: return NS_OK; michael@0: michael@0: nsresult rv; michael@0: michael@0: const char *host; michael@0: int32_t port; michael@0: nsHttpAuthIdentity *ident; michael@0: nsAutoCString path, scheme; michael@0: nsISupports **continuationState; michael@0: rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, michael@0: path, ident, continuationState); michael@0: if (NS_FAILED(rv)) michael@0: OnAuthCancelled(aContext, false); michael@0: michael@0: nsAutoCString realm; michael@0: ParseRealm(mCurrentChallenge.get(), realm); michael@0: michael@0: nsCOMPtr chan = do_QueryInterface(mAuthChannel); michael@0: uint32_t appId; michael@0: bool isInBrowserElement; michael@0: GetAppIdAndBrowserStatus(chan, &appId, &isInBrowserElement); michael@0: michael@0: nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate); michael@0: nsHttpAuthEntry *entry = nullptr; michael@0: authCache->GetAuthEntryForDomain(scheme.get(), host, port, michael@0: realm.get(), appId, michael@0: isInBrowserElement, michael@0: &entry); michael@0: michael@0: nsCOMPtr sessionStateGrip; michael@0: if (entry) michael@0: sessionStateGrip = entry->mMetaData; michael@0: michael@0: nsAuthInformationHolder* holder = michael@0: static_cast(aAuthInfo); michael@0: ident->Set(holder->Domain().get(), michael@0: holder->User().get(), michael@0: holder->Password().get()); michael@0: michael@0: nsAutoCString unused; michael@0: nsCOMPtr auth; michael@0: rv = GetAuthenticator(mCurrentChallenge.get(), unused, michael@0: getter_AddRefs(auth)); michael@0: if (NS_FAILED(rv)) { michael@0: MOZ_ASSERT(false, "GetAuthenticator failed"); michael@0: OnAuthCancelled(aContext, true); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsXPIDLCString creds; michael@0: rv = GenCredsAndSetEntry(auth, mProxyAuth, michael@0: scheme.get(), host, port, path.get(), michael@0: realm.get(), mCurrentChallenge.get(), *ident, michael@0: sessionStateGrip, getter_Copies(creds)); michael@0: michael@0: mCurrentChallenge.Truncate(); michael@0: if (NS_FAILED(rv)) { michael@0: OnAuthCancelled(aContext, true); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return ContinueOnAuthAvailable(creds); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthCancelled(nsISupports *aContext, michael@0: bool userCancel) michael@0: { michael@0: LOG(("nsHttpChannelAuthProvider::OnAuthCancelled [this=%p channel=%p]", michael@0: this, mAuthChannel)); michael@0: michael@0: mAsyncPromptAuthCancelable = nullptr; michael@0: if (!mAuthChannel) michael@0: return NS_OK; michael@0: michael@0: if (userCancel) { michael@0: if (!mRemainingChallenges.IsEmpty()) { michael@0: // there are still some challenges to process, do so michael@0: nsresult rv; michael@0: michael@0: nsAutoCString creds; michael@0: rv = GetCredentials(mRemainingChallenges.get(), mProxyAuth, creds); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // GetCredentials loaded the credentials from the cache or michael@0: // some other way in a synchronous manner, process those michael@0: // credentials now michael@0: mRemainingChallenges.Truncate(); michael@0: return ContinueOnAuthAvailable(creds); michael@0: } michael@0: else if (rv == NS_ERROR_IN_PROGRESS) { michael@0: // GetCredentials successfully queued another authprompt for michael@0: // a challenge from the list, we are now waiting for the user michael@0: // to provide the credentials michael@0: return NS_OK; michael@0: } michael@0: michael@0: // otherwise, we failed... michael@0: } michael@0: michael@0: mRemainingChallenges.Truncate(); michael@0: } michael@0: michael@0: mAuthChannel->OnAuthCancelled(userCancel); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannelAuthProvider::ContinueOnAuthAvailable(const nsCSubstring& creds) michael@0: { michael@0: nsresult rv; michael@0: if (mProxyAuth) michael@0: rv = mAuthChannel->SetProxyCredentials(creds); michael@0: else michael@0: rv = mAuthChannel->SetWWWCredentials(creds); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // drop our remaining list of challenges. We don't need them, because we michael@0: // have now authenticated against a challenge and will be sending that michael@0: // information to the server (or proxy). If it doesn't accept our michael@0: // authentication it'll respond with failure and resend the challenge list michael@0: mRemainingChallenges.Truncate(); michael@0: michael@0: mAuthChannel->OnAuthAvailable(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsHttpChannelAuthProvider::ConfirmAuth(const nsString &bundleKey, michael@0: bool doYesNoPrompt) michael@0: { michael@0: // skip prompting the user if michael@0: // 1) we've already prompted the user michael@0: // 2) we're not a toplevel channel michael@0: // 3) the userpass length is less than the "phishy" threshold michael@0: michael@0: uint32_t loadFlags; michael@0: nsresult rv = mAuthChannel->GetLoadFlags(&loadFlags); michael@0: if (NS_FAILED(rv)) michael@0: return true; michael@0: michael@0: if (mSuppressDefensiveAuth || michael@0: !(loadFlags & nsIChannel::LOAD_INITIAL_DOCUMENT_URI)) michael@0: return true; michael@0: michael@0: nsAutoCString userPass; michael@0: rv = mURI->GetUserPass(userPass); michael@0: if (NS_FAILED(rv) || michael@0: (userPass.Length() < gHttpHandler->PhishyUserPassLength())) michael@0: return true; michael@0: michael@0: // we try to confirm by prompting the user. if we cannot do so, then michael@0: // assume the user said ok. this is done to keep things working in michael@0: // embedded builds, where the string bundle might not be present, etc. michael@0: michael@0: nsCOMPtr bundleService = michael@0: do_GetService(NS_STRINGBUNDLE_CONTRACTID); michael@0: if (!bundleService) michael@0: return true; michael@0: michael@0: nsCOMPtr bundle; michael@0: bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle)); michael@0: if (!bundle) michael@0: return true; michael@0: michael@0: nsAutoCString host; michael@0: rv = mURI->GetHost(host); michael@0: if (NS_FAILED(rv)) michael@0: return true; michael@0: michael@0: nsAutoCString user; michael@0: rv = mURI->GetUsername(user); michael@0: if (NS_FAILED(rv)) michael@0: return true; michael@0: michael@0: NS_ConvertUTF8toUTF16 ucsHost(host), ucsUser(user); michael@0: const char16_t *strs[2] = { ucsHost.get(), ucsUser.get() }; michael@0: michael@0: nsXPIDLString msg; michael@0: bundle->FormatStringFromName(bundleKey.get(), strs, 2, getter_Copies(msg)); michael@0: if (!msg) michael@0: return true; michael@0: michael@0: nsCOMPtr callbacks; michael@0: rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks)); michael@0: if (NS_FAILED(rv)) michael@0: return true; michael@0: michael@0: nsCOMPtr loadGroup; michael@0: rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: if (NS_FAILED(rv)) michael@0: return true; michael@0: michael@0: nsCOMPtr prompt; michael@0: NS_QueryNotificationCallbacks(callbacks, loadGroup, NS_GET_IID(nsIPrompt), michael@0: getter_AddRefs(prompt)); michael@0: if (!prompt) michael@0: return true; michael@0: michael@0: // do not prompt again michael@0: mSuppressDefensiveAuth = true; michael@0: michael@0: bool confirmed; michael@0: if (doYesNoPrompt) { michael@0: int32_t choice; michael@0: bool checkState = false; michael@0: rv = prompt->ConfirmEx(nullptr, msg, michael@0: nsIPrompt::BUTTON_POS_1_DEFAULT + michael@0: nsIPrompt::STD_YES_NO_BUTTONS, michael@0: nullptr, nullptr, nullptr, nullptr, michael@0: &checkState, &choice); michael@0: if (NS_FAILED(rv)) michael@0: return true; michael@0: michael@0: confirmed = choice == 0; michael@0: } michael@0: else { michael@0: rv = prompt->Confirm(nullptr, msg, &confirmed); michael@0: if (NS_FAILED(rv)) michael@0: return true; michael@0: } michael@0: michael@0: return confirmed; michael@0: } michael@0: michael@0: void michael@0: nsHttpChannelAuthProvider::SetAuthorizationHeader(nsHttpAuthCache *authCache, michael@0: nsHttpAtom header, michael@0: const char *scheme, michael@0: const char *host, michael@0: int32_t port, michael@0: const char *path, michael@0: nsHttpAuthIdentity &ident) michael@0: { michael@0: nsHttpAuthEntry *entry = nullptr; michael@0: nsresult rv; michael@0: michael@0: // set informations that depend on whether michael@0: // we're authenticating against a proxy michael@0: // or a webserver michael@0: nsISupports **continuationState; michael@0: michael@0: if (header == nsHttp::Proxy_Authorization) { michael@0: continuationState = &mProxyAuthContinuationState; michael@0: } else { michael@0: continuationState = &mAuthContinuationState; michael@0: } michael@0: michael@0: nsCOMPtr chan = do_QueryInterface(mAuthChannel); michael@0: uint32_t appId; michael@0: bool isInBrowserElement; michael@0: GetAppIdAndBrowserStatus(chan, &appId, &isInBrowserElement); michael@0: michael@0: rv = authCache->GetAuthEntryForPath(scheme, host, port, path, michael@0: appId, isInBrowserElement, &entry); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // if we are trying to add a header for origin server auth and if the michael@0: // URL contains an explicit username, then try the given username first. michael@0: // we only want to do this, however, if we know the URL requires auth michael@0: // based on the presence of an auth cache entry for this URL (which is michael@0: // true since we are here). but, if the username from the URL matches michael@0: // the username from the cache, then we should prefer the password michael@0: // stored in the cache since that is most likely to be valid. michael@0: if (header == nsHttp::Authorization && entry->Domain()[0] == '\0') { michael@0: GetIdentityFromURI(0, ident); michael@0: // if the usernames match, then clear the ident so we will pick michael@0: // up the one from the auth cache instead. michael@0: // when this is undesired, specify LOAD_EXPLICIT_CREDENTIALS load michael@0: // flag. michael@0: if (nsCRT::strcmp(ident.User(), entry->User()) == 0) { michael@0: uint32_t loadFlags; michael@0: if (NS_SUCCEEDED(mAuthChannel->GetLoadFlags(&loadFlags)) && michael@0: !(loadFlags & nsIChannel::LOAD_EXPLICIT_CREDENTIALS)) { michael@0: ident.Clear(); michael@0: } michael@0: } michael@0: } michael@0: bool identFromURI; michael@0: if (ident.IsEmpty()) { michael@0: ident.Set(entry->Identity()); michael@0: identFromURI = false; michael@0: } michael@0: else michael@0: identFromURI = true; michael@0: michael@0: nsXPIDLCString temp; michael@0: const char *creds = entry->Creds(); michael@0: const char *challenge = entry->Challenge(); michael@0: // we can only send a preemptive Authorization header if we have either michael@0: // stored credentials or a stored challenge from which to derive michael@0: // credentials. if the identity is from the URI, then we cannot use michael@0: // the stored credentials. michael@0: if ((!creds[0] || identFromURI) && challenge[0]) { michael@0: nsCOMPtr auth; michael@0: nsAutoCString unused; michael@0: rv = GetAuthenticator(challenge, unused, getter_AddRefs(auth)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: bool proxyAuth = (header == nsHttp::Proxy_Authorization); michael@0: rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port, michael@0: path, entry->Realm(), challenge, ident, michael@0: entry->mMetaData, getter_Copies(temp)); michael@0: if (NS_SUCCEEDED(rv)) michael@0: creds = temp.get(); michael@0: michael@0: // make sure the continuation state is null since we do not michael@0: // support mixing preemptive and 'multirequest' authentication. michael@0: NS_IF_RELEASE(*continuationState); michael@0: } michael@0: } michael@0: if (creds[0]) { michael@0: LOG((" adding \"%s\" request header\n", header.get())); michael@0: if (header == nsHttp::Proxy_Authorization) michael@0: mAuthChannel->SetProxyCredentials(nsDependentCString(creds)); michael@0: else michael@0: mAuthChannel->SetWWWCredentials(nsDependentCString(creds)); michael@0: michael@0: // suppress defensive auth prompting for this channel since we know michael@0: // that we already prompted at least once this session. we only do michael@0: // this for non-proxy auth since the URL's userpass is not used for michael@0: // proxy auth. michael@0: if (header == nsHttp::Authorization) michael@0: mSuppressDefensiveAuth = true; michael@0: } michael@0: else michael@0: ident.Clear(); // don't remember the identity michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannelAuthProvider::GetCurrentPath(nsACString &path) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr url = do_QueryInterface(mURI); michael@0: if (url) michael@0: rv = url->GetDirectory(path); michael@0: else michael@0: rv = mURI->GetPath(path); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsHttpChannelAuthProvider, nsICancelable, michael@0: nsIHttpChannelAuthProvider, nsIAuthPromptCallback) michael@0: michael@0: } // namespace mozilla::net michael@0: } // namespace mozilla