diff options
Diffstat (limited to 'net/http/http_network_transaction.cc')
-rw-r--r-- | net/http/http_network_transaction.cc | 240 |
1 files changed, 188 insertions, 52 deletions
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc index 0c87b54..9783025 100644 --- a/net/http/http_network_transaction.cc +++ b/net/http/http_network_transaction.cc @@ -23,11 +23,6 @@ #include "net/http/http_request_info.h" #include "net/http/http_util.h" -// TODO(darin): -// - authentication -// + pre-emptive authorization -// + use the username/password encoded in the URL. - using base::Time; namespace net { @@ -101,16 +96,12 @@ int HttpNetworkTransaction::RestartWithAuth( HttpAuth::AUTH_PROXY : HttpAuth::AUTH_SERVER; // Update the username/password. - auth_data_[target]->state = AUTH_STATE_HAVE_AUTH; - auth_data_[target]->username = username; - auth_data_[target]->password = password; - - next_state_ = STATE_INIT_CONNECTION; - connection_.set_socket(NULL); - connection_.Reset(); + auth_identity_[target].source = HttpAuth::IDENT_SRC_EXTERNAL; + auth_identity_[target].invalid = false; + auth_identity_[target].username = username; + auth_identity_[target].password = password; - // Reset the other member variables. - ResetStateForRestart(); + PrepareForAuthRestart(target); DCHECK(user_callback_ == NULL); int rv = DoLoop(OK); @@ -120,6 +111,26 @@ int HttpNetworkTransaction::RestartWithAuth( return rv; } +void HttpNetworkTransaction::PrepareForAuthRestart(HttpAuth::Target target) { + DCHECK(HaveAuth(target)); + DCHECK(auth_identity_[target].source != HttpAuth::IDENT_SRC_PATH_LOOKUP); + + // Add the auth entry to the cache before restarting. We don't know whether + // the identity is valid yet, but if it is valid we want other transactions + // to know about it. If an entry for (origin, handler->realm()) already + // exists, we update it. + session_->auth_cache()->Add(AuthOrigin(target), auth_handler_[target], + auth_identity_[target].username, auth_identity_[target].password, + AuthPath(target)); + + next_state_ = STATE_INIT_CONNECTION; + connection_.set_socket(NULL); + connection_.Reset(); + + // Reset the other member variables. + ResetStateForRestart(); +} + int HttpNetworkTransaction::Read(char* buf, int buf_len, CompletionCallback* callback) { DCHECK(response_.headers); @@ -840,7 +851,11 @@ int HttpNetworkTransaction::DidReadResponseHeaders() { response_.headers = headers; response_.vary_data.Init(*request_, *response_.headers); - int rv = PopulateAuthChallenge(); + int rv = HandleAuthChallenge(); + if (rv == WILL_RESTART_TRANSACTION) { + DCHECK(next_state_ == STATE_INIT_CONNECTION); + return OK; + } if (rv != OK) return rv; @@ -1028,16 +1043,17 @@ int HttpNetworkTransaction::ReconsiderProxyAfterError(int error) { } void HttpNetworkTransaction::AddAuthorizationHeader(HttpAuth::Target target) { - DCHECK(HaveAuth(target)); - DCHECK(!auth_cache_key_[target].empty()); + // If we have no authentication information, check if we can select + // a cache entry preemptively (based on the path). + if(!HaveAuth(target) && !SelectPreemptiveAuth(target)) + return; - // Add auth data to cache - session_->auth_cache()->Add(auth_cache_key_[target], auth_data_[target]); + DCHECK(HaveAuth(target)); // Add a Authorization/Proxy-Authorization header line. std::string credentials = auth_handler_[target]->GenerateCredentials( - auth_data_[target]->username, - auth_data_[target]->password, + auth_identity_[target].username, + auth_identity_[target].password, request_, &proxy_info_); request_headers_ += HttpAuth::GetAuthorizationHeaderName(target) + @@ -1054,15 +1070,124 @@ void HttpNetworkTransaction::ApplyAuth() { // Don't send origin server auth while establishing tunnel. bool should_apply_server_auth = !establishing_tunnel_; - if (should_apply_proxy_auth && HaveAuth(HttpAuth::AUTH_PROXY)) + if (should_apply_proxy_auth) AddAuthorizationHeader(HttpAuth::AUTH_PROXY); - if (should_apply_server_auth && HaveAuth(HttpAuth::AUTH_SERVER)) + if (should_apply_server_auth) AddAuthorizationHeader(HttpAuth::AUTH_SERVER); } -// Populates response_.auth_challenge with the authentication challenge info. -// This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo(). -int HttpNetworkTransaction::PopulateAuthChallenge() { +GURL HttpNetworkTransaction::AuthOrigin(HttpAuth::Target target) const { + return target == HttpAuth::AUTH_PROXY ? + GURL("http://" + proxy_info_.proxy_server()) : + request_->url.GetOrigin(); +} + +std::string HttpNetworkTransaction::AuthPath(HttpAuth::Target target) + const { + // Proxy authentication realms apply to all paths. So we will use + // empty string in place of an absolute path. + return target == HttpAuth::AUTH_PROXY ? + std::string() : request_->url.path(); +} + +void HttpNetworkTransaction::InvalidateRejectedAuthFromCache( + HttpAuth::Target target) { + DCHECK(HaveAuth(target)); + + // TODO(eroman): this short-circuit can be relaxed. If the realm of + // the preemptively used auth entry matches the realm of the subsequent + // challenge, then we can invalidate the preemptively used entry. + // Otherwise as-is we may send the failed credentials one extra time. + if (auth_identity_[target].source == HttpAuth::IDENT_SRC_PATH_LOOKUP) + return; + + // Clear the cache entry for the identity we just failed on. + // Note: we require the username/password to match before invalidating + // since the entry in the cache may be newer than what we used last time. + session_->auth_cache()->Remove(AuthOrigin(target), + auth_handler_[target]->realm(), + auth_identity_[target].username, + auth_identity_[target].password); +} + +bool HttpNetworkTransaction::SelectPreemptiveAuth(HttpAuth::Target target) { + DCHECK(!HaveAuth(target)); + + // Don't do preemptive authorization if the URL contains a username/password, + // since we must first be challenged in order to use the URL's identity. + if (request_->url.has_username()) + return false; + + // SelectPreemptiveAuth() is on the critical path for each request, so it + // is expected to be fast. LookupByPath() is fast in the common case, since + // the number of http auth cache entries is expected to be very small. + // (For most users in fact, it will be 0.) + + HttpAuthCache::Entry* entry = session_->auth_cache()->LookupByPath( + AuthOrigin(target), AuthPath(target)); + + if (entry) { + auth_identity_[target].source = HttpAuth::IDENT_SRC_PATH_LOOKUP; + auth_identity_[target].invalid = false; + auth_identity_[target].username = entry->username(); + auth_identity_[target].password = entry->password(); + auth_handler_[target] = entry->handler(); + return true; + } + return false; +} + +bool HttpNetworkTransaction::SelectNextAuthIdentityToTry( + HttpAuth::Target target) { + DCHECK(auth_handler_[target]); + DCHECK(auth_identity_[target].invalid); + + // Try to use the username/password encoded into the URL first. + // (By checking source == IDENT_SRC_NONE, we make sure that this + // is only done once for the transaction.) + if (target == HttpAuth::AUTH_SERVER && request_->url.has_username() && + auth_identity_[target].source == HttpAuth::IDENT_SRC_NONE) { + auth_identity_[target].source = HttpAuth::IDENT_SRC_URL; + auth_identity_[target].invalid = false; + auth_identity_[target].username = UTF8ToWide(request_->url.username()); + auth_identity_[target].password = UTF8ToWide(request_->url.password()); + // TODO(eroman): If the password is blank, should we also try combining + // with a password from the cache? + return true; + } + + // Check the auth cache for a realm entry. + HttpAuthCache::Entry* entry = session_->auth_cache()->LookupByRealm( + AuthOrigin(target), auth_handler_[target]->realm()); + + if (entry) { + // Disallow re-using of identity if the scheme of the originating challenge + // does not match. This protects against the following situation: + // 1. Browser prompts user to sign into DIGEST realm="Foo". + // 2. Since the auth-scheme is not BASIC, the user is reasured that it + // will not be sent over the wire in clear text. So they use their + // most trusted password. + // 3. Next, the browser receives a challenge for BASIC realm="Foo". This + // is the same realm that we have a cached identity for. However if + // we use that identity, it would get sent over the wire in + // clear text (which isn't what the user agreed to when entering it). + if (entry->handler()->scheme() != auth_handler_[target]->scheme()) { + LOG(WARNING) << "The scheme of realm " << auth_handler_[target]->realm() + << " has changed from " << entry->handler()->scheme() + << " to " << auth_handler_[target]->scheme(); + return false; + } + + auth_identity_[target].source = HttpAuth::IDENT_SRC_REALM_LOOKUP; + auth_identity_[target].invalid = false; + auth_identity_[target].username = entry->username(); + auth_identity_[target].password = entry->password(); + return true; + } + return false; +} + +int HttpNetworkTransaction::HandleAuthChallenge() { DCHECK(response_.headers); int status = response_.headers->response_code(); @@ -1074,46 +1199,57 @@ int HttpNetworkTransaction::PopulateAuthChallenge() { if (target == HttpAuth::AUTH_PROXY && proxy_info_.is_direct()) return ERR_UNEXPECTED_PROXY_AUTH; + // The auth we tried just failed, hence it can't be valid. Remove it from + // the cache so it won't be used again. + if (HaveAuth(target)) + InvalidateRejectedAuthFromCache(target); + + auth_identity_[target].invalid = true; + // Find the best authentication challenge that we support. - scoped_ptr<HttpAuthHandler> auth_handler( - HttpAuth::ChooseBestChallenge(response_.headers.get(), target)); + HttpAuth::ChooseBestChallenge(response_.headers.get(), + target, + &auth_handler_[target]); // We found no supported challenge -- let the transaction continue // so we end up displaying the error page. - if (!auth_handler.get()) + if (!auth_handler_[target]) return OK; - // Construct an AuthChallengeInfo. - scoped_refptr<AuthChallengeInfo> auth_info = new AuthChallengeInfo; + // Pick a new auth identity to try, by looking to the URL and auth cache. + // If an identity to try is found, it is saved to auth_identity_[target]. + bool has_identity_to_try = SelectNextAuthIdentityToTry(target); + DCHECK(has_identity_to_try == !auth_identity_[target].invalid); + + if (has_identity_to_try) { + DCHECK(user_callback_); + PrepareForAuthRestart(target); + return WILL_RESTART_TRANSACTION; + } else { + // We have exhausted all identity possibilities, all we can do now is + // pass the challenge information back to the client. + PopulateAuthChallenge(target); + } + + return OK; +} + +void HttpNetworkTransaction::PopulateAuthChallenge(HttpAuth::Target target) { + // Populates response_.auth_challenge with the authentication challenge info. + // This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo(). + + AuthChallengeInfo* auth_info = new AuthChallengeInfo; auth_info->is_proxy = target == HttpAuth::AUTH_PROXY; - auth_info->scheme = ASCIIToWide(auth_handler->scheme()); + auth_info->scheme = ASCIIToWide(auth_handler_[target]->scheme()); // TODO(eroman): decode realm according to RFC 2047. - auth_info->realm = ASCIIToWide(auth_handler->realm()); + auth_info->realm = ASCIIToWide(auth_handler_[target]->realm()); if (target == HttpAuth::AUTH_PROXY) { auth_info->host = ASCIIToWide(proxy_info_.proxy_server()); } else { DCHECK(target == HttpAuth::AUTH_SERVER); auth_info->host = ASCIIToWide(request_->url.host()); } - - // Update the auth cache key and remove any data in the auth cache. - if (!auth_data_[target]) - auth_data_[target] = new AuthData; - auth_cache_key_[target] = AuthCache::HttpKey(request_->url, *auth_info); - DCHECK(!auth_cache_key_[target].empty()); - auth_data_[target]->scheme = auth_info->scheme; - if (auth_data_[target]->state == AUTH_STATE_HAVE_AUTH) { - // The cached identity probably isn't valid so remove it. - // The assumption here is that the cached auth data is what we - // just used. - session_->auth_cache()->Remove(auth_cache_key_[target]); - auth_data_[target]->state = AUTH_STATE_NEED_AUTH; - } - - response_.auth_challenge.swap(auth_info); - auth_handler_[target].reset(auth_handler.release()); - - return OK; + response_.auth_challenge = auth_info; } } // namespace net |