diff options
Diffstat (limited to 'chromium/net/http/http_network_transaction.cc')
-rw-r--r-- | chromium/net/http/http_network_transaction.cc | 1488 |
1 files changed, 1488 insertions, 0 deletions
diff --git a/chromium/net/http/http_network_transaction.cc b/chromium/net/http/http_network_transaction.cc new file mode 100644 index 00000000000..a63a2aa43b9 --- /dev/null +++ b/chromium/net/http/http_network_transaction.cc @@ -0,0 +1,1488 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http/http_network_transaction.h" + +#include <set> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/compiler_specific.h" +#include "base/format_macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/field_trial.h" +#include "base/metrics/histogram.h" +#include "base/metrics/stats_counters.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "build/build_config.h" +#include "net/base/auth.h" +#include "net/base/host_port_pair.h" +#include "net/base/io_buffer.h" +#include "net/base/load_flags.h" +#include "net/base/load_timing_info.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/base/upload_data_stream.h" +#include "net/http/http_auth.h" +#include "net/http/http_auth_handler.h" +#include "net/http/http_auth_handler_factory.h" +#include "net/http/http_basic_stream.h" +#include "net/http/http_chunked_decoder.h" +#include "net/http/http_network_session.h" +#include "net/http/http_proxy_client_socket.h" +#include "net/http/http_proxy_client_socket_pool.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_response_info.h" +#include "net/http/http_server_properties.h" +#include "net/http/http_status_code.h" +#include "net/http/http_stream_base.h" +#include "net/http/http_stream_factory.h" +#include "net/http/http_util.h" +#include "net/http/transport_security_state.h" +#include "net/http/url_security_manager.h" +#include "net/socket/client_socket_factory.h" +#include "net/socket/socks_client_socket_pool.h" +#include "net/socket/ssl_client_socket.h" +#include "net/socket/ssl_client_socket_pool.h" +#include "net/socket/transport_client_socket_pool.h" +#include "net/spdy/spdy_http_stream.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/ssl/ssl_cert_request_info.h" +#include "net/ssl/ssl_connection_status_flags.h" +#include "url/gurl.h" + +using base::Time; + +namespace net { + +namespace { + +void ProcessAlternateProtocol( + HttpStreamFactory* factory, + const base::WeakPtr<HttpServerProperties>& http_server_properties, + const HttpResponseHeaders& headers, + const HostPortPair& http_host_port_pair) { + std::string alternate_protocol_str; + + if (!headers.EnumerateHeader(NULL, kAlternateProtocolHeader, + &alternate_protocol_str)) { + // Header is not present. + return; + } + + factory->ProcessAlternateProtocol(http_server_properties, + alternate_protocol_str, + http_host_port_pair); +} + +// Returns true if |error| is a client certificate authentication error. +bool IsClientCertificateError(int error) { + switch (error) { + case ERR_BAD_SSL_CLIENT_AUTH_CERT: + case ERR_SSL_CLIENT_AUTH_PRIVATE_KEY_ACCESS_DENIED: + case ERR_SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY: + case ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED: + return true; + default: + return false; + } +} + +base::Value* NetLogSSLVersionFallbackCallback( + const GURL* url, + int net_error, + uint16 version_before, + uint16 version_after, + NetLog::LogLevel /* log_level */) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetString("host_and_port", GetHostAndPort(*url)); + dict->SetInteger("net_error", net_error); + dict->SetInteger("version_before", version_before); + dict->SetInteger("version_after", version_after); + return dict; +} + +} // namespace + +//----------------------------------------------------------------------------- + +HttpNetworkTransaction::HttpNetworkTransaction(RequestPriority priority, + HttpNetworkSession* session) + : pending_auth_target_(HttpAuth::AUTH_NONE), + io_callback_(base::Bind(&HttpNetworkTransaction::OnIOComplete, + base::Unretained(this))), + session_(session), + request_(NULL), + priority_(priority), + headers_valid_(false), + logged_response_time_(false), + request_headers_(), + read_buf_len_(0), + next_state_(STATE_NONE), + establishing_tunnel_(false) { + session->ssl_config_service()->GetSSLConfig(&server_ssl_config_); + if (session->http_stream_factory()->has_next_protos()) { + server_ssl_config_.next_protos = + session->http_stream_factory()->next_protos(); + } + proxy_ssl_config_ = server_ssl_config_; +} + +HttpNetworkTransaction::~HttpNetworkTransaction() { + if (stream_.get()) { + HttpResponseHeaders* headers = GetResponseHeaders(); + // TODO(mbelshe): The stream_ should be able to compute whether or not the + // stream should be kept alive. No reason to compute here + // and pass it in. + bool try_to_keep_alive = + next_state_ == STATE_NONE && + stream_->CanFindEndOfResponse() && + (!headers || headers->IsKeepAlive()); + if (!try_to_keep_alive) { + stream_->Close(true /* not reusable */); + } else { + if (stream_->IsResponseBodyComplete()) { + // If the response body is complete, we can just reuse the socket. + stream_->Close(false /* reusable */); + } else if (stream_->IsSpdyHttpStream()) { + // Doesn't really matter for SpdyHttpStream. Just close it. + stream_->Close(true /* not reusable */); + } else { + // Otherwise, we try to drain the response body. + HttpStreamBase* stream = stream_.release(); + stream->Drain(session_.get()); + } + } + } +} + +int HttpNetworkTransaction::Start(const HttpRequestInfo* request_info, + const CompletionCallback& callback, + const BoundNetLog& net_log) { + SIMPLE_STATS_COUNTER("HttpNetworkTransaction.Count"); + + net_log_ = net_log; + request_ = request_info; + start_time_ = base::Time::Now(); + + if (request_->load_flags & LOAD_DISABLE_CERT_REVOCATION_CHECKING) { + server_ssl_config_.rev_checking_enabled = false; + proxy_ssl_config_.rev_checking_enabled = false; + } + + // Channel ID is enabled unless --disable-tls-channel-id flag is set, + // or if privacy mode is enabled. + bool channel_id_enabled = server_ssl_config_.channel_id_enabled && + (request_->privacy_mode == kPrivacyModeDisabled); + server_ssl_config_.channel_id_enabled = channel_id_enabled; + + next_state_ = STATE_CREATE_STREAM; + int rv = DoLoop(OK); + if (rv == ERR_IO_PENDING) + callback_ = callback; + return rv; +} + +int HttpNetworkTransaction::RestartIgnoringLastError( + const CompletionCallback& callback) { + DCHECK(!stream_.get()); + DCHECK(!stream_request_.get()); + DCHECK_EQ(STATE_NONE, next_state_); + + next_state_ = STATE_CREATE_STREAM; + + int rv = DoLoop(OK); + if (rv == ERR_IO_PENDING) + callback_ = callback; + return rv; +} + +int HttpNetworkTransaction::RestartWithCertificate( + X509Certificate* client_cert, const CompletionCallback& callback) { + // In HandleCertificateRequest(), we always tear down existing stream + // requests to force a new connection. So we shouldn't have one here. + DCHECK(!stream_request_.get()); + DCHECK(!stream_.get()); + DCHECK_EQ(STATE_NONE, next_state_); + + SSLConfig* ssl_config = response_.cert_request_info->is_proxy ? + &proxy_ssl_config_ : &server_ssl_config_; + ssl_config->send_client_cert = true; + ssl_config->client_cert = client_cert; + session_->ssl_client_auth_cache()->Add( + response_.cert_request_info->host_and_port, client_cert); + // Reset the other member variables. + // Note: this is necessary only with SSL renegotiation. + ResetStateForRestart(); + next_state_ = STATE_CREATE_STREAM; + int rv = DoLoop(OK); + if (rv == ERR_IO_PENDING) + callback_ = callback; + return rv; +} + +int HttpNetworkTransaction::RestartWithAuth( + const AuthCredentials& credentials, const CompletionCallback& callback) { + HttpAuth::Target target = pending_auth_target_; + if (target == HttpAuth::AUTH_NONE) { + NOTREACHED(); + return ERR_UNEXPECTED; + } + pending_auth_target_ = HttpAuth::AUTH_NONE; + + auth_controllers_[target]->ResetAuth(credentials); + + DCHECK(callback_.is_null()); + + int rv = OK; + if (target == HttpAuth::AUTH_PROXY && establishing_tunnel_) { + // In this case, we've gathered credentials for use with proxy + // authentication of a tunnel. + DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_); + DCHECK(stream_request_ != NULL); + auth_controllers_[target] = NULL; + ResetStateForRestart(); + rv = stream_request_->RestartTunnelWithProxyAuth(credentials); + } else { + // In this case, we've gathered credentials for the server or the proxy + // but it is not during the tunneling phase. + DCHECK(stream_request_ == NULL); + PrepareForAuthRestart(target); + rv = DoLoop(OK); + } + + if (rv == ERR_IO_PENDING) + callback_ = callback; + return rv; +} + +void HttpNetworkTransaction::PrepareForAuthRestart(HttpAuth::Target target) { + DCHECK(HaveAuth(target)); + DCHECK(!stream_request_.get()); + + bool keep_alive = false; + // Even if the server says the connection is keep-alive, we have to be + // able to find the end of each response in order to reuse the connection. + if (GetResponseHeaders()->IsKeepAlive() && + stream_->CanFindEndOfResponse()) { + // If the response body hasn't been completely read, we need to drain + // it first. + if (!stream_->IsResponseBodyComplete()) { + next_state_ = STATE_DRAIN_BODY_FOR_AUTH_RESTART; + read_buf_ = new IOBuffer(kDrainBodyBufferSize); // A bit bucket. + read_buf_len_ = kDrainBodyBufferSize; + return; + } + keep_alive = true; + } + + // We don't need to drain the response body, so we act as if we had drained + // the response body. + DidDrainBodyForAuthRestart(keep_alive); +} + +void HttpNetworkTransaction::DidDrainBodyForAuthRestart(bool keep_alive) { + DCHECK(!stream_request_.get()); + + if (stream_.get()) { + HttpStream* new_stream = NULL; + if (keep_alive && stream_->IsConnectionReusable()) { + // We should call connection_->set_idle_time(), but this doesn't occur + // often enough to be worth the trouble. + stream_->SetConnectionReused(); + new_stream = + static_cast<HttpStream*>(stream_.get())->RenewStreamForAuth(); + } + + if (!new_stream) { + // Close the stream and mark it as not_reusable. Even in the + // keep_alive case, we've determined that the stream_ is not + // reusable if new_stream is NULL. + stream_->Close(true); + next_state_ = STATE_CREATE_STREAM; + } else { + next_state_ = STATE_INIT_STREAM; + } + stream_.reset(new_stream); + } + + // Reset the other member variables. + ResetStateForAuthRestart(); +} + +bool HttpNetworkTransaction::IsReadyToRestartForAuth() { + return pending_auth_target_ != HttpAuth::AUTH_NONE && + HaveAuth(pending_auth_target_); +} + +int HttpNetworkTransaction::Read(IOBuffer* buf, int buf_len, + const CompletionCallback& callback) { + DCHECK(buf); + DCHECK_LT(0, buf_len); + + State next_state = STATE_NONE; + + scoped_refptr<HttpResponseHeaders> headers(GetResponseHeaders()); + if (headers_valid_ && headers.get() && stream_request_.get()) { + // We're trying to read the body of the response but we're still trying + // to establish an SSL tunnel through an HTTP proxy. We can't read these + // bytes when establishing a tunnel because they might be controlled by + // an active network attacker. We don't worry about this for HTTP + // because an active network attacker can already control HTTP sessions. + // We reach this case when the user cancels a 407 proxy auth prompt. We + // also don't worry about this for an HTTPS Proxy, because the + // communication with the proxy is secure. + // See http://crbug.com/8473. + DCHECK(proxy_info_.is_http() || proxy_info_.is_https()); + DCHECK_EQ(headers->response_code(), HTTP_PROXY_AUTHENTICATION_REQUIRED); + LOG(WARNING) << "Blocked proxy response with status " + << headers->response_code() << " to CONNECT request for " + << GetHostAndPort(request_->url) << "."; + return ERR_TUNNEL_CONNECTION_FAILED; + } + + // Are we using SPDY or HTTP? + next_state = STATE_READ_BODY; + + read_buf_ = buf; + read_buf_len_ = buf_len; + + next_state_ = next_state; + int rv = DoLoop(OK); + if (rv == ERR_IO_PENDING) + callback_ = callback; + return rv; +} + +bool HttpNetworkTransaction::GetFullRequestHeaders( + HttpRequestHeaders* headers) const { + // TODO(ttuttle): Make sure we've populated request_headers_. + *headers = request_headers_; + return true; +} + +const HttpResponseInfo* HttpNetworkTransaction::GetResponseInfo() const { + return ((headers_valid_ && response_.headers.get()) || + response_.ssl_info.cert.get() || response_.cert_request_info.get()) + ? &response_ + : NULL; +} + +LoadState HttpNetworkTransaction::GetLoadState() const { + // TODO(wtc): Define a new LoadState value for the + // STATE_INIT_CONNECTION_COMPLETE state, which delays the HTTP request. + switch (next_state_) { + case STATE_CREATE_STREAM_COMPLETE: + return stream_request_->GetLoadState(); + case STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE: + case STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE: + case STATE_SEND_REQUEST_COMPLETE: + return LOAD_STATE_SENDING_REQUEST; + case STATE_READ_HEADERS_COMPLETE: + return LOAD_STATE_WAITING_FOR_RESPONSE; + case STATE_READ_BODY_COMPLETE: + return LOAD_STATE_READING_RESPONSE; + default: + return LOAD_STATE_IDLE; + } +} + +UploadProgress HttpNetworkTransaction::GetUploadProgress() const { + if (!stream_.get()) + return UploadProgress(); + + // TODO(bashi): This cast is temporary. Remove later. + return static_cast<HttpStream*>(stream_.get())->GetUploadProgress(); +} + +bool HttpNetworkTransaction::GetLoadTimingInfo( + LoadTimingInfo* load_timing_info) const { + if (!stream_ || !stream_->GetLoadTimingInfo(load_timing_info)) + return false; + + load_timing_info->proxy_resolve_start = + proxy_info_.proxy_resolve_start_time(); + load_timing_info->proxy_resolve_end = proxy_info_.proxy_resolve_end_time(); + load_timing_info->send_start = send_start_time_; + load_timing_info->send_end = send_end_time_; + return true; +} + +void HttpNetworkTransaction::SetPriority(RequestPriority priority) { + priority_ = priority; + if (stream_request_) + stream_request_->SetPriority(priority); + if (stream_) + stream_->SetPriority(priority); +} + +void HttpNetworkTransaction::OnStreamReady(const SSLConfig& used_ssl_config, + const ProxyInfo& used_proxy_info, + HttpStreamBase* stream) { + DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_); + DCHECK(stream_request_.get()); + + stream_.reset(stream); + server_ssl_config_ = used_ssl_config; + proxy_info_ = used_proxy_info; + response_.was_npn_negotiated = stream_request_->was_npn_negotiated(); + response_.npn_negotiated_protocol = SSLClientSocket::NextProtoToString( + stream_request_->protocol_negotiated()); + response_.was_fetched_via_spdy = stream_request_->using_spdy(); + response_.was_fetched_via_proxy = !proxy_info_.is_direct(); + + OnIOComplete(OK); +} + +void HttpNetworkTransaction::OnWebSocketStreamReady( + const SSLConfig& used_ssl_config, + const ProxyInfo& used_proxy_info, + WebSocketStreamBase* stream) { + NOTREACHED() << "This function should never be called."; +} + +void HttpNetworkTransaction::OnStreamFailed(int result, + const SSLConfig& used_ssl_config) { + DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_); + DCHECK_NE(OK, result); + DCHECK(stream_request_.get()); + DCHECK(!stream_.get()); + server_ssl_config_ = used_ssl_config; + + OnIOComplete(result); +} + +void HttpNetworkTransaction::OnCertificateError( + int result, + const SSLConfig& used_ssl_config, + const SSLInfo& ssl_info) { + DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_); + DCHECK_NE(OK, result); + DCHECK(stream_request_.get()); + DCHECK(!stream_.get()); + + response_.ssl_info = ssl_info; + server_ssl_config_ = used_ssl_config; + + // TODO(mbelshe): For now, we're going to pass the error through, and that + // will close the stream_request in all cases. This means that we're always + // going to restart an entire STATE_CREATE_STREAM, even if the connection is + // good and the user chooses to ignore the error. This is not ideal, but not + // the end of the world either. + + OnIOComplete(result); +} + +void HttpNetworkTransaction::OnNeedsProxyAuth( + const HttpResponseInfo& proxy_response, + const SSLConfig& used_ssl_config, + const ProxyInfo& used_proxy_info, + HttpAuthController* auth_controller) { + DCHECK(stream_request_.get()); + DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_); + + establishing_tunnel_ = true; + response_.headers = proxy_response.headers; + response_.auth_challenge = proxy_response.auth_challenge; + headers_valid_ = true; + server_ssl_config_ = used_ssl_config; + proxy_info_ = used_proxy_info; + + auth_controllers_[HttpAuth::AUTH_PROXY] = auth_controller; + pending_auth_target_ = HttpAuth::AUTH_PROXY; + + DoCallback(OK); +} + +void HttpNetworkTransaction::OnNeedsClientAuth( + const SSLConfig& used_ssl_config, + SSLCertRequestInfo* cert_info) { + DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_); + + server_ssl_config_ = used_ssl_config; + response_.cert_request_info = cert_info; + OnIOComplete(ERR_SSL_CLIENT_AUTH_CERT_NEEDED); +} + +void HttpNetworkTransaction::OnHttpsProxyTunnelResponse( + const HttpResponseInfo& response_info, + const SSLConfig& used_ssl_config, + const ProxyInfo& used_proxy_info, + HttpStreamBase* stream) { + DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_); + + headers_valid_ = true; + response_ = response_info; + server_ssl_config_ = used_ssl_config; + proxy_info_ = used_proxy_info; + stream_.reset(stream); + stream_request_.reset(); // we're done with the stream request + OnIOComplete(ERR_HTTPS_PROXY_TUNNEL_RESPONSE); +} + +bool HttpNetworkTransaction::is_https_request() const { + return request_->url.SchemeIs("https"); +} + +void HttpNetworkTransaction::DoCallback(int rv) { + DCHECK_NE(rv, ERR_IO_PENDING); + DCHECK(!callback_.is_null()); + + // Since Run may result in Read being called, clear user_callback_ up front. + CompletionCallback c = callback_; + callback_.Reset(); + c.Run(rv); +} + +void HttpNetworkTransaction::OnIOComplete(int result) { + int rv = DoLoop(result); + if (rv != ERR_IO_PENDING) + DoCallback(rv); +} + +int HttpNetworkTransaction::DoLoop(int result) { + DCHECK(next_state_ != STATE_NONE); + + int rv = result; + do { + State state = next_state_; + next_state_ = STATE_NONE; + switch (state) { + case STATE_CREATE_STREAM: + DCHECK_EQ(OK, rv); + rv = DoCreateStream(); + break; + case STATE_CREATE_STREAM_COMPLETE: + rv = DoCreateStreamComplete(rv); + break; + case STATE_INIT_STREAM: + DCHECK_EQ(OK, rv); + rv = DoInitStream(); + break; + case STATE_INIT_STREAM_COMPLETE: + rv = DoInitStreamComplete(rv); + break; + case STATE_GENERATE_PROXY_AUTH_TOKEN: + DCHECK_EQ(OK, rv); + rv = DoGenerateProxyAuthToken(); + break; + case STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE: + rv = DoGenerateProxyAuthTokenComplete(rv); + break; + case STATE_GENERATE_SERVER_AUTH_TOKEN: + DCHECK_EQ(OK, rv); + rv = DoGenerateServerAuthToken(); + break; + case STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE: + rv = DoGenerateServerAuthTokenComplete(rv); + break; + case STATE_INIT_REQUEST_BODY: + DCHECK_EQ(OK, rv); + rv = DoInitRequestBody(); + break; + case STATE_INIT_REQUEST_BODY_COMPLETE: + rv = DoInitRequestBodyComplete(rv); + break; + case STATE_BUILD_REQUEST: + DCHECK_EQ(OK, rv); + net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST); + rv = DoBuildRequest(); + break; + case STATE_BUILD_REQUEST_COMPLETE: + rv = DoBuildRequestComplete(rv); + break; + case STATE_SEND_REQUEST: + DCHECK_EQ(OK, rv); + rv = DoSendRequest(); + break; + case STATE_SEND_REQUEST_COMPLETE: + rv = DoSendRequestComplete(rv); + net_log_.EndEventWithNetErrorCode( + NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, rv); + break; + case STATE_READ_HEADERS: + DCHECK_EQ(OK, rv); + net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS); + rv = DoReadHeaders(); + break; + case STATE_READ_HEADERS_COMPLETE: + rv = DoReadHeadersComplete(rv); + net_log_.EndEventWithNetErrorCode( + NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, rv); + break; + case STATE_READ_BODY: + DCHECK_EQ(OK, rv); + net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_READ_BODY); + rv = DoReadBody(); + break; + case STATE_READ_BODY_COMPLETE: + rv = DoReadBodyComplete(rv); + net_log_.EndEventWithNetErrorCode( + NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, rv); + break; + case STATE_DRAIN_BODY_FOR_AUTH_RESTART: + DCHECK_EQ(OK, rv); + net_log_.BeginEvent( + NetLog::TYPE_HTTP_TRANSACTION_DRAIN_BODY_FOR_AUTH_RESTART); + rv = DoDrainBodyForAuthRestart(); + break; + case STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE: + rv = DoDrainBodyForAuthRestartComplete(rv); + net_log_.EndEventWithNetErrorCode( + NetLog::TYPE_HTTP_TRANSACTION_DRAIN_BODY_FOR_AUTH_RESTART, rv); + break; + default: + NOTREACHED() << "bad state"; + rv = ERR_FAILED; + break; + } + } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); + + return rv; +} + +int HttpNetworkTransaction::DoCreateStream() { + next_state_ = STATE_CREATE_STREAM_COMPLETE; + + stream_request_.reset( + session_->http_stream_factory()->RequestStream( + *request_, + priority_, + server_ssl_config_, + proxy_ssl_config_, + this, + net_log_)); + DCHECK(stream_request_.get()); + return ERR_IO_PENDING; +} + +int HttpNetworkTransaction::DoCreateStreamComplete(int result) { + if (result == OK) { + next_state_ = STATE_INIT_STREAM; + DCHECK(stream_.get()); + } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) { + result = HandleCertificateRequest(result); + } else if (result == ERR_HTTPS_PROXY_TUNNEL_RESPONSE) { + // Return OK and let the caller read the proxy's error page + next_state_ = STATE_NONE; + return OK; + } + + // Handle possible handshake errors that may have occurred if the stream + // used SSL for one or more of the layers. + result = HandleSSLHandshakeError(result); + + // At this point we are done with the stream_request_. + stream_request_.reset(); + return result; +} + +int HttpNetworkTransaction::DoInitStream() { + DCHECK(stream_.get()); + next_state_ = STATE_INIT_STREAM_COMPLETE; + return stream_->InitializeStream(request_, priority_, net_log_, io_callback_); +} + +int HttpNetworkTransaction::DoInitStreamComplete(int result) { + if (result == OK) { + next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN; + } else { + if (result < 0) + result = HandleIOError(result); + + // The stream initialization failed, so this stream will never be useful. + stream_.reset(); + } + + return result; +} + +int HttpNetworkTransaction::DoGenerateProxyAuthToken() { + next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE; + if (!ShouldApplyProxyAuth()) + return OK; + HttpAuth::Target target = HttpAuth::AUTH_PROXY; + if (!auth_controllers_[target].get()) + auth_controllers_[target] = + new HttpAuthController(target, + AuthURL(target), + session_->http_auth_cache(), + session_->http_auth_handler_factory()); + return auth_controllers_[target]->MaybeGenerateAuthToken(request_, + io_callback_, + net_log_); +} + +int HttpNetworkTransaction::DoGenerateProxyAuthTokenComplete(int rv) { + DCHECK_NE(ERR_IO_PENDING, rv); + if (rv == OK) + next_state_ = STATE_GENERATE_SERVER_AUTH_TOKEN; + return rv; +} + +int HttpNetworkTransaction::DoGenerateServerAuthToken() { + next_state_ = STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE; + HttpAuth::Target target = HttpAuth::AUTH_SERVER; + if (!auth_controllers_[target].get()) + auth_controllers_[target] = + new HttpAuthController(target, + AuthURL(target), + session_->http_auth_cache(), + session_->http_auth_handler_factory()); + if (!ShouldApplyServerAuth()) + return OK; + return auth_controllers_[target]->MaybeGenerateAuthToken(request_, + io_callback_, + net_log_); +} + +int HttpNetworkTransaction::DoGenerateServerAuthTokenComplete(int rv) { + DCHECK_NE(ERR_IO_PENDING, rv); + if (rv == OK) + next_state_ = STATE_INIT_REQUEST_BODY; + return rv; +} + +void HttpNetworkTransaction::BuildRequestHeaders(bool using_proxy) { + request_headers_.SetHeader(HttpRequestHeaders::kHost, + GetHostAndOptionalPort(request_->url)); + + // For compat with HTTP/1.0 servers and proxies: + if (using_proxy) { + request_headers_.SetHeader(HttpRequestHeaders::kProxyConnection, + "keep-alive"); + } else { + request_headers_.SetHeader(HttpRequestHeaders::kConnection, "keep-alive"); + } + + // Add a content length header? + if (request_->upload_data_stream) { + if (request_->upload_data_stream->is_chunked()) { + request_headers_.SetHeader( + HttpRequestHeaders::kTransferEncoding, "chunked"); + } else { + request_headers_.SetHeader( + HttpRequestHeaders::kContentLength, + base::Uint64ToString(request_->upload_data_stream->size())); + } + } else if (request_->method == "POST" || request_->method == "PUT" || + request_->method == "HEAD") { + // An empty POST/PUT request still needs a content length. As for HEAD, + // IE and Safari also add a content length header. Presumably it is to + // support sending a HEAD request to an URL that only expects to be sent a + // POST or some other method that normally would have a message body. + request_headers_.SetHeader(HttpRequestHeaders::kContentLength, "0"); + } + + // Honor load flags that impact proxy caches. + if (request_->load_flags & LOAD_BYPASS_CACHE) { + request_headers_.SetHeader(HttpRequestHeaders::kPragma, "no-cache"); + request_headers_.SetHeader(HttpRequestHeaders::kCacheControl, "no-cache"); + } else if (request_->load_flags & LOAD_VALIDATE_CACHE) { + request_headers_.SetHeader(HttpRequestHeaders::kCacheControl, "max-age=0"); + } + + if (ShouldApplyProxyAuth() && HaveAuth(HttpAuth::AUTH_PROXY)) + auth_controllers_[HttpAuth::AUTH_PROXY]->AddAuthorizationHeader( + &request_headers_); + if (ShouldApplyServerAuth() && HaveAuth(HttpAuth::AUTH_SERVER)) + auth_controllers_[HttpAuth::AUTH_SERVER]->AddAuthorizationHeader( + &request_headers_); + + request_headers_.MergeFrom(request_->extra_headers); + response_.did_use_http_auth = + request_headers_.HasHeader(HttpRequestHeaders::kAuthorization) || + request_headers_.HasHeader(HttpRequestHeaders::kProxyAuthorization); +} + +int HttpNetworkTransaction::DoInitRequestBody() { + next_state_ = STATE_INIT_REQUEST_BODY_COMPLETE; + int rv = OK; + if (request_->upload_data_stream) + rv = request_->upload_data_stream->Init(io_callback_); + return rv; +} + +int HttpNetworkTransaction::DoInitRequestBodyComplete(int result) { + if (result == OK) + next_state_ = STATE_BUILD_REQUEST; + return result; +} + +int HttpNetworkTransaction::DoBuildRequest() { + next_state_ = STATE_BUILD_REQUEST_COMPLETE; + headers_valid_ = false; + + // This is constructed lazily (instead of within our Start method), so that + // we have proxy info available. + if (request_headers_.IsEmpty()) { + bool using_proxy = (proxy_info_.is_http() || proxy_info_.is_https()) && + !is_https_request(); + BuildRequestHeaders(using_proxy); + } + + return OK; +} + +int HttpNetworkTransaction::DoBuildRequestComplete(int result) { + if (result == OK) + next_state_ = STATE_SEND_REQUEST; + return result; +} + +int HttpNetworkTransaction::DoSendRequest() { + send_start_time_ = base::TimeTicks::Now(); + next_state_ = STATE_SEND_REQUEST_COMPLETE; + + return stream_->SendRequest(request_headers_, &response_, io_callback_); +} + +int HttpNetworkTransaction::DoSendRequestComplete(int result) { + send_end_time_ = base::TimeTicks::Now(); + if (result < 0) + return HandleIOError(result); + response_.network_accessed = true; + next_state_ = STATE_READ_HEADERS; + return OK; +} + +int HttpNetworkTransaction::DoReadHeaders() { + next_state_ = STATE_READ_HEADERS_COMPLETE; + return stream_->ReadResponseHeaders(io_callback_); +} + +int HttpNetworkTransaction::HandleConnectionClosedBeforeEndOfHeaders() { + if (!response_.headers.get() && !stream_->IsConnectionReused()) { + // The connection was closed before any data was sent. Likely an error + // rather than empty HTTP/0.9 response. + return ERR_EMPTY_RESPONSE; + } + + return OK; +} + +int HttpNetworkTransaction::DoReadHeadersComplete(int result) { + // We can get a certificate error or ERR_SSL_CLIENT_AUTH_CERT_NEEDED here + // due to SSL renegotiation. + if (IsCertificateError(result)) { + // We don't handle a certificate error during SSL renegotiation, so we + // have to return an error that's not in the certificate error range + // (-2xx). + LOG(ERROR) << "Got a server certificate with error " << result + << " during SSL renegotiation"; + result = ERR_CERT_ERROR_IN_SSL_RENEGOTIATION; + } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) { + // TODO(wtc): Need a test case for this code path! + DCHECK(stream_.get()); + DCHECK(is_https_request()); + response_.cert_request_info = new SSLCertRequestInfo; + stream_->GetSSLCertRequestInfo(response_.cert_request_info.get()); + result = HandleCertificateRequest(result); + if (result == OK) + return result; + } + + if (result < 0 && result != ERR_CONNECTION_CLOSED) + return HandleIOError(result); + + if (result == ERR_CONNECTION_CLOSED && ShouldResendRequest(result)) { + ResetConnectionAndRequestForResend(); + return OK; + } + + // After we call RestartWithAuth a new response_time will be recorded, and + // we need to be cautious about incorrectly logging the duration across the + // authentication activity. + if (result == OK) + LogTransactionConnectedMetrics(); + + if (result == ERR_CONNECTION_CLOSED) { + // For now, if we get at least some data, we do the best we can to make + // sense of it and send it back up the stack. + int rv = HandleConnectionClosedBeforeEndOfHeaders(); + if (rv != OK) + return rv; + } + DCHECK(response_.headers.get()); + + // Server-induced fallback is supported only if this is a PAC configured + // proxy. See: http://crbug.com/143712 + if (response_.was_fetched_via_proxy && proxy_info_.did_use_pac_script() && + response_.headers.get() != NULL) { + bool should_fallback = + response_.headers->HasHeaderValue("connection", "proxy-bypass"); + // Additionally, fallback if a 500 is returned via the data reduction proxy. + // This is conservative, as the 500 might have been generated by the origin, + // and not the proxy. +#if defined(SPDY_PROXY_AUTH_ORIGIN) + if (!should_fallback) { + should_fallback = + response_.headers->response_code() == HTTP_INTERNAL_SERVER_ERROR && + proxy_info_.proxy_server().host_port_pair().Equals( + HostPortPair::FromURL(GURL(SPDY_PROXY_AUTH_ORIGIN))); + } +#endif + if (should_fallback) { + ProxyService* proxy_service = session_->proxy_service(); + if (proxy_service->MarkProxyAsBad(proxy_info_, net_log_)) { + // Only retry in the case of GETs. We don't want to resubmit a POST + // if the proxy took some action. + if (request_->method == "GET") { + ResetConnectionAndRequestForResend(); + return OK; + } + } + } + } + + // Like Net.HttpResponseCode, but only for MAIN_FRAME loads. + if (request_->load_flags & LOAD_MAIN_FRAME) { + const int response_code = response_.headers->response_code(); + UMA_HISTOGRAM_ENUMERATION( + "Net.HttpResponseCode_Nxx_MainFrame", response_code/100, 10); + } + + net_log_.AddEvent( + NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS, + base::Bind(&HttpResponseHeaders::NetLogCallback, response_.headers)); + + if (response_.headers->GetParsedHttpVersion() < HttpVersion(1, 0)) { + // HTTP/0.9 doesn't support the PUT method, so lack of response headers + // indicates a buggy server. See: + // https://bugzilla.mozilla.org/show_bug.cgi?id=193921 + if (request_->method == "PUT") + return ERR_METHOD_NOT_SUPPORTED; + } + + // Check for an intermediate 100 Continue response. An origin server is + // allowed to send this response even if we didn't ask for it, so we just + // need to skip over it. + // We treat any other 1xx in this same way (although in practice getting + // a 1xx that isn't a 100 is rare). + if (response_.headers->response_code() / 100 == 1) { + response_.headers = new HttpResponseHeaders(std::string()); + next_state_ = STATE_READ_HEADERS; + return OK; + } + + HostPortPair endpoint = HostPortPair(request_->url.HostNoBrackets(), + request_->url.EffectiveIntPort()); + ProcessAlternateProtocol(session_->http_stream_factory(), + session_->http_server_properties(), + *response_.headers.get(), + endpoint); + + int rv = HandleAuthChallenge(); + if (rv != OK) + return rv; + + if (is_https_request()) + stream_->GetSSLInfo(&response_.ssl_info); + + headers_valid_ = true; + return OK; +} + +int HttpNetworkTransaction::DoReadBody() { + DCHECK(read_buf_.get()); + DCHECK_GT(read_buf_len_, 0); + DCHECK(stream_ != NULL); + + next_state_ = STATE_READ_BODY_COMPLETE; + return stream_->ReadResponseBody( + read_buf_.get(), read_buf_len_, io_callback_); +} + +int HttpNetworkTransaction::DoReadBodyComplete(int result) { + // We are done with the Read call. + bool done = false; + if (result <= 0) { + DCHECK_NE(ERR_IO_PENDING, result); + done = true; + } + + bool keep_alive = false; + if (stream_->IsResponseBodyComplete()) { + // Note: Just because IsResponseBodyComplete is true, we're not + // necessarily "done". We're only "done" when it is the last + // read on this HttpNetworkTransaction, which will be signified + // by a zero-length read. + // TODO(mbelshe): The keepalive property is really a property of + // the stream. No need to compute it here just to pass back + // to the stream's Close function. + // TODO(rtenneti): CanFindEndOfResponse should return false if there are no + // ResponseHeaders. + if (stream_->CanFindEndOfResponse()) { + HttpResponseHeaders* headers = GetResponseHeaders(); + if (headers) + keep_alive = headers->IsKeepAlive(); + } + } + + // Clean up connection if we are done. + if (done) { + LogTransactionMetrics(); + stream_->Close(!keep_alive); + // Note: we don't reset the stream here. We've closed it, but we still + // need it around so that callers can call methods such as + // GetUploadProgress() and have them be meaningful. + // TODO(mbelshe): This means we closed the stream here, and we close it + // again in ~HttpNetworkTransaction. Clean that up. + + // The next Read call will return 0 (EOF). + } + + // Clear these to avoid leaving around old state. + read_buf_ = NULL; + read_buf_len_ = 0; + + return result; +} + +int HttpNetworkTransaction::DoDrainBodyForAuthRestart() { + // This method differs from DoReadBody only in the next_state_. So we just + // call DoReadBody and override the next_state_. Perhaps there is a more + // elegant way for these two methods to share code. + int rv = DoReadBody(); + DCHECK(next_state_ == STATE_READ_BODY_COMPLETE); + next_state_ = STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE; + return rv; +} + +// TODO(wtc): This method and the DoReadBodyComplete method are almost +// the same. Figure out a good way for these two methods to share code. +int HttpNetworkTransaction::DoDrainBodyForAuthRestartComplete(int result) { + // keep_alive defaults to true because the very reason we're draining the + // response body is to reuse the connection for auth restart. + bool done = false, keep_alive = true; + if (result < 0) { + // Error or closed connection while reading the socket. + done = true; + keep_alive = false; + } else if (stream_->IsResponseBodyComplete()) { + done = true; + } + + if (done) { + DidDrainBodyForAuthRestart(keep_alive); + } else { + // Keep draining. + next_state_ = STATE_DRAIN_BODY_FOR_AUTH_RESTART; + } + + return OK; +} + +void HttpNetworkTransaction::LogTransactionConnectedMetrics() { + if (logged_response_time_) + return; + + logged_response_time_ = true; + + base::TimeDelta total_duration = response_.response_time - start_time_; + + UMA_HISTOGRAM_CUSTOM_TIMES( + "Net.Transaction_Connected", + total_duration, + base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10), + 100); + + bool reused_socket = stream_->IsConnectionReused(); + if (!reused_socket) { + UMA_HISTOGRAM_CUSTOM_TIMES( + "Net.Transaction_Connected_New_b", + total_duration, + base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10), + 100); + } + + // Currently, non-HIGHEST priority requests are frame or sub-frame resource + // types. This will change when we also prioritize certain subresources like + // css, js, etc. + if (priority_ != HIGHEST) { + UMA_HISTOGRAM_CUSTOM_TIMES( + "Net.Priority_High_Latency_b", + total_duration, + base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10), + 100); + } else { + UMA_HISTOGRAM_CUSTOM_TIMES( + "Net.Priority_Low_Latency_b", + total_duration, + base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10), + 100); + } +} + +void HttpNetworkTransaction::LogTransactionMetrics() const { + base::TimeDelta duration = base::Time::Now() - + response_.request_time; + if (60 < duration.InMinutes()) + return; + + base::TimeDelta total_duration = base::Time::Now() - start_time_; + + UMA_HISTOGRAM_CUSTOM_TIMES("Net.Transaction_Latency_b", duration, + base::TimeDelta::FromMilliseconds(1), + base::TimeDelta::FromMinutes(10), + 100); + UMA_HISTOGRAM_CUSTOM_TIMES("Net.Transaction_Latency_Total", + total_duration, + base::TimeDelta::FromMilliseconds(1), + base::TimeDelta::FromMinutes(10), 100); + + if (!stream_->IsConnectionReused()) { + UMA_HISTOGRAM_CUSTOM_TIMES( + "Net.Transaction_Latency_Total_New_Connection", + total_duration, base::TimeDelta::FromMilliseconds(1), + base::TimeDelta::FromMinutes(10), 100); + } +} + +int HttpNetworkTransaction::HandleCertificateRequest(int error) { + // There are two paths through which the server can request a certificate + // from us. The first is during the initial handshake, the second is + // during SSL renegotiation. + // + // In both cases, we want to close the connection before proceeding. + // We do this for two reasons: + // First, we don't want to keep the connection to the server hung for a + // long time while the user selects a certificate. + // Second, even if we did keep the connection open, NSS has a bug where + // restarting the handshake for ClientAuth is currently broken. + DCHECK_EQ(error, ERR_SSL_CLIENT_AUTH_CERT_NEEDED); + + if (stream_.get()) { + // Since we already have a stream, we're being called as part of SSL + // renegotiation. + DCHECK(!stream_request_.get()); + stream_->Close(true); + stream_.reset(); + } + + // The server is asking for a client certificate during the initial + // handshake. + stream_request_.reset(); + + // If the user selected one of the certificates in client_certs or declined + // to provide one for this server before, use the past decision + // automatically. + scoped_refptr<X509Certificate> client_cert; + bool found_cached_cert = session_->ssl_client_auth_cache()->Lookup( + response_.cert_request_info->host_and_port, &client_cert); + if (!found_cached_cert) + return error; + + // Check that the certificate selected is still a certificate the server + // is likely to accept, based on the criteria supplied in the + // CertificateRequest message. + if (client_cert.get()) { + const std::vector<std::string>& cert_authorities = + response_.cert_request_info->cert_authorities; + + bool cert_still_valid = cert_authorities.empty() || + client_cert->IsIssuedByEncoded(cert_authorities); + if (!cert_still_valid) + return error; + } + + // TODO(davidben): Add a unit test which covers this path; we need to be + // able to send a legitimate certificate and also bypass/clear the + // SSL session cache. + SSLConfig* ssl_config = response_.cert_request_info->is_proxy ? + &proxy_ssl_config_ : &server_ssl_config_; + ssl_config->send_client_cert = true; + ssl_config->client_cert = client_cert; + next_state_ = STATE_CREATE_STREAM; + // Reset the other member variables. + // Note: this is necessary only with SSL renegotiation. + ResetStateForRestart(); + return OK; +} + +// TODO(rch): This does not correctly handle errors when an SSL proxy is +// being used, as all of the errors are handled as if they were generated +// by the endpoint host, request_->url, rather than considering if they were +// generated by the SSL proxy. http://crbug.com/69329 +int HttpNetworkTransaction::HandleSSLHandshakeError(int error) { + DCHECK(request_); + if (server_ssl_config_.send_client_cert && + (error == ERR_SSL_PROTOCOL_ERROR || IsClientCertificateError(error))) { + session_->ssl_client_auth_cache()->Remove( + GetHostAndPort(request_->url)); + } + + bool should_fallback = false; + uint16 version_max = server_ssl_config_.version_max; + + switch (error) { + case ERR_SSL_PROTOCOL_ERROR: + case ERR_SSL_VERSION_OR_CIPHER_MISMATCH: + if (version_max >= SSL_PROTOCOL_VERSION_TLS1 && + version_max > server_ssl_config_.version_min) { + // This could be a TLS-intolerant server or a server that chose a + // cipher suite defined only for higher protocol versions (such as + // an SSL 3.0 server that chose a TLS-only cipher suite). Fall + // back to the next lower version and retry. + // NOTE: if the SSLClientSocket class doesn't support TLS 1.1, + // specifying TLS 1.1 in version_max will result in a TLS 1.0 + // handshake, so falling back from TLS 1.1 to TLS 1.0 will simply + // repeat the TLS 1.0 handshake. To avoid this problem, the default + // version_max should match the maximum protocol version supported + // by the SSLClientSocket class. + version_max--; + + // Fallback to the lower SSL version. + // While SSL 3.0 fallback should be eliminated because of security + // reasons, there is a high risk of breaking the servers if this is + // done in general. + // For now SSL 3.0 fallback is disabled for Google servers first, + // and will be expanded to other servers after enough experiences + // have been gained showing that this experiment works well with + // today's Internet. + if (version_max > SSL_PROTOCOL_VERSION_SSL3 || + (server_ssl_config_.unrestricted_ssl3_fallback_enabled || + !TransportSecurityState::IsGooglePinnedProperty( + request_->url.host(), true /* include SNI */))) { + should_fallback = true; + } + } + break; + case ERR_SSL_BAD_RECORD_MAC_ALERT: + if (version_max >= SSL_PROTOCOL_VERSION_TLS1_1 && + version_max > server_ssl_config_.version_min) { + // Some broken SSL devices negotiate TLS 1.0 when sent a TLS 1.1 or + // 1.2 ClientHello, but then return a bad_record_mac alert. See + // crbug.com/260358. In order to make the fallback as minimal as + // possible, this fallback is only triggered for >= TLS 1.1. + version_max--; + should_fallback = true; + } + break; + } + + if (should_fallback) { + net_log_.AddEvent( + NetLog::TYPE_SSL_VERSION_FALLBACK, + base::Bind(&NetLogSSLVersionFallbackCallback, + &request_->url, error, server_ssl_config_.version_max, + version_max)); + server_ssl_config_.version_max = version_max; + server_ssl_config_.version_fallback = true; + ResetConnectionAndRequestForResend(); + error = OK; + } + + return error; +} + +// This method determines whether it is safe to resend the request after an +// IO error. It can only be called in response to request header or body +// write errors or response header read errors. It should not be used in +// other cases, such as a Connect error. +int HttpNetworkTransaction::HandleIOError(int error) { + // SSL errors may happen at any time during the stream and indicate issues + // with the underlying connection. Because the peer may request + // renegotiation at any time, check and handle any possible SSL handshake + // related errors. In addition to renegotiation, TLS False Start may cause + // SSL handshake errors (specifically servers with buggy DEFLATE support) + // to be delayed until the first Read on the underlying connection. + error = HandleSSLHandshakeError(error); + + switch (error) { + // If we try to reuse a connection that the server is in the process of + // closing, we may end up successfully writing out our request (or a + // portion of our request) only to find a connection error when we try to + // read from (or finish writing to) the socket. + case ERR_CONNECTION_RESET: + case ERR_CONNECTION_CLOSED: + case ERR_CONNECTION_ABORTED: + // There can be a race between the socket pool checking checking whether a + // socket is still connected, receiving the FIN, and sending/reading data + // on a reused socket. If we receive the FIN between the connectedness + // check and writing/reading from the socket, we may first learn the socket + // is disconnected when we get a ERR_SOCKET_NOT_CONNECTED. This will most + // likely happen when trying to retrieve its IP address. + // See http://crbug.com/105824 for more details. + case ERR_SOCKET_NOT_CONNECTED: + if (ShouldResendRequest(error)) { + net_log_.AddEventWithNetErrorCode( + NetLog::TYPE_HTTP_TRANSACTION_RESTART_AFTER_ERROR, error); + ResetConnectionAndRequestForResend(); + error = OK; + } + break; + case ERR_PIPELINE_EVICTION: + if (!session_->force_http_pipelining()) { + net_log_.AddEventWithNetErrorCode( + NetLog::TYPE_HTTP_TRANSACTION_RESTART_AFTER_ERROR, error); + ResetConnectionAndRequestForResend(); + error = OK; + } + break; + case ERR_SPDY_PING_FAILED: + case ERR_SPDY_SERVER_REFUSED_STREAM: + net_log_.AddEventWithNetErrorCode( + NetLog::TYPE_HTTP_TRANSACTION_RESTART_AFTER_ERROR, error); + ResetConnectionAndRequestForResend(); + error = OK; + break; + } + return error; +} + +void HttpNetworkTransaction::ResetStateForRestart() { + ResetStateForAuthRestart(); + stream_.reset(); +} + +void HttpNetworkTransaction::ResetStateForAuthRestart() { + send_start_time_ = base::TimeTicks(); + send_end_time_ = base::TimeTicks(); + + pending_auth_target_ = HttpAuth::AUTH_NONE; + read_buf_ = NULL; + read_buf_len_ = 0; + headers_valid_ = false; + request_headers_.Clear(); + response_ = HttpResponseInfo(); + establishing_tunnel_ = false; +} + +HttpResponseHeaders* HttpNetworkTransaction::GetResponseHeaders() const { + return response_.headers.get(); +} + +bool HttpNetworkTransaction::ShouldResendRequest(int error) const { + bool connection_is_proven = stream_->IsConnectionReused(); + bool has_received_headers = GetResponseHeaders() != NULL; + + // NOTE: we resend a request only if we reused a keep-alive connection. + // This automatically prevents an infinite resend loop because we'll run + // out of the cached keep-alive connections eventually. + if (connection_is_proven && !has_received_headers) + return true; + return false; +} + +void HttpNetworkTransaction::ResetConnectionAndRequestForResend() { + if (stream_.get()) { + stream_->Close(true); + stream_.reset(); + } + + // We need to clear request_headers_ because it contains the real request + // headers, but we may need to resend the CONNECT request first to recreate + // the SSL tunnel. + request_headers_.Clear(); + next_state_ = STATE_CREATE_STREAM; // Resend the request. +} + +bool HttpNetworkTransaction::ShouldApplyProxyAuth() const { + return !is_https_request() && + (proxy_info_.is_https() || proxy_info_.is_http()); +} + +bool HttpNetworkTransaction::ShouldApplyServerAuth() const { + return !(request_->load_flags & LOAD_DO_NOT_SEND_AUTH_DATA); +} + +int HttpNetworkTransaction::HandleAuthChallenge() { + scoped_refptr<HttpResponseHeaders> headers(GetResponseHeaders()); + DCHECK(headers.get()); + + int status = headers->response_code(); + if (status != HTTP_UNAUTHORIZED && + status != HTTP_PROXY_AUTHENTICATION_REQUIRED) + return OK; + HttpAuth::Target target = status == HTTP_PROXY_AUTHENTICATION_REQUIRED ? + HttpAuth::AUTH_PROXY : HttpAuth::AUTH_SERVER; + if (target == HttpAuth::AUTH_PROXY && proxy_info_.is_direct()) + return ERR_UNEXPECTED_PROXY_AUTH; + + // This case can trigger when an HTTPS server responds with a "Proxy + // authentication required" status code through a non-authenticating + // proxy. + if (!auth_controllers_[target].get()) + return ERR_UNEXPECTED_PROXY_AUTH; + + int rv = auth_controllers_[target]->HandleAuthChallenge( + headers, (request_->load_flags & LOAD_DO_NOT_SEND_AUTH_DATA) != 0, false, + net_log_); + if (auth_controllers_[target]->HaveAuthHandler()) + pending_auth_target_ = target; + + scoped_refptr<AuthChallengeInfo> auth_info = + auth_controllers_[target]->auth_info(); + if (auth_info.get()) + response_.auth_challenge = auth_info; + + return rv; +} + +bool HttpNetworkTransaction::HaveAuth(HttpAuth::Target target) const { + return auth_controllers_[target].get() && + auth_controllers_[target]->HaveAuth(); +} + +GURL HttpNetworkTransaction::AuthURL(HttpAuth::Target target) const { + switch (target) { + case HttpAuth::AUTH_PROXY: { + if (!proxy_info_.proxy_server().is_valid() || + proxy_info_.proxy_server().is_direct()) { + return GURL(); // There is no proxy server. + } + const char* scheme = proxy_info_.is_https() ? "https://" : "http://"; + return GURL(scheme + + proxy_info_.proxy_server().host_port_pair().ToString()); + } + case HttpAuth::AUTH_SERVER: + return request_->url; + default: + return GURL(); + } +} + +#define STATE_CASE(s) \ + case s: \ + description = base::StringPrintf("%s (0x%08X)", #s, s); \ + break + +std::string HttpNetworkTransaction::DescribeState(State state) { + std::string description; + switch (state) { + STATE_CASE(STATE_CREATE_STREAM); + STATE_CASE(STATE_CREATE_STREAM_COMPLETE); + STATE_CASE(STATE_INIT_REQUEST_BODY); + STATE_CASE(STATE_INIT_REQUEST_BODY_COMPLETE); + STATE_CASE(STATE_BUILD_REQUEST); + STATE_CASE(STATE_BUILD_REQUEST_COMPLETE); + STATE_CASE(STATE_SEND_REQUEST); + STATE_CASE(STATE_SEND_REQUEST_COMPLETE); + STATE_CASE(STATE_READ_HEADERS); + STATE_CASE(STATE_READ_HEADERS_COMPLETE); + STATE_CASE(STATE_READ_BODY); + STATE_CASE(STATE_READ_BODY_COMPLETE); + STATE_CASE(STATE_DRAIN_BODY_FOR_AUTH_RESTART); + STATE_CASE(STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE); + STATE_CASE(STATE_NONE); + default: + description = base::StringPrintf("Unknown state 0x%08X (%u)", state, + state); + break; + } + return description; +} + +#undef STATE_CASE + +} // namespace net |