Files
palemoon27/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
T
roytam1 8a0a002cf2 import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1148708: Add missing 'override' annotations in DocAccessibleChild.h. rs=ehsan (d606358545)
- Bug 1210408 - make nsMaiInterfaceAction work with proxies, r=tbsaunde (f7c819c6ae)
- Bug 1210407 - teach nsMaiInterfaceTable to use proxies, r=tbsaunde (4ca4f10b5f)
- bug 1185157 make sure we don't send an event to a destroyed ipc document r=billm (23acf53f75)
- bug 1214864 - make SetCarretOffset() async r=davidb (e3079e9b2d)
- missing of Bug 1139972 - IPC Proxy for charAt, r=tbsaunde (e9593ed752)
- bug 1191598 - Pass MOZ_CURRENT_PROJECT in environment when running post-build automation steps for universal mac builds. r=gps (fc342c6ced)
- Bug 1164596 - Add mach android-emulator command; r=ahal (afeb9b27d1)
- Bug 1223149 - Add basic usage documentation for mach build; r=glandium (bfb802d175)
- Bug 1182301 - Improve 'mach build' notifications. r=gps (2c65a122d1)
- Bug 1184696 - Add clobber targets to |mach clobber|; Ability to clobber compiled python files, r=gps (35d8be292e)
- Bug 1117958 - Allow any debugging options to the run or gtest mach subcommands to automatically enable debugging. r=gps (32f986af4b)
- Bug 1180081 - Properly rebuild gtest/libxul before running gtests. r=gps (80db9a3d49)
- Bug 1171647, part 1 - Define a new function to convert the mode to a string. r=njn (61ad16f5ba)
- Bug 1171647, part 2 - Remove redundant assertion for dark matter mode. r=njn (b5ac9519f3)
- Bug 1058178, part 1 - Implement DMD heap scanning mode. r=njn (60e1079536)
- Bug 1058178, part 2 - Implement address clamping analysis for DMD scan logs. r=njn (45c0326b93)
- Bug 1102388 - Fix DMD static constructor ordering dependency. r=mccr8 (59b87897a1)
- Bug 1128705 - Don't redefine PAGE_SIZE in DMD if it's already defined. r=erahm (49216348ee)
- Bug 1179042 - Add a script for analyzing memory blocks using a heap scan DMD log. r=njn DONTBUILD (1c08d2d66e)
- Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat (1c999d139b)
- Bug 1158772 - fix non-idiomatic memset call in nsDeque.cpp; r=erahm (de6b555245)
- Bug 1199400 - Part 1: Use CheckedInt when growing nsDeque capacity. r=froydnj (dfdf6814a3)
- Bug 1199400 - Part 0: Remove unused nsDequeIterator. r=froydnj (38d69d7f47)
- Bug 1199400 - Part 2: Add tests for possible nsDeque corner cases. r=froydnj (931baff195)
- Bug 1201997 - Part 1 - Converted compiled test to gtest for nsDeque class. Added tests to test untested methods. r=froydn (e893916651)
- Bug 1201997 - Part 2 - Removing unused methods from the nsDeque class. r=froydn (41595a90ac)
- Bug 1201997 - Part 3 - Make internally used methods private. r=froydn (8cd3afd96f)
- Bug 1201997 - Part 4 - Change size and offset variables to size_t.r=froydn (73eabc8d60)
- Bug 1215140 P1 Add an nsIConsoleReportCollector interface to support navigation channel logging. r=bz (8a41535e2b)
- Bug 1215140 P2 Make HttpBaseChannel implement nsIConsoleReportCollector. r=bz (75fca301f2)
- Bug 1197679 - If nsUnknownDecoder is involved in e10s DivertToParent can break. r=jduell (5d94a12504)
- Bug 1178991 - smartptr for http converter r=hurley (8e7fbc8443)
- bug 366559 - patch 2, fix nsHTTPCompressConv indentation r=bagder (ba762da587)
- bug 366559 - patch 3, fix nsHTTPCompressConv bracing style r=bagder (54195ab451)
- bits of  bug 366559 - patch 7, content-encoding brotli for http (f0b4051022)
- Bug 1205112 - Make PushEvent.data nullable. r=mt,smaug (775db32856)
- Bug 1193414 - SharedWorkers thread should be kept alive also when the SharedWorker object is CCed, r=khuey (b77ea8125c)
- Bug 1206520: Add about:config prefs to enable throwing on asm.js validation failures; r=bz (c42126665d)
- Bug 1193414 - Telemetry for SharedWorker spawning. r=bkelly (77984b7bcc)
- Bug 1205676 - Enable WPT service-worker/unregister-then-register-new-script.https.html in e10s, r=nsm (ec24939cf6)
- Bug 1193133 - Throw when calling postMessage from a Service Worker dom object with no global. r=bkelly (526dcacfab)
- Bug 1181871 P1 Only enforce Cache Context shared data destruction on target thread after init. r=ehsan (cdbf3ed3a8)
- Bug 1181871 P2 Fix ServiceWorkerManager usage of stack-based ErrorResult. r=ehsan (c449195d90)
- minor cleanup and missing bit of 1198230 (02f459db05)
- Bug 1143717 - Implement the ServiceWorkerMessageEvent interface. r=baku (027b3465f2)
- fix misspatch (708eee4e84)
- Bug 1188545 - Disentangle service workers from shared workers and refactor event dispatching code into a separate class. r=nsm,mrbkap (fb5b5341c9)
- Bug 1205228 - Change PackagedAppVerifier to notify the verification result asynchronously. r=valentin. (9edda0fa00)
- Bug 1178518 - Packaged App Utils. r=valentin (f60f3b7a93)
- Bug 1213150 - Part 1: Add a nsContentUtils::IsNonSubresourceRequest helper; r=jdm (b509cc3cc9)
- Bug 1213150 - Part 2: Rework ShouldPrepareForIntercept() in terms of subresource requests; r=jdm (2e92fe8780)
- Bug 1213150 - Part 3: Remove nsIInterceptedChannel.isNavigation; r=jdm (becf1cc12f)
- Bug 1213150 follow-up: fix build bustage (8d73d6ca73)
- Bug 1198394 - Part 1: Allow interception of HSTS upgraded connections in non-e10s mode; r=mcmanus (f504c5be08)
- Bug 1198394 - Part 2: Add a test for interception of HSTS upgraded connections; r=jdm (054e984eef)
- Bug 1187011 - Don't allow response body with null body status. r=bkelly (b1860741d1)
- missing bit of 1140788 (29d319712e)
- Bug 1213436 - Reject core dumps with node IDs that don't fit in an IEEE 754 double; r=sfink (3c1f6fdda0)
- Bug 1211006 - Add Debugger.Source.prototype.canonicalId; r=ejpbruel (eef7b79fce)
- Bug 1199218 - Implement JS::ubi::Node::size for js::LazyScript referents; r=sfink (098a48d240)
- Bug 1220031 - Add JS::ubi::Node::scriptFilename; r=sfink (6b824ae680)
- Bug 1143575. Remove unused MediaQueue::Empty. r=cpearce (de737f3433)
- Bug 1209933 - Make sure all parent runtime pointers are the topmost parent, r=billm. (fe824d967d)
- Bug 1197012 - Fix ThrowTypeError in Notification. r=mccr8 (0b1a097526)
- Bug 1197893 - Check the number of arguments for ThrowTypeError() and ThrowRangeError() at compile time. r=peterv (d98c7d78a0)
- Bug 1142083 - Add test for IDN Unicode domain redirect. r=mcmanus (0c8961fe17)
- Bug 1187159 - Add mochitest for loading packaged apps (iframe+fetch+mozapp) r=jduell (ce90ea561b)
- Bug 1186290 - Notify TabParent to switch process when loading a signed package. r=honzab, r=kanru. (c58a14554a)
- fix (15e2df75eb)
- Bug 1206124 P1 Fix "same-origin" CORS credentials in FetchDriver. r=ehsan (fae1bb6ab3)
- Bug 1206124 P2 Test fetch() with credentials and redirects. r=ehsan (ffc6254112)
- Bug 1211751: Remove nsIChannelEventSink-forwarding from EventSource and FetchDriver. It's never needed. r=smaug (adafe5737a)
- Bug 1212433 Fail fetch() calls that require preflight and also redirect. r=sicking a=abillings (c0d6742b9e)
- Bug 1193128 - Fix base64 decoding when fetching data URIs. r=baku (80bafa291a)
- Bug 1195167 part 1: Let necko handle all protocols. r=bkelly (bb932b0ada)
- Bug 1195167 part 2: Remove redundant aCORSFlag argument and instead use mCORSFlagEverSet. r=bkelly (beadafcad0)
- Bug 1195167 part 3: Remove more scheme-specific handling from FetchDriver. r=bkelly (d00b38db9e)
- Bug 1195167 part 4: Remove FetchDriver::BasicFetch since it is empty. r=bkelly (c5ed097267)
- Bug 1210413 P2 Test CORS credentials on cross-origin redirects. r=sicking a=dveditz (b4eeb8aac0)
- Bug 1210413 P1 Propagate new channel load flags from child to parent on redirect. r=jduell a=dveditz (8b329af4fa)
- Bug 1195167 part 5: Make FetchDriver use AsyncOpen2. r=bkelly (cc217c4cc1)
- Bug 1195167 part 6: Some code simplification since necko handles fetch recursion. r=bkelly (f3b6da2262)
- Bug 1195167: Followup to fix test which I forgot to change (81e7439a2e)
- Bug 1215746: Remove RequestMode::Cors_with_forced_preflight. r=bkelly (0336e812b6)
- Bug 1211000: Move CORS preflight logic from nsCORSListenerProxy to nsCORSPreflightListener. r=ehsan (bf2f71cf22)
- missing bit of Bug 1211443 - Drop scheduled update if decoder initialization isn't done yet. r=jya (f6bc074e33)
- Bug 1182571: Fix nsILoadInfo->GetContentPolicyType API to be less ambigious. Audit and fix all users of it. r=ckerschb (5af6fa7442)
- fix (e40c8e7625)
- Bug 1173811 - Part 1: Propagate the response URL to intercepted channels when necessary (non-e10s). r=mayhemer,bkelly (26f4f13c28)
- Bug 1173811 - Part 2: Propagate the response URL to intercepted channels when necessary (e10s). r=mayhemer,bkelly (a603fe1df2)
- Bug 1154309 - Add New Resource Timing Fields r=bz,hurley (1d14eb6bef)
- Bug 1175685 - add OriginAttribute to LoadInfo. r=jonas, r=ckerschb, r=michal (a5d18bb637)
- Bug 1175685 - add OriginAttribute to LoadInfo. r=jonas, r=ckerschb, r=michal (fb07d2c8aa)
- Bug 1212904 P1 Add a LoadTainting enumeration. r=jduell (a1db8a3e99)
- Bug 1212904 P2 Add LoadTainting information to nsILoadInfo. r=jduell (2482e5e334)
- Bug 1221151 - use [infallible] in nsILoadInfo.idl instead of manual %{C++ blocks; r=jduell (aae73129b6)
- Bug 1045891 - CSP 2 child-src implementation r=ckerschb (792920aeb9)
- Bug 1219931 - CSP: Don't allow removing a policy (r=sicking) (9daaab4186)
- Bug 1208661 - Dump client-side layer textures. r=BenWa (1f2d17d515)
2022-10-17 11:08:37 +08:00

1464 lines
50 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set expandtab ts=4 sw=4 sts=4 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/Preferences.h"
#include "nsHttpChannelAuthProvider.h"
#include "nsNetUtil.h"
#include "nsHttpHandler.h"
#include "nsIHttpAuthenticator.h"
#include "nsIAuthPrompt2.h"
#include "nsIAuthPromptProvider.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsEscape.h"
#include "nsAuthInformationHolder.h"
#include "nsIStringBundle.h"
#include "nsIPrompt.h"
#include "netCore.h"
#include "nsIHttpAuthenticableChannel.h"
#include "nsIURI.h"
#include "nsContentUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsILoadContext.h"
#include "nsIURL.h"
namespace mozilla {
namespace 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 0
#define HTTP_AUTH_DIALOG_SAME_ORIGIN_SUBRESOURCE 1
#define HTTP_AUTH_DIALOG_CROSS_ORIGIN_SUBRESOURCE 2
#define MAX_DISPLAYED_USER_LENGTH 64
#define MAX_DISPLAYED_HOST_LENGTH 64
static void
GetAppIdAndBrowserStatus(nsIChannel* aChan, uint32_t* aAppId, bool* aInBrowserElem)
{
nsCOMPtr<nsILoadContext> loadContext;
if (aChan) {
NS_QueryNotificationCallbacks(aChan, loadContext);
}
if (!loadContext) {
*aAppId = NECKO_NO_APP_ID;
*aInBrowserElem = false;
} else {
loadContext->GetAppId(aAppId);
loadContext->GetIsInBrowserElement(aInBrowserElem);
}
}
nsHttpChannelAuthProvider::nsHttpChannelAuthProvider()
: mAuthChannel(nullptr)
, mIsPrivate(false)
, mProxyAuthContinuationState(nullptr)
, mAuthContinuationState(nullptr)
, mProxyAuth(false)
, mTriedProxyAuth(false)
, mTriedHostAuth(false)
, mSuppressDefensiveAuth(false)
, mHttpHandler(gHttpHandler)
{
}
nsHttpChannelAuthProvider::~nsHttpChannelAuthProvider()
{
MOZ_ASSERT(!mAuthChannel, "Disconnect wasn't called");
}
uint32_t nsHttpChannelAuthProvider::sAuthAllowPref =
SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN;
void
nsHttpChannelAuthProvider::InitializePrefs()
{
MOZ_ASSERT(NS_IsMainThread());
mozilla::Preferences::AddUintVarCache(&sAuthAllowPref,
"network.auth.allow-subresource-auth",
SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN);
}
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;
mAuthChannel->GetIsSSL(&mUsingSSL);
if (NS_FAILED(rv)) return rv;
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.get(), 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()
{
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
const char *proxyHost = ProxyHost();
if (proxyHost && UsingHttpProxy())
SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization,
"http", proxyHost, ProxyPort(),
nullptr, // proxy has no path
mProxyIdent);
if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
LOG(("Skipping Authorization header for anonymous load\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.get(),
Host(),
Port(),
path.get(),
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(NS_LITERAL_STRING("SuperfluousAuth"), true)) {
// calling cancel here sets our mStatus and aborts the HTTP
// transaction, which prevents OnDataAvailable events.
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;
}
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannelAuthProvider::Disconnect(nsresult status)
{
mAuthChannel = nullptr;
if (mAsyncPromptAuthCancelable) {
mAsyncPromptAuthCancelable->Cancel(status);
mAsyncPromptAuthCancelable = nullptr;
}
NS_IF_RELEASE(mProxyAuthContinuationState);
NS_IF_RELEASE(mAuthContinuationState);
return NS_OK;
}
// buf contains "domain\user"
static void
ParseUserDomain(char16_t *buf,
const char16_t **user,
const char16_t **domain)
{
char16_t *p = buf;
while (*p && *p != '\\') ++p;
if (!*p)
return;
*p = '\0';
*domain = buf;
*user = p + 1;
}
// helper function for setting identity from raw user:pass
static void
SetIdent(nsHttpAuthIdentity &ident,
uint32_t authFlags,
char16_t *userBuf,
char16_t *passBuf)
{
const char16_t *user = userBuf;
const char16_t *domain = nullptr;
if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN)
ParseUserDomain(userBuf, &user, &domain);
ident.Set(domain, user, passBuf);
}
// 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 char *scheme,
const char *host,
int32_t port,
const char *directory,
const char *realm,
const char *challenge,
const nsHttpAuthIdentity &ident,
nsCOMPtr<nsISupports> &sessionState,
char **result)
{
nsresult rv;
uint32_t authFlags;
rv = auth->GetAuthFlags(&authFlags);
if (NS_FAILED(rv)) return 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;
}
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));
#endif
// 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);
nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
uint32_t appId;
bool isInBrowserElement;
GetAppIdAndBrowserStatus(chan, &appId, &isInBrowserElement);
// 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 ? *result : nullptr,
saveChallenge ? challenge : nullptr,
appId, isInBrowserElement,
saveIdentity ? &ident : nullptr,
sessionState);
return rv;
}
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.get(), 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(EmptyCString());
if (NS_FAILED(rv)) return rv;
LOG((" cleared proxy authorization header"));
}
}
return NS_OK;
}
nsresult
nsHttpChannelAuthProvider::GetCredentials(const char *challenges,
bool proxyAuth,
nsAFlatCString &creds)
{
nsCOMPtr<nsIHttpAuthenticator> auth;
nsAutoCString challenge;
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 (const char *eol = challenges - 1; eol; ) {
const char *p = eol + 1;
// get the challenge string (LF separated -- see nsHttpHeaderArray)
if ((eol = strchr(p, '\n')) != nullptr)
challenge.Assign(p, eol - p);
else
challenge.Assign(p);
rv = GetAuthenticator(challenge.get(), authType, getter_AddRefs(auth));
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(challenge.get(), authType.get(),
proxyAuth, auth, creds);
if (NS_SUCCEEDED(rv)) {
gotCreds = true;
*currentAuthType = authType;
break;
}
else 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 = challenge;
mRemainingChallenges = eol ? eol+1 : nullptr;
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,
nsCSubstring& scheme,
const char*& host,
int32_t& port,
nsCSubstring& 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 char *challenge,
const char *authType,
bool proxyAuth,
nsIHttpAuthenticator *auth,
nsAFlatCString &creds)
{
LOG(("nsHttpChannelAuthProvider::GetCredentialsForChallenge "
"[this=%p channel=%p proxyAuth=%d challenges=%s]\n",
this, mAuthChannel, proxyAuth, challenge));
// 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(challenge, 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
const char *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;
if (!proxyAuth) {
// 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;
}
nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
uint32_t appId;
bool isInBrowserElement;
GetAppIdAndBrowserStatus(chan, &appId, &isInBrowserElement);
//
// 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;
authCache->GetAuthEntryForDomain(scheme.get(), host, port,
realm.get(), appId,
isInBrowserElement, &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;
// for digest auth, maybe our cached nonce value simply timed out...
bool identityInvalid;
nsISupports *sessionState = sessionStateGrip;
rv = auth->ChallengeReceived(mAuthChannel,
challenge,
proxyAuth,
&sessionState,
&*continuationState,
&identityInvalid);
sessionStateGrip.swap(sessionState);
if (NS_FAILED(rv)) return rv;
LOG((" identity invalid = %d\n", identityInvalid));
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.get(), host,
port, realm.get(),
appId, isInBrowserElement);
entry = nullptr;
ident->Clear();
}
}
else if (!identFromURI ||
(nsCRT::strcmp(ident->User(),
entry->Identity().User()) == 0 &&
!(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->Set(entry->Identity());
identFromURI = false;
if (entry->Creds()[0] != '\0') {
LOG((" using cached credentials!\n"));
creds.Assign(entry->Creds());
return entry->AddPath(path.get());
}
}
}
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 (mUsingSSL)
level = nsIAuthPrompt2::LEVEL_SECURE;
else if (authFlags & nsIHttpAuthenticator::IDENTITY_ENCRYPTED)
level = nsIAuthPrompt2::LEVEL_PW_ENCRYPTED;
// 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.
if (BlockPrompt()) {
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.get(),
authType, 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(NS_LITERAL_STRING("AutomaticAuth"), false)) {
// calling cancel here sets our mStatus and aborts the HTTP
// transaction, which prevents OnDataAvailable events.
mAuthChannel->Cancel(NS_ERROR_ABORT);
// 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.
//
nsXPIDLCString result;
rv = GenCredsAndSetEntry(auth, proxyAuth, scheme.get(), host, port,
path.get(), realm.get(), challenge, *ident,
sessionStateGrip, getter_Copies(result));
if (NS_SUCCEEDED(rv))
creds = result;
return rv;
}
bool
nsHttpChannelAuthProvider::BlockPrompt()
{
nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
nsCOMPtr<nsILoadInfo> loadInfo;
chan->GetLoadInfo(getter_AddRefs(loadInfo));
if (!loadInfo) {
return false;
}
// Allow if it is the top-level document or xhr.
if ((loadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DOCUMENT) ||
(loadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_XMLHTTPREQUEST)) {
return false;
}
switch (sAuthAllowPref) {
case SUBRESOURCE_AUTH_DIALOG_DISALLOW_ALL:
// Do not open the http-authentication credentials dialog for
// the sub-resources.
return true;
break;
case SUBRESOURCE_AUTH_DIALOG_DISALLOW_CROSS_ORIGIN:
// Do not open the http-authentication credentials dialog for
// the sub-resources only if they are not cross-origin.
{
nsCOMPtr<nsIPrincipal> loadingPrincipal =
loadInfo->LoadingPrincipal();
if (!loadingPrincipal) {
return false;
}
if (NS_FAILED(loadingPrincipal->CheckMayLoad(mURI, false, false))) {
return true;
}
}
break;
case SUBRESOURCE_AUTH_DIALOG_ALLOW_ALL:
// Allow the http-authentication dialog.
return false;
default:
// This is an invalid value.
MOZ_ASSERT(false, "A non valid value!");
}
return false;
}
inline void
GetAuthType(const char *challenge, nsCString &authType)
{
const char *p;
// get the challenge type
if ((p = strchr(challenge, ' ')) != nullptr)
authType.Assign(challenge, p - challenge);
else
authType.Assign(challenge);
}
nsresult
nsHttpChannelAuthProvider::GetAuthenticator(const char *challenge,
nsCString &authType,
nsIHttpAuthenticator **auth)
{
LOG(("nsHttpChannelAuthProvider::GetAuthenticator [this=%p channel=%p]\n",
this, mAuthChannel));
GetAuthType(challenge, authType);
// normalize to lowercase
ToLowerCase(authType);
nsAutoCString contractid;
contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
contractid.Append(authType);
return CallGetService(contractid.get(), auth);
}
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);
CopyASCIItoUTF16(buf, userBuf);
mURI->GetPassword(buf);
if (!buf.IsEmpty()) {
NS_UnescapeURL(buf);
CopyASCIItoUTF16(buf, passBuf);
}
}
if (!userBuf.IsEmpty()) {
SetIdent(ident, authFlags, (char16_t *) userBuf.get(),
(char16_t *) passBuf.get());
}
}
void
nsHttpChannelAuthProvider::ParseRealm(const char *challenge,
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 char *p = PL_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);
}
}
}
class nsHTTPAuthInformation : public nsAuthInformationHolder {
public:
nsHTTPAuthInformation(uint32_t aFlags, const nsString& aRealm,
const nsCString& aAuthType)
: nsAuthInformationHolder(aFlags, aRealm, aAuthType) {}
void SetToHttpAuthIdentity(uint32_t authFlags,
nsHttpAuthIdentity& identity);
};
void
nsHTTPAuthInformation::SetToHttpAuthIdentity(uint32_t authFlags,
nsHttpAuthIdentity& identity)
{
identity.Set(Domain().get(), User().get(), Password().get());
}
nsresult
nsHttpChannelAuthProvider::PromptForIdentity(uint32_t level,
bool proxyAuth,
const char *realm,
const char *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;
RefPtr<nsHTTPAuthInformation> holder =
new nsHTTPAuthInformation(promptFlags, realmU,
nsDependentCString(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;
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;
const char *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.get(), realm);
nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
uint32_t appId;
bool isInBrowserElement;
GetAppIdAndBrowserStatus(chan, &appId, &isInBrowserElement);
nsHttpAuthCache *authCache = gHttpHandler->AuthCache(mIsPrivate);
nsHttpAuthEntry *entry = nullptr;
authCache->GetAuthEntryForDomain(scheme.get(), host, port,
realm.get(), appId,
isInBrowserElement,
&entry);
nsCOMPtr<nsISupports> sessionStateGrip;
if (entry)
sessionStateGrip = entry->mMetaData;
nsAuthInformationHolder* holder =
static_cast<nsAuthInformationHolder*>(aAuthInfo);
ident->Set(holder->Domain().get(),
holder->User().get(),
holder->Password().get());
nsAutoCString unused;
nsCOMPtr<nsIHttpAuthenticator> auth;
rv = GetAuthenticator(mCurrentChallenge.get(), unused,
getter_AddRefs(auth));
if (NS_FAILED(rv)) {
MOZ_ASSERT(false, "GetAuthenticator failed");
OnAuthCancelled(aContext, true);
return NS_OK;
}
nsXPIDLCString creds;
rv = GenCredsAndSetEntry(auth, mProxyAuth,
scheme.get(), host, port, path.get(),
realm.get(), mCurrentChallenge.get(), *ident,
sessionStateGrip, getter_Copies(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;
if (userCancel) {
if (!mRemainingChallenges.IsEmpty()) {
// there are still some challenges to process, do so
nsresult rv;
nsAutoCString creds;
rv = GetCredentials(mRemainingChallenges.get(), 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);
}
else 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();
}
mAuthChannel->OnAuthCancelled(userCancel);
return NS_OK;
}
nsresult
nsHttpChannelAuthProvider::ContinueOnAuthAvailable(const nsCSubstring& 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();
mAuthChannel->OnAuthAvailable();
return NS_OK;
}
bool
nsHttpChannelAuthProvider::ConfirmAuth(const nsString &bundleKey,
bool doYesNoPrompt)
{
// skip prompting the user if
// 1) we've already prompted the user
// 2) we're not a toplevel channel
// 3) the userpass length is less than the "phishy" threshold
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());
}
}
const char16_t *strs[2] = { ucsHost.get(), ucsUser.get() };
nsXPIDLString msg;
bundle->FormatStringFromName(bundleKey.get(), strs, 2, getter_Copies(msg));
if (!msg)
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<nsIPrompt> prompt;
NS_QueryNotificationCallbacks(callbacks, loadGroup, NS_GET_IID(nsIPrompt),
getter_AddRefs(prompt));
if (!prompt)
return true;
// do not prompt again
mSuppressDefensiveAuth = true;
bool confirmed;
if (doYesNoPrompt) {
int32_t choice;
bool checkState = false;
rv = prompt->ConfirmEx(nullptr, msg,
nsIPrompt::BUTTON_POS_1_DEFAULT +
nsIPrompt::STD_YES_NO_BUTTONS,
nullptr, nullptr, nullptr, nullptr,
&checkState, &choice);
if (NS_FAILED(rv))
return true;
confirmed = choice == 0;
}
else {
rv = prompt->Confirm(nullptr, msg, &confirmed);
if (NS_FAILED(rv))
return true;
}
return confirmed;
}
void
nsHttpChannelAuthProvider::SetAuthorizationHeader(nsHttpAuthCache *authCache,
nsHttpAtom header,
const char *scheme,
const char *host,
int32_t port,
const char *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;
if (header == nsHttp::Proxy_Authorization) {
continuationState = &mProxyAuthContinuationState;
} else {
continuationState = &mAuthContinuationState;
}
nsCOMPtr<nsIChannel> chan = do_QueryInterface(mAuthChannel);
uint32_t appId;
bool isInBrowserElement;
GetAppIdAndBrowserStatus(chan, &appId, &isInBrowserElement);
rv = authCache->GetAuthEntryForPath(scheme, host, port, path,
appId, isInBrowserElement, &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 (nsCRT::strcmp(ident.User(), entry->User()) == 0) {
uint32_t loadFlags;
if (NS_SUCCEEDED(mAuthChannel->GetLoadFlags(&loadFlags)) &&
!(loadFlags & nsIChannel::LOAD_EXPLICIT_CREDENTIALS)) {
ident.Clear();
}
}
}
bool identFromURI;
if (ident.IsEmpty()) {
ident.Set(entry->Identity());
identFromURI = false;
}
else
identFromURI = true;
nsXPIDLCString temp;
const char *creds = entry->Creds();
const char *challenge = entry->Challenge();
// 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[0] || identFromURI) && challenge[0]) {
nsCOMPtr<nsIHttpAuthenticator> auth;
nsAutoCString unused;
rv = GetAuthenticator(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(), challenge, ident,
entry->mMetaData, getter_Copies(temp));
if (NS_SUCCEEDED(rv))
creds = temp.get();
// make sure the continuation state is null since we do not
// support mixing preemptive and 'multirequest' authentication.
NS_IF_RELEASE(*continuationState);
}
}
if (creds[0]) {
LOG((" adding \"%s\" request header\n", header.get()));
if (header == nsHttp::Proxy_Authorization)
mAuthChannel->SetProxyCredentials(nsDependentCString(creds));
else
mAuthChannel->SetWWWCredentials(nsDependentCString(creds));
// 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->GetPath(path);
return rv;
}
NS_IMPL_ISUPPORTS(nsHttpChannelAuthProvider, nsICancelable,
nsIHttpChannelAuthProvider, nsIAuthPromptCallback)
} // namespace net
} // namespace mozilla