| // Copyright (c) 2013 Google Inc. 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/shell/http_transaction_shell.h" |
| |
| #include "base/message_loop.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/net_errors.h" |
| #include "net/base/net_util.h" |
| #include "net/base/ssl_cert_request_info.h" |
| #include "net/base/ssl_connection_status_flags.h" |
| #include "net/base/upload_data_stream.h" |
| #include "net/http/http_response_headers.h" |
| |
| namespace net { |
| |
| HttpTransactionShell::HttpTransactionShell( |
| const net::HttpNetworkSession::Params* params, HttpStreamShell* stream) |
| : stream_(stream), |
| state_(STATE_CREATED), |
| result_(OK), |
| pending_callback_(false), |
| ALLOW_THIS_IN_INITIALIZER_LIST(resolve_proxy_callback_( |
| base::Bind(&HttpTransactionShell::OnResolveProxyComplete, |
| base::Unretained(this)))), |
| ALLOW_THIS_IN_INITIALIZER_LIST(init_stream_callback_( |
| base::Bind(&HttpTransactionShell::OnInitStreamComplete, |
| base::Unretained(this)))), |
| ALLOW_THIS_IN_INITIALIZER_LIST(init_upload_callback_( |
| base::Bind(&HttpTransactionShell::OnInitUploadComplete, |
| base::Unretained(this)))), |
| ALLOW_THIS_IN_INITIALIZER_LIST(send_request_callback_( |
| base::Bind(&HttpTransactionShell::OnSendRequestComplete, |
| base::Unretained(this)))), |
| ALLOW_THIS_IN_INITIALIZER_LIST(read_response_headers_callback_( |
| base::Bind(&HttpTransactionShell::OnReadResponseHeadersComplete, |
| base::Unretained(this)))), |
| ALLOW_THIS_IN_INITIALIZER_LIST(read_response_body_callback_( |
| base::Bind(&HttpTransactionShell::OnReadResponseBodyComplete, |
| base::Unretained(this)))), |
| read_buf_len_(0), |
| request_(NULL), |
| params_(params) { |
| DCHECK(stream_); |
| current_message_loop_ = base::MessageLoopProxy::current(); |
| } |
| |
| // Starts the HTTP transaction (i.e., sends the HTTP request). |
| // The process contains the steps below: |
| // - ResolveProxy |
| // - InitStream |
| // - InitUploadData |
| // - SendRequest |
| // - ReadResponseHeaders |
| int HttpTransactionShell::Start(const HttpRequestInfo* request_info, |
| const CompletionCallback& callback, |
| const BoundNetLog& net_log) { |
| // Validate port |
| int port = request_info->url.EffectiveIntPort(); |
| if (!IsPortAllowedByDefault(port) && !IsPortAllowedByOverride(port)) { |
| return ERR_UNSAFE_PORT; |
| } |
| request_ = request_info; |
| net_log_ = net_log; |
| DCHECK(callback_.is_null()); |
| callback_ = callback; |
| |
| int result = ResolveProxy(); |
| // If not IO pending, continue. |
| if (result != ERR_IO_PENDING) { |
| OnResolveProxyComplete(result); |
| return result_; |
| } |
| pending_callback_ = true; |
| return result; |
| } |
| |
| void HttpTransactionShell::OnResolveProxyComplete(int result) { |
| DCHECK_EQ(state_, STATE_CREATED); |
| DCHECK(!callback_.is_null()); |
| if (result != OK) { |
| SetFailed(result); |
| result_ = result; |
| return; |
| } |
| |
| // Send proxy information to stream. |
| bool using_proxy = !proxy_info_.is_empty() && |
| (proxy_info_.is_http() || proxy_info_.is_https()); |
| if (using_proxy) { |
| // Remove unsupported proxies from the list. |
| proxy_info_.RemoveProxiesWithoutScheme(ProxyServer::SCHEME_DIRECT | |
| ProxyServer::SCHEME_HTTP | ProxyServer::SCHEME_HTTPS); |
| if (proxy_info_.is_empty()) { |
| // No proxies/direct to choose from. This happens when we don't support |
| // any of the proxies in the returned list. |
| result_ = ERR_NO_SUPPORTED_PROXIES; |
| return; |
| } |
| stream_->SetProxy(&proxy_info_); |
| } else { |
| stream_->SetProxy(NULL); |
| } |
| result = stream_->InitializeStream(request_, net_log_, init_stream_callback_); |
| // If not IO pending, continue. |
| if (result != ERR_IO_PENDING) { |
| OnInitStreamComplete(result); |
| return; |
| } |
| pending_callback_ = true; |
| result_ = result; |
| } |
| |
| void HttpTransactionShell::OnInitStreamComplete(int result) { |
| DCHECK_EQ(state_, STATE_CREATED); |
| DCHECK(!callback_.is_null()); |
| if (result != OK) { |
| SetFailed(result); |
| result_ = result; |
| return; |
| } |
| |
| state_ = STATE_STREAM_INITIALIZED; |
| BuildRequestHeaders(); |
| if (request_->upload_data_stream) |
| result = request_->upload_data_stream->Init(init_upload_callback_); |
| |
| if (result != ERR_IO_PENDING) { |
| OnInitUploadComplete(result); |
| return; |
| } |
| |
| pending_callback_ = true; |
| result_ = result; |
| } |
| |
| void HttpTransactionShell::OnInitUploadComplete(int result) { |
| DCHECK_EQ(state_, STATE_STREAM_INITIALIZED); |
| DCHECK(!callback_.is_null()); |
| if (result != OK) { |
| SetFailed(result); |
| result_ = result; |
| return; |
| } |
| net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST); |
| BuildRequestHeaders(); |
| result = stream_->SendRequest(request_headers_, &response_, |
| send_request_callback_); |
| |
| if (result != ERR_IO_PENDING) { |
| OnSendRequestComplete(result); |
| return; |
| } |
| pending_callback_ = true; |
| result_ = result; |
| } |
| |
| void HttpTransactionShell::OnSendRequestComplete(int result) { |
| DCHECK_EQ(state_, STATE_STREAM_INITIALIZED); |
| DCHECK(!callback_.is_null()); |
| state_ = STATE_REQUEST_SENT; |
| net_log_.EndEventWithNetErrorCode( |
| NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, result); |
| if (result != OK) { |
| SetFailed(result); |
| result_ = result; |
| return; |
| } |
| // Read header |
| net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS); |
| result = stream_->ReadResponseHeaders(read_response_headers_callback_); |
| if (result != ERR_IO_PENDING) { |
| OnReadResponseHeadersComplete(result); |
| return; |
| } |
| pending_callback_ = true; |
| result_ = result; |
| } |
| |
| void HttpTransactionShell::OnReadResponseHeadersComplete(int result) { |
| DCHECK_EQ(state_, STATE_REQUEST_SENT); |
| DCHECK(!callback_.is_null()); |
| state_ = STATE_RESPONSE_HEADERS_RECEIVED; |
| net_log_.EndEventWithNetErrorCode( |
| NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, 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) { |
| } |
| |
| |
| if (result != OK) { |
| SetFailed(result); |
| return; |
| } |
| |
| net_log_.AddEvent( |
| NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS, |
| base::Bind(&HttpResponseHeaders::NetLogCallback, response_.headers)); |
| |
| // This is the last step we need for Start(), Perform callback |
| // if needed, and reset it. |
| DoCallback(result); |
| } |
| |
| // Restarts the HTTP transaction, ignoring the last error. |
| int HttpTransactionShell::RestartIgnoringLastError( |
| const CompletionCallback& callback) { |
| // StreamLoader objects should handle this case |
| NOTREACHED(); |
| return ERR_UNEXPECTED; |
| } |
| |
| // Restarts the HTTP transaction with a client certificate. |
| int HttpTransactionShell::RestartWithCertificate(X509Certificate* client_cert, |
| const CompletionCallback& callback) { |
| // StreamLoader objects should handle this case |
| NOTREACHED(); |
| return ERR_UNEXPECTED; |
| } |
| |
| // Restarts the HTTP transaction with authentication credentials. |
| int HttpTransactionShell::RestartWithAuth(const AuthCredentials& credentials, |
| const CompletionCallback& callback) { |
| // StreamLoader objects should handle this case |
| NOTREACHED(); |
| return ERR_UNEXPECTED; |
| } |
| |
| // Returns true if auth is ready to be continued. |
| bool HttpTransactionShell::IsReadyToRestartForAuth() { |
| // No need to restart, hence always returning false here. |
| return false; |
| } |
| |
| // Once response info is available for the transaction, response data may be |
| // read by calling this method. |
| int HttpTransactionShell::Read(IOBuffer* buf, int buf_len, |
| const CompletionCallback& callback) { |
| // No data to return |
| if (state_ == STATE_DONE_READING) |
| return OK; |
| |
| DCHECK_EQ(state_, STATE_RESPONSE_HEADERS_RECEIVED); |
| |
| DCHECK(stream_.get()); |
| net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_READ_BODY); |
| int result = stream_->ReadResponseBody(buf, buf_len, |
| read_response_body_callback_); |
| // If not IO pending, continue |
| if (result != ERR_IO_PENDING) { |
| OnReadResponseBodyComplete(result); |
| return result_; |
| } |
| callback_ = callback; |
| pending_callback_ = true; |
| return result; // IO_PENDING |
| } |
| |
| void HttpTransactionShell::OnReadResponseBodyComplete(int result) { |
| DCHECK_EQ(state_, STATE_RESPONSE_HEADERS_RECEIVED); |
| net_log_.EndEventWithNetErrorCode( |
| NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, result); |
| result_ = result; |
| if (result < 0) { |
| DCHECK_NE(result, ERR_IO_PENDING); |
| SetFailed(result); |
| return; |
| } |
| // If reading is done, update state before callback. |
| if (stream_->IsResponseBodyComplete()) { |
| DoneReading(); |
| } |
| |
| DoCallback(result); |
| } |
| |
| // Stops further caching of this request by the HTTP cache, if there is any. |
| void HttpTransactionShell::StopCaching() { |
| NOTIMPLEMENTED(); |
| } |
| |
| // Called to tell the transaction that we have successfully reached the end |
| // of the stream. |
| void HttpTransactionShell::DoneReading() { |
| if (state_ != STATE_FAILED && state_ != STATE_DONE_READING) { |
| state_ = STATE_DONE_READING; |
| CloseStream(); |
| read_buf_ = NULL; |
| read_buf_len_ = 0; |
| } |
| } |
| |
| // Returns the response info for this transaction or NULL if the response |
| // info is not available. |
| const HttpResponseInfo* HttpTransactionShell::GetResponseInfo() const { |
| return &response_; |
| } |
| |
| // Returns the load state for this transaction. |
| LoadState HttpTransactionShell::GetLoadState() const { |
| switch (state_) { |
| case STATE_CREATED: |
| return LOAD_STATE_IDLE; |
| case STATE_STREAM_INITIALIZED: |
| return LOAD_STATE_SENDING_REQUEST; |
| case STATE_REQUEST_SENT: |
| return LOAD_STATE_WAITING_FOR_RESPONSE; |
| case STATE_RESPONSE_HEADERS_RECEIVED: |
| return LOAD_STATE_READING_RESPONSE; |
| case STATE_DONE_READING: |
| return LOAD_STATE_IDLE; |
| case STATE_FAILED: |
| return LOAD_STATE_IDLE; |
| } |
| return LOAD_STATE_IDLE; |
| } |
| |
| // Returns the upload progress in bytes. If there is no upload data, |
| // zero will be returned. This does not include the request headers. |
| UploadProgress HttpTransactionShell::GetUploadProgress() const { |
| if (!request_->upload_data_stream) |
| return UploadProgress(); |
| |
| return UploadProgress(request_->upload_data_stream->position(), |
| request_->upload_data_stream->size()); |
| } |
| |
| int HttpTransactionShell::ResolveProxy() { |
| if (request_->load_flags & LOAD_BYPASS_PROXY) { |
| proxy_info_.UseDirect(); |
| return OK; |
| } |
| |
| return params_->proxy_service->ResolveProxy(request_->url, &proxy_info_, |
| resolve_proxy_callback_, NULL, net_log_); |
| } |
| |
| void HttpTransactionShell::BuildRequestHeaders() { |
| request_headers_.SetHeader(HttpRequestHeaders::kHost, |
| GetHostAndOptionalPort(request_->url)); |
| |
| 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"); |
| } |
| request_headers_.MergeFrom(request_->extra_headers); |
| } |
| |
| void HttpTransactionShell::SetFailed(int result) { |
| // Real Error happens |
| state_ = STATE_FAILED; |
| CloseStream(); |
| DoCallback(result); |
| } |
| |
| void HttpTransactionShell::CloseStream() { |
| stream_->Close(true); |
| stream_.reset(); |
| } |
| |
| void HttpTransactionShell::DoCallback(int result) { |
| if (pending_callback_) { |
| DCHECK(current_message_loop_->BelongsToCurrentThread()); |
| |
| DCHECK(!callback_.is_null()); |
| CompletionCallback c = callback_; |
| callback_.Reset(); |
| pending_callback_ = false; |
| c.Run(result); |
| } |
| } |
| |
| } // namespace net |