diff --git a/extensions/auth/nsHttpNegotiateAuth.cpp b/extensions/auth/nsHttpNegotiateAuth.cpp index 598193aec573..8097af6abe6a 100644 --- a/extensions/auth/nsHttpNegotiateAuth.cpp +++ b/extensions/auth/nsHttpNegotiateAuth.cpp @@ -54,7 +54,7 @@ #include "nsAuth.h" #include "nsHttpNegotiateAuth.h" -#include "nsIHttpChannel.h" +#include "nsIHttpAuthenticableChannel.h" #include "nsIProxiedChannel.h" #include "nsIAuthModule.h" #include "nsIServiceManager.h" @@ -107,7 +107,7 @@ nsHttpNegotiateAuth::GetAuthFlags(PRUint32 *flags) // there is no correct way to get the users credentials. // NS_IMETHODIMP -nsHttpNegotiateAuth::ChallengeReceived(nsIHttpChannel *httpChannel, +nsHttpNegotiateAuth::ChallengeReceived(nsIHttpAuthenticableChannel *authChannel, const char *challenge, PRBool isProxyAuth, nsISupports **sessionState, @@ -123,7 +123,7 @@ nsHttpNegotiateAuth::ChallengeReceived(nsIHttpChannel *httpChannel, nsresult rv; nsCOMPtr uri; - rv = httpChannel->GetURI(getter_AddRefs(uri)); + rv = authChannel->GetURI(getter_AddRefs(uri)); if (NS_FAILED(rv)) return rv; @@ -136,12 +136,8 @@ nsHttpNegotiateAuth::ChallengeReceived(nsIHttpChannel *httpChannel, return NS_ERROR_ABORT; } - nsCOMPtr proxied = - do_QueryInterface(httpChannel); - NS_ENSURE_STATE(proxied); - nsCOMPtr proxyInfo; - proxied->GetProxyInfo(getter_AddRefs(proxyInfo)); + authChannel->GetProxyInfo(getter_AddRefs(proxyInfo)); NS_ENSURE_STATE(proxyInfo); proxyInfo->GetHost(service); @@ -213,7 +209,7 @@ NS_IMPL_ISUPPORTS1(nsHttpNegotiateAuth, nsIHttpAuthenticator) // blob to pass to the server that requested "Negotiate" authentication. // NS_IMETHODIMP -nsHttpNegotiateAuth::GenerateCredentials(nsIHttpChannel *httpChannel, +nsHttpNegotiateAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel, const char *challenge, PRBool isProxyAuth, const PRUnichar *domain, diff --git a/netwerk/base/public/nsIProtocolProxyService.idl b/netwerk/base/public/nsIProtocolProxyService.idl index 004deb9a89a2..110f6f367c39 100644 --- a/netwerk/base/public/nsIProtocolProxyService.idl +++ b/netwerk/base/public/nsIProtocolProxyService.idl @@ -52,7 +52,7 @@ interface nsIURI; * * @status UNDER_REVIEW */ -[scriptable, uuid(e38ab577-786e-4a7f-936b-7ae4c7d877b2)] +[scriptable, uuid(d7ec6237-162e-40f5-a2b4-46ccd5fa83c9)] interface nsIProtocolProxyService : nsISupports { /** @@ -71,6 +71,39 @@ interface nsIProtocolProxyService : nsISupports */ const unsigned long RESOLVE_NON_BLOCKING = 1 << 0; + /** + * When the proxy configuration is manual this flag may be passed to the + * resolve and asyncResolve methods to request to prefer the SOCKS proxy + * to HTTP ones. + */ + const unsigned long RESOLVE_PREFER_SOCKS_PROXY = 1 << 1; + + /** + * When the proxy configuration is manual this flag may be passed to the + * resolve and asyncResolve methods to request to not analyze the uri's + * scheme specific proxy. When this flag is set the main HTTP proxy is the + * preferred one. + * + * NOTE: if RESOLVE_PREFER_SOCKS_PROXY is set then the SOCKS proxy is + * the preferred one. + * + * NOTE: if RESOLVE_PREFER_HTTPS_PROXY is set then the HTTPS proxy + * is the preferred one. + */ + const unsigned long RESOLVE_IGNORE_URI_SCHEME = 1 << 2; + + /** + * When the proxy configuration is manual this flag may be passed to the + * resolve and asyncResolve methods to request to prefer the HTTPS proxy + * to the others HTTP ones. + * + * NOTE: RESOLVE_PREFER_SOCKS_PROXY takes precedence over this flag. + * + * NOTE: This flag implies RESOLVE_IGNORE_URI_SCHEME. + */ + const unsigned long RESOLVE_PREFER_HTTPS_PROXY = + (1 << 3) | RESOLVE_IGNORE_URI_SCHEME; + /** * This method returns a nsIProxyInfo instance that identifies a proxy to * be used for loading the given URI. Otherwise, this method returns null @@ -228,4 +261,19 @@ interface nsIProtocolProxyService : nsISupports * The nsIProtocolProxyFilter instance to be unregistered. */ void unregisterFilter(in nsIProtocolProxyFilter aFilter); + + /** + * These values correspond to the possible integer values for the + * network.proxy.type preference. + */ + const unsigned long PROXYCONFIG_DIRECT = 0; + const unsigned long PROXYCONFIG_MANUAL = 1; + const unsigned long PROXYCONFIG_PAC = 2; + const unsigned long PROXYCONFIG_WPAD = 4; + const unsigned long PROXYCONFIG_SYSTEM = 5; + + /** + * This attribute specifies the current type of proxy configuration. + */ + readonly attribute unsigned long proxyConfigType; }; diff --git a/netwerk/base/src/nsProtocolProxyService.cpp b/netwerk/base/src/nsProtocolProxyService.cpp index eee94fe093ca..ac481c4f8850 100644 --- a/netwerk/base/src/nsProtocolProxyService.cpp +++ b/netwerk/base/src/nsProtocolProxyService.cpp @@ -286,6 +286,9 @@ proxy_GetBoolPref(nsIPrefBranch *aPrefBranch, //---------------------------------------------------------------------------- +static const PRInt32 PROXYCONFIG_DIRECT4X = 3; +static const PRInt32 PROXYCONFIG_COUNT = 6; + NS_IMPL_ADDREF(nsProtocolProxyService) NS_IMPL_RELEASE(nsProtocolProxyService) NS_IMPL_QUERY_INTERFACE3_CI(nsProtocolProxyService, @@ -298,7 +301,7 @@ NS_IMPL_CI_INTERFACE_GETTER2(nsProtocolProxyService, nsProtocolProxyService::nsProtocolProxyService() : mFilters(nsnull) - , mProxyConfig(eProxyConfig_Direct) + , mProxyConfig(PROXYCONFIG_DIRECT) , mHTTPProxyPort(-1) , mFTPProxyPort(-1) , mHTTPSProxyPort(-1) @@ -386,26 +389,26 @@ nsProtocolProxyService::PrefsChanged(nsIPrefBranch *prefBranch, rv = prefBranch->GetIntPref(PROXY_PREF("type"), &type); if (NS_SUCCEEDED(rv)) { // bug 115720 - for ns4.x backwards compatability - if (type == eProxyConfig_Direct4x) { - type = eProxyConfig_Direct; + if (type == PROXYCONFIG_DIRECT4X) { + type = PROXYCONFIG_DIRECT; // Reset the type so that the dialog looks correct, and we // don't have to handle this case everywhere else // I'm paranoid about a loop of some sort - only do this // if we're enumerating all prefs, and ignore any error if (!pref) prefBranch->SetIntPref(PROXY_PREF("type"), type); - } else if (type >= eProxyConfig_Last) { + } else if (type >= PROXYCONFIG_COUNT) { LOG(("unknown proxy type: %lu; assuming direct\n", type)); - type = eProxyConfig_Direct; + type = PROXYCONFIG_DIRECT; } - mProxyConfig = static_cast(type); + mProxyConfig = type; reloadPAC = PR_TRUE; } - if (mProxyConfig == eProxyConfig_System) { + if (mProxyConfig == PROXYCONFIG_SYSTEM) { mSystemProxySettings = do_GetService(NS_SYSTEMPROXYSETTINGS_CONTRACTID); if (!mSystemProxySettings) - mProxyConfig = eProxyConfig_Direct; + mProxyConfig = PROXYCONFIG_DIRECT; } else { mSystemProxySettings = nsnull; } @@ -462,8 +465,8 @@ nsProtocolProxyService::PrefsChanged(nsIPrefBranch *prefBranch, // We're done if not using something that could give us a PAC URL // (PAC, WPAD or System) - if (mProxyConfig != eProxyConfig_PAC && mProxyConfig != eProxyConfig_WPAD && - mProxyConfig != eProxyConfig_System) + if (mProxyConfig != PROXYCONFIG_PAC && mProxyConfig != PROXYCONFIG_WPAD && + mProxyConfig != PROXYCONFIG_SYSTEM) return; // OK, we need to reload the PAC file if: @@ -475,10 +478,10 @@ nsProtocolProxyService::PrefsChanged(nsIPrefBranch *prefBranch, if (reloadPAC) { tempString.Truncate(); - if (mProxyConfig == eProxyConfig_PAC) { + if (mProxyConfig == PROXYCONFIG_PAC) { prefBranch->GetCharPref(PROXY_PREF("autoconfig_url"), getter_Copies(tempString)); - } else if (mProxyConfig == eProxyConfig_WPAD) { + } else if (mProxyConfig == PROXYCONFIG_WPAD) { // We diverge from the WPAD spec here in that we don't walk the // hosts's FQDN, stripping components until we hit a TLD. Doing so // is dangerous in the face of an incomplete list of TLDs, and TLDs @@ -814,9 +817,9 @@ nsProtocolProxyService::ReloadPAC() return NS_OK; nsXPIDLCString pacSpec; - if (type == eProxyConfig_PAC) + if (type == PROXYCONFIG_PAC) prefs->GetCharPref(PROXY_PREF("autoconfig_url"), getter_Copies(pacSpec)); - else if (type == eProxyConfig_WPAD) + else if (type == PROXYCONFIG_WPAD) pacSpec.AssignLiteral(WPAD_URL); if (!pacSpec.IsEmpty()) @@ -835,7 +838,7 @@ nsProtocolProxyService::Resolve(nsIURI *uri, PRUint32 flags, return rv; PRBool usePAC; - rv = Resolve_Internal(uri, info, &usePAC, result); + rv = Resolve_Internal(uri, info, flags, &usePAC, result); if (NS_FAILED(rv)) return rv; @@ -884,7 +887,7 @@ nsProtocolProxyService::AsyncResolve(nsIURI *uri, PRUint32 flags, PRBool usePAC; nsCOMPtr pi; - rv = Resolve_Internal(uri, info, &usePAC, getter_AddRefs(pi)); + rv = Resolve_Internal(uri, info, flags, &usePAC, getter_AddRefs(pi)); if (NS_FAILED(rv)) return rv; @@ -946,8 +949,8 @@ nsProtocolProxyService::GetFailoverForProxy(nsIProxyInfo *aProxy, { // We only support failover when a PAC file is configured, either // directly or via system settings - if (mProxyConfig != eProxyConfig_PAC && mProxyConfig != eProxyConfig_WPAD && - mProxyConfig != eProxyConfig_System) + if (mProxyConfig != PROXYCONFIG_PAC && mProxyConfig != PROXYCONFIG_WPAD && + mProxyConfig != PROXYCONFIG_SYSTEM) return NS_ERROR_NOT_AVAILABLE; // Verify that |aProxy| is one of our nsProxyInfo objects. @@ -1033,6 +1036,14 @@ nsProtocolProxyService::UnregisterFilter(nsIProtocolProxyFilter *filter) // No need to throw an exception in this case. return NS_OK; } + +NS_IMETHODIMP +nsProtocolProxyService::GetProxyConfigType(PRUint32* aProxyConfigType) +{ + *aProxyConfigType = mProxyConfig; + return NS_OK; +} + void nsProtocolProxyService::LoadHostFilters(const char *filters) { @@ -1220,6 +1231,7 @@ nsProtocolProxyService::NewProxyInfo_Internal(const char *aType, nsresult nsProtocolProxyService::Resolve_Internal(nsIURI *uri, const nsProtocolInfo &info, + PRUint32 flags, PRBool *usePAC, nsIProxyInfo **result) { @@ -1254,14 +1266,14 @@ nsProtocolProxyService::Resolve_Internal(nsIURI *uri, // if proxies are enabled and this host:port combo is supposed to use a // proxy, check for a proxy. - if (mProxyConfig == eProxyConfig_Direct || - (mProxyConfig == eProxyConfig_Manual && + if (mProxyConfig == PROXYCONFIG_DIRECT || + (mProxyConfig == PROXYCONFIG_MANUAL && !CanUseProxy(uri, info.defaultPort))) return NS_OK; // Proxy auto config magic... - if (mProxyConfig == eProxyConfig_PAC || mProxyConfig == eProxyConfig_WPAD || - mProxyConfig == eProxyConfig_System) { + if (mProxyConfig == PROXYCONFIG_PAC || mProxyConfig == PROXYCONFIG_WPAD || + mProxyConfig == PROXYCONFIG_SYSTEM) { // Do not query PAC now. *usePAC = PR_TRUE; return NS_OK; @@ -1274,19 +1286,39 @@ nsProtocolProxyService::Resolve_Internal(nsIURI *uri, PRUint32 proxyFlags = 0; - if (!mHTTPProxyHost.IsEmpty() && mHTTPProxyPort > 0 && - info.scheme.EqualsLiteral("http")) { + if ((flags & RESOLVE_PREFER_SOCKS_PROXY) && + !mSOCKSProxyHost.IsEmpty() && mSOCKSProxyPort > 0) { + host = &mSOCKSProxyHost; + if (mSOCKSProxyVersion == 4) + type = kProxyType_SOCKS4; + else + type = kProxyType_SOCKS; + port = mSOCKSProxyPort; + if (mSOCKSProxyRemoteDNS) + proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST; + } + else if ((flags & RESOLVE_PREFER_HTTPS_PROXY) && + !mHTTPSProxyHost.IsEmpty() && mHTTPSProxyPort > 0) { + host = &mHTTPSProxyHost; + type = kProxyType_HTTP; + port = mHTTPSProxyPort; + } + else if (!mHTTPProxyHost.IsEmpty() && mHTTPProxyPort > 0 && + ((flags & RESOLVE_IGNORE_URI_SCHEME) || + info.scheme.EqualsLiteral("http"))) { host = &mHTTPProxyHost; type = kProxyType_HTTP; port = mHTTPProxyPort; } else if (!mHTTPSProxyHost.IsEmpty() && mHTTPSProxyPort > 0 && + !(flags & RESOLVE_IGNORE_URI_SCHEME) && info.scheme.EqualsLiteral("https")) { host = &mHTTPSProxyHost; type = kProxyType_HTTP; port = mHTTPSProxyPort; } else if (!mFTPProxyHost.IsEmpty() && mFTPProxyPort > 0 && + !(flags & RESOLVE_IGNORE_URI_SCHEME) && info.scheme.EqualsLiteral("ftp")) { host = &mFTPProxyHost; type = kProxyType_HTTP; diff --git a/netwerk/base/src/nsProtocolProxyService.h b/netwerk/base/src/nsProtocolProxyService.h index 47e7e52315ea..f6ed3813d029 100644 --- a/netwerk/base/src/nsProtocolProxyService.h +++ b/netwerk/base/src/nsProtocolProxyService.h @@ -220,6 +220,8 @@ protected: * The URI to test. * @param info * Information about the URI's protocol. + * @param flags + * The flags passed to either the resolve or the asyncResolve method. * @param usePAC * If this flag is set upon return, then PAC should be queried to * resolve the proxy info. @@ -228,6 +230,7 @@ protected: */ NS_HIDDEN_(nsresult) Resolve_Internal(nsIURI *uri, const nsProtocolInfo &info, + PRUint32 flags, PRBool *usePAC, nsIProxyInfo **result); @@ -306,17 +309,6 @@ public: PRUint32 host_len; }; - // These values correspond to the integer network.proxy.type preference - enum ProxyConfig { - eProxyConfig_Direct, - eProxyConfig_Manual, - eProxyConfig_PAC, - eProxyConfig_Direct4x, - eProxyConfig_WPAD, - eProxyConfig_System, // use system proxy settings if available, otherwise DIRECT - eProxyConfig_Last - }; - protected: // simplified array of filters defined by this struct @@ -357,7 +349,7 @@ protected: // of FilterLink objects. FilterLink *mFilters; - ProxyConfig mProxyConfig; + PRUint32 mProxyConfig; nsCString mHTTPProxyHost; PRInt32 mHTTPProxyPort; diff --git a/netwerk/build/nsNetCID.h b/netwerk/build/nsNetCID.h index b6023cc4666d..abf715d81c15 100644 --- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -556,6 +556,18 @@ {0x9f, 0xd4, 0xe0, 0x65, 0xe8, 0x55, 0x68, 0xf4} \ } +#define NS_HTTPCHANNELAUTHPROVIDER_CLASSNAME \ + "nsHttpChannelAuthProvider" +#define NS_HTTPCHANNELAUTHPROVIDER_CONTRACTID \ + "@mozilla.org/network/http-channel-auth-provider;1" +#define NS_HTTPCHANNELAUTHPROVIDER_CID \ +{ /* 02f5a8d8-4ef3-48b1-b527-8a643056abbd */ \ + 0x02f5a8d8, \ + 0x4ef3, \ + 0x48b1, \ + {0xb5, 0x27, 0x8a, 0x64, 0x30, 0x56, 0xab, 0xbd} \ +} + #define NS_HTTPACTIVITYDISTRIBUTOR_CLASSNAME \ "nsHttpActivityDistributor" #define NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID \ diff --git a/netwerk/build/nsNetModule.cpp b/netwerk/build/nsNetModule.cpp index abba1a7f6283..b4ba036b824c 100644 --- a/netwerk/build/nsNetModule.cpp +++ b/netwerk/build/nsNetModule.cpp @@ -228,6 +228,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsFtpProtocolHandler, Init) #undef LOG #undef LOG_ENABLED #include "nsHttpAuthManager.h" +#include "nsHttpChannelAuthProvider.h" #include "nsHttpBasicAuth.h" #include "nsHttpDigestAuth.h" #include "nsHttpNTLMAuth.h" @@ -238,6 +239,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpNTLMAuth) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsHttpHandler, Init) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsHttpsHandler, Init) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsHttpAuthManager, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpChannelAuthProvider) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsHttpActivityDistributor, Init) NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpBasicAuth) NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpDigestAuth) @@ -976,6 +978,11 @@ static const nsModuleComponentInfo gNetModuleInfo[] = { NS_HTTPAUTHMANAGER_CONTRACTID, nsHttpAuthManagerConstructor }, + { NS_HTTPCHANNELAUTHPROVIDER_CLASSNAME, + NS_HTTPCHANNELAUTHPROVIDER_CID, + NS_HTTPCHANNELAUTHPROVIDER_CONTRACTID, + nsHttpChannelAuthProviderConstructor }, + { NS_HTTPACTIVITYDISTRIBUTOR_CLASSNAME, NS_HTTPACTIVITYDISTRIBUTOR_CID, NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID, diff --git a/netwerk/dns/nsDNSService2.cpp b/netwerk/dns/nsDNSService2.cpp index e4a192385983..adf1ba1df25b 100644 --- a/netwerk/dns/nsDNSService2.cpp +++ b/netwerk/dns/nsDNSService2.cpp @@ -51,7 +51,7 @@ #include "nsNetCID.h" #include "nsNetError.h" #include "nsDNSPrefetch.h" -#include "nsProtocolProxyService.h" +#include "nsIProtocolProxyService.h" #include "prsystem.h" #include "prnetdb.h" #include "prmon.h" @@ -333,7 +333,7 @@ nsDNSService::Init() PRBool enableIDN = PR_TRUE; PRBool disableIPv6 = PR_FALSE; PRBool disablePrefetch = PR_FALSE; - int proxyType = nsProtocolProxyService::eProxyConfig_Direct; + int proxyType = nsIProtocolProxyService::PROXYCONFIG_DIRECT; nsAdoptingCString ipv4OnlyDomains; @@ -395,7 +395,7 @@ nsDNSService::Init() mDisableIPv6 = disableIPv6; // Disable prefetching either by explicit preference or if a manual proxy is configured - mDisablePrefetch = disablePrefetch || (proxyType == nsProtocolProxyService::eProxyConfig_Manual); + mDisablePrefetch = disablePrefetch || (proxyType == nsIProtocolProxyService::PROXYCONFIG_MANUAL); } nsDNSPrefetch::Initialize(this); diff --git a/netwerk/protocol/http/Makefile.in b/netwerk/protocol/http/Makefile.in index d452de6e998c..4b912ed84432 100644 --- a/netwerk/protocol/http/Makefile.in +++ b/netwerk/protocol/http/Makefile.in @@ -63,6 +63,8 @@ XPIDLSRCS = \ nsIHttpChannelInternal.idl \ nsIHttpEventSink.idl \ nsIHttpProtocolHandler.idl \ + nsIHttpChannelAuthProvider.idl \ + nsIHttpAuthenticableChannel.idl \ $(NULL) CPPSRCS = \ @@ -84,6 +86,7 @@ CPPSRCS = \ nsHttpChannel.cpp \ nsHttpPipeline.cpp \ nsHttpActivityDistributor.cpp \ + nsHttpChannelAuthProvider.cpp \ $(NULL) LOCAL_INCLUDES = \ diff --git a/netwerk/protocol/http/nsHttpBasicAuth.cpp b/netwerk/protocol/http/nsHttpBasicAuth.cpp index a86342790aed..4c37c56467f4 100644 --- a/netwerk/protocol/http/nsHttpBasicAuth.cpp +++ b/netwerk/protocol/http/nsHttpBasicAuth.cpp @@ -70,7 +70,7 @@ NS_IMPL_ISUPPORTS1(nsHttpBasicAuth, nsIHttpAuthenticator) //----------------------------------------------------------------------------- NS_IMETHODIMP -nsHttpBasicAuth::ChallengeReceived(nsIHttpChannel *httpChannel, +nsHttpBasicAuth::ChallengeReceived(nsIHttpAuthenticableChannel *authChannel, const char *challenge, PRBool isProxyAuth, nsISupports **sessionState, @@ -84,7 +84,7 @@ nsHttpBasicAuth::ChallengeReceived(nsIHttpChannel *httpChannel, } NS_IMETHODIMP -nsHttpBasicAuth::GenerateCredentials(nsIHttpChannel *httpChannel, +nsHttpBasicAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel, const char *challenge, PRBool isProxyAuth, const PRUnichar *domain, diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 416b1cdd56ea..00574580936f 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -50,12 +50,9 @@ #include "nsHttpAuthCache.h" #include "nsHttpResponseHead.h" #include "nsHttp.h" -#include "nsIHttpAuthenticator.h" #include "nsIApplicationCacheService.h" #include "nsIApplicationCacheContainer.h" #include "nsIAuthInformation.h" -#include "nsIAuthPrompt2.h" -#include "nsIAuthPromptProvider.h" #include "nsIStringBundle.h" #include "nsXPCOM.h" #include "nsISupportsPrimitives.h" @@ -80,7 +77,6 @@ #include "nsChannelProperties.h" #include "nsStreamUtils.h" #include "nsIOService.h" -#include "nsAuthInformationHolder.h" #include "nsICacheService.h" #include "nsDNSPrefetch.h" #include "nsChannelClassifier.h" @@ -109,8 +105,6 @@ nsHttpChannel::nsHttpChannel() , mCacheAccess(0) , mPostID(0) , mRequestTime(0) - , mProxyAuthContinuationState(nsnull) - , mAuthContinuationState(nsnull) , mStartPos(LL_MAXUINT) , mPendingAsyncCallOnResume(nsnull) , mSuspendCount(0) @@ -126,10 +120,6 @@ nsHttpChannel::nsHttpChannel() , mTransactionReplaced(PR_FALSE) , mUploadStreamHasHeaders(PR_FALSE) , mAuthRetryPending(PR_FALSE) - , mProxyAuth(PR_FALSE) - , mTriedProxyAuth(PR_FALSE) - , mTriedHostAuth(PR_FALSE) - , mSuppressDefensiveAuth(PR_FALSE) , mResuming(PR_FALSE) , mInitedCacheEntry(PR_FALSE) , mCacheForOfflineUse(PR_FALSE) @@ -153,12 +143,14 @@ nsHttpChannel::~nsHttpChannel() { LOG(("Destroying nsHttpChannel [this=%p]\n", this)); + if (mAuthProvider) { + mAuthProvider->Disconnect(NS_ERROR_ABORT); + mAuthProvider = nsnull; + } + NS_IF_RELEASE(mConnectionInfo); NS_IF_RELEASE(mTransaction); - NS_IF_RELEASE(mProxyAuthContinuationState); - NS_IF_RELEASE(mAuthContinuationState); - delete mResponseHead; delete mCachedResponseHead; @@ -236,7 +228,16 @@ nsHttpChannel::Init(nsIURI *uri, AddStandardRequestHeaders(&mRequestHead.Headers(), caps, !mConnectionInfo->UsingSSL() && mConnectionInfo->UsingHttpProxy()); + if (NS_FAILED(rv)) return rv; + mAuthProvider = + do_CreateInstance("@mozilla.org/network/http-channel-auth-provider;1", + &rv); + if (NS_FAILED(rv)) return rv; + + rv = mAuthProvider->Init(this); + if (NS_FAILED(rv)) return rv; + return rv; } @@ -348,7 +349,7 @@ nsHttpChannel::Connect(PRBool firstTime) } // check to see if authorization headers should be included - AddAuthorizationHeaders(); + mAuthProvider->AddAuthorizationHeaders(); if (mLoadFlags & LOAD_NO_NETWORK_IO) { return NS_ERROR_DOCUMENT_NOT_CACHED; @@ -968,16 +969,16 @@ nsHttpChannel::ProcessResponse() // handle unused username and password in url (see bug 232567) if (httpStatus != 401 && httpStatus != 407) { - CheckForSuperfluousAuth(); + if (!mAuthRetryPending) + mAuthProvider->CheckForSuperfluousAuth(); if (mCanceled) return CallOnStartRequest(); - if (mAuthContinuationState) { - // reset the current continuation state because our last - // authentication attempt has been completed successfully - NS_RELEASE(mAuthContinuationState); - LOG((" continuation state has been reset")); - } + // reset the authentication's current continuation state because our + // last authentication attempt has been completed successfully + mAuthProvider->Disconnect(NS_ERROR_ABORT); + mAuthProvider = nsnull; + LOG((" continuation state has been reset")); } // handle different server response categories. Note that we handle @@ -1044,14 +1045,31 @@ nsHttpChannel::ProcessResponse() break; case 401: case 407: - rv = ProcessAuthentication(httpStatus); - if (NS_FAILED(rv)) { + rv = mAuthProvider->ProcessAuthentication( + httpStatus, mConnectionInfo->UsingSSL() && + mTransaction->SSLConnectFailed()); + if (rv == NS_ERROR_IN_PROGRESS) { + // authentication prompt has been invoked and result + // is expected asynchronously + mAuthRetryPending = PR_TRUE; + // suspend the transaction pump to stop receiving the + // unauthenticated content data. We will throw that data + // away when user provides credentials or resume the pump + // when user refuses to authenticate. + LOG(("Suspending the transaction, asynchronously prompting for credentials")); + mTransactionPump->Suspend(); + rv = NS_OK; + } + else if (NS_FAILED(rv)) { LOG(("ProcessAuthentication failed [rv=%x]\n", rv)); if (mTransaction->SSLConnectFailed()) return ProcessFailedSSLConnect(httpStatus); - CheckForSuperfluousAuth(); + if (!mAuthRetryPending) + mAuthProvider->CheckForSuperfluousAuth(); rv = ProcessNormal(); } + else + mAuthRetryPending = PR_TRUE; // see DoAuthRetry break; default: rv = ProcessNormal(); @@ -3030,822 +3048,25 @@ nsHttpChannel::ProcessRedirection(PRUint32 redirectType) // nsHttpChannel //----------------------------------------------------------------------------- -// buf contains "domain\user" -static void -ParseUserDomain(PRUnichar *buf, - const PRUnichar **user, - const PRUnichar **domain) -{ - PRUnichar *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, - PRUint32 authFlags, - PRUnichar *userBuf, - PRUnichar *passBuf) -{ - const PRUnichar *user = userBuf; - const PRUnichar *domain = nsnull; - - 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, PRBool proxyAuth, - nsIAuthPrompt2 **result) -{ - if (!ifreq) - return; - - PRUint32 promptReason; - if (proxyAuth) - promptReason = nsIAuthPromptProvider::PROMPT_PROXY; - else - promptReason = nsIAuthPromptProvider::PROMPT_NORMAL; - - nsCOMPtr promptProvider = do_GetInterface(ifreq); - if (promptProvider) - promptProvider->GetAuthPrompt(promptReason, - NS_GET_IID(nsIAuthPrompt2), - reinterpret_cast(result)); - else - NS_QueryAuthPrompt2(ifreq, result); -} - -// generate credentials for the given challenge, and update the auth cache. -nsresult -nsHttpChannel::GenCredsAndSetEntry(nsIHttpAuthenticator *auth, - PRBool proxyAuth, - const char *scheme, - const char *host, - PRInt32 port, - const char *directory, - const char *realm, - const char *challenge, - const nsHttpAuthIdentity &ident, - nsCOMPtr &sessionState, - char **result) -{ - nsresult rv; - PRUint32 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; - } - - PRUint32 generateFlags; - rv = auth->GenerateCredentials(this, - 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. - PRBool saveCreds = - 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CREDENTIALS); - PRBool saveChallenge = - 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CHALLENGE); - - PRBool saveIdentity = - 0 == (generateFlags & nsIHttpAuthenticator::USING_INTERNAL_IDENTITY); - - // this getter never fails - nsHttpAuthCache *authCache = gHttpHandler->AuthCache(); - - // 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 : nsnull, - saveChallenge ? challenge : nsnull, - saveIdentity ? &ident : nsnull, - sessionState); - return rv; -} - -nsresult -nsHttpChannel::ProcessAuthentication(PRUint32 httpStatus) -{ - LOG(("nsHttpChannel::ProcessAuthentication [this=%p code=%u]\n", - this, httpStatus)); - - if (mLoadFlags & LOAD_ANONYMOUS) { - return NS_ERROR_NOT_AVAILABLE; - } - - const char *challenges; - mProxyAuth = (httpStatus == 407); - - nsresult 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 inadvertantly 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 (!mConnectionInfo->UsingHttpProxy()) { - LOG(("rejecting 407 when proxy server not configured!\n")); - return NS_ERROR_UNEXPECTED; - } - if (mConnectionInfo->UsingSSL() && !mTransaction->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; - } - challenges = mResponseHead->PeekHeader(nsHttp::Proxy_Authenticate); - } - else - challenges = mResponseHead->PeekHeader(nsHttp::WWW_Authenticate); - NS_ENSURE_TRUE(challenges, NS_ERROR_UNEXPECTED); - - nsCAutoString creds; - rv = GetCredentials(challenges, mProxyAuth, creds); - if (rv == NS_ERROR_IN_PROGRESS) { - // authentication prompt has been invoked and result - // is expected asynchronously - mAuthRetryPending = PR_TRUE; - // suspend the transaction pump to stop receiving the - // unauthenticated content data. We will throw that data - // away when user provides credentials or resume the pump - // when user refuses to authenticate. - LOG(("Suspending the transaction, asynchronously prompting for credentials")); - mTransactionPump->Suspend(); - return NS_OK; - } - else if (NS_FAILED(rv)) - LOG(("unable to authenticate\n")); - else { - // set the authentication credentials - if (mProxyAuth) - mRequestHead.SetHeader(nsHttp::Proxy_Authorization, creds); - else - mRequestHead.SetHeader(nsHttp::Authorization, creds); - - mAuthRetryPending = PR_TRUE; // see DoAuthRetry - } - return rv; -} - -nsresult -nsHttpChannel::PrepareForAuthentication(PRBool proxyAuth) -{ - LOG(("nsHttpChannel::PrepareForAuthentication [this=%p]\n", this)); - - 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 (!mConnectionInfo->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). - - nsCAutoString contractId; - contractId.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX); - contractId.Append(mProxyAuthType); - - nsresult rv; - nsCOMPtr precedingAuth = - do_GetService(contractId.get(), &rv); - if (NS_FAILED(rv)) - return rv; - - PRUint32 precedingAuthFlags; - rv = precedingAuth->GetAuthFlags(&precedingAuthFlags); - if (NS_FAILED(rv)) - return rv; - - if (!(precedingAuthFlags & nsIHttpAuthenticator::REQUEST_BASED)) { - const char *challenges = - mResponseHead->PeekHeader(nsHttp::Proxy_Authenticate); - if (!challenges) { - // delete the proxy authorization header because we weren't - // asked to authenticate - mRequestHead.ClearHeader(nsHttp::Proxy_Authorization); - LOG((" cleared proxy authorization header")); - } - } - - return NS_OK; -} - -nsresult -nsHttpChannel::GetCredentials(const char *challenges, - PRBool proxyAuth, - nsAFlatCString &creds) -{ - nsCOMPtr auth; - nsCAutoString 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; - PRBool gotCreds = PR_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')) != nsnull) - 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 = PR_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 : nsnull; - 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 -nsHttpChannel::GetAuthorizationMembers(PRBool proxyAuth, - nsCSubstring& scheme, - const char*& host, - PRInt32& port, - nsCSubstring& path, - nsHttpAuthIdentity*& ident, - nsISupports**& continuationState) -{ - if (proxyAuth) { - NS_ASSERTION (mConnectionInfo->UsingHttpProxy(), "proxyAuth is true, but no HTTP proxy is configured!"); - - host = mConnectionInfo->ProxyHost(); - port = mConnectionInfo->ProxyPort(); - ident = &mProxyIdent; - scheme.AssignLiteral("http"); - - continuationState = &mProxyAuthContinuationState; - } - else { - host = mConnectionInfo->Host(); - port = mConnectionInfo->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 -nsHttpChannel::GetCredentialsForChallenge(const char *challenge, - const char *authType, - PRBool proxyAuth, - nsIHttpAuthenticator *auth, - nsAFlatCString &creds) -{ - LOG(("nsHttpChannel::GetCredentialsForChallenge [this=%p proxyAuth=%d challenges=%s]\n", - this, proxyAuth, challenge)); - - // this getter never fails - nsHttpAuthCache *authCache = gHttpHandler->AuthCache(); - - PRUint32 authFlags; - nsresult rv = auth->GetAuthFlags(&authFlags); - if (NS_FAILED(rv)) return rv; - - nsCAutoString 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; - PRInt32 port; - nsHttpAuthIdentity *ident; - nsCAutoString path, scheme; - PRBool identFromURI = PR_FALSE; - nsISupports **continuationState; - - rv = GetAuthorizationMembers(proxyAuth, scheme, host, port, path, ident, continuationState); - 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 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 = nsnull; - authCache->GetAuthEntryForDomain(scheme.get(), host, port, realm.get(), &entry); - - // hold reference to the auth session state (in case we clear our - // reference to the entry). - nsCOMPtr sessionStateGrip; - if (entry) - sessionStateGrip = entry->mMetaData; - - // for digest auth, maybe our cached nonce value simply timed out... - PRBool identityInvalid; - nsISupports *sessionState = sessionStateGrip; - rv = auth->ChallengeReceived(this, - 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())) { - 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()); - entry = nsnull; - ident->Clear(); - } - else if (!identFromURI || nsCRT::strcmp(ident->User(), entry->Identity().User()) == 0) { - 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 = PR_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()) { - PRUint32 level = nsIAuthPrompt2::LEVEL_NONE; - if (scheme.EqualsLiteral("https")) - level = nsIAuthPrompt2::LEVEL_SECURE; - else if (authFlags & nsIHttpAuthenticator::IDENTITY_ENCRYPTED) - level = nsIAuthPrompt2::LEVEL_PW_ENCRYPTED; - - // 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 = PR_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"), PR_FALSE)) { - // calling cancel here sets our mStatus and aborts the HTTP - // transaction, which prevents OnDataAvailable events. - 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; -} - -nsresult -nsHttpChannel::GetAuthenticator(const char *challenge, - nsCString &authType, - nsIHttpAuthenticator **auth) -{ - LOG(("nsHttpChannel::GetAuthenticator [this=%p]\n", this)); - - GetAuthType(challenge, authType); - - // normalize to lowercase - ToLowerCase(authType); - - nsCAutoString contractid; - contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX); - contractid.Append(authType); - - return CallGetService(contractid.get(), auth); -} - -void -nsHttpChannel::GetIdentityFromURI(PRUint32 authFlags, nsHttpAuthIdentity &ident) -{ - LOG(("nsHttpChannel::GetIdentityFromURI [this=%p]\n", this)); - - nsAutoString userBuf; - nsAutoString passBuf; - - // XXX i18n - nsCAutoString 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, (PRUnichar *) userBuf.get(), (PRUnichar *) passBuf.get()); -} - -void -nsHttpChannel::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) { - PRBool has_quote = PR_FALSE; - p += 6; - if (*p == '"') { - has_quote = PR_TRUE; - p++; - } - - const char *end = p; - while (*end && has_quote) { - // Loop through all the string characters to find the closing - // quote, ignoring escaped quotes. - if (*end == '"' && end[-1] != '\\') - break; - ++end; - } - - if (!has_quote) - end = strchr(p, ' '); - if (end) - realm.Assign(p, end - p); - else - realm.Assign(p); - } -} - - -class nsHTTPAuthInformation : public nsAuthInformationHolder { -public: - nsHTTPAuthInformation(PRUint32 aFlags, const nsString& aRealm, - const nsCString& aAuthType) - : nsAuthInformationHolder(aFlags, aRealm, aAuthType) {} - - void SetToHttpAuthIdentity(PRUint32 authFlags, nsHttpAuthIdentity& identity); -}; - -void -nsHTTPAuthInformation::SetToHttpAuthIdentity(PRUint32 authFlags, nsHttpAuthIdentity& identity) -{ - identity.Set(Domain().get(), User().get(), Password().get()); -} - -nsresult -nsHttpChannel::PromptForIdentity(PRUint32 level, - PRBool proxyAuth, - const char *realm, - const char *authType, - PRUint32 authFlags, - nsHttpAuthIdentity &ident) -{ - LOG(("nsHttpChannel::PromptForIdentity [this=%p]\n", this)); - - nsCOMPtr authPrompt; - GetAuthPrompt(mCallbacks, proxyAuth, getter_AddRefs(authPrompt)); - if (!authPrompt && mLoadGroup) { - nsCOMPtr cbs; - mLoadGroup->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); - - nsresult rv; - - // prompt the user... - PRUint32 promptFlags = 0; - if (proxyAuth) - { - promptFlags |= nsIAuthInformation::AUTH_PROXY; - if (mTriedProxyAuth) - promptFlags |= nsIAuthInformation::PREVIOUS_FAILED; - mTriedProxyAuth = PR_TRUE; - } - else { - promptFlags |= nsIAuthInformation::AUTH_HOST; - if (mTriedHostAuth) - promptFlags |= nsIAuthInformation::PREVIOUS_FAILED; - mTriedHostAuth = PR_TRUE; - } - - if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN) - promptFlags |= nsIAuthInformation::NEED_DOMAIN; - - nsRefPtr holder = - new nsHTTPAuthInformation(promptFlags, realmU, - nsDependentCString(authType)); - if (!holder) - return NS_ERROR_OUT_OF_MEMORY; - - rv = authPrompt->AsyncPromptAuth(this, this, nsnull, 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 - PRBool retval = PR_FALSE; - rv = authPrompt->PromptAuth(this, 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 = PR_TRUE; - - return rv; -} - -NS_IMETHODIMP nsHttpChannel::OnAuthAvailable(nsISupports *aContext, - nsIAuthInformation *aAuthInfo) +NS_IMETHODIMP nsHttpChannel::OnAuthAvailable() { LOG(("nsHttpChannel::OnAuthAvailable [this=%p]", this)); - mAsyncPromptAuthCancelable = nsnull; - nsresult rv; - - const char *host; - PRInt32 port; - nsHttpAuthIdentity *ident; - nsCAutoString path, scheme; - nsISupports **continuationState; - rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, path, ident, continuationState); - if (NS_FAILED(rv)) - OnAuthCancelled(aContext, PR_FALSE); - - nsCAutoString realm; - ParseRealm(mCurrentChallenge.get(), realm); - - nsHttpAuthCache *authCache = gHttpHandler->AuthCache(); - nsHttpAuthEntry *entry = nsnull; - authCache->GetAuthEntryForDomain(scheme.get(), host, port, realm.get(), &entry); - - nsCOMPtr sessionStateGrip; - if (entry) - sessionStateGrip = entry->mMetaData; - - nsAuthInformationHolder* holder = - static_cast(aAuthInfo); - ident->Set(holder->Domain().get(), - holder->User().get(), - holder->Password().get()); - - nsCAutoString unused; - nsCOMPtr auth; - rv = GetAuthenticator(mCurrentChallenge.get(), unused, getter_AddRefs(auth)); - if (NS_FAILED(rv)) { - NS_ASSERTION(PR_FALSE, "GetAuthenticator failed"); - OnAuthCancelled(aContext, PR_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, PR_TRUE); - return NS_OK; - } - - return ContinueOnAuthAvailable(creds); + // setting mAuthRetryPending flag and resuming the transaction + // triggers process of throwing away the unauthenticated data already + // coming from the network + mAuthRetryPending = PR_TRUE; + LOG(("Resuming the transaction, we got credentials from user")); + mTransactionPump->Resume(); + + return NS_OK; } -NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(nsISupports *aContext, - PRBool userCancel) +NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(PRBool userCancel) { LOG(("nsHttpChannel::OnAuthCancelled [this=%p]", this)); - mAsyncPromptAuthCancelable = nsnull; + if (userCancel) { - if (!mRemainingChallenges.IsEmpty()) { - // there are still some challenges to process, do so - nsresult rv; - - nsCAutoString 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(); - // ensure call of OnStartRequest of the current listener here, // it would not be called otherwise at all nsresult rv = CallOnStartRequest(); @@ -3859,261 +3080,8 @@ NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(nsISupports *aContext, if (NS_FAILED(rv)) mTransactionPump->Cancel(rv); } - - return NS_OK; -} - -nsresult -nsHttpChannel::ContinueOnAuthAvailable(const nsCSubstring& creds) -{ - if (mProxyAuth) - mRequestHead.SetHeader(nsHttp::Proxy_Authorization, creds); - else - mRequestHead.SetHeader(nsHttp::Authorization, creds); - - // 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(); - - // setting mAuthRetryPending flag and resuming the transaction - // triggers process of throwing away the unauthenticated data already - // coming from the network - mAuthRetryPending = PR_TRUE; - LOG(("Resuming the transaction, we got credentials from user")); - mTransactionPump->Resume(); - - return NS_OK; -} - -PRBool -nsHttpChannel::ConfirmAuth(const nsString &bundleKey, PRBool 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 - - if (mSuppressDefensiveAuth || !(mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)) - return PR_TRUE; - - nsresult rv; - nsCAutoString userPass; - rv = mURI->GetUserPass(userPass); - if (NS_FAILED(rv) || (userPass.Length() < gHttpHandler->PhishyUserPassLength())) - return PR_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 bundleService = - do_GetService(NS_STRINGBUNDLE_CONTRACTID); - if (!bundleService) - return PR_TRUE; - - nsCOMPtr bundle; - bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle)); - if (!bundle) - return PR_TRUE; - - nsCAutoString host; - rv = mURI->GetHost(host); - if (NS_FAILED(rv)) - return PR_TRUE; - - nsCAutoString user; - rv = mURI->GetUsername(user); - if (NS_FAILED(rv)) - return PR_TRUE; - - NS_ConvertUTF8toUTF16 ucsHost(host), ucsUser(user); - const PRUnichar *strs[2] = { ucsHost.get(), ucsUser.get() }; - - nsXPIDLString msg; - bundle->FormatStringFromName(bundleKey.get(), strs, 2, getter_Copies(msg)); - if (!msg) - return PR_TRUE; - nsCOMPtr prompt; - GetCallback(prompt); - if (!prompt) - return PR_TRUE; - - // do not prompt again - mSuppressDefensiveAuth = PR_TRUE; - - PRBool confirmed; - if (doYesNoPrompt) { - PRInt32 choice; - PRBool checkState; - rv = prompt->ConfirmEx(nsnull, msg, - nsIPrompt::BUTTON_POS_1_DEFAULT + - nsIPrompt::STD_YES_NO_BUTTONS, - nsnull, nsnull, nsnull, nsnull, &checkState, &choice); - if (NS_FAILED(rv)) - return PR_TRUE; - - confirmed = choice == 0; - } - else { - rv = prompt->Confirm(nsnull, msg, &confirmed); - if (NS_FAILED(rv)) - return PR_TRUE; - } - - return confirmed; -} - -void -nsHttpChannel::CheckForSuperfluousAuth() -{ - // 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 (!mAuthRetryPending) { - // ask user... - if (!ConfirmAuth(NS_LITERAL_STRING("SuperfluousAuth"), PR_TRUE)) { - // calling cancel here sets our mStatus and aborts the HTTP - // transaction, which prevents OnDataAvailable events. - Cancel(NS_ERROR_ABORT); - } - } -} - -void -nsHttpChannel::SetAuthorizationHeader(nsHttpAuthCache *authCache, - nsHttpAtom header, - const char *scheme, - const char *host, - PRInt32 port, - const char *path, - nsHttpAuthIdentity &ident) -{ - nsHttpAuthEntry *entry = nsnull; - 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; - } - - rv = authCache->GetAuthEntryForPath(scheme, host, port, path, &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. - if (nsCRT::strcmp(ident.User(), entry->User()) == 0) - ident.Clear(); - } - PRBool identFromURI; - if (ident.IsEmpty()) { - ident.Set(entry->Identity()); - identFromURI = PR_FALSE; - } - else - identFromURI = PR_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 auth; - nsCAutoString unused; - rv = GetAuthenticator(challenge, unused, getter_AddRefs(auth)); - if (NS_SUCCEEDED(rv)) { - PRBool 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())); - mRequestHead.SetHeader(header, 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 = PR_TRUE; - } - else - ident.Clear(); // don't remember the identity - } -} - -void -nsHttpChannel::AddAuthorizationHeaders() -{ - LOG(("nsHttpChannel::AddAuthorizationHeaders? [this=%p]\n", this)); - - if (mLoadFlags & LOAD_ANONYMOUS) { - return; - } - - // this getter never fails - nsHttpAuthCache *authCache = gHttpHandler->AuthCache(); - - // check if proxy credentials should be sent - const char *proxyHost = mConnectionInfo->ProxyHost(); - if (proxyHost && mConnectionInfo->UsingHttpProxy()) - SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization, - "http", proxyHost, mConnectionInfo->ProxyPort(), - nsnull, // proxy has no path - mProxyIdent); - - // check if server credentials should be sent - nsCAutoString path, scheme; - if (NS_SUCCEEDED(GetCurrentPath(path)) && - NS_SUCCEEDED(mURI->GetScheme(scheme))) { - SetAuthorizationHeader(authCache, nsHttp::Authorization, - scheme.get(), - mConnectionInfo->Host(), - mConnectionInfo->Port(), - path.get(), - mIdent); - } -} - -nsresult -nsHttpChannel::GetCurrentPath(nsACString &path) -{ - nsresult rv; - nsCOMPtr url = do_QueryInterface(mURI); - if (url) - rv = url->GetDirectory(path); - else - rv = mURI->GetPath(path); - return rv; + return NS_OK; } //----------------------------------------------------------------------------- @@ -4140,10 +3108,10 @@ NS_INTERFACE_MAP_BEGIN(nsHttpChannel) NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback) NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel) + NS_INTERFACE_MAP_ENTRY(nsIHttpAuthenticableChannel) NS_INTERFACE_MAP_ENTRY(nsITraceableChannel) NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer) NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel) - NS_INTERFACE_MAP_ENTRY(nsIAuthPromptCallback) NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag) //----------------------------------------------------------------------------- @@ -4191,8 +3159,8 @@ nsHttpChannel::Cancel(nsresult status) mTransactionPump->Cancel(status); if (mCachePump) mCachePump->Cancel(status); - if (mAsyncPromptAuthCancelable) - mAsyncPromptAuthCancelable->Cancel(status); + if (mAuthProvider) + mAuthProvider->Cancel(status); return NS_OK; } @@ -5157,6 +4125,61 @@ nsHttpChannel::GetProxyInfo(nsIProxyInfo **result) return NS_OK; } +//----------------------------------------------------------------------------- +// nsHttpChannel::nsIHttpAuthenticableChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsHttpChannel::GetIsSSL(PRBool *aIsSSL) +{ + *aIsSSL = mConnectionInfo->UsingSSL(); + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::GetProxyMethodIsConnect(PRBool *aProxyMethodIsConnect) +{ + *aProxyMethodIsConnect = + (mConnectionInfo->UsingHttpProxy() && mConnectionInfo->UsingSSL()); + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::GetServerResponseHeader(nsACString &value) +{ + if (!mResponseHead) + return NS_ERROR_NOT_AVAILABLE; + return mResponseHead->GetHeader(nsHttp::Server, value); +} + +NS_IMETHODIMP +nsHttpChannel::GetProxyChallenges(nsACString &value) +{ + if (!mResponseHead) + return NS_ERROR_UNEXPECTED; + return mResponseHead->GetHeader(nsHttp::Proxy_Authenticate, value); +} + +NS_IMETHODIMP +nsHttpChannel::GetWWWChallenges(nsACString &value) +{ + if (!mResponseHead) + return NS_ERROR_UNEXPECTED; + return mResponseHead->GetHeader(nsHttp::WWW_Authenticate, value); +} + +NS_IMETHODIMP +nsHttpChannel::SetProxyCredentials(const nsACString &value) +{ + return mRequestHead.SetHeader(nsHttp::Proxy_Authorization, value); +} + +NS_IMETHODIMP +nsHttpChannel::SetWWWCredentials(const nsACString &value) +{ + return mRequestHead.SetHeader(nsHttp::Authorization, value); +} + //----------------------------------------------------------------------------- // nsHttpChannel::nsIRequestObserver //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index 7ac5e995a51d..534f615ea695 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -43,7 +43,6 @@ #include "nsHttpTransaction.h" #include "nsHttpRequestHead.h" -#include "nsHttpAuthCache.h" #include "nsHashPropertyBag.h" #include "nsInputStreamPump.h" #include "nsThreadUtils.h" @@ -83,13 +82,12 @@ #include "nsISupportsPriority.h" #include "nsIProtocolProxyCallback.h" #include "nsICancelable.h" -#include "nsIProxiedChannel.h" +#include "nsIHttpAuthenticableChannel.h" #include "nsITraceableChannel.h" -#include "nsIAuthPromptCallback.h" +#include "nsIHttpChannelAuthProvider.h" class nsHttpResponseHead; class nsAHttpConnection; -class nsIHttpAuthenticator; class nsProxyInfo; //----------------------------------------------------------------------------- @@ -109,10 +107,9 @@ class nsHttpChannel : public nsHashPropertyBag , public nsIResumableChannel , public nsISupportsPriority , public nsIProtocolProxyCallback - , public nsIProxiedChannel + , public nsIHttpAuthenticableChannel , public nsITraceableChannel , public nsIApplicationCacheChannel - , public nsIAuthPromptCallback { public: NS_DECL_ISUPPORTS_INHERITED @@ -135,7 +132,19 @@ public: NS_DECL_NSITRACEABLECHANNEL NS_DECL_NSIAPPLICATIONCACHECONTAINER NS_DECL_NSIAPPLICATIONCACHECHANNEL - NS_DECL_NSIAUTHPROMPTCALLBACK + + // nsIHttpAuthenticableChannel. We can't use + // NS_DECL_NSIHTTPAUTHENTICABLECHANNEL because it duplicates cancel() and + // others. + NS_IMETHOD GetIsSSL(PRBool *aIsSSL); + NS_IMETHOD GetProxyMethodIsConnect(PRBool *aProxyMethodIsConnect); + NS_IMETHOD GetServerResponseHeader(nsACString & aServerResponseHeader); + NS_IMETHOD GetProxyChallenges(nsACString & aChallenges); + NS_IMETHOD GetWWWChallenges(nsACString & aChallenges); + NS_IMETHOD SetProxyCredentials(const nsACString & aCredentials); + NS_IMETHOD SetWWWCredentials(const nsACString & aCredentials); + NS_IMETHOD OnAuthAvailable(); + NS_IMETHOD OnAuthCancelled(PRBool userCancel); nsHttpChannel(); virtual ~nsHttpChannel(); @@ -180,7 +189,6 @@ private: nsresult ProcessRedirection(PRUint32 httpStatus); PRBool ShouldSSLProxyResponseContinue(PRUint32 httpStatus); nsresult ProcessFailedSSLConnect(PRUint32 httpStatus); - nsresult ProcessAuthentication(PRUint32 httpStatus); nsresult ProcessFallback(PRBool *fallingBack); PRBool ResponseWouldVary(); @@ -226,41 +234,8 @@ private: nsresult ProcessPartialContent(); nsresult OnDoneReadingPartialCacheEntry(PRBool *streamDone); - // auth specific methods - nsresult PrepareForAuthentication(PRBool proxyAuth); - nsresult GenCredsAndSetEntry(nsIHttpAuthenticator *, PRBool proxyAuth, const char *scheme, const char *host, PRInt32 port, const char *dir, const char *realm, const char *challenge, const nsHttpAuthIdentity &ident, nsCOMPtr &session, char **result); - nsresult GetAuthenticator(const char *challenge, nsCString &scheme, nsIHttpAuthenticator **auth); - void ParseRealm(const char *challenge, nsACString &realm); - void GetIdentityFromURI(PRUint32 authFlags, nsHttpAuthIdentity&); - /** - * Following three methods return NS_ERROR_IN_PROGRESS when - * nsIAuthPrompt2.asyncPromptAuth method is called. This result indicates - * the user's decision will be gathered in a callback and is not an actual - * error. - */ - nsresult GetCredentials(const char *challenges, PRBool proxyAuth, nsAFlatCString &creds); - nsresult GetCredentialsForChallenge(const char *challenge, const char *scheme, PRBool proxyAuth, nsIHttpAuthenticator *auth, nsAFlatCString &creds); - nsresult PromptForIdentity(PRUint32 level, PRBool proxyAuth, const char *realm, const char *authType, PRUint32 authFlags, nsHttpAuthIdentity &); - - PRBool ConfirmAuth(const nsString &bundleKey, PRBool doYesNoPrompt); - void CheckForSuperfluousAuth(); - void SetAuthorizationHeader(nsHttpAuthCache *, nsHttpAtom header, const char *scheme, const char *host, PRInt32 port, const char *path, nsHttpAuthIdentity &ident); - void AddAuthorizationHeaders(); - nsresult GetCurrentPath(nsACString &); - /** - * Return all information needed to build authorization information, - * all paramters except proxyAuth are out parameters. proxyAuth specifies - * with what authorization we work (WWW or proxy). - */ - nsresult GetAuthorizationMembers(PRBool proxyAuth, nsCSubstring& scheme, const char*& host, PRInt32& port, nsCSubstring& path, nsHttpAuthIdentity*& ident, nsISupports**& continuationState); nsresult DoAuthRetry(nsAHttpConnection *); PRBool MustValidateBasedOnQueryUrl(); - /** - * Method called to resume suspended transaction after we got credentials - * from the user. Called from OnAuthAvailable callback or OnAuthCancelled - * when credentials for next challenge were obtained synchronously. - */ - nsresult ContinueOnAuthAvailable(const nsCSubstring& creds); private: nsCOMPtr mOriginalURI; @@ -311,25 +286,7 @@ private: nsCOMPtr mApplicationCache; // auth specific data - nsISupports *mProxyAuthContinuationState; - nsCString mProxyAuthType; - nsISupports *mAuthContinuationState; - nsCString mAuthType; - nsHttpAuthIdentity mIdent; - nsHttpAuthIdentity mProxyIdent; - - // Reference to the prompt wating in prompt queue. The channel is - // responsible to call its cancel method when user in any way cancels - // this request. - nsCOMPtr mAsyncPromptAuthCancelable; - // Saved in GetCredentials when prompt is asynchronous, the first challenge - // we obtained from the server with 401/407 response, will be processed in - // OnAuthAvailable callback. - nsCString mCurrentChallenge; - // Saved in GetCredentials when prompt is asynchronous, remaning challenges - // we have to process when user cancels the auth dialog for the current - // challenge. - nsCString mRemainingChallenges; + nsCOMPtr mAuthProvider; // Resumable channel specific data nsCString mEntityID; @@ -367,12 +324,6 @@ private: PRUint32 mTransactionReplaced : 1; PRUint32 mUploadStreamHasHeaders : 1; PRUint32 mAuthRetryPending : 1; - // True when we need to authenticate to proxy, i.e. when we get 407 - // response. Used in OnAuthAvailable and OnAuthCancelled callbacks. - PRUint32 mProxyAuth : 1; - PRUint32 mTriedProxyAuth : 1; - PRUint32 mTriedHostAuth : 1; - PRUint32 mSuppressDefensiveAuth : 1; PRUint32 mResuming : 1; PRUint32 mInitedCacheEntry : 1; PRUint32 mCacheForOfflineUse : 1; diff --git a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp new file mode 100644 index 000000000000..a4e1f4d94d2e --- /dev/null +++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp @@ -0,0 +1,1308 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set expandtab ts=4 sw=4 sts=4 cin: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Netscape Communications. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher (original author) + * Christian Biesinger + * Google Inc. + * Jan Wrobel + * Jan Odvarko + * Dave Camp + * Honza Bambas + * Wellington Fernando de Macedo + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#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" + +nsHttpChannelAuthProvider::nsHttpChannelAuthProvider() + : mAuthChannel(nsnull) + , mProxyAuthContinuationState(nsnull) + , mAuthContinuationState(nsnull) + , mProxyAuth(PR_FALSE) + , mTriedProxyAuth(PR_FALSE) + , mTriedHostAuth(PR_FALSE) + , mSuppressDefensiveAuth(PR_FALSE) +{ + // grab a reference to the handler to ensure that it doesn't go away. + nsHttpHandler *handler = gHttpHandler; + NS_ADDREF(handler); +} + +nsHttpChannelAuthProvider::~nsHttpChannelAuthProvider() +{ + NS_ASSERTION(!mAuthChannel, "Disconnect wasn't called"); + + // release our reference to the handler + nsHttpHandler *handler = gHttpHandler; + NS_RELEASE(handler); +} + +NS_IMETHODIMP +nsHttpChannelAuthProvider::Init(nsIHttpAuthenticableChannel *channel) +{ + NS_ASSERTION(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; + + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannelAuthProvider::ProcessAuthentication(PRUint32 httpStatus, + PRBool SSLConnectFailed) +{ + LOG(("nsHttpChannelAuthProvider::ProcessAuthentication " + "[this=%p channel=%p code=%u SSLConnectFailed=%d]\n", + this, mAuthChannel, httpStatus, SSLConnectFailed)); + + NS_ASSERTION(mAuthChannel, "Channel not initialized"); + + nsCOMPtr 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; + } + + PRUint32 loadFlags; + rv = mAuthChannel->GetLoadFlags(&loadFlags); + if (NS_FAILED(rv)) return rv; + + if (loadFlags & nsIRequest::LOAD_ANONYMOUS) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCAutoString 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 inadvertantly 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; + + nsCAutoString 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)); + + NS_ASSERTION(mAuthChannel, "Channel not initialized"); + + nsCOMPtr 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; + } + + PRUint32 loadFlags; + rv = mAuthChannel->GetLoadFlags(&loadFlags); + if (NS_FAILED(rv)) return rv; + + if (loadFlags & nsIRequest::LOAD_ANONYMOUS) { + return NS_OK; + } + + // this getter never fails + nsHttpAuthCache *authCache = gHttpHandler->AuthCache(); + + // check if proxy credentials should be sent + const char *proxyHost = ProxyHost(); + if (proxyHost && UsingHttpProxy()) + SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization, + "http", proxyHost, ProxyPort(), + nsnull, // proxy has no path + mProxyIdent); + + // check if server credentials should be sent + nsCAutoString 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)); + + NS_ASSERTION(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"), PR_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) +{ + NS_ASSERTION(mAuthChannel, "Channel not initialized"); + + if (mAsyncPromptAuthCancelable) { + mAsyncPromptAuthCancelable->Cancel(status); + mAsyncPromptAuthCancelable = nsnull; + } + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannelAuthProvider::Disconnect(nsresult status) +{ + mAuthChannel = nsnull; + + if (mAsyncPromptAuthCancelable) { + mAsyncPromptAuthCancelable->Cancel(status); + mAsyncPromptAuthCancelable = nsnull; + } + + NS_IF_RELEASE(mProxyAuthContinuationState); + NS_IF_RELEASE(mAuthContinuationState); + + return NS_OK; +} + +// buf contains "domain\user" +static void +ParseUserDomain(PRUnichar *buf, + const PRUnichar **user, + const PRUnichar **domain) +{ + PRUnichar *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, + PRUint32 authFlags, + PRUnichar *userBuf, + PRUnichar *passBuf) +{ + const PRUnichar *user = userBuf; + const PRUnichar *domain = nsnull; + + 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, PRBool proxyAuth, + nsIAuthPrompt2 **result) +{ + if (!ifreq) + return; + + PRUint32 promptReason; + if (proxyAuth) + promptReason = nsIAuthPromptProvider::PROMPT_PROXY; + else + promptReason = nsIAuthPromptProvider::PROMPT_NORMAL; + + nsCOMPtr promptProvider = do_GetInterface(ifreq); + if (promptProvider) + promptProvider->GetAuthPrompt(promptReason, + NS_GET_IID(nsIAuthPrompt2), + reinterpret_cast(result)); + else + NS_QueryAuthPrompt2(ifreq, result); +} + +// generate credentials for the given challenge, and update the auth cache. +nsresult +nsHttpChannelAuthProvider::GenCredsAndSetEntry(nsIHttpAuthenticator *auth, + PRBool proxyAuth, + const char *scheme, + const char *host, + PRInt32 port, + const char *directory, + const char *realm, + const char *challenge, + const nsHttpAuthIdentity &ident, + nsCOMPtr &sessionState, + char **result) +{ + nsresult rv; + PRUint32 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; + } + + PRUint32 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. + PRBool saveCreds = + 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CREDENTIALS); + PRBool saveChallenge = + 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CHALLENGE); + + PRBool saveIdentity = + 0 == (generateFlags & nsIHttpAuthenticator::USING_INTERNAL_IDENTITY); + + // this getter never fails + nsHttpAuthCache *authCache = gHttpHandler->AuthCache(); + + // 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 : nsnull, + saveChallenge ? challenge : nsnull, + saveIdentity ? &ident : nsnull, + sessionState); + return rv; +} + +nsresult +nsHttpChannelAuthProvider::PrepareForAuthentication(PRBool 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). + + nsCAutoString contractId; + contractId.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX); + contractId.Append(mProxyAuthType); + + nsresult rv; + nsCOMPtr precedingAuth = + do_GetService(contractId.get(), &rv); + if (NS_FAILED(rv)) + return rv; + + PRUint32 precedingAuthFlags; + rv = precedingAuth->GetAuthFlags(&precedingAuthFlags); + if (NS_FAILED(rv)) + return rv; + + if (!(precedingAuthFlags & nsIHttpAuthenticator::REQUEST_BASED)) { + nsCAutoString 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, + PRBool proxyAuth, + nsAFlatCString &creds) +{ + nsCOMPtr auth; + nsCAutoString 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; + PRBool gotCreds = PR_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')) != nsnull) + 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 = PR_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 : nsnull; + 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(PRBool proxyAuth, + nsCSubstring& scheme, + const char*& host, + PRInt32& port, + nsCSubstring& path, + nsHttpAuthIdentity*& ident, + nsISupports**& continuationState) +{ + if (proxyAuth) { + NS_ASSERTION (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, + PRBool 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(); + + PRUint32 authFlags; + nsresult rv = auth->GetAuthFlags(&authFlags); + if (NS_FAILED(rv)) return rv; + + nsCAutoString 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; + PRInt32 port; + nsHttpAuthIdentity *ident; + nsCAutoString path, scheme; + PRBool identFromURI = PR_FALSE; + nsISupports **continuationState; + + rv = GetAuthorizationMembers(proxyAuth, scheme, host, port, + path, ident, continuationState); + 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 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 = nsnull; + authCache->GetAuthEntryForDomain(scheme.get(), host, port, + realm.get(), &entry); + + // hold reference to the auth session state (in case we clear our + // reference to the entry). + nsCOMPtr sessionStateGrip; + if (entry) + sessionStateGrip = entry->mMetaData; + + // for digest auth, maybe our cached nonce value simply timed out... + PRBool 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())) { + 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()); + entry = nsnull; + ident->Clear(); + } + else if (!identFromURI || + nsCRT::strcmp(ident->User(), + entry->Identity().User()) == 0) { + 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 = PR_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()) { + PRUint32 level = nsIAuthPrompt2::LEVEL_NONE; + if (mUsingSSL) + level = nsIAuthPrompt2::LEVEL_SECURE; + else if (authFlags & nsIHttpAuthenticator::IDENTITY_ENCRYPTED) + level = nsIAuthPrompt2::LEVEL_PW_ENCRYPTED; + + // 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 = PR_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"), PR_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; +} + +inline void +GetAuthType(const char *challenge, nsCString &authType) +{ + const char *p; + + // get the challenge type + if ((p = strchr(challenge, ' ')) != nsnull) + 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); + + nsCAutoString contractid; + contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX); + contractid.Append(authType); + + return CallGetService(contractid.get(), auth); +} + +void +nsHttpChannelAuthProvider::GetIdentityFromURI(PRUint32 authFlags, + nsHttpAuthIdentity &ident) +{ + LOG(("nsHttpChannelAuthProvider::GetIdentityFromURI [this=%p channel=%p]\n", + this, mAuthChannel)); + + nsAutoString userBuf; + nsAutoString passBuf; + + // XXX i18n + nsCAutoString 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, (PRUnichar *) userBuf.get(), + (PRUnichar *) 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) { + PRBool has_quote = PR_FALSE; + p += 6; + if (*p == '"') { + has_quote = PR_TRUE; + p++; + } + + const char *end = p; + while (*end && has_quote) { + // Loop through all the string characters to find the closing + // quote, ignoring escaped quotes. + if (*end == '"' && end[-1] != '\\') + break; + ++end; + } + + if (!has_quote) + end = strchr(p, ' '); + if (end) + realm.Assign(p, end - p); + else + realm.Assign(p); + } +} + + +class nsHTTPAuthInformation : public nsAuthInformationHolder { +public: + nsHTTPAuthInformation(PRUint32 aFlags, const nsString& aRealm, + const nsCString& aAuthType) + : nsAuthInformationHolder(aFlags, aRealm, aAuthType) {} + + void SetToHttpAuthIdentity(PRUint32 authFlags, + nsHttpAuthIdentity& identity); +}; + +void +nsHTTPAuthInformation::SetToHttpAuthIdentity(PRUint32 authFlags, + nsHttpAuthIdentity& identity) +{ + identity.Set(Domain().get(), User().get(), Password().get()); +} + +nsresult +nsHttpChannelAuthProvider::PromptForIdentity(PRUint32 level, + PRBool proxyAuth, + const char *realm, + const char *authType, + PRUint32 authFlags, + nsHttpAuthIdentity &ident) +{ + LOG(("nsHttpChannelAuthProvider::PromptForIdentity [this=%p channel=%p]\n", + this, mAuthChannel)); + + nsresult rv; + + nsCOMPtr callbacks; + rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr loadGroup; + rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr authPrompt; + GetAuthPrompt(callbacks, proxyAuth, getter_AddRefs(authPrompt)); + if (!authPrompt && loadGroup) { + nsCOMPtr 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... + PRUint32 promptFlags = 0; + if (proxyAuth) + { + promptFlags |= nsIAuthInformation::AUTH_PROXY; + if (mTriedProxyAuth) + promptFlags |= nsIAuthInformation::PREVIOUS_FAILED; + mTriedProxyAuth = PR_TRUE; + } + else { + promptFlags |= nsIAuthInformation::AUTH_HOST; + if (mTriedHostAuth) + promptFlags |= nsIAuthInformation::PREVIOUS_FAILED; + mTriedHostAuth = PR_TRUE; + } + + if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN) + promptFlags |= nsIAuthInformation::NEED_DOMAIN; + + nsRefPtr holder = + new nsHTTPAuthInformation(promptFlags, realmU, + nsDependentCString(authType)); + if (!holder) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr channel(do_QueryInterface(mAuthChannel, &rv)); + if (NS_FAILED(rv)) return rv; + + rv = + authPrompt->AsyncPromptAuth(channel, this, nsnull, 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 + PRBool retval = PR_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 = PR_TRUE; + + return rv; +} + +NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthAvailable(nsISupports *aContext, + nsIAuthInformation *aAuthInfo) +{ + LOG(("nsHttpChannelAuthProvider::OnAuthAvailable [this=%p channel=%p]", + this, mAuthChannel)); + + mAsyncPromptAuthCancelable = nsnull; + if (!mAuthChannel) + return NS_OK; + + nsresult rv; + + const char *host; + PRInt32 port; + nsHttpAuthIdentity *ident; + nsCAutoString path, scheme; + nsISupports **continuationState; + rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, + path, ident, continuationState); + if (NS_FAILED(rv)) + OnAuthCancelled(aContext, PR_FALSE); + + nsCAutoString realm; + ParseRealm(mCurrentChallenge.get(), realm); + + nsHttpAuthCache *authCache = gHttpHandler->AuthCache(); + nsHttpAuthEntry *entry = nsnull; + authCache->GetAuthEntryForDomain(scheme.get(), host, port, + realm.get(), &entry); + + nsCOMPtr sessionStateGrip; + if (entry) + sessionStateGrip = entry->mMetaData; + + nsAuthInformationHolder* holder = + static_cast(aAuthInfo); + ident->Set(holder->Domain().get(), + holder->User().get(), + holder->Password().get()); + + nsCAutoString unused; + nsCOMPtr auth; + rv = GetAuthenticator(mCurrentChallenge.get(), unused, + getter_AddRefs(auth)); + if (NS_FAILED(rv)) { + NS_ASSERTION(PR_FALSE, "GetAuthenticator failed"); + OnAuthCancelled(aContext, PR_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, PR_TRUE); + return NS_OK; + } + + return ContinueOnAuthAvailable(creds); +} + +NS_IMETHODIMP nsHttpChannelAuthProvider::OnAuthCancelled(nsISupports *aContext, + PRBool userCancel) +{ + LOG(("nsHttpChannelAuthProvider::OnAuthCancelled [this=%p channel=%p]", + this, mAuthChannel)); + + mAsyncPromptAuthCancelable = nsnull; + if (!mAuthChannel) + return NS_OK; + + if (userCancel) { + if (!mRemainingChallenges.IsEmpty()) { + // there are still some challenges to process, do so + nsresult rv; + + nsCAutoString 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; +} + +PRBool +nsHttpChannelAuthProvider::ConfirmAuth(const nsString &bundleKey, + PRBool 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 + + PRUint32 loadFlags; + nsresult rv = mAuthChannel->GetLoadFlags(&loadFlags); + if (NS_FAILED(rv)) return rv; + + if (mSuppressDefensiveAuth || + !(loadFlags & nsIChannel::LOAD_INITIAL_DOCUMENT_URI)) + return PR_TRUE; + + nsCAutoString userPass; + rv = mURI->GetUserPass(userPass); + if (NS_FAILED(rv) || + (userPass.Length() < gHttpHandler->PhishyUserPassLength())) + return PR_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 bundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID); + if (!bundleService) + return PR_TRUE; + + nsCOMPtr bundle; + bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle)); + if (!bundle) + return PR_TRUE; + + nsCAutoString host; + rv = mURI->GetHost(host); + if (NS_FAILED(rv)) + return PR_TRUE; + + nsCAutoString user; + rv = mURI->GetUsername(user); + if (NS_FAILED(rv)) + return PR_TRUE; + + NS_ConvertUTF8toUTF16 ucsHost(host), ucsUser(user); + const PRUnichar *strs[2] = { ucsHost.get(), ucsUser.get() }; + + nsXPIDLString msg; + bundle->FormatStringFromName(bundleKey.get(), strs, 2, getter_Copies(msg)); + if (!msg) + return PR_TRUE; + + nsCOMPtr callbacks; + rv = mAuthChannel->GetNotificationCallbacks(getter_AddRefs(callbacks)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr loadGroup; + rv = mAuthChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr prompt; + NS_QueryNotificationCallbacks(callbacks, loadGroup, NS_GET_IID(nsIPrompt), + getter_AddRefs(prompt)); + if (!prompt) + return PR_TRUE; + + // do not prompt again + mSuppressDefensiveAuth = PR_TRUE; + + PRBool confirmed; + if (doYesNoPrompt) { + PRInt32 choice; + PRBool checkState; + rv = prompt->ConfirmEx(nsnull, msg, + nsIPrompt::BUTTON_POS_1_DEFAULT + + nsIPrompt::STD_YES_NO_BUTTONS, + nsnull, nsnull, nsnull, nsnull, + &checkState, &choice); + if (NS_FAILED(rv)) + return PR_TRUE; + + confirmed = choice == 0; + } + else { + rv = prompt->Confirm(nsnull, msg, &confirmed); + if (NS_FAILED(rv)) + return PR_TRUE; + } + + return confirmed; +} + +void +nsHttpChannelAuthProvider::SetAuthorizationHeader(nsHttpAuthCache *authCache, + nsHttpAtom header, + const char *scheme, + const char *host, + PRInt32 port, + const char *path, + nsHttpAuthIdentity &ident) +{ + nsHttpAuthEntry *entry = nsnull; + 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; + } + + rv = authCache->GetAuthEntryForPath(scheme, host, port, path, &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. + if (nsCRT::strcmp(ident.User(), entry->User()) == 0) + ident.Clear(); + } + PRBool identFromURI; + if (ident.IsEmpty()) { + ident.Set(entry->Identity()); + identFromURI = PR_FALSE; + } + else + identFromURI = PR_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 auth; + nsCAutoString unused; + rv = GetAuthenticator(challenge, unused, getter_AddRefs(auth)); + if (NS_SUCCEEDED(rv)) { + PRBool 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 = PR_TRUE; + } + else + ident.Clear(); // don't remember the identity + } +} + +nsresult +nsHttpChannelAuthProvider::GetCurrentPath(nsACString &path) +{ + nsresult rv; + nsCOMPtr url = do_QueryInterface(mURI); + if (url) + rv = url->GetDirectory(path); + else + rv = mURI->GetPath(path); + return rv; +} + +NS_IMPL_ISUPPORTS3(nsHttpChannelAuthProvider, nsICancelable, + nsIHttpChannelAuthProvider, nsIAuthPromptCallback) diff --git a/netwerk/protocol/http/nsHttpChannelAuthProvider.h b/netwerk/protocol/http/nsHttpChannelAuthProvider.h new file mode 100644 index 000000000000..7da4542548a9 --- /dev/null +++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.h @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set et cin ts=4 sw=4 sts=4: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Netscape Communications. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher (original author) + * Christian Biesinger + * Wellington Fernando de Macedo + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nsHttpChannelAuthProvider_h__ +#define nsHttpChannelAuthProvider_h__ + +#include "nsIHttpChannelAuthProvider.h" +#include "nsIAuthPromptCallback.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsIHttpAuthenticableChannel.h" +#include "nsIURI.h" +#include "nsHttpAuthCache.h" +#include "nsProxyInfo.h" + +class nsIHttpAuthenticator; + +class nsHttpChannelAuthProvider : public nsIHttpChannelAuthProvider + , public nsIAuthPromptCallback +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICANCELABLE + NS_DECL_NSIHTTPCHANNELAUTHPROVIDER + NS_DECL_NSIAUTHPROMPTCALLBACK + + nsHttpChannelAuthProvider(); + virtual ~nsHttpChannelAuthProvider(); + +private: + const char *ProxyHost() const + { return mProxyInfo ? mProxyInfo->Host().get() : nsnull; } + + PRInt32 ProxyPort() const + { return mProxyInfo ? mProxyInfo->Port() : -1; } + + const char *Host() const { return mHost.get(); } + PRInt32 Port() const { return mPort; } + PRBool UsingSSL() const { return mUsingSSL; } + + PRBool UsingHttpProxy() const + { return !!(mProxyInfo && !nsCRT::strcmp(mProxyInfo->Type(), "http")); } + + nsresult PrepareForAuthentication(PRBool proxyAuth); + nsresult GenCredsAndSetEntry(nsIHttpAuthenticator *, PRBool proxyAuth, + const char *scheme, const char *host, + PRInt32 port, const char *dir, + const char *realm, const char *challenge, + const nsHttpAuthIdentity &ident, + nsCOMPtr &session, char **result); + nsresult GetAuthenticator(const char *challenge, nsCString &scheme, + nsIHttpAuthenticator **auth); + void ParseRealm(const char *challenge, nsACString &realm); + void GetIdentityFromURI(PRUint32 authFlags, nsHttpAuthIdentity&); + /** + * Following three methods return NS_ERROR_IN_PROGRESS when + * nsIAuthPrompt2.asyncPromptAuth method is called. This result indicates + * the user's decision will be gathered in a callback and is not an actual + * error. + */ + nsresult GetCredentials(const char *challenges, PRBool proxyAuth, + nsAFlatCString &creds); + nsresult GetCredentialsForChallenge(const char *challenge, + const char *scheme, PRBool proxyAuth, + nsIHttpAuthenticator *auth, + nsAFlatCString &creds); + nsresult PromptForIdentity(PRUint32 level, PRBool proxyAuth, + const char *realm, const char *authType, + PRUint32 authFlags, nsHttpAuthIdentity &); + + PRBool ConfirmAuth(const nsString &bundleKey, PRBool doYesNoPrompt); + void SetAuthorizationHeader(nsHttpAuthCache *, nsHttpAtom header, + const char *scheme, const char *host, + PRInt32 port, const char *path, + nsHttpAuthIdentity &ident); + nsresult GetCurrentPath(nsACString &); + /** + * Return all information needed to build authorization information, + * all parameters except proxyAuth are out parameters. proxyAuth specifies + * with what authorization we work (WWW or proxy). + */ + nsresult GetAuthorizationMembers(PRBool proxyAuth, nsCSubstring& scheme, + const char*& host, PRInt32& port, + nsCSubstring& path, + nsHttpAuthIdentity*& ident, + nsISupports**& continuationState); + /** + * Method called to resume suspended transaction after we got credentials + * from the user. Called from OnAuthAvailable callback or OnAuthCancelled + * when credentials for next challenge were obtained synchronously. + */ + nsresult ContinueOnAuthAvailable(const nsCSubstring& creds); + +private: + nsIHttpAuthenticableChannel *mAuthChannel; // weak ref + + nsCOMPtr mURI; + nsCOMPtr mProxyInfo; + nsCString mHost; + PRInt32 mPort; + PRBool mUsingSSL; + + nsISupports *mProxyAuthContinuationState; + nsCString mProxyAuthType; + nsISupports *mAuthContinuationState; + nsCString mAuthType; + nsHttpAuthIdentity mIdent; + nsHttpAuthIdentity mProxyIdent; + + // Reference to the prompt wating in prompt queue. The channel is + // responsible to call its cancel method when user in any way cancels + // this request. + nsCOMPtr mAsyncPromptAuthCancelable; + // Saved in GetCredentials when prompt is asynchronous, the first challenge + // we obtained from the server with 401/407 response, will be processed in + // OnAuthAvailable callback. + nsCString mCurrentChallenge; + // Saved in GetCredentials when prompt is asynchronous, remaning challenges + // we have to process when user cancels the auth dialog for the current + // challenge. + nsCString mRemainingChallenges; + + // True when we need to authenticate to proxy, i.e. when we get 407 + // response. Used in OnAuthAvailable and OnAuthCancelled callbacks. + PRUint32 mProxyAuth : 1; + PRUint32 mTriedProxyAuth : 1; + PRUint32 mTriedHostAuth : 1; + PRUint32 mSuppressDefensiveAuth : 1; +}; + +#endif // nsHttpChannelAuthProvider_h__ diff --git a/netwerk/protocol/http/nsHttpDigestAuth.cpp b/netwerk/protocol/http/nsHttpDigestAuth.cpp index 22fe10d08dbc..ab848b93d6c8 100644 --- a/netwerk/protocol/http/nsHttpDigestAuth.cpp +++ b/netwerk/protocol/http/nsHttpDigestAuth.cpp @@ -42,7 +42,7 @@ #include #include "nsHttp.h" #include "nsHttpDigestAuth.h" -#include "nsIHttpChannel.h" +#include "nsIHttpAuthenticableChannel.h" #include "nsIServiceManager.h" #include "nsXPCOM.h" #include "nsISupportsPrimitives.h" @@ -109,23 +109,19 @@ nsHttpDigestAuth::MD5Hash(const char *buf, PRUint32 len) } nsresult -nsHttpDigestAuth::GetMethodAndPath(nsIHttpChannel *httpChannel, - PRBool isProxyAuth, - nsCString &httpMethod, - nsCString &path) +nsHttpDigestAuth::GetMethodAndPath(nsIHttpAuthenticableChannel *authChannel, + PRBool isProxyAuth, + nsCString &httpMethod, + nsCString &path) { nsresult rv; nsCOMPtr uri; - rv = httpChannel->GetURI(getter_AddRefs(uri)); + rv = authChannel->GetURI(getter_AddRefs(uri)); if (NS_SUCCEEDED(rv)) { - PRBool isSecure; - rv = uri->SchemeIs("https", &isSecure); + PRBool proxyMethodIsConnect; + rv = authChannel->GetProxyMethodIsConnect(&proxyMethodIsConnect); if (NS_SUCCEEDED(rv)) { - // - // if we are being called in response to a 407, and if the protocol - // is HTTPS, then we are really using a CONNECT method. - // - if (isSecure && isProxyAuth) { + if (proxyMethodIsConnect && isProxyAuth) { httpMethod.AssignLiteral("CONNECT"); // // generate hostname:port string. (unfortunately uri->GetHostPort @@ -141,7 +137,7 @@ nsHttpDigestAuth::GetMethodAndPath(nsIHttpChannel *httpChannel, } } else { - rv = httpChannel->GetRequestMethod(httpMethod); + rv = authChannel->GetRequestMethod(httpMethod); rv |= uri->GetPath(path); if (NS_SUCCEEDED(rv)) { // @@ -171,7 +167,7 @@ nsHttpDigestAuth::GetMethodAndPath(nsIHttpChannel *httpChannel, //----------------------------------------------------------------------------- NS_IMETHODIMP -nsHttpDigestAuth::ChallengeReceived(nsIHttpChannel *httpChannel, +nsHttpDigestAuth::ChallengeReceived(nsIHttpAuthenticableChannel *authChannel, const char *challenge, PRBool isProxyAuth, nsISupports **sessionState, @@ -197,7 +193,7 @@ nsHttpDigestAuth::ChallengeReceived(nsIHttpChannel *httpChannel, } NS_IMETHODIMP -nsHttpDigestAuth::GenerateCredentials(nsIHttpChannel *httpChannel, +nsHttpDigestAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel, const char *challenge, PRBool isProxyAuth, const PRUnichar *userdomain, @@ -222,7 +218,7 @@ nsHttpDigestAuth::GenerateCredentials(nsIHttpChannel *httpChannel, PRBool requireExtraQuotes = PR_FALSE; { nsCAutoString serverVal; - httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Server"), serverVal); + authChannel->GetServerResponseHeader(serverVal); if (!serverVal.IsEmpty()) { requireExtraQuotes = !PL_strncasecmp(serverVal.get(), "Microsoft-IIS", 13); } @@ -231,7 +227,7 @@ nsHttpDigestAuth::GenerateCredentials(nsIHttpChannel *httpChannel, nsresult rv; nsCAutoString httpMethod; nsCAutoString path; - rv = GetMethodAndPath(httpChannel, isProxyAuth, httpMethod, path); + rv = GetMethodAndPath(authChannel, isProxyAuth, httpMethod, path); if (NS_FAILED(rv)) return rv; nsCAutoString realm, domain, nonce, opaque; diff --git a/netwerk/protocol/http/nsHttpDigestAuth.h b/netwerk/protocol/http/nsHttpDigestAuth.h index 044236afc9c5..3b882647642c 100644 --- a/netwerk/protocol/http/nsHttpDigestAuth.h +++ b/netwerk/protocol/http/nsHttpDigestAuth.h @@ -107,7 +107,8 @@ class nsHttpDigestAuth : public nsIHttpAuthenticator // result is in mHashBuf nsresult MD5Hash(const char *buf, PRUint32 len); - nsresult GetMethodAndPath(nsIHttpChannel *, PRBool, nsCString &, nsCString &); + nsresult GetMethodAndPath(nsIHttpAuthenticableChannel *, + PRBool, nsCString &, nsCString &); // append the quoted version of value to aHeaderLine nsresult AppendQuotedString(const nsACString & value, diff --git a/netwerk/protocol/http/nsHttpNTLMAuth.cpp b/netwerk/protocol/http/nsHttpNTLMAuth.cpp index 1b438bafe2f8..2fb16ae17894 100644 --- a/netwerk/protocol/http/nsHttpNTLMAuth.cpp +++ b/netwerk/protocol/http/nsHttpNTLMAuth.cpp @@ -50,7 +50,7 @@ #include "nsIPrefBranch.h" #include "nsIPrefService.h" #include "nsIServiceManager.h" -#include "nsIHttpChannel.h" +#include "nsIHttpAuthenticableChannel.h" #include "nsIURI.h" static const char kAllowProxies[] = "network.automatic-ntlm-auth.allow-proxies"; @@ -189,7 +189,8 @@ ForceGenericNTLM() // Check to see if we should use default credentials for this host or proxy. static PRBool -CanUseDefaultCredentials(nsIHttpChannel *channel, PRBool isProxyAuth) +CanUseDefaultCredentials(nsIHttpAuthenticableChannel *channel, + PRBool isProxyAuth) { nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); if (!prefs) @@ -224,7 +225,7 @@ NS_IMPL_ISUPPORTS0(nsNTLMSessionState) NS_IMPL_ISUPPORTS1(nsHttpNTLMAuth, nsIHttpAuthenticator) NS_IMETHODIMP -nsHttpNTLMAuth::ChallengeReceived(nsIHttpChannel *channel, +nsHttpNTLMAuth::ChallengeReceived(nsIHttpAuthenticableChannel *channel, const char *challenge, PRBool isProxyAuth, nsISupports **sessionState, @@ -316,7 +317,7 @@ nsHttpNTLMAuth::ChallengeReceived(nsIHttpChannel *channel, } NS_IMETHODIMP -nsHttpNTLMAuth::GenerateCredentials(nsIHttpChannel *httpChannel, +nsHttpNTLMAuth::GenerateCredentials(nsIHttpAuthenticableChannel *authChannel, const char *challenge, PRBool isProxyAuth, const PRUnichar *domain, @@ -351,7 +352,7 @@ nsHttpNTLMAuth::GenerateCredentials(nsIHttpChannel *httpChannel, if (PL_strcasecmp(challenge, "NTLM") == 0) { // NTLM service name format is 'HTTP@host' for both http and https nsCOMPtr uri; - rv = httpChannel->GetURI(getter_AddRefs(uri)); + rv = authChannel->GetURI(getter_AddRefs(uri)); if (NS_FAILED(rv)) return rv; nsCAutoString serviceName, host; diff --git a/netwerk/protocol/http/nsIHttpAuthenticableChannel.idl b/netwerk/protocol/http/nsIHttpAuthenticableChannel.idl new file mode 100644 index 000000000000..9733e460fbbc --- /dev/null +++ b/netwerk/protocol/http/nsIHttpAuthenticableChannel.idl @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Wellington Fernando de Macedo. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Wellington Fernando de Macedo (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsIProxiedChannel.idl" +#include "nsIChannel.idl" + +interface nsILoadGroup; +interface nsIURI; +interface nsIInterfaceRequestor; + +[scriptable, uuid(701093ac-5c7f-429c-99e3-423b041fccb4)] +interface nsIHttpAuthenticableChannel : nsIProxiedChannel +{ + /** + * If the channel being authenticated is using SSL. + */ + readonly attribute boolean isSSL; + + /** + * Returns if the proxy HTTP method used is CONNECT. If no proxy is being + * used it must return PR_FALSE. + */ + readonly attribute boolean proxyMethodIsConnect; + + /** + * Cancels the current request. See nsIRequest. + */ + void cancel(in nsresult aStatus); + + /** + * The load flags of this request. See nsIRequest. + */ + readonly attribute nsLoadFlags loadFlags; + + /** + * The URI corresponding to the channel. See nsIChannel. + */ + readonly attribute nsIURI URI; + + /** + * The load group of this request. It is here for querying its + * notificationCallbacks. See nsIRequest. + */ + readonly attribute nsILoadGroup loadGroup; + + /** + * The notification callbacks for the channel. See nsIChannel. + */ + readonly attribute nsIInterfaceRequestor notificationCallbacks; + + /** + * The HTTP request method. See nsIHttpChannel. + */ + readonly attribute ACString requestMethod; + + /** + * The "Server" response header. + * Return NS_ERROR_NOT_AVAILABLE if not available. + */ + readonly attribute ACString serverResponseHeader; + + /** + * The Proxy-Authenticate response header. + */ + readonly attribute ACString proxyChallenges; + + /** + * The WWW-Authenticate response header. + */ + readonly attribute ACString WWWChallenges; + + /** + * Sets the Proxy-Authorization request header. An empty string + * will clear it. + */ + void setProxyCredentials(in ACString credentials); + + /** + * Sets the Authorization request header. An empty string + * will clear it. + */ + void setWWWCredentials(in ACString credentials); + + /** + * Called when authentication information is ready and has been set on this + * object using setWWWCredentials/setProxyCredentials. Implementations can + * continue with the request and send the given information to the server. + * + * It is called asynchronously from + * nsIHttpChannelAuthProvider::processAuthentication if that method returns + * NS_ERROR_IN_PROGRESS. + * + * @note Any exceptions thrown from this method should be ignored. + */ + void onAuthAvailable(); + + /** + * Notifies that the prompt was cancelled. It is called asynchronously + * from nsIHttpChannelAuthProvider::processAuthentication if that method + * returns NS_ERROR_IN_PROGRESS. + * + * @param userCancel + * If the user was cancelled has cancelled the authentication prompt. + */ + void onAuthCancelled(in boolean userCancel); +}; diff --git a/netwerk/protocol/http/nsIHttpAuthenticator.idl b/netwerk/protocol/http/nsIHttpAuthenticator.idl index 4798b725212c..19a204e439d3 100644 --- a/netwerk/protocol/http/nsIHttpAuthenticator.idl +++ b/netwerk/protocol/http/nsIHttpAuthenticator.idl @@ -38,7 +38,7 @@ #include "nsISupports.idl" -interface nsIHttpChannel; +interface nsIHttpAuthenticableChannel; /** * nsIHttpAuthenticator @@ -51,7 +51,7 @@ interface nsIHttpChannel; * where is the lower-cased value of the authentication scheme * found in the server challenge per the rules of RFC 2617. */ -[scriptable, uuid(36402c9d-c280-4860-b4b0-2e7eb35b0aaf)] +[scriptable, uuid(16784db0-fcb1-4352-b0c9-6a3a67e3cf79)] interface nsIHttpAuthenticator : nsISupports { /** @@ -78,12 +78,12 @@ interface nsIHttpAuthenticator : nsISupports * return value indicating whether or not to prompt the user for a * revised identity. */ - void challengeReceived(in nsIHttpChannel aChannel, - in string aChallenge, - in boolean aProxyAuth, - inout nsISupports aSessionState, - inout nsISupports aContinuationState, - out boolean aInvalidatesIdentity); + void challengeReceived(in nsIHttpAuthenticableChannel aChannel, + in string aChallenge, + in boolean aProxyAuth, + inout nsISupports aSessionState, + inout nsISupports aContinuationState, + out boolean aInvalidatesIdentity); /** * Called to generate the authentication credentials for a particular @@ -121,15 +121,15 @@ interface nsIHttpAuthenticator : nsISupports * @param aFlags * authenticator may return one of the generate flags bellow. */ - string generateCredentials(in nsIHttpChannel aChannel, - in string aChallenge, - in boolean aProxyAuth, - in wstring aDomain, - in wstring aUser, - in wstring aPassword, - inout nsISupports aSessionState, - inout nsISupports aContinuationState, - out unsigned long aFlags); + string generateCredentials(in nsIHttpAuthenticableChannel aChannel, + in string aChallenge, + in boolean aProxyAuth, + in wstring aDomain, + in wstring aUser, + in wstring aPassword, + inout nsISupports aSessionState, + inout nsISupports aContinuationState, + out unsigned long aFlags); /** * Generate flags diff --git a/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl b/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl new file mode 100644 index 000000000000..4385c55bef5a --- /dev/null +++ b/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Wellington Fernando de Macedo. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Wellington Fernando de Macedo (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsICancelable.idl" + +interface nsIHttpChannel; +interface nsIHttpAuthenticableChannel; + +/** + * nsIHttpChannelAuthProvider + * + * This interface is intended for providing authentication for http-style + * channels, like nsIHttpChannel and nsIWebSocket, which implement the + * nsIHttpAuthenticableChannel interface. + * + * When requesting pages AddAuthorizationHeaders MUST be called + * in order to get the http cached headers credentials. When the request is + * unsuccessful because of receiving either a 401 or 407 http response code + * ProcessAuthentication MUST be called and the page MUST be requested again + * with the new credentials that the user has provided. After a successful + * request, checkForSuperfluousAuth MAY be called, and disconnect MUST be + * called. + */ + +[scriptable, uuid(c68f3def-c7c8-4ee8-861c-eef49a48b702)] +interface nsIHttpChannelAuthProvider : nsICancelable +{ + /** + * Initializes the http authentication support for the channel. + * Implementations must hold a weak reference of the channel. + */ + void init(in nsIHttpAuthenticableChannel channel); + + /** + * Upon receipt of a server challenge, this function is called to determine + * the credentials to send. + * + * @param httpStatus + * the http status received. + * @param sslConnectFailed + * if the last ssl tunnel connection attempt was or not successful. + * @param callback + * the callback to be called when it returns NS_ERROR_IN_PROGRESS. + * The implementation must hold a weak reference. + * + * @returns NS_OK if the credentials were got and set successfully. + * NS_ERROR_IN_PROGRESS if the credentials are going to be asked to + * the user. The channel reference must be + * alive until the feedback from + * nsIHttpAuthenticableChannel's methods or + * until disconnect be called. + */ + void processAuthentication(in unsigned long httpStatus, + in boolean sslConnectFailed); + + /** + * Add credentials from the http auth cache. + */ + void addAuthorizationHeaders(); + + /** + * Check if an unnecessary(and maybe malicious) url authentication has been + * provided. + */ + void checkForSuperfluousAuth(); + + /** + * Cancel pending user auth prompts and release the callback and channel + * weak references. + */ + void disconnect(in nsresult status); +};