diff options
author | vandebo@chromium.org <vandebo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-17 22:29:57 +0000 |
---|---|---|
committer | vandebo@chromium.org <vandebo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-17 22:29:57 +0000 |
commit | 0877e3db672aa26d89549f545e6e6c64904fec4c (patch) | |
tree | 127ce4a310fd137427c1963dc6eb02410bcb235d /net | |
parent | b71e79d427311fdd9ec36ac5a122fc987bdb2020 (diff) | |
download | chromium_src-0877e3db672aa26d89549f545e6e6c64904fec4c.zip chromium_src-0877e3db672aa26d89549f545e6e6c64904fec4c.tar.gz chromium_src-0877e3db672aa26d89549f545e6e6c64904fec4c.tar.bz2 |
Refactor HttpNetworkTransaction so that HttpStream is responsible for parsing the Http traffic. HttpBasicStream delegates parsing to HttpStreamParser in preparation for HttpPipelinedStream.
Original review: http://codereview.chromium.org/249031
BUG=13289
TEST=unittests
Review URL: http://codereview.chromium.org/283022
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@29379 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/http/http_basic_stream.cc | 50 | ||||
-rw-r--r-- | net/http/http_basic_stream.h | 46 | ||||
-rw-r--r-- | net/http/http_network_transaction.cc | 635 | ||||
-rw-r--r-- | net/http/http_network_transaction.h | 126 | ||||
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 256 | ||||
-rw-r--r-- | net/http/http_stream.h | 81 | ||||
-rw-r--r-- | net/http/http_stream_parser.cc | 512 | ||||
-rw-r--r-- | net/http/http_stream_parser.h | 167 | ||||
-rw-r--r-- | net/net.gyp | 3 | ||||
-rw-r--r-- | net/socket/client_socket_handle.h | 13 |
10 files changed, 1255 insertions, 634 deletions
diff --git a/net/http/http_basic_stream.cc b/net/http/http_basic_stream.cc new file mode 100644 index 0000000..f1e6100 --- /dev/null +++ b/net/http/http_basic_stream.cc @@ -0,0 +1,50 @@ +// Copyright (c) 2006-2009 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_basic_stream.h" + +namespace net { + +HttpBasicStream::HttpBasicStream(ClientSocketHandle* handle) + : read_buf_(new GrowableIOBuffer()), + parser_(new HttpStreamParser(handle, read_buf_)) { +} + +int HttpBasicStream::SendRequest(const HttpRequestInfo* request, + const std::string& headers, + UploadDataStream* request_body, + CompletionCallback* callback) { + return parser_->SendRequest(request, headers, request_body, callback); +} + +uint64 HttpBasicStream::GetUploadProgress() const { + return parser_->GetUploadProgress(); +} + +int HttpBasicStream::ReadResponseHeaders(CompletionCallback* callback) { + return parser_->ReadResponseHeaders(callback); +} + +HttpResponseInfo* HttpBasicStream::GetResponseInfo() const { + return parser_->GetResponseInfo(); +} + +int HttpBasicStream::ReadResponseBody(IOBuffer* buf, int buf_len, + CompletionCallback* callback) { + return parser_->ReadResponseBody(buf, buf_len, callback); +} + +bool HttpBasicStream::IsResponseBodyComplete() const { + return parser_->IsResponseBodyComplete(); +} + +bool HttpBasicStream::CanFindEndOfResponse() const { + return parser_->CanFindEndOfResponse(); +} + +bool HttpBasicStream::IsMoreDataBuffered() const { + return parser_->IsMoreDataBuffered(); +} + +} // namespace net diff --git a/net/http/http_basic_stream.h b/net/http/http_basic_stream.h index 2a82b7a..1b1f68a 100644 --- a/net/http/http_basic_stream.h +++ b/net/http/http_basic_stream.h @@ -9,32 +9,50 @@ #ifndef NET_HTTP_HTTP_BASIC_STREAM_H_ #define NET_HTTP_HTTP_BASIC_STREAM_H_ +#include <string> + #include "base/basictypes.h" +#include "net/base/io_buffer.h" #include "net/http/http_stream.h" -#include "net/socket/client_socket_handle.h" +#include "net/http/http_stream_parser.h" namespace net { +class ClientSocketHandle; +class HttpRequestInfo; +class HttpResponseInfo; +class UploadDataStream; + class HttpBasicStream : public HttpStream { public: - explicit HttpBasicStream(ClientSocketHandle* handle) : handle_(handle) {} + explicit HttpBasicStream(ClientSocketHandle* handle); virtual ~HttpBasicStream() {} // HttpStream methods: - virtual int Read(IOBuffer* buf, - int buf_len, - CompletionCallback* callback) { - return handle_->socket()->Read(buf, buf_len, callback); - } - - virtual int Write(IOBuffer* buf, - int buf_len, - CompletionCallback* callback) { - return handle_->socket()->Write(buf, buf_len, callback); - } + virtual int SendRequest(const HttpRequestInfo* request, + const std::string& headers, + UploadDataStream* request_body, + CompletionCallback* callback); + + virtual uint64 GetUploadProgress() const; + + virtual int ReadResponseHeaders(CompletionCallback* callback); + + virtual HttpResponseInfo* GetResponseInfo() const; + + virtual int ReadResponseBody(IOBuffer* buf, int buf_len, + CompletionCallback* callback); + + virtual bool IsResponseBodyComplete() const; + + virtual bool CanFindEndOfResponse() const; + + virtual bool IsMoreDataBuffered() const; private: - ClientSocketHandle* const handle_; + scoped_refptr<GrowableIOBuffer> read_buf_; + + scoped_ptr<HttpStreamParser> parser_; DISALLOW_COPY_AND_ASSIGN(HttpBasicStream); }; diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc index 6324d7d..9e8b98a 100644 --- a/net/http/http_network_transaction.cc +++ b/net/http/http_network_transaction.cc @@ -25,6 +25,7 @@ #include "net/http/http_network_session.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_util.h" #include "net/socket/client_socket_factory.h" #include "net/socket/socks5_client_socket.h" @@ -35,10 +36,6 @@ using base::Time; namespace net { -void HttpNetworkTransaction::ResponseHeaders::Realloc(size_t new_size) { - headers_.reset(static_cast<char*>(realloc(headers_.release(), new_size))); -} - namespace { void BuildRequestHeaders(const HttpRequestInfo* request_info, @@ -138,20 +135,12 @@ HttpNetworkTransaction::HttpNetworkTransaction(HttpNetworkSession* session) request_(NULL), pac_request_(NULL), reused_socket_(false), + headers_valid_(false), + logged_response_time(false), using_ssl_(false), proxy_mode_(kDirectConnection), establishing_tunnel_(false), - reading_body_from_socket_(false), embedded_identity_used_(false), - request_headers_(new RequestHeaders()), - request_headers_bytes_sent_(0), - header_buf_(new ResponseHeaders()), - header_buf_capacity_(0), - header_buf_len_(0), - header_buf_body_offset_(-1), - header_buf_http_offset_(-1), - response_body_length_(-1), // -1 means unspecified. - response_body_read_(0), read_buf_len_(0), next_state_(STATE_NONE) { session->ssl_config_service()->GetSSLConfig(&ssl_config_); @@ -176,7 +165,7 @@ int HttpNetworkTransaction::Start(const HttpRequestInfo* request_info, int HttpNetworkTransaction::RestartIgnoringLastError( CompletionCallback* callback) { if (connection_.socket()->IsConnected()) { - next_state_ = STATE_WRITE_HEADERS; + next_state_ = STATE_SEND_REQUEST; } else { connection_.socket()->Disconnect(); connection_.Reset(); @@ -266,19 +255,19 @@ void HttpNetworkTransaction::PrepareForAuthRestart(HttpAuth::Target target) { } bool keep_alive = false; - if (response_.headers->IsKeepAlive()) { - // If there is a response body of known length, we need to drain it first. - if (response_body_length_ > 0 || chunked_decoder_.get()) { + // 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() && + http_stream_->CanFindEndOfResponse()) { + // If the response body hasn't been completely read, we need to drain + // it first. + if (!http_stream_->IsResponseBodyComplete()) { next_state_ = STATE_DRAIN_BODY_FOR_AUTH_RESTART; - read_buf_ = new IOBuffer(kDrainBodyBufferSize); // A bit bucket + read_buf_ = new IOBuffer(kDrainBodyBufferSize); // A bit bucket. read_buf_len_ = kDrainBodyBufferSize; return; } - if (response_body_length_ == 0) // No response body to drain. - keep_alive = true; - // response_body_length_ is -1 and we're not using chunked encoding. We - // don't know the length of the response body, so we can't reuse this - // connection even though the server says it's keep-alive. + keep_alive = true; } // We don't need to drain the response body, so we act as if we had drained @@ -288,7 +277,7 @@ void HttpNetworkTransaction::PrepareForAuthRestart(HttpAuth::Target target) { void HttpNetworkTransaction::DidDrainBodyForAuthRestart(bool keep_alive) { if (keep_alive) { - next_state_ = STATE_WRITE_HEADERS; + next_state_ = STATE_SEND_REQUEST; reused_socket_ = true; } else { next_state_ = STATE_INIT_CONNECTION; @@ -302,7 +291,8 @@ void HttpNetworkTransaction::DidDrainBodyForAuthRestart(bool keep_alive) { int HttpNetworkTransaction::Read(IOBuffer* buf, int buf_len, CompletionCallback* callback) { - DCHECK(response_.headers); + scoped_refptr<HttpResponseHeaders> headers = GetResponseHeaders(); + DCHECK(headers.get()); DCHECK(buf); DCHECK_LT(0, buf_len); @@ -317,8 +307,8 @@ int HttpNetworkTransaction::Read(IOBuffer* buf, int buf_len, // network attacker can already control HTTP sessions. // We reach this case when the user cancels a 407 proxy auth prompt. // See http://crbug.com/8473 - DCHECK_EQ(407, response_.headers->response_code()); - LogBlockedTunnelResponse(response_.headers->response_code()); + DCHECK_EQ(407, headers->response_code()); + LogBlockedTunnelResponse(headers->response_code()); return ERR_TUNNEL_CONNECTION_FAILED; } @@ -338,8 +328,9 @@ int HttpNetworkTransaction::Read(IOBuffer* buf, int buf_len, } const HttpResponseInfo* HttpNetworkTransaction::GetResponseInfo() const { - return (response_.headers || response_.ssl_info.cert || - response_.cert_request_info) ? &response_ : NULL; + const HttpResponseInfo* response = http_stream_->GetResponseInfo(); + return ((headers_valid_ && response->headers) || response->ssl_info.cert || + response->cert_request_info) ? response : NULL; } LoadState HttpNetworkTransaction::GetLoadState() const { @@ -350,8 +341,7 @@ LoadState HttpNetworkTransaction::GetLoadState() const { return LOAD_STATE_RESOLVING_PROXY_FOR_URL; case STATE_INIT_CONNECTION_COMPLETE: return connection_.GetLoadState(); - case STATE_WRITE_HEADERS_COMPLETE: - case STATE_WRITE_BODY_COMPLETE: + case STATE_SEND_REQUEST_COMPLETE: return LOAD_STATE_SENDING_REQUEST; case STATE_READ_HEADERS_COMPLETE: return LOAD_STATE_WAITING_FOR_RESPONSE; @@ -363,10 +353,10 @@ LoadState HttpNetworkTransaction::GetLoadState() const { } uint64 HttpNetworkTransaction::GetUploadProgress() const { - if (!request_body_stream_.get()) + if (!http_stream_.get()) return 0; - return request_body_stream_->position(); + return http_stream_->GetUploadProgress(); } HttpNetworkTransaction::~HttpNetworkTransaction() { @@ -439,23 +429,14 @@ int HttpNetworkTransaction::DoLoop(int result) { rv = DoSSLConnectComplete(rv); TRACE_EVENT_END("http.ssl_connect", request_, request_->url.spec()); break; - case STATE_WRITE_HEADERS: + case STATE_SEND_REQUEST: DCHECK_EQ(OK, rv); - TRACE_EVENT_BEGIN("http.write_headers", request_, request_->url.spec()); - rv = DoWriteHeaders(); + TRACE_EVENT_BEGIN("http.send_request", request_, request_->url.spec()); + rv = DoSendRequest(); break; - case STATE_WRITE_HEADERS_COMPLETE: - rv = DoWriteHeadersComplete(rv); - TRACE_EVENT_END("http.write_headers", request_, request_->url.spec()); - break; - case STATE_WRITE_BODY: - DCHECK_EQ(OK, rv); - TRACE_EVENT_BEGIN("http.write_body", request_, request_->url.spec()); - rv = DoWriteBody(); - break; - case STATE_WRITE_BODY_COMPLETE: - rv = DoWriteBodyComplete(rv); - TRACE_EVENT_END("http.write_body", request_, request_->url.spec()); + case STATE_SEND_REQUEST_COMPLETE: + rv = DoSendRequestComplete(rv); + TRACE_EVENT_END("http.send_request", request_, request_->url.spec()); break; case STATE_READ_HEADERS: DCHECK_EQ(OK, rv); @@ -611,7 +592,7 @@ int HttpNetworkTransaction::DoInitConnectionComplete(int result) { // trying to reuse a keep-alive connection. reused_socket_ = connection_.is_reused(); if (reused_socket_) { - next_state_ = STATE_WRITE_HEADERS; + next_state_ = STATE_SEND_REQUEST; } else { // Now we have a TCP connected socket. Perform other connection setup as // needed. @@ -620,11 +601,12 @@ int HttpNetworkTransaction::DoInitConnectionComplete(int result) { else if (using_ssl_ && proxy_mode_ == kDirectConnection) { next_state_ = STATE_SSL_CONNECT; } else { - next_state_ = STATE_WRITE_HEADERS; + next_state_ = STATE_SEND_REQUEST; if (proxy_mode_ == kHTTPProxyUsingTunnel) establishing_tunnel_ = true; } } + headers_valid_ = false; http_stream_.reset(new HttpBasicStream(&connection_)); return OK; } @@ -655,7 +637,7 @@ int HttpNetworkTransaction::DoSOCKSConnectComplete(int result) { if (using_ssl_) { next_state_ = STATE_SSL_CONNECT; } else { - next_state_ = STATE_WRITE_HEADERS; + next_state_ = STATE_SEND_REQUEST; } } else { result = ReconsiderProxyAfterError(result); @@ -694,7 +676,7 @@ int HttpNetworkTransaction::DoSSLConnectComplete(int result) { base::TimeDelta::FromMinutes(10), 100); - next_state_ = STATE_WRITE_HEADERS; + next_state_ = STATE_SEND_REQUEST; } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) { result = HandleCertificateRequest(result); } else { @@ -703,12 +685,16 @@ int HttpNetworkTransaction::DoSSLConnectComplete(int result) { return result; } -int HttpNetworkTransaction::DoWriteHeaders() { - next_state_ = STATE_WRITE_HEADERS_COMPLETE; +int HttpNetworkTransaction::DoSendRequest() { + next_state_ = STATE_SEND_REQUEST_COMPLETE; + + UploadDataStream* request_body = NULL; + if (!establishing_tunnel_ && request_->upload_data) + request_body = new UploadDataStream(request_->upload_data); // This is constructed lazily (instead of within our Start method), so that // we have proxy info available. - if (request_headers_->headers_.empty()) { + if (request_headers_.empty()) { // Figure out if we can/should add Proxy-Authentication & Authentication // headers. bool have_proxy_auth = @@ -733,91 +719,30 @@ int HttpNetworkTransaction::DoWriteHeaders() { BuildAuthorizationHeader(HttpAuth::AUTH_SERVER)); if (establishing_tunnel_) { - BuildTunnelRequest(request_, authorization_headers, - &request_headers_->headers_); + BuildTunnelRequest(request_, authorization_headers, &request_headers_); } else { - if (request_->upload_data) - request_body_stream_.reset(new UploadDataStream(request_->upload_data)); - BuildRequestHeaders(request_, authorization_headers, - request_body_stream_.get(), - proxy_mode_ == kHTTPProxy, - &request_headers_->headers_); + BuildRequestHeaders(request_, authorization_headers, request_body, + proxy_mode_ == kHTTPProxy, &request_headers_); } } - // Record our best estimate of the 'request time' as the time when we send - // out the first bytes of the request headers. - if (request_headers_bytes_sent_ == 0) { - response_.request_time = Time::Now(); - } - - request_headers_->SetDataOffset(request_headers_bytes_sent_); - int buf_len = static_cast<int>(request_headers_->headers_.size() - - request_headers_bytes_sent_); - DCHECK_GT(buf_len, 0); - - return http_stream_->Write(request_headers_, buf_len, &io_callback_); + return http_stream_->SendRequest(request_, request_headers_, request_body, + &io_callback_); } -int HttpNetworkTransaction::DoWriteHeadersComplete(int result) { +int HttpNetworkTransaction::DoSendRequestComplete(int result) { if (result < 0) return HandleIOError(result); - request_headers_bytes_sent_ += result; - if (request_headers_bytes_sent_ < request_headers_->headers_.size()) { - next_state_ = STATE_WRITE_HEADERS; - } else if (!establishing_tunnel_ && request_body_stream_.get() && - request_body_stream_->size()) { - next_state_ = STATE_WRITE_BODY; - } else { - next_state_ = STATE_READ_HEADERS; - } - return OK; -} - -int HttpNetworkTransaction::DoWriteBody() { - next_state_ = STATE_WRITE_BODY_COMPLETE; - - DCHECK(request_body_stream_.get()); - DCHECK(request_body_stream_->size()); - - int buf_len = static_cast<int>(request_body_stream_->buf_len()); - - return http_stream_->Write(request_body_stream_->buf(), buf_len, - &io_callback_); -} + next_state_ = STATE_READ_HEADERS; -int HttpNetworkTransaction::DoWriteBodyComplete(int result) { - if (result < 0) - return HandleIOError(result); - - request_body_stream_->DidConsume(result); - - if (request_body_stream_->position() < request_body_stream_->size()) { - next_state_ = STATE_WRITE_BODY; - } else { - next_state_ = STATE_READ_HEADERS; - } return OK; } int HttpNetworkTransaction::DoReadHeaders() { next_state_ = STATE_READ_HEADERS_COMPLETE; - // Grow the read buffer if necessary. - if (header_buf_len_ == header_buf_capacity_) { - header_buf_capacity_ += kHeaderBufInitialSize; - header_buf_->Realloc(header_buf_capacity_); - } - - int buf_len = header_buf_capacity_ - header_buf_len_; - header_buf_->set_data(header_buf_len_); - - // http://crbug.com/16371: We're seeing |user_buf_->data()| return NULL. - // See if the user is passing in an IOBuffer with a NULL |data_|. - CHECK(header_buf_->data()); - - return http_stream_->Read(header_buf_, buf_len, &io_callback_); + return http_stream_->ReadResponseHeaders(&io_callback_); } int HttpNetworkTransaction::HandleConnectionClosedBeforeEndOfHeaders() { @@ -826,24 +751,12 @@ int HttpNetworkTransaction::HandleConnectionClosedBeforeEndOfHeaders() { return ERR_TUNNEL_CONNECTION_FAILED; } - if (has_found_status_line_start()) { - // Assume EOF is end-of-headers. - header_buf_body_offset_ = header_buf_len_; - return OK; - } - - // No status line was matched yet. Could have been a HTTP/0.9 response, or - // a partial HTTP/1.x response. - - if (header_buf_len_ == 0) { + if (!http_stream_->GetResponseInfo()->headers) { // The connection was closed before any data was sent. Likely an error // rather than empty HTTP/0.9 response. return ERR_EMPTY_RESPONSE; } - // Assume everything else is a HTTP/0.9 response (including responses - // of 'h', 'ht', 'htt'). - header_buf_body_offset_ = 0; return OK; } @@ -865,99 +778,115 @@ int HttpNetworkTransaction::DoReadHeadersComplete(int result) { } } - if (result < 0) + if (result < 0 && result != ERR_CONNECTION_CLOSED) return HandleIOError(result); - if (result == 0 && ShouldResendRequest(result)) { + if (result == ERR_CONNECTION_CLOSED && ShouldResendRequest(result)) { ResetConnectionAndRequestForResend(); - return result; + return OK; } - // Record our best estimate of the 'response time' as the time when we read - // the first bytes of the response headers. - if (header_buf_len_ == 0) { - // After we call RestartWithAuth header_buf_len will be zero again, and - // we need to be cautious about incorrectly logging the duration across the - // authentication activitiy. - bool first_response = response_.response_time == Time(); - response_.response_time = Time::Now(); - if (first_response) - LogTransactionConnectedMetrics(); + // 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. + HttpResponseInfo* response = http_stream_->GetResponseInfo(); + if (!logged_response_time) { + LogTransactionConnectedMetrics(); + logged_response_time = true; } - // The socket was closed before we found end-of-headers. - if (result == 0) { + if (result == ERR_CONNECTION_CLOSED) { int rv = HandleConnectionClosedBeforeEndOfHeaders(); if (rv != OK) return rv; - } else { - header_buf_len_ += result; - DCHECK(header_buf_len_ <= header_buf_capacity_); + // TODO(wtc): Traditionally this code has returned 0 when reading a closed + // socket. That is partially corrected in classes that we call, but + // callers need to be updated. + result = 0; + } - // Look for the start of the status line, if it hasn't been found yet. - if (!has_found_status_line_start()) { - header_buf_http_offset_ = HttpUtil::LocateStartOfStatusLine( - header_buf_->headers(), header_buf_len_); - } + if (response->headers->GetParsedHttpVersion() < HttpVersion(1, 0)) { + // Require the "HTTP/1.x" status line for SSL CONNECT. + if (establishing_tunnel_) + return ERR_TUNNEL_CONNECTION_FAILED; - if (has_found_status_line_start()) { - int eoh = HttpUtil::LocateEndOfHeaders( - header_buf_->headers(), header_buf_len_, header_buf_http_offset_); - if (eoh == -1) { - // Prevent growing the headers buffer indefinitely. - if (header_buf_len_ >= kMaxHeaderBufSize) - return ERR_RESPONSE_HEADERS_TOO_BIG; + // 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; + } - // Haven't found the end of headers yet, keep reading. - next_state_ = STATE_READ_HEADERS; + if (establishing_tunnel_) { + switch (response->headers->response_code()) { + case 200: // OK + if (http_stream_->IsMoreDataBuffered()) { + // The proxy sent extraneous data after the headers. + return ERR_TUNNEL_CONNECTION_FAILED; + } + next_state_ = STATE_SSL_CONNECT; + // Reset for the real request and response headers. + request_headers_.clear(); + http_stream_.reset(new HttpBasicStream(&connection_)); + headers_valid_ = false; + establishing_tunnel_ = false; return OK; - } - header_buf_body_offset_ = eoh; - } else if (header_buf_len_ < 8) { - // Not enough data to decide whether this is HTTP/0.9 yet. - // 8 bytes = (4 bytes of junk) + "http".length() - next_state_ = STATE_READ_HEADERS; - return OK; - } else { - // Enough data was read -- there is no status line. - header_buf_body_offset_ = 0; + + // We aren't able to CONNECT to the remote host through the proxy. We + // need to be very suspicious about the response because an active network + // attacker can force us into this state by masquerading as the proxy. + // The only safe thing to do here is to fail the connection because our + // client is expecting an SSL protected response. + // See http://crbug.com/7338. + case 407: // Proxy Authentication Required + // We need this status code to allow proxy authentication. Our + // authentication code is smart enough to avoid being tricked by an + // active network attacker. + break; + default: + // For all other status codes, we conservatively fail the CONNECT + // request. + // We lose something by doing this. We have seen proxy 403, 404, and + // 501 response bodies that contain a useful error message. For + // example, Squid uses a 404 response to report the DNS error: "The + // domain name does not exist." + LogBlockedTunnelResponse(response->headers->response_code()); + return ERR_TUNNEL_CONNECTION_FAILED; } } - // And, we are done with the Start or the SSL tunnel CONNECT sequence. - return DidReadResponseHeaders(); + // 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) { + next_state_ = STATE_READ_HEADERS; + return OK; + } + + int rv = HandleAuthChallenge(); + if (rv != OK) + return rv; + + if (using_ssl_ && !establishing_tunnel_) { + SSLClientSocket* ssl_socket = + reinterpret_cast<SSLClientSocket*>(connection_.socket()); + ssl_socket->GetSSLInfo(&response->ssl_info); + } + + headers_valid_ = true; + return OK; } int HttpNetworkTransaction::DoReadBody() { DCHECK(read_buf_); DCHECK_GT(read_buf_len_, 0); DCHECK(connection_.is_initialized()); - DCHECK(!header_buf_->headers() || header_buf_body_offset_ >= 0); next_state_ = STATE_READ_BODY_COMPLETE; - - // We may have already consumed the indicated content length. - if (response_body_length_ != -1 && - response_body_read_ >= response_body_length_) - return 0; - - // We may have some data remaining in the header buffer. - if (header_buf_->headers() && header_buf_body_offset_ < header_buf_len_) { - int n = std::min(read_buf_len_, header_buf_len_ - header_buf_body_offset_); - memcpy(read_buf_->data(), header_buf_->headers() + header_buf_body_offset_, - n); - header_buf_body_offset_ += n; - if (header_buf_body_offset_ == header_buf_len_) { - header_buf_->Reset(); - header_buf_capacity_ = 0; - header_buf_len_ = 0; - header_buf_body_offset_ = -1; - } - return n; - } - - reading_body_from_socket_ = true; - return http_stream_->Read(read_buf_, read_buf_len_, &io_callback_); + return http_stream_->ReadResponseBody(read_buf_, read_buf_len_, + &io_callback_); } int HttpNetworkTransaction::DoReadBodyComplete(int result) { @@ -965,39 +894,18 @@ int HttpNetworkTransaction::DoReadBodyComplete(int result) { DCHECK(!establishing_tunnel_) << "We should never read a response body of a tunnel."; - bool unfiltered_eof = (result == 0 && reading_body_from_socket_); - reading_body_from_socket_ = false; - - // Filter incoming data if appropriate. FilterBuf may return an error. - if (result > 0 && chunked_decoder_.get()) { - result = chunked_decoder_->FilterBuf(read_buf_->data(), result); - if (result == 0 && !chunked_decoder_->reached_eof()) { - // Don't signal completion of the Read call yet or else it'll look like - // we received end-of-file. Wait for more data. - next_state_ = STATE_READ_BODY; - return OK; - } - } - bool done = false, keep_alive = false; if (result < 0) { - // Error while reading the socket. + // Error or closed connection while reading the socket. done = true; - } else { - response_body_read_ += result; - if (unfiltered_eof || - (response_body_length_ != -1 && - response_body_read_ >= response_body_length_) || - (chunked_decoder_.get() && chunked_decoder_->reached_eof())) { - done = true; - keep_alive = response_.headers->IsKeepAlive(); - // We can't reuse the connection if we read more than the advertised - // content length. - if (unfiltered_eof || - (response_body_length_ != -1 && - response_body_read_ > response_body_length_)) - keep_alive = false; - } + // TODO(wtc): Traditionally this code has returned 0 when reading a closed + // socket. That is partially corrected in classes that we call, but + // callers need to be updated. + if (result == ERR_CONNECTION_CLOSED) + result = 0; + } else if (http_stream_->IsResponseBodyComplete()) { + done = true; + keep_alive = GetResponseHeaders()->IsKeepAlive(); } // Clean up connection_ if we are done. @@ -1026,45 +934,18 @@ int HttpNetworkTransaction::DoDrainBodyForAuthRestart() { return rv; } -// TODO(wtc): The first two thirds of this method and the DoReadBodyComplete -// method are almost the same. Figure out a good way for these two methods -// to share code. +// 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) { - bool unfiltered_eof = (result == 0 && reading_body_from_socket_); - reading_body_from_socket_ = false; - - // Filter incoming data if appropriate. FilterBuf may return an error. - if (result > 0 && chunked_decoder_.get()) { - result = chunked_decoder_->FilterBuf(read_buf_->data(), result); - if (result == 0 && !chunked_decoder_->reached_eof()) { - // Don't signal completion of the Read call yet or else it'll look like - // we received end-of-file. Wait for more data. - next_state_ = STATE_DRAIN_BODY_FOR_AUTH_RESTART; - return OK; - } - } - // 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 while reading the socket. + // Error or closed connection while reading the socket. done = true; keep_alive = false; - } else { - response_body_read_ += result; - if (unfiltered_eof || - (response_body_length_ != -1 && - response_body_read_ >= response_body_length_) || - (chunked_decoder_.get() && chunked_decoder_->reached_eof())) { - done = true; - // We can't reuse the connection if we read more than the advertised - // content length. - if (unfiltered_eof || - (response_body_length_ != -1 && - response_body_read_ > response_body_length_)) - keep_alive = false; - } + } else if (http_stream_->IsResponseBodyComplete()) { + done = true; } if (done) { @@ -1213,7 +1094,8 @@ void HttpNetworkTransaction::LogIOErrorMetrics( } void HttpNetworkTransaction::LogTransactionConnectedMetrics() const { - base::TimeDelta total_duration = response_.response_time - start_time_; + base::TimeDelta total_duration = + http_stream_->GetResponseInfo()->response_time - start_time_; UMA_HISTOGRAM_CLIPPED_TIMES( "Net.Transaction_Connected_Under_10", @@ -1272,7 +1154,8 @@ void HttpNetworkTransaction::LogTransactionConnectedMetrics() const { } void HttpNetworkTransaction::LogTransactionMetrics() const { - base::TimeDelta duration = base::Time::Now() - response_.request_time; + base::TimeDelta duration = base::Time::Now() - + http_stream_->GetResponseInfo()->request_time; if (60 < duration.InMinutes()) return; @@ -1300,143 +1183,6 @@ void HttpNetworkTransaction::LogBlockedTunnelResponse( << GetHostAndPort(request_->url) << "."; } -int HttpNetworkTransaction::DidReadResponseHeaders() { - DCHECK_GE(header_buf_body_offset_, 0); - - scoped_refptr<HttpResponseHeaders> headers; - if (has_found_status_line_start()) { - headers = new HttpResponseHeaders( - HttpUtil::AssembleRawHeaders( - header_buf_->headers(), header_buf_body_offset_)); - } else { - // Fabricate a status line to to preserve the HTTP/0.9 version. - // (otherwise HttpResponseHeaders will default it to HTTP/1.0). - headers = new HttpResponseHeaders(std::string("HTTP/0.9 200 OK")); - } - - if (headers->GetParsedHttpVersion() < HttpVersion(1, 0)) { - // Require the "HTTP/1.x" status line for SSL CONNECT. - if (establishing_tunnel_) - return ERR_TUNNEL_CONNECTION_FAILED; - - // 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; - } - - if (establishing_tunnel_) { - switch (headers->response_code()) { - case 200: // OK - if (header_buf_body_offset_ != header_buf_len_) { - // The proxy sent extraneous data after the headers. - return ERR_TUNNEL_CONNECTION_FAILED; - } - next_state_ = STATE_SSL_CONNECT; - // Reset for the real request and response headers. - request_headers_->headers_.clear(); - request_headers_bytes_sent_ = 0; - header_buf_len_ = 0; - header_buf_body_offset_ = -1; - establishing_tunnel_ = false; - return OK; - - // We aren't able to CONNECT to the remote host through the proxy. We - // need to be very suspicious about the response because an active network - // attacker can force us into this state by masquerading as the proxy. - // The only safe thing to do here is to fail the connection because our - // client is expecting an SSL protected response. - // See http://crbug.com/7338. - case 407: // Proxy Authentication Required - // We need this status code to allow proxy authentication. Our - // authentication code is smart enough to avoid being tricked by an - // active network attacker. - break; - default: - // For all other status codes, we conservatively fail the CONNECT - // request. - // We lose something by doing this. We have seen proxy 403, 404, and - // 501 response bodies that contain a useful error message. For - // example, Squid uses a 404 response to report the DNS error: "The - // domain name does not exist." - LogBlockedTunnelResponse(headers->response_code()); - return ERR_TUNNEL_CONNECTION_FAILED; - } - } - - // 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 (headers->response_code() / 100 == 1) { - header_buf_len_ -= header_buf_body_offset_; - // If we've already received some bytes after the 1xx response, - // move them to the beginning of header_buf_. - if (header_buf_len_) { - memmove(header_buf_->headers(), - header_buf_->headers() + header_buf_body_offset_, - header_buf_len_); - } - header_buf_body_offset_ = -1; - next_state_ = STATE_READ_HEADERS; - return OK; - } - - response_.headers = headers; - response_.vary_data.Init(*request_, *response_.headers); - - // Figure how to determine EOF: - - // For certain responses, we know the content length is always 0. From - // RFC 2616 Section 4.3 Message Body: - // - // For response messages, whether or not a message-body is included with - // a message is dependent on both the request method and the response - // status code (section 6.1.1). All responses to the HEAD request method - // MUST NOT include a message-body, even though the presence of entity- - // header fields might lead one to believe they do. All 1xx - // (informational), 204 (no content), and 304 (not modified) responses - // MUST NOT include a message-body. All other responses do include a - // message-body, although it MAY be of zero length. - switch (response_.headers->response_code()) { - // Note that 1xx was already handled earlier. - case 204: // No Content - case 205: // Reset Content - case 304: // Not Modified - response_body_length_ = 0; - break; - } - if (request_->method == "HEAD") - response_body_length_ = 0; - - if (response_body_length_ == -1) { - // Ignore spurious chunked responses from HTTP/1.0 servers and proxies. - // Otherwise "Transfer-Encoding: chunked" trumps "Content-Length: N" - if (response_.headers->GetHttpVersion() >= HttpVersion(1, 1) && - response_.headers->HasHeaderValue("Transfer-Encoding", "chunked")) { - chunked_decoder_.reset(new HttpChunkedDecoder()); - } else { - response_body_length_ = response_.headers->GetContentLength(); - // If response_body_length_ is still -1, then we have to wait for the - // server to close the connection. - } - } - - int rv = HandleAuthChallenge(); - if (rv != OK) - return rv; - - if (using_ssl_ && !establishing_tunnel_) { - SSLClientSocket* ssl_socket = - reinterpret_cast<SSLClientSocket*>(connection_.socket()); - ssl_socket->GetSSLInfo(&response_.ssl_info); - } - - return OK; -} - int HttpNetworkTransaction::HandleCertificateError(int error) { DCHECK(using_ssl_); @@ -1462,17 +1208,18 @@ int HttpNetworkTransaction::HandleCertificateError(int error) { } if (error != OK) { + HttpResponseInfo* response = http_stream_->GetResponseInfo(); SSLClientSocket* ssl_socket = reinterpret_cast<SSLClientSocket*>(connection_.socket()); - ssl_socket->GetSSLInfo(&response_.ssl_info); + ssl_socket->GetSSLInfo(&response->ssl_info); // Add the bad certificate to the set of allowed certificates in the // SSL info object. This data structure will be consulted after calling // RestartIgnoringLastError(). And the user will be asked interactively // before RestartIgnoringLastError() is ever called. SSLConfig::CertAndStatus bad_cert; - bad_cert.cert = response_.ssl_info.cert; - bad_cert.cert_status = response_.ssl_info.cert_status; + bad_cert.cert = response->ssl_info.cert; + bad_cert.cert_status = response->ssl_info.cert_status; ssl_config_.allowed_bad_certs.push_back(bad_cert); } return error; @@ -1489,10 +1236,11 @@ int HttpNetworkTransaction::HandleCertificateRequest(int error) { // test. DCHECK(reused_socket_ || !ssl_config_.send_client_cert); - response_.cert_request_info = new SSLCertRequestInfo; + HttpResponseInfo* response = http_stream_->GetResponseInfo(); + response->cert_request_info = new SSLCertRequestInfo; SSLClientSocket* ssl_socket = reinterpret_cast<SSLClientSocket*>(connection_.socket()); - ssl_socket->GetSSLCertRequestInfo(response_.cert_request_info); + ssl_socket->GetSSLCertRequestInfo(response->cert_request_info); // Close the connection while the user is selecting a certificate to send // to the server. @@ -1505,7 +1253,7 @@ int HttpNetworkTransaction::HandleCertificateRequest(int error) { Lookup(GetHostAndPort(request_->url)); if (client_cert) { const std::vector<scoped_refptr<X509Certificate> >& client_certs = - response_.cert_request_info->client_certs; + response->cert_request_info->client_certs; for (size_t i = 0; i < client_certs.size(); ++i) { if (client_cert->fingerprint().Equals(client_certs[i]->fingerprint())) { ssl_config_.client_cert = client_cert; @@ -1570,20 +1318,16 @@ int HttpNetworkTransaction::HandleIOError(int error) { void HttpNetworkTransaction::ResetStateForRestart() { pending_auth_target_ = HttpAuth::AUTH_NONE; - header_buf_->Reset(); - header_buf_capacity_ = 0; - header_buf_len_ = 0; - header_buf_body_offset_ = -1; - header_buf_http_offset_ = -1; - response_body_length_ = -1; - response_body_read_ = 0; read_buf_ = NULL; read_buf_len_ = 0; - request_headers_->headers_.clear(); - request_headers_bytes_sent_ = 0; - chunked_decoder_.reset(); - // Reset all the members of response_. - response_ = HttpResponseInfo(); + http_stream_.reset(new HttpBasicStream(&connection_)); + headers_valid_ = false; + request_headers_.clear(); +} + +HttpResponseHeaders* HttpNetworkTransaction::GetResponseHeaders() const { + CHECK(http_stream_.get()); + return http_stream_->GetResponseInfo()->headers; } bool HttpNetworkTransaction::ShouldResendRequest(int error) const { @@ -1591,12 +1335,8 @@ bool HttpNetworkTransaction::ShouldResendRequest(int error) const { // This automatically prevents an infinite resend loop because we'll run // out of the cached keep-alive connections eventually. if (establishing_tunnel_ || - // We used a socket that was never idle. - connection_.reuse_type() == ClientSocketHandle::UNUSED || - // We used an unused, idle socket and got a error that wasn't a TCP RST. - (connection_.reuse_type() == ClientSocketHandle::UNUSED_IDLE && - (error != OK && error != ERR_CONNECTION_RESET)) || - header_buf_len_) { // We have received some response headers. + !connection_.ShouldResendFailedRequest(error) || + GetResponseHeaders()) { // We have received some response headers. return false; } return true; @@ -1605,13 +1345,10 @@ bool HttpNetworkTransaction::ShouldResendRequest(int error) const { void HttpNetworkTransaction::ResetConnectionAndRequestForResend() { connection_.socket()->Disconnect(); connection_.Reset(); - // There are two reasons we need to clear request_headers_. 1) It contains - // the real request headers, but we may need to resend the CONNECT request - // first to recreate the SSL tunnel. 2) An empty request_headers_ causes - // BuildRequestHeaders to be called, which rewinds request_body_stream_ to - // the beginning of request_->upload_data. - request_headers_->headers_.clear(); - request_headers_bytes_sent_ = 0; + // 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_INIT_CONNECTION; // Resend the request. } @@ -1653,7 +1390,6 @@ int HttpNetworkTransaction::ReconsiderProxyAfterError(int error) { if (connection_.socket()) connection_.socket()->Disconnect(); connection_.Reset(); - DCHECK(!request_headers_bytes_sent_); next_state_ = STATE_RESOLVE_PROXY_COMPLETE; } else { rv = error; @@ -1813,15 +1549,14 @@ std::string HttpNetworkTransaction::AuthChallengeLogMessage() const { std::string msg; std::string header_val; void* iter = NULL; - while (response_.headers->EnumerateHeader(&iter, "proxy-authenticate", - &header_val)) { + scoped_refptr<HttpResponseHeaders> headers = GetResponseHeaders(); + while (headers->EnumerateHeader(&iter, "proxy-authenticate", &header_val)) { msg.append("\n Has header Proxy-Authenticate: "); msg.append(header_val); } iter = NULL; - while (response_.headers->EnumerateHeader(&iter, "www-authenticate", - &header_val)) { + while (headers->EnumerateHeader(&iter, "www-authenticate", &header_val)) { msg.append("\n Has header WWW-Authenticate: "); msg.append(header_val); } @@ -1830,8 +1565,7 @@ std::string HttpNetworkTransaction::AuthChallengeLogMessage() const { // authentication with a "Proxy-Support: Session-Based-Authentication" // response header. iter = NULL; - while (response_.headers->EnumerateHeader(&iter, "proxy-support", - &header_val)) { + while (headers->EnumerateHeader(&iter, "proxy-support", &header_val)) { msg.append("\n Has header Proxy-Support: "); msg.append(header_val); } @@ -1840,9 +1574,10 @@ std::string HttpNetworkTransaction::AuthChallengeLogMessage() const { } int HttpNetworkTransaction::HandleAuthChallenge() { - DCHECK(response_.headers); + scoped_refptr<HttpResponseHeaders> headers = GetResponseHeaders(); + DCHECK(headers); - int status = response_.headers->response_code(); + int status = headers->response_code(); if (status != 401 && status != 407) return OK; HttpAuth::Target target = status == 407 ? @@ -1874,9 +1609,7 @@ int HttpNetworkTransaction::HandleAuthChallenge() { if (target != HttpAuth::AUTH_SERVER || !(request_->load_flags & LOAD_DO_NOT_SEND_AUTH_DATA)) { // Find the best authentication challenge that we support. - HttpAuth::ChooseBestChallenge(response_.headers.get(), - target, - auth_origin, + HttpAuth::ChooseBestChallenge(headers, target, auth_origin, &auth_handler_[target]); } @@ -1932,7 +1665,7 @@ void HttpNetworkTransaction::PopulateAuthChallenge(HttpAuth::Target target, auth_info->scheme = ASCIIToWide(auth_handler_[target]->scheme()); // TODO(eroman): decode realm according to RFC 2047. auth_info->realm = ASCIIToWide(auth_handler_[target]->realm()); - response_.auth_challenge = auth_info; + http_stream_->GetResponseInfo()->auth_challenge = auth_info; } } // namespace net diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h index 22acf492..f540bd9 100644 --- a/net/http/http_network_transaction.h +++ b/net/http/http_network_transaction.h @@ -28,10 +28,8 @@ namespace net { class ClientSocketFactory; -class HttpChunkedDecoder; class HttpNetworkSession; class HttpStream; -class UploadDataStream; class HttpNetworkTransaction : public HttpTransaction { public: @@ -62,38 +60,6 @@ class HttpNetworkTransaction : public HttpTransaction { private: FRIEND_TEST(HttpNetworkTransactionTest, ResetStateForRestart); - // This version of IOBuffer lets us use a string as the real storage and - // "move" the data pointer inside the string before using it to do actual IO. - class RequestHeaders : public net::IOBuffer { - public: - RequestHeaders() : net::IOBuffer() {} - ~RequestHeaders() { data_ = NULL; } - - void SetDataOffset(size_t offset) { - data_ = const_cast<char*>(headers_.data()) + offset; - } - - // This is intentionally a public member. - std::string headers_; - }; - - // This version of IOBuffer lets us use a malloc'ed buffer as the real storage - // and "move" the data pointer inside the buffer before using it to do actual - // IO. - class ResponseHeaders : public net::IOBuffer { - public: - ResponseHeaders() : net::IOBuffer() {} - ~ResponseHeaders() { data_ = NULL; } - - void set_data(size_t offset) { data_ = headers_.get() + offset; } - char* headers() { return headers_.get(); } - void Reset() { headers_.reset(); } - void Realloc(size_t new_size); - - private: - scoped_ptr_malloc<char> headers_; - }; - enum State { STATE_RESOLVE_PROXY, STATE_RESOLVE_PROXY_COMPLETE, @@ -103,10 +69,8 @@ class HttpNetworkTransaction : public HttpTransaction { STATE_SOCKS_CONNECT_COMPLETE, STATE_SSL_CONNECT, STATE_SSL_CONNECT_COMPLETE, - STATE_WRITE_HEADERS, - STATE_WRITE_HEADERS_COMPLETE, - STATE_WRITE_BODY, - STATE_WRITE_BODY_COMPLETE, + STATE_SEND_REQUEST, + STATE_SEND_REQUEST_COMPLETE, STATE_READ_HEADERS, STATE_READ_HEADERS_COMPLETE, STATE_READ_BODY, @@ -141,10 +105,8 @@ class HttpNetworkTransaction : public HttpTransaction { int DoSOCKSConnectComplete(int result); int DoSSLConnect(); int DoSSLConnectComplete(int result); - int DoWriteHeaders(); - int DoWriteHeadersComplete(int result); - int DoWriteBody(); - int DoWriteBodyComplete(int result); + int DoSendRequest(); + int DoSendRequestComplete(int result); int DoReadHeaders(); int DoReadHeadersComplete(int result); int DoReadBody(); @@ -167,9 +129,6 @@ class HttpNetworkTransaction : public HttpTransaction { static void LogIOErrorMetrics(const ClientSocketHandle& handle); - // Called when header_buf_ contains the complete response headers. - int DidReadResponseHeaders(); - // Called to handle a certificate error. Returns OK if the error should be // ignored. Otherwise, stores the certificate in response_.ssl_info and // returns the same error code. @@ -188,6 +147,9 @@ class HttpNetworkTransaction : public HttpTransaction { // is returned. int HandleIOError(int error); + // Gets the response headers from the HttpStream. + HttpResponseHeaders* GetResponseHeaders() const; + // Called when we reached EOF or got an error. Returns true if we should // resend the request. |error| is OK when we reached EOF. bool ShouldResendRequest(int error) const; @@ -209,13 +171,6 @@ class HttpNetworkTransaction : public HttpTransaction { // requests. int HandleConnectionClosedBeforeEndOfHeaders(); - // Return true if based on the bytes read so far, the start of the - // status line is known. This is used to distingish between HTTP/0.9 - // responses (which have no status line) and HTTP/1.x responses. - bool has_found_status_line_start() const { - return header_buf_http_offset_ != -1; - } - // Sets up the state machine to restart the transaction with auth. void PrepareForAuthRestart(HttpAuth::Target target); @@ -309,7 +264,6 @@ class HttpNetworkTransaction : public HttpTransaction { scoped_refptr<LoadLog> load_log_; const HttpRequestInfo* request_; - HttpResponseInfo response_; ProxyService::PacRequest* pac_request_; ProxyInfo proxy_info_; @@ -318,33 +272,24 @@ class HttpNetworkTransaction : public HttpTransaction { scoped_ptr<HttpStream> http_stream_; bool reused_socket_; + // True if we've validated the headers that the stream parser has returned. + bool headers_valid_; + + // True if we've logged the time of the first response byte. Used to + // prevent logging across authentication activity where we see multiple + // responses. + bool logged_response_time; + bool using_ssl_; // True if handling a HTTPS request ProxyMode proxy_mode_; // True while establishing a tunnel. This allows the HTTP CONNECT - // request/response to reuse the STATE_WRITE_HEADERS, - // STATE_WRITE_HEADERS_COMPLETE, STATE_READ_HEADERS, and + // request/response to reuse the STATE_SEND_REQUEST, + // STATE_SEND_REQUEST_COMPLETE, STATE_READ_HEADERS, and // STATE_READ_HEADERS_COMPLETE states and allows us to tell them apart from // the real request/response of the transaction. bool establishing_tunnel_; - // Only used between the states - // STATE_READ_BODY/STATE_DRAIN_BODY_FOR_AUTH and - // STATE_READ_BODY_COMPLETE/STATE_DRAIN_BODY_FOR_AUTH_COMPLETE. - // - // Set to true when DoReadBody or DoDrainBodyForAuthRestart starts to read - // the response body from the socket, and set to false when the socket read - // call completes. DoReadBodyComplete and DoDrainBodyForAuthRestartComplete - // use this boolean to disambiguate a |result| of 0 between a connection - // closure (EOF) and reaching the end of the response body (no more data). - // - // TODO(wtc): this is similar to the |ignore_ok_result_| member of the - // SSLClientSocketWin class. We may want to add an internal error code, say - // ERR_EOF, to indicate a connection closure, so that 0 simply means 0 bytes - // or OK. Note that we already have an ERR_CONNECTION_CLOSED error code, - // but it isn't really being used. - bool reading_body_from_socket_; - // True if we've used the username/password embedded in the URL. This // makes sure we use the embedded identity only once for the transaction, // preventing an infinite auth restart loop. @@ -352,48 +297,13 @@ class HttpNetworkTransaction : public HttpTransaction { SSLConfig ssl_config_; - scoped_refptr<RequestHeaders> request_headers_; - size_t request_headers_bytes_sent_; - scoped_ptr<UploadDataStream> request_body_stream_; - - // The read buffer |header_buf_| may be larger than it is full. The - // 'capacity' indicates the allocation size of the buffer, and the 'len' - // indicates how much data is in the buffer already. The 'body offset' - // indicates the offset of the start of the response body within the read - // buffer. - scoped_refptr<ResponseHeaders> header_buf_; - int header_buf_capacity_; - int header_buf_len_; - int header_buf_body_offset_; - - // The number of bytes by which the header buffer is grown when it reaches - // capacity. - enum { kHeaderBufInitialSize = 4096 }; - - // |kMaxHeaderBufSize| is the number of bytes that the response headers can - // grow to. If the body start is not found within this range of the - // response, the transaction will fail with ERR_RESPONSE_HEADERS_TOO_BIG. - // Note: |kMaxHeaderBufSize| should be a multiple of |kHeaderBufInitialSize|. - enum { kMaxHeaderBufSize = 256 * 1024 }; // 256 kilobytes. + std::string request_headers_; // The size in bytes of the buffer we use to drain the response body that // we want to throw away. The response body is typically a small error // page just a few hundred bytes long. enum { kDrainBodyBufferSize = 1024 }; - // The position where status line starts; -1 if not found yet. - int header_buf_http_offset_; - - // Indicates the content length remaining to read. If this value is less - // than zero (and chunked_decoder_ is null), then we read until the server - // closes the connection. - int64 response_body_length_; - - // Keeps track of the number of response body bytes read so far. - int64 response_body_read_; - - scoped_ptr<HttpChunkedDecoder> chunked_decoder_; - // User buffer and length passed to the Read method. scoped_refptr<IOBuffer> read_buf_; int read_buf_len_; diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index a3a4647..b47e34a 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -12,8 +12,10 @@ #include "net/base/test_completion_callback.h" #include "net/base/upload_data.h" #include "net/http/http_auth_handler_ntlm.h" +#include "net/http/http_basic_stream.h" #include "net/http/http_network_session.h" #include "net/http/http_network_transaction.h" +#include "net/http/http_stream.h" #include "net/http/http_transaction_unittest.h" #include "net/proxy/proxy_config_service_fixed.h" #include "net/socket/client_socket_factory.h" @@ -320,6 +322,24 @@ TEST_F(HttpNetworkTransactionTest, StopsReading204) { EXPECT_EQ("", out.response_data); } +// A simple request using chunked encoding with some extra data after. +// (Like might be seen in a pipelined response.) +TEST_F(HttpNetworkTransactionTest, ChunkedEncoding) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"), + MockRead("5\r\nHello\r\n"), + MockRead("1\r\n"), + MockRead(" \r\n"), + MockRead("5\r\nworld\r\n"), + MockRead("0\r\n\r\nHTTP/1.1 200 OK\r\n"), + MockRead(false, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("Hello world", out.response_data); +} + // Do a request using the HEAD method. Verify that we don't try to read the // message body (since HEAD has none). TEST_F(HttpNetworkTransactionTest, Head) { @@ -471,7 +491,7 @@ TEST_F(HttpNetworkTransactionTest, Ignores100) { // This test is almost the same as Ignores100 above, but the response contains // a 102 instead of a 100. Also, instead of HTTP/1.0 the response is -// HTTP/1.1. +// HTTP/1.1 and the two status headers are read in one read. TEST_F(HttpNetworkTransactionTest, Ignores1xx) { SessionDependencies session_deps; scoped_ptr<HttpTransaction> trans( @@ -483,8 +503,8 @@ TEST_F(HttpNetworkTransactionTest, Ignores1xx) { request.load_flags = 0; MockRead data_reads[] = { - MockRead("HTTP/1.1 102 Unspecified status code\r\n\r\n"), - MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("HTTP/1.1 102 Unspecified status code\r\n\r\n" + "HTTP/1.1 200 OK\r\n\r\n"), MockRead("hello world"), MockRead(false, OK), }; @@ -2678,53 +2698,40 @@ TEST_F(HttpNetworkTransactionTest, ResetStateForRestart) { new HttpNetworkTransaction(CreateSession(&session_deps))); // Setup some state (which we expect ResetStateForRestart() will clear). - trans->header_buf_->Realloc(10); - trans->header_buf_capacity_ = 10; - trans->header_buf_len_ = 3; - trans->header_buf_body_offset_ = 11; - trans->header_buf_http_offset_ = 0; - trans->response_body_length_ = 100; - trans->response_body_read_ = 1; trans->read_buf_ = new IOBuffer(15); trans->read_buf_len_ = 15; - trans->request_headers_->headers_ = "Authorization: NTLM"; - trans->request_headers_bytes_sent_ = 3; + trans->request_headers_ = "Authorization: NTLM"; // Setup state in response_ - trans->response_.auth_challenge = new AuthChallengeInfo(); - trans->response_.ssl_info.cert_status = -15; - trans->response_.response_time = base::Time::Now(); - trans->response_.was_cached = true; // (Wouldn't ever actually be true...) + trans->http_stream_.reset(new HttpBasicStream(NULL)); + HttpResponseInfo* response = trans->http_stream_->GetResponseInfo(); + response->auth_challenge = new AuthChallengeInfo(); + response->ssl_info.cert_status = -15; + response->response_time = base::Time::Now(); + response->was_cached = true; // (Wouldn't ever actually be true...) { // Setup state for response_.vary_data HttpRequestInfo request; std::string temp("HTTP/1.1 200 OK\nVary: foo, bar\n\n"); std::replace(temp.begin(), temp.end(), '\n', '\0'); - scoped_refptr<HttpResponseHeaders> response = new HttpResponseHeaders(temp); + scoped_refptr<HttpResponseHeaders> headers = new HttpResponseHeaders(temp); request.extra_headers = "Foo: 1\nbar: 23"; - EXPECT_TRUE(trans->response_.vary_data.Init(request, *response)); + EXPECT_TRUE(response->vary_data.Init(request, *headers)); } // Cause the above state to be reset. trans->ResetStateForRestart(); // Verify that the state that needed to be reset, has been reset. - EXPECT_TRUE(trans->header_buf_->headers() == NULL); - EXPECT_EQ(0, trans->header_buf_capacity_); - EXPECT_EQ(0, trans->header_buf_len_); - EXPECT_EQ(-1, trans->header_buf_body_offset_); - EXPECT_EQ(-1, trans->header_buf_http_offset_); - EXPECT_EQ(-1, trans->response_body_length_); - EXPECT_EQ(0, trans->response_body_read_); + response = trans->http_stream_->GetResponseInfo(); EXPECT_TRUE(trans->read_buf_.get() == NULL); EXPECT_EQ(0, trans->read_buf_len_); - EXPECT_EQ("", trans->request_headers_->headers_); - EXPECT_EQ(0U, trans->request_headers_bytes_sent_); - EXPECT_TRUE(trans->response_.auth_challenge.get() == NULL); - EXPECT_TRUE(trans->response_.headers.get() == NULL); - EXPECT_EQ(false, trans->response_.was_cached); - EXPECT_EQ(0, trans->response_.ssl_info.cert_status); - EXPECT_FALSE(trans->response_.vary_data.is_valid()); + EXPECT_EQ(0U, trans->request_headers_.size()); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_TRUE(response->headers.get() == NULL); + EXPECT_EQ(false, response->was_cached); + EXPECT_EQ(0, response->ssl_info.cert_status); + EXPECT_FALSE(response->vary_data.is_valid()); } // Test HTTPS connections to a site with a bad certificate @@ -3595,4 +3602,189 @@ TEST_F(HttpNetworkTransactionTest, BypassHostCacheOnRefresh) { EXPECT_EQ(ERR_NAME_NOT_RESOLVED, rv); } +// Make sure we can handle an error when writing the request. +TEST_F(HttpNetworkTransactionTest, RequestWriteError) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session = CreateSession(&session_deps); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + MockWrite write_failure[] = { + MockWrite(true, ERR_CONNECTION_RESET), + }; + StaticMockSocket data(NULL, write_failure); + session_deps.socket_factory.AddMockSocket(&data); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, &callback, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CONNECTION_RESET, rv); +} + +// Check that a connection closed after the start of the headers finishes ok. +TEST_F(HttpNetworkTransactionTest, ConnectionClosedAfterStartOfHeaders) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session = CreateSession(&session_deps); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + MockRead data_reads[] = { + MockRead("HTTP/1."), + MockRead(false, OK), + }; + + StaticMockSocket data(data_reads, NULL); + session_deps.socket_factory.AddMockSocket(&data); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, &callback, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("", response_data); +} + +// Make sure that a dropped connection while draining the body for auth +// restart does the right thing. +TEST_F(HttpNetworkTransactionTest, DrainResetOK) { + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 14\r\n\r\n"), + MockRead("Unauth"), + MockRead(true, ERR_CONNECTION_RESET), + }; + + StaticMockSocket data1(data_reads1, data_writes1); + session_deps.socket_factory.AddMockSocket(&data1); + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads2[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(false, OK), + }; + + StaticMockSocket data2(data_reads2, data_writes2); + session_deps.socket_factory.AddMockSocket(&data2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_FALSE(response == NULL); + + // The password prompt info should have been set in response->auth_challenge. + EXPECT_FALSE(response->auth_challenge.get() == NULL); + + EXPECT_EQ(L"www.google.com:80", response->auth_challenge->host_and_port); + EXPECT_EQ(L"MyRealm1", response->auth_challenge->realm); + EXPECT_EQ(L"basic", response->auth_challenge->scheme); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth(L"foo", L"bar", &callback2); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + EXPECT_FALSE(response == NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); +} + +// Test HTTPS connections going through a proxy that sends extra data. +TEST_F(HttpNetworkTransactionTest, HTTPSViaProxyWithExtraData) { + SessionDependencies session_deps(CreateFixedProxyService("myproxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockRead proxy_reads[] = { + MockRead("HTTP/1.0 200 Connected\r\n\r\nExtra data"), + MockRead(false, OK) + }; + + StaticMockSocket data(proxy_reads, NULL); + MockSSLSocket ssl(true, OK); + + session_deps.socket_factory.AddMockSocket(&data); + session_deps.socket_factory.AddMockSSLSocket(&ssl); + + TestCompletionCallback callback; + + session_deps.socket_factory.ResetNextMockIndexes(); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, &callback, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv); +} + } // namespace net diff --git a/net/http/http_stream.h b/net/http/http_stream.h index 361edb0..d0b9dc9 100644 --- a/net/http/http_stream.h +++ b/net/http/http_stream.h @@ -6,50 +6,73 @@ // keeps the client agnostic of the actual underlying transport layer. This // provides an abstraction for both a basic http stream as well as http // pipelining implementations. -// -// NOTE(willchan): This interface is a work in progress. It will most likely -// change, since for a pipelining implementation, the stream needs to contain -// the http parsing code. For symmetry, the writing methods will probably -// contain the code for constructing http requests. #ifndef NET_HTTP_HTTP_STREAM_H_ #define NET_HTTP_HTTP_STREAM_H_ +#include <string> + #include "base/basictypes.h" -#include "net/base/completion_callback.h" +#include "net/socket/client_socket_handle.h" namespace net { +class HttpRequestInfo; +class HttpResponseInfo; class IOBuffer; +class UploadDataStream; class HttpStream { public: HttpStream() {} virtual ~HttpStream() {} - // Reads data, up to buf_len bytes, from the socket. The number of bytes - // read is returned, or an error is returned upon failure. Zero is returned - // to indicate end-of-file. ERR_IO_PENDING is returned if the operation - // could not be completed synchronously, in which case the result will be - // passed to the callback when available. If the operation is not completed - // immediately, the socket acquires a reference to the provided buffer until - // the callback is invoked or the socket is destroyed. - virtual int Read(IOBuffer* buf, - int buf_len, - CompletionCallback* callback) = 0; - - // Writes data, up to buf_len bytes, to the socket. Note: only part of the - // data may be written! The number of bytes written is returned, or an error - // is returned upon failure. ERR_IO_PENDING is returned if the operation - // could not be completed synchronously, in which case the result will be - // passed to the callback when available. If the operation is not completed - // immediately, the socket acquires a reference to the provided buffer until - // the callback is invoked or the socket is destroyed. - // Implementations of this method should not modify the contents of the actual - // buffer that is written to the socket. - virtual int Write(IOBuffer* buf, - int buf_len, - CompletionCallback* callback) = 0; + // Writes the headers and uploads body data to the underlying socket. + // ERR_IO_PENDING is returned if the operation could not be completed + // synchronously, in which case the result will be passed to the callback + // when available. Returns OK on success. The HttpStream takes ownership + // of the request_body. + virtual int SendRequest(const HttpRequestInfo* request, + const std::string& request_headers, + UploadDataStream* request_body, + CompletionCallback* callback) = 0; + + // Queries the UploadDataStream for its progress (bytes sent). + virtual uint64 GetUploadProgress() const = 0; + + // Reads from the underlying socket until the response headers have been + // completely received. ERR_IO_PENDING is returned if the operation could + // not be completed synchronously, in which case the result will be passed + // to the callback when available. Returns OK on success. The response + // headers are available in the HttpResponseInfo returned by GetResponseInfo + virtual int ReadResponseHeaders(CompletionCallback* callback) = 0; + + // Provides access to HttpResponseInfo (owned by HttpStream). + virtual HttpResponseInfo* GetResponseInfo() const = 0; + + // Reads response body data, up to |buf_len| bytes. The number of bytes read + // is returned, or an error is returned upon failure. ERR_CONNECTION_CLOSED + // is returned to indicate end-of-connection. ERR_IO_PENDING is returned if + // the operation could not be completed synchronously, in which case the + // result will be passed to the callback when available. If the operation is + // not completed immediately, the socket acquires a reference to the provided + // buffer until the callback is invoked or the socket is destroyed. + virtual int ReadResponseBody(IOBuffer* buf, int buf_len, + CompletionCallback* callback) = 0; + + // Indicates if the response body has been completely read. + virtual bool IsResponseBodyComplete() const = 0; + + // Indicates that the end of the response is detectable. This means that + // the response headers indicate either chunked encoding or content length. + // If neither is sent, the server must close the connection for us to detect + // the end of the response. + virtual bool CanFindEndOfResponse() const = 0; + + // After the response headers have been read and after the response body + // is complete, this function indicates if more data (either erroneous or + // as part of the next pipelined response) has been read from the socket. + virtual bool IsMoreDataBuffered() const = 0; private: DISALLOW_COPY_AND_ASSIGN(HttpStream); diff --git a/net/http/http_stream_parser.cc b/net/http/http_stream_parser.cc new file mode 100644 index 0000000..6456c9b --- /dev/null +++ b/net/http/http_stream_parser.cc @@ -0,0 +1,512 @@ +// Copyright (c) 2006-2009 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_stream_parser.h" + +#include "base/compiler_specific.h" +#include "base/trace_event.h" +#include "net/base/io_buffer.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_util.h" + +namespace net { + +HttpStreamParser::HttpStreamParser(ClientSocketHandle* connection, + GrowableIOBuffer* read_buffer) + : io_state_(STATE_NONE), + request_(NULL), + request_headers_(NULL), + request_body_(NULL), + read_buf_(read_buffer), + read_buf_unused_offset_(0), + response_header_start_offset_(-1), + response_body_length_(-1), + response_body_read_(0), + chunked_decoder_(NULL), + user_read_buf_(NULL), + user_read_buf_len_(0), + user_callback_(NULL), + connection_(connection), + ALLOW_THIS_IN_INITIALIZER_LIST( + io_callback_(this, &HttpStreamParser::OnIOComplete)) { + DCHECK_EQ(0, read_buffer->offset()); +} + +int HttpStreamParser::SendRequest(const HttpRequestInfo* request, + const std::string& headers, + UploadDataStream* request_body, + CompletionCallback* callback) { + DCHECK_EQ(STATE_NONE, io_state_); + DCHECK(!user_callback_); + DCHECK(callback); + + request_ = request; + scoped_refptr<StringIOBuffer> headers_io_buf = new StringIOBuffer(headers); + request_headers_ = new DrainableIOBuffer(headers_io_buf, + headers_io_buf->size()); + request_body_.reset(request_body); + + io_state_ = STATE_SENDING_HEADERS; + int result = DoLoop(OK); + if (result == ERR_IO_PENDING) + user_callback_ = callback; + + return result > 0 ? OK : result; +} + +int HttpStreamParser::ReadResponseHeaders(CompletionCallback* callback) { + DCHECK_EQ(STATE_REQUEST_SENT, io_state_); + DCHECK(!user_callback_); + DCHECK(callback); + + int result = OK; + io_state_ = STATE_READ_HEADERS; + + if (read_buf_->offset() > 0) { + // Simulate the state where the data was just read from the socket. + result = read_buf_->offset() - read_buf_unused_offset_; + read_buf_->set_offset(read_buf_unused_offset_); + } + if (result > 0) + io_state_ = STATE_READ_HEADERS_COMPLETE; + + result = DoLoop(result); + if (result == ERR_IO_PENDING) + user_callback_ = callback; + + return result > 0 ? OK : result; +} + +int HttpStreamParser::ReadResponseBody(IOBuffer* buf, int buf_len, + CompletionCallback* callback) { + DCHECK(io_state_ == STATE_BODY_PENDING || io_state_ == STATE_DONE); + DCHECK(!user_callback_); + DCHECK(callback); + + if (io_state_ == STATE_DONE) + return OK; + + user_read_buf_ = buf; + user_read_buf_len_ = buf_len; + io_state_ = STATE_READ_BODY; + + int result = DoLoop(OK); + if (result == ERR_IO_PENDING) + user_callback_ = callback; + + return result; +} + +void HttpStreamParser::OnIOComplete(int result) { + result = DoLoop(result); + + // The client callback can do anything, including destroying this class, + // so any pending callback must be issued after everything else is done. + if (result != ERR_IO_PENDING && user_callback_) { + CompletionCallback* c = user_callback_; + user_callback_ = NULL; + c->Run(result); + } +} + +int HttpStreamParser::DoLoop(int result) { + bool can_do_more = true; + do { + switch (io_state_) { + case STATE_SENDING_HEADERS: + TRACE_EVENT_BEGIN("http.write_headers", request_, request_->url.spec()); + if (result < 0) + can_do_more = false; + else + result = DoSendHeaders(result); + TRACE_EVENT_END("http.write_headers", request_, request_->url.spec()); + break; + case STATE_SENDING_BODY: + TRACE_EVENT_BEGIN("http.write_body", request_, request_->url.spec()); + if (result < 0) + can_do_more = false; + else + result = DoSendBody(result); + TRACE_EVENT_END("http.write_body", request_, request_->url.spec()); + break; + case STATE_REQUEST_SENT: + DCHECK(result != ERR_IO_PENDING); + can_do_more = false; + break; + case STATE_READ_HEADERS: + TRACE_EVENT_BEGIN("http.read_headers", request_, request_->url.spec()); + result = DoReadHeaders(); + break; + case STATE_READ_HEADERS_COMPLETE: + result = DoReadHeadersComplete(result); + TRACE_EVENT_END("http.read_headers", request_, request_->url.spec()); + break; + case STATE_BODY_PENDING: + DCHECK(result != ERR_IO_PENDING); + can_do_more = false; + break; + case STATE_READ_BODY: + TRACE_EVENT_BEGIN("http.read_body", request_, request_->url.spec()); + result = DoReadBody(); + // DoReadBodyComplete handles error conditions. + break; + case STATE_READ_BODY_COMPLETE: + result = DoReadBodyComplete(result); + TRACE_EVENT_END("http.read_body", request_, request_->url.spec()); + break; + case STATE_DONE: + DCHECK(result != ERR_IO_PENDING); + can_do_more = false; + break; + default: + NOTREACHED(); + can_do_more = false; + break; + } + } while (result != ERR_IO_PENDING && can_do_more); + + return result; +} + +int HttpStreamParser::DoSendHeaders(int result) { + request_headers_->DidConsume(result); + + if (request_headers_->BytesRemaining() > 0) { + // Record our best estimate of the 'request time' as the time when we send + // out the first bytes of the request headers. + if (request_headers_->BytesRemaining() == request_headers_->size()) { + response_.request_time = base::Time::Now(); + } + result = connection_->socket()->Write(request_headers_, + request_headers_->BytesRemaining(), + &io_callback_); + } else if (request_body_ != NULL && request_body_->size()) { + io_state_ = STATE_SENDING_BODY; + result = OK; + } else { + io_state_ = STATE_REQUEST_SENT; + } + return result; +} + +int HttpStreamParser::DoSendBody(int result) { + request_body_->DidConsume(result); + + if (request_body_->position() < request_body_->size()) { + int buf_len = static_cast<int>(request_body_->buf_len()); + result = connection_->socket()->Write(request_body_->buf(), buf_len, + &io_callback_); + } else { + io_state_ = STATE_REQUEST_SENT; + } + return result; +} + +int HttpStreamParser::DoReadHeaders() { + io_state_ = STATE_READ_HEADERS_COMPLETE; + + // Grow the read buffer if necessary. + if (read_buf_->RemainingCapacity() == 0) + read_buf_->set_capacity(read_buf_->capacity() + kHeaderBufInitialSize); + + // http://crbug.com/16371: We're seeing |user_buf_->data()| return NULL. + // See if the user is passing in an IOBuffer with a NULL |data_|. + CHECK(read_buf_->data()); + + int bytes_read = connection_->socket()->Read(read_buf_, + read_buf_->RemainingCapacity(), + &io_callback_); + if (bytes_read == 0) + bytes_read = ERR_CONNECTION_CLOSED; + + return bytes_read; +} + +int HttpStreamParser::DoReadHeadersComplete(int result) { + if (result < 0 && result != ERR_CONNECTION_CLOSED) { + io_state_ = STATE_DONE; + return result; + } + if (result == ERR_CONNECTION_CLOSED && read_buf_->offset() == 0 && + connection_->ShouldResendFailedRequest(result)) { + io_state_ = STATE_DONE; + return result; + } + + // Record our best estimate of the 'response time' as the time when we read + // the first bytes of the response headers. + if (read_buf_->offset() == 0 && result != ERR_CONNECTION_CLOSED) + response_.response_time = base::Time::Now(); + + if (result == ERR_CONNECTION_CLOSED) { + // The connection closed before we detected the end of the headers. + // parse things as well as we can and let the caller decide what to do. + if (read_buf_->offset() == 0) { + // The connection was closed before any data was sent. Likely an error + // rather than empty HTTP/0.9 response. + io_state_ = STATE_DONE; + return ERR_EMPTY_RESPONSE; + } else { + int end_offset; + if (response_header_start_offset_ >= 0) { + io_state_ = STATE_READ_BODY_COMPLETE; + end_offset = read_buf_->offset(); + } else { + io_state_ = STATE_BODY_PENDING; + end_offset = 0; + } + DoParseResponseHeaders(end_offset); + return result; + } + } + + read_buf_->set_offset(read_buf_->offset() + result); + DCHECK_LE(read_buf_->offset(), read_buf_->capacity()); + DCHECK(result >= 0); + + int end_of_header_offset = ParseResponseHeaders(); + if (end_of_header_offset == -1) { + io_state_ = STATE_READ_HEADERS; + // Prevent growing the headers buffer indefinitely. + if (read_buf_->offset() - read_buf_unused_offset_ >= kMaxHeaderBufSize) { + io_state_ = STATE_DONE; + return ERR_RESPONSE_HEADERS_TOO_BIG; + } + } else { + // Note where the headers stop. + read_buf_unused_offset_ = end_of_header_offset; + + if (response_.headers->response_code() / 100 == 1) { + // After processing a 1xx response, the caller will ask for the next + // header, so reset state to support that. We don't just skip these + // completely because 1xx codes aren't acceptable when establishing a + // tunnel. + io_state_ = STATE_REQUEST_SENT; + response_header_start_offset_ = -1; + } else { + io_state_ = STATE_BODY_PENDING; + CalculateResponseBodySize(); + // If the body is 0, the caller may not call ReadResponseBody, which + // is where any extra data is copied to read_buf_, so we trigger + // the progression to DONE here. + if (response_body_length_ == 0) { + io_state_ = STATE_READ_BODY; + user_read_buf_ = read_buf_; + user_read_buf_len_ = read_buf_->capacity(); + return OK; + } + } + } + return result; +} + +int HttpStreamParser::DoReadBody() { + io_state_ = STATE_READ_BODY_COMPLETE; + + int bytes_read; + // There may be some data left over from reading the response headers. + if (read_buf_->offset()) { + int available = read_buf_->offset() - read_buf_unused_offset_; + if (available) { + bytes_read = std::min(available, user_read_buf_len_); + // memmove is used here so that the caller can pass read_buf_ + // for user_read_buf. + memmove(user_read_buf_->data(), + read_buf_->StartOfBuffer() + read_buf_unused_offset_, + bytes_read); + read_buf_unused_offset_ += bytes_read; + if (bytes_read == available) { + read_buf_->set_capacity(0); + read_buf_unused_offset_ = 0; + } + return bytes_read; + } else { + read_buf_->set_capacity(0); + read_buf_unused_offset_ = 0; + } + } + + // Check to see if we're done reading. + if (IsResponseBodyComplete()) + return 0; + + DCHECK_EQ(0, read_buf_->offset()); + bytes_read = connection_->socket()->Read(user_read_buf_, user_read_buf_len_, + &io_callback_); + if (bytes_read == 0) + bytes_read = ERR_CONNECTION_CLOSED; + + return bytes_read; +} + +int HttpStreamParser::DoReadBodyComplete(int result) { + // Filter incoming data if appropriate. FilterBuf may return an error. + if (result > 0 && chunked_decoder_.get()) { + result = chunked_decoder_->FilterBuf(user_read_buf_->data(), result); + if (result == 0 && !chunked_decoder_->reached_eof()) { + // Don't signal completion of the Read call yet or else it'll look like + // we received end-of-file. Wait for more data. + io_state_ = STATE_READ_BODY; + return OK; + } + } + + if (result > 0) + response_body_read_ += result; + + if (result < 0 || IsResponseBodyComplete()) { + io_state_ = STATE_DONE; + + // Save the overflow data, which can be in two places. There may be + // some left over in |user_read_buf_|, plus there may be more + // in |read_buf_|. But the part left over in |user_read_buf_| must have + // come from the |read_buf_|, so there's room to put it back at the + // start first. + int save_amount = 0; + int additional_save_amount = read_buf_->offset() - read_buf_unused_offset_; + if (chunked_decoder_.get()) { + save_amount = chunked_decoder_->bytes_after_eof(); + } else if (response_body_length_ >= 0) { + save_amount = static_cast<int>(response_body_read_ - + response_body_length_); + if (result > 0) + result -= save_amount; + } + if (save_amount > 0) { + if (static_cast<int>(read_buf_->capacity()) < save_amount) + read_buf_->set_capacity(save_amount + additional_save_amount); + // memmove is used here so that the caller can pass read_buf_ + // for body_buf. + memmove(read_buf_->StartOfBuffer(), user_read_buf_->data() + result, + save_amount); + read_buf_->set_offset(save_amount); + } + if (additional_save_amount) { + memmove(read_buf_->data(), + read_buf_->StartOfBuffer() + read_buf_unused_offset_, + additional_save_amount); + read_buf_->set_offset(save_amount + additional_save_amount); + } + read_buf_unused_offset_ = 0; + } else { + io_state_ = STATE_BODY_PENDING; + user_read_buf_ = NULL; + user_read_buf_len_ = 0; + } + + return result; +} + +int HttpStreamParser::ParseResponseHeaders() { + int end_offset = -1; + + // Look for the start of the status line, if it hasn't been found yet. + if (response_header_start_offset_ < 0) { + response_header_start_offset_ = HttpUtil::LocateStartOfStatusLine( + read_buf_->StartOfBuffer() + read_buf_unused_offset_, + read_buf_->offset() - read_buf_unused_offset_); + } + + if (response_header_start_offset_ >= 0) { + end_offset = HttpUtil::LocateEndOfHeaders( + read_buf_->StartOfBuffer() + read_buf_unused_offset_, + read_buf_->offset() - read_buf_unused_offset_, + response_header_start_offset_); + } else if (read_buf_->offset() - read_buf_unused_offset_ >= 8) { + // Enough data to decide that this is an HTTP/0.9 response. + // 8 bytes = (4 bytes of junk) + "http".length() + end_offset = 0; + } + + if (end_offset == -1) + return -1; + + DoParseResponseHeaders(end_offset); + return end_offset + read_buf_unused_offset_; +} + +void HttpStreamParser::DoParseResponseHeaders(int end_offset) { + scoped_refptr<HttpResponseHeaders> headers; + if (response_header_start_offset_ >= 0) { + headers = new HttpResponseHeaders(HttpUtil::AssembleRawHeaders( + read_buf_->StartOfBuffer() + read_buf_unused_offset_, end_offset)); + } else { + // Enough data was read -- there is no status line. + headers = new HttpResponseHeaders(std::string("HTTP/0.9 200 OK")); + } + + response_.headers = headers; + response_.vary_data.Init(*request_, *response_.headers); +} + +void HttpStreamParser::CalculateResponseBodySize() { + // Figure how to determine EOF: + + // For certain responses, we know the content length is always 0. From + // RFC 2616 Section 4.3 Message Body: + // + // For response messages, whether or not a message-body is included with + // a message is dependent on both the request method and the response + // status code (section 6.1.1). All responses to the HEAD request method + // MUST NOT include a message-body, even though the presence of entity- + // header fields might lead one to believe they do. All 1xx + // (informational), 204 (no content), and 304 (not modified) responses + // MUST NOT include a message-body. All other responses do include a + // message-body, although it MAY be of zero length. + switch (response_.headers->response_code()) { + // Note that 1xx was already handled earlier. + case 204: // No Content + case 205: // Reset Content + case 304: // Not Modified + response_body_length_ = 0; + break; + } + if (request_->method == "HEAD") + response_body_length_ = 0; + + if (response_body_length_ == -1) { + // Ignore spurious chunked responses from HTTP/1.0 servers and + // proxies. Otherwise "Transfer-Encoding: chunked" trumps + // "Content-Length: N" + if (response_.headers->GetHttpVersion() >= HttpVersion(1, 1) && + response_.headers->HasHeaderValue("Transfer-Encoding", "chunked")) { + chunked_decoder_.reset(new HttpChunkedDecoder()); + } else { + response_body_length_ = response_.headers->GetContentLength(); + // If response_body_length_ is still -1, then we have to wait + // for the server to close the connection. + } + } +} + +uint64 HttpStreamParser::GetUploadProgress() const { + if (!request_body_.get()) + return 0; + + return request_body_->position(); +} + +HttpResponseInfo* HttpStreamParser::GetResponseInfo() { + return &response_; +} + +bool HttpStreamParser::IsResponseBodyComplete() const { + if (chunked_decoder_.get()) + return chunked_decoder_->reached_eof(); + if (response_body_length_ != -1) + return response_body_read_ >= response_body_length_; + + return false; // Must read to EOF. +} + +bool HttpStreamParser::CanFindEndOfResponse() const { + return chunked_decoder_.get() || response_body_length_ >= 0; +} + +bool HttpStreamParser::IsMoreDataBuffered() const { + return read_buf_->offset() > read_buf_unused_offset_; +} + +} // namespace net diff --git a/net/http/http_stream_parser.h b/net/http/http_stream_parser.h new file mode 100644 index 0000000..012039f --- /dev/null +++ b/net/http/http_stream_parser.h @@ -0,0 +1,167 @@ +// Copyright (c) 2009 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. + +#ifndef NET_HTTP_HTTP_STREAM_PARSER_H_ +#define NET_HTTP_HTTP_STREAM_PARSER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "net/base/io_buffer.h" +#include "net/base/upload_data_stream.h" +#include "net/http/http_chunked_decoder.h" +#include "net/http/http_response_info.h" +#include "net/socket/client_socket_handle.h" + +namespace net { + +class ClientSocketHandle; +class HttpRequestInfo; + +class HttpStreamParser { + public: + // Any data in |read_buffer| will be used before reading from the socket + // and any data left over after parsing the stream will be put into + // |read_buffer|. The left over data will start at offset 0 and the + // buffer's offset will be set to the first free byte. |read_buffer| may + // have its capacity changed. + HttpStreamParser(ClientSocketHandle* connection, + GrowableIOBuffer* read_buffer); + ~HttpStreamParser() {} + + // These functions implement the interface described in HttpStream with + // some additional functionality + int SendRequest(const HttpRequestInfo* request, const std::string& headers, + UploadDataStream* request_body, CompletionCallback* callback); + + int ReadResponseHeaders(CompletionCallback* callback); + + int ReadResponseBody(IOBuffer* buf, int buf_len, + CompletionCallback* callback); + + uint64 GetUploadProgress() const; + + HttpResponseInfo* GetResponseInfo(); + + bool IsResponseBodyComplete() const; + + bool CanFindEndOfResponse() const; + + bool IsMoreDataBuffered() const; + + private: + // FOO_COMPLETE states implement the second half of potentially asynchronous + // operations and don't necessarily mean that FOO is complete. + enum State { + STATE_NONE, + STATE_SENDING_HEADERS, + STATE_SENDING_BODY, + STATE_REQUEST_SENT, + STATE_READ_HEADERS, + STATE_READ_HEADERS_COMPLETE, + STATE_BODY_PENDING, + STATE_READ_BODY, + STATE_READ_BODY_COMPLETE, + STATE_DONE + }; + + // The number of bytes by which the header buffer is grown when it reaches + // capacity. + enum { kHeaderBufInitialSize = 4096 }; + + // |kMaxHeaderBufSize| is the number of bytes that the response headers can + // grow to. If the body start is not found within this range of the + // response, the transaction will fail with ERR_RESPONSE_HEADERS_TOO_BIG. + // Note: |kMaxHeaderBufSize| should be a multiple of |kHeaderBufInitialSize|. + enum { kMaxHeaderBufSize = 256 * 1024 }; // 256 kilobytes. + + // Handle callbacks. + void OnIOComplete(int result); + + // Try to make progress sending/receiving the request/response. + int DoLoop(int result); + + // The implementations of each state of the state machine. + int DoSendHeaders(int result); + int DoSendBody(int result); + int DoReadHeaders(); + int DoReadHeadersComplete(int result); + int DoReadBody(); + int DoReadBodyComplete(int result); + + // Examines |read_buf_| to find the start and end of the headers. Return + // the offset for the end of the headers, or -1 if the complete headers + // were not found. If they are are found, parse them with + // DoParseResponseHeaders(). + int ParseResponseHeaders(); + + // Parse the headers into response_. + void DoParseResponseHeaders(int end_of_header_offset); + + // Examine the parsed headers to try to determine the response body size. + void CalculateResponseBodySize(); + + // Current state of the request. + State io_state_; + + // The request to send. + const HttpRequestInfo* request_; + + // The request header data. + scoped_refptr<DrainableIOBuffer> request_headers_; + + // The request body data. + scoped_ptr<UploadDataStream> request_body_; + + // Temporary buffer for reading. + scoped_refptr<GrowableIOBuffer> read_buf_; + + // Offset of the first unused byte in |read_buf_|. May be nonzero due to + // a 1xx header, or body data in the same packet as header data. + int read_buf_unused_offset_; + + // The amount beyond |read_buf_unused_offset_| where the status line starts; + // -1 if not found yet. + int response_header_start_offset_; + + // The parsed response headers. + HttpResponseInfo response_; + + // Indicates the content length. If this value is less than zero + // (and chunked_decoder_ is null), then we must read until the server + // closes the connection. + int64 response_body_length_; + + // Keep track of the number of response body bytes read so far. + int64 response_body_read_; + + // Helper if the data is chunked. + scoped_ptr<HttpChunkedDecoder> chunked_decoder_; + + // Where the caller wants the body data. + scoped_refptr<IOBuffer> user_read_buf_; + int user_read_buf_len_; + + // The callback to notify a user that their request or response is + // complete or there was an error + CompletionCallback* user_callback_; + + // In the client callback, the client can do anything, including + // destroying this class, so any pending callback must be issued + // after everything else is done. When it is time to issue the client + // callback, move it from |user_callback_| to |scheduled_callback_|. + CompletionCallback* scheduled_callback_; + + // The underlying socket. + ClientSocketHandle* const connection_; + + // Callback to be used when doing IO. + CompletionCallbackImpl<HttpStreamParser> io_callback_; + + DISALLOW_COPY_AND_ASSIGN(HttpStreamParser); +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_STREAM_PARSER_H_ diff --git a/net/net.gyp b/net/net.gyp index 2d0b342..00c5661 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -240,6 +240,7 @@ 'http/http_auth_handler_ntlm.h', 'http/http_auth_handler_ntlm_portable.cc', 'http/http_auth_handler_ntlm_win.cc', + 'http/http_basic_stream.cc', 'http/http_basic_stream.h', 'http/http_byte_range.cc', 'http/http_byte_range.h', @@ -259,6 +260,8 @@ 'http/http_response_info.cc', 'http/http_response_info.h', 'http/http_stream.h', + 'http/http_stream_parser.cc', + 'http/http_stream_parser.h', 'http/http_transaction.h', 'http/http_transaction_factory.h', 'http/http_util.cc', diff --git a/net/socket/client_socket_handle.h b/net/socket/client_socket_handle.h index 83aa70f..25a2f35 100644 --- a/net/socket/client_socket_handle.h +++ b/net/socket/client_socket_handle.h @@ -104,6 +104,19 @@ class ClientSocketHandle { return UNUSED_IDLE; } } + bool ShouldResendFailedRequest(int error) const { + // 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 ( // We used a socket that was never idle. + reuse_type() == ClientSocketHandle::UNUSED || + // We used an unused, idle socket and got a error that wasn't a TCP RST. + (reuse_type() == ClientSocketHandle::UNUSED_IDLE && + (error != OK && error != ERR_CONNECTION_RESET))) { + return false; + } + return true; + } private: // Called on asynchronous completion of an Init() request. |