blob: 20b09eba778d5dff4af83b7f6d043f1310e01d30 [file] [log] [blame]
// 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