<commit_before>// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/http/http_network_transaction.h"

#include "base/scoped_ptr.h"
#include "base/compiler_specific.h"
#include "base/field_trial.h"
#include "base/string_util.h"
#include "base/trace_event.h"
#include "build/build_config.h"
#include "net/base/client_socket_factory.h"
#include "net/base/connection_type_histograms.h"
#include "net/base/dns_resolution_observer.h"
#include "net/base/host_resolver.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/base/net_util.h"
#include "net/base/ssl_client_socket.h"
#include "net/base/upload_data_stream.h"
#include "net/http/http_auth.h"
#include "net/http/http_auth_handler.h"
#include "net/http/http_chunked_decoder.h"
#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_util.h"

using base::Time;

namespace net {

//-----------------------------------------------------------------------------

HttpNetworkTransaction::HttpNetworkTransaction(HttpNetworkSession* session,
                                               ClientSocketFactory* csf)
    : pending_auth_target_(HttpAuth::AUTH_NONE),
      ALLOW_THIS_IN_INITIALIZER_LIST(
          io_callback_(this, &HttpNetworkTransaction::OnIOComplete)),
      user_callback_(NULL),
      session_(session),
      request_(NULL),
      pac_request_(NULL),
      socket_factory_(csf),
      connection_(session->connection_pool()),
      reused_socket_(false),
      using_ssl_(false),
      using_proxy_(false),
      using_tunnel_(false),
      establishing_tunnel_(false),
      request_headers_bytes_sent_(0),
      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) {
#if defined(OS_WIN)
  // TODO(port): Port the SSLConfigService class to Linux and Mac OS X.
  session->ssl_config_service()->GetSSLConfig(&ssl_config_);
#endif
}

int HttpNetworkTransaction::Start(const HttpRequestInfo* request_info,
                                  CompletionCallback* callback) {
  UpdateConnectionTypeHistograms(CONNECTION_ANY);

  request_ = request_info;
  start_time_ = base::Time::Now();

  next_state_ = STATE_RESOLVE_PROXY;
  int rv = DoLoop(OK);
  if (rv == ERR_IO_PENDING)
    user_callback_ = callback;
  return rv;
}

int HttpNetworkTransaction::RestartIgnoringLastError(
    CompletionCallback* callback) {
  // TODO(wtc): If the connection is no longer alive, call
  // connection_.socket()->ReconnectIgnoringLastError().
  next_state_ = STATE_WRITE_HEADERS;
  int rv = DoLoop(OK);
  if (rv == ERR_IO_PENDING)
    user_callback_ = callback;
  return rv;
}

int HttpNetworkTransaction::RestartWithAuth(
    const std::wstring& username,
    const std::wstring& password,
    CompletionCallback* callback) {
  HttpAuth::Target target = pending_auth_target_;
  if (target == HttpAuth::AUTH_NONE) {
    NOTREACHED();
    return ERR_UNEXPECTED;
  }

  pending_auth_target_ = HttpAuth::AUTH_NONE;

  DCHECK(auth_identity_[target].invalid ||
         (username.empty() && password.empty()));

  if (auth_identity_[target].invalid) {
    // Update the username/password.
    auth_identity_[target].source = HttpAuth::IDENT_SRC_EXTERNAL;
    auth_identity_[target].invalid = false;
    auth_identity_[target].username = username;
    auth_identity_[target].password = password;
  }

  PrepareForAuthRestart(target);

  DCHECK(user_callback_ == NULL);
  int rv = DoLoop(OK);
  if (rv == ERR_IO_PENDING)
    user_callback_ = callback;

  return rv;
}

void HttpNetworkTransaction::PrepareForAuthRestart(HttpAuth::Target target) {
  DCHECK(HaveAuth(target));
  DCHECK(auth_identity_[target].source != HttpAuth::IDENT_SRC_PATH_LOOKUP);

  // Add the auth entry to the cache before restarting. We don't know whether
  // the identity is valid yet, but if it is valid we want other transactions
  // to know about it. If an entry for (origin, handler->realm()) already
  // exists, we update it.
  //
  // If auth_identity_[target].source is HttpAuth::IDENT_SRC_NONE,
  // auth_identity_[target] contains no identity because identity is not
  // required yet.
  if (auth_identity_[target].source != HttpAuth::IDENT_SRC_NONE) {
    session_->auth_cache()->Add(AuthOrigin(target), auth_handler_[target],
        auth_identity_[target].username, auth_identity_[target].password,
        AuthPath(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()) {
      next_state_ = STATE_DRAIN_BODY_FOR_AUTH_RESTART;
      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.
  }

  // If the auth scheme is connection-based but the proxy/server mistakenly
  // marks the connection as not keep-alive, the auth is going to fail, so log
  // an error message.
  if (!keep_alive && auth_handler_[target]->is_connection_based() &&
      auth_identity_[target].source != HttpAuth::IDENT_SRC_NONE) {
    std::string auth_target(target == HttpAuth::AUTH_PROXY ?
                            "proxy" : "server");
    LOG(ERROR) << "Can't perform " << auth_handler_[target]->scheme()
               << " auth to the " << auth_target << " "
               << AuthOrigin(target).spec()
               << " over a non-keep-alive connection";

    HttpVersion http_version = response_.headers->GetHttpVersion();
    LOG(ERROR) << "  HTTP version is " << http_version.major_value() << "."
               << http_version.minor_value();

    std::string connection_val;
    void* iter = NULL;
    while (response_.headers->EnumerateHeader(&iter, "connection",
                                              &connection_val)) {
      LOG(ERROR) << "  Has header Connection: " << connection_val;
    }

    iter = NULL;
    while (response_.headers->EnumerateHeader(&iter, "proxy-connection",
                                              &connection_val)) {
      LOG(ERROR) << "  Has header Proxy-Connection: " << connection_val;
    }
  }

  // We don't need to drain the response body, so we act as if we had drained
  // the response body.
  DidDrainBodyForAuthRestart(keep_alive);
}

void HttpNetworkTransaction::DidDrainBodyForAuthRestart(bool keep_alive) {
  if (keep_alive) {
    next_state_ = STATE_WRITE_HEADERS;
    reused_socket_ = true;
  } else {
    next_state_ = STATE_INIT_CONNECTION;
    connection_.set_socket(NULL);
    connection_.Reset();
  }

  // Reset the other member variables.
  ResetStateForRestart();
}

int HttpNetworkTransaction::Read(IOBuffer* buf, int buf_len,
                                 CompletionCallback* callback) {
  DCHECK(response_.headers);
  DCHECK(buf);
  DCHECK(buf_len > 0);

  if (!connection_.is_initialized())
    return 0;  // connection_ has been reset.  Treat like EOF.

  if (establishing_tunnel_) {
    // We're trying to read the body of the response but we're still trying to
    // establish an SSL tunnel through the proxy.  We can't read these bytes
    // when establishing a tunnel because they might be controlled by an active
    // network attacker.  We don't worry about this for HTTP because an active
    // network attacker can already control HTTP sessions.
    // We reach this case when the user cancels a 407 proxy auth prompt.
    // See http://crbug.com/8473
    DCHECK(response_.headers->response_code() == 407);
    LogBlockedTunnelResponse(response_.headers->response_code());
    return ERR_TUNNEL_CONNECTION_FAILED;
  }

  read_buf_ = buf;
  read_buf_len_ = buf_len;

  next_state_ = STATE_READ_BODY;
  int rv = DoLoop(OK);
  if (rv == ERR_IO_PENDING)
    user_callback_ = callback;
  return rv;
}

const HttpResponseInfo* HttpNetworkTransaction::GetResponseInfo() const {
  return (response_.headers || response_.ssl_info.cert) ? &response_ : NULL;
}

LoadState HttpNetworkTransaction::GetLoadState() const {
  // TODO(wtc): Define a new LoadState value for the
  // STATE_INIT_CONNECTION_COMPLETE state, which delays the HTTP request.
  switch (next_state_) {
    case STATE_RESOLVE_PROXY_COMPLETE:
      return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
    case STATE_RESOLVE_HOST_COMPLETE:
      return LOAD_STATE_RESOLVING_HOST;
    case STATE_CONNECT_COMPLETE:
      return LOAD_STATE_CONNECTING;
    case STATE_WRITE_HEADERS_COMPLETE:
    case STATE_WRITE_BODY_COMPLETE:
      return LOAD_STATE_SENDING_REQUEST;
    case STATE_READ_HEADERS_COMPLETE:
      return LOAD_STATE_WAITING_FOR_RESPONSE;
    case STATE_READ_BODY_COMPLETE:
      return LOAD_STATE_READING_RESPONSE;
    default:
      return LOAD_STATE_IDLE;
  }
}

uint64 HttpNetworkTransaction::GetUploadProgress() const {
  if (!request_body_stream_.get())
    return 0;

  return request_body_stream_->position();
}

HttpNetworkTransaction::~HttpNetworkTransaction() {
  // If we still have an open socket, then make sure to close it so we don't
  // try to reuse it later on.
  if (connection_.is_initialized())
    connection_.set_socket(NULL);

  if (pac_request_)
    session_->proxy_service()->CancelPacRequest(pac_request_);
}

void HttpNetworkTransaction::BuildRequestHeaders() {
  // For proxy use the full url. Otherwise just the absolute path.
  // This strips out any reference/username/password.
  std::string path = using_proxy_ ?
      HttpUtil::SpecForRequest(request_->url) :
      HttpUtil::PathForRequest(request_->url);

  request_headers_ = request_->method + " " + path +
      " HTTP/1.1\r\nHost: " + request_->url.host();
  if (request_->url.IntPort() != -1)
    request_headers_ += ":" + request_->url.port();
  request_headers_ += "\r\n";

  // For compat with HTTP/1.0 servers and proxies:
  if (using_proxy_)
    request_headers_ += "Proxy-";
  request_headers_ += "Connection: keep-alive\r\n";

  if (!request_->user_agent.empty())
    request_headers_ += "User-Agent: " + request_->user_agent + "\r\n";

  // Our consumer should have made sure that this is a safe referrer.  See for
  // instance WebCore::FrameLoader::HideReferrer.
  if (request_->referrer.is_valid())
    request_headers_ += "Referer: " + request_->referrer.spec() + "\r\n";

  // Add a content length header?
  if (request_->upload_data) {
    request_body_stream_.reset(new UploadDataStream(request_->upload_data));
    request_headers_ +=
        "Content-Length: " + Uint64ToString(request_body_stream_->size()) +
        "\r\n";
  } 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_ += "Content-Length: 0\r\n";
  }

  // Honor load flags that impact proxy caches.
  if (request_->load_flags & LOAD_BYPASS_CACHE) {
    request_headers_ += "Pragma: no-cache\r\nCache-Control: no-cache\r\n";
  } else if (request_->load_flags & LOAD_VALIDATE_CACHE) {
    request_headers_ += "Cache-Control: max-age=0\r\n";
  }

  ApplyAuth();

  // TODO(darin): Need to prune out duplicate headers.

  request_headers_ += request_->extra_headers;
  request_headers_ += "\r\n";
}

// The HTTP CONNECT method for establishing a tunnel connection is documented
// in draft-luotonen-web-proxy-tunneling-01.txt and RFC 2817, Sections 5.2 and
// 5.3.
void HttpNetworkTransaction::BuildTunnelRequest() {
  // RFC 2616 Section 9 says the Host request-header field MUST accompany all
  // HTTP/1.1 requests.
  request_headers_ = StringPrintf("CONNECT %s:%d HTTP/1.1\r\n",
      request_->url.host().c_str(), request_->url.EffectiveIntPort());
  request_headers_ += "Host: " + request_->url.host();
  if (request_->url.has_port())
    request_headers_ += ":" + request_->url.port();
  request_headers_ += "\r\n";

  if (!request_->user_agent.empty())
    request_headers_ += "User-Agent: " + request_->user_agent + "\r\n";

  ApplyAuth();

  request_headers_ += "\r\n";
}

void HttpNetworkTransaction::DoCallback(int rv) {
  DCHECK(rv != ERR_IO_PENDING);
  DCHECK(user_callback_);

  // Since Run may result in Read being called, clear user_callback_ up front.
  CompletionCallback* c = user_callback_;
  user_callback_ = NULL;
  c->Run(rv);
}

void HttpNetworkTransaction::OnIOComplete(int result) {
  int rv = DoLoop(result);
  if (rv != ERR_IO_PENDING)
    DoCallback(rv);
}

int HttpNetworkTransaction::DoLoop(int result) {
  DCHECK(next_state_ != STATE_NONE);

  int rv = result;
  do {
    State state = next_state_;
    next_state_ = STATE_NONE;
    switch (state) {
      case STATE_RESOLVE_PROXY:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.resolve_proxy", request_, request_->url.spec());
        rv = DoResolveProxy();
        break;
      case STATE_RESOLVE_PROXY_COMPLETE:
        rv = DoResolveProxyComplete(rv);
        TRACE_EVENT_END("http.resolve_proxy", request_, request_->url.spec());
        break;
      case STATE_INIT_CONNECTION:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.init_conn", request_, request_->url.spec());
        rv = DoInitConnection();
        break;
      case STATE_INIT_CONNECTION_COMPLETE:
        rv = DoInitConnectionComplete(rv);
        TRACE_EVENT_END("http.init_conn", request_, request_->url.spec());
        break;
      case STATE_RESOLVE_HOST:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.resolve_host", request_, request_->url.spec());
        rv = DoResolveHost();
        break;
      case STATE_RESOLVE_HOST_COMPLETE:
        rv = DoResolveHostComplete(rv);
        TRACE_EVENT_END("http.resolve_host", request_, request_->url.spec());
        break;
      case STATE_CONNECT:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.connect", request_, request_->url.spec());
        rv = DoConnect();
        break;
      case STATE_CONNECT_COMPLETE:
        rv = DoConnectComplete(rv);
        TRACE_EVENT_END("http.connect", request_, request_->url.spec());
        break;
      case STATE_SSL_CONNECT_OVER_TUNNEL:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.ssl_tunnel", request_, request_->url.spec());
        rv = DoSSLConnectOverTunnel();
        break;
      case STATE_SSL_CONNECT_OVER_TUNNEL_COMPLETE:
        rv = DoSSLConnectOverTunnelComplete(rv);
        TRACE_EVENT_END("http.ssl_tunnel", request_, request_->url.spec());
        break;
      case STATE_WRITE_HEADERS:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.write_headers", request_, request_->url.spec());
        rv = DoWriteHeaders();
        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());
        break;
      case STATE_READ_HEADERS:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.read_headers", request_, request_->url.spec());
        rv = DoReadHeaders();
        break;
      case STATE_READ_HEADERS_COMPLETE:
        rv = DoReadHeadersComplete(rv);
        TRACE_EVENT_END("http.read_headers", request_, request_->url.spec());
        break;
      case STATE_READ_BODY:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.read_body", request_, request_->url.spec());
        rv = DoReadBody();
        break;
      case STATE_READ_BODY_COMPLETE:
        rv = DoReadBodyComplete(rv);
        TRACE_EVENT_END("http.read_body", request_, request_->url.spec());
        break;
      case STATE_DRAIN_BODY_FOR_AUTH_RESTART:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.drain_body_for_auth_restart",
                          request_, request_->url.spec());
        rv = DoDrainBodyForAuthRestart();
        break;
      case STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE:
        rv = DoDrainBodyForAuthRestartComplete(rv);
        TRACE_EVENT_END("http.drain_body_for_auth_restart",
                        request_, request_->url.spec());
        break;
      default:
        NOTREACHED() << "bad state";
        rv = ERR_FAILED;
        break;
    }
  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);

  return rv;
}

int HttpNetworkTransaction::DoResolveProxy() {
  DCHECK(!pac_request_);

  next_state_ = STATE_RESOLVE_PROXY_COMPLETE;

  if (request_->load_flags & LOAD_BYPASS_PROXY) {
    proxy_info_.UseDirect();
    return OK;
  }

  return session_->proxy_service()->ResolveProxy(
      request_->url, &proxy_info_, &io_callback_, &pac_request_);
}

int HttpNetworkTransaction::DoResolveProxyComplete(int result) {
  next_state_ = STATE_INIT_CONNECTION;

  // Since we only support HTTP proxies or DIRECT connections, remove
  // any other type of proxy from the list (i.e. SOCKS).
  // Supporting SOCKS is issue http://crbug.com/469.
  proxy_info_.RemoveProxiesWithoutScheme(
      ProxyServer::SCHEME_DIRECT | ProxyServer::SCHEME_HTTP);

  pac_request_ = NULL;

  if (result != OK) {
    DLOG(ERROR) << "Failed to resolve proxy: " << result;
    proxy_info_.UseDirect();
  }
  return OK;
}

int HttpNetworkTransaction::DoInitConnection() {
  DCHECK(!connection_.is_initialized());

  next_state_ = STATE_INIT_CONNECTION_COMPLETE;

  using_ssl_ = request_->url.SchemeIs("https");
  using_proxy_ = !proxy_info_.is_direct() && !using_ssl_;
  using_tunnel_ = !proxy_info_.is_direct() && using_ssl_;

  // Build the string used to uniquely identify connections of this type.
  std::string connection_group;
  if (using_proxy_ || using_tunnel_)
    connection_group = "proxy/" + proxy_info_.proxy_server().ToURI() + "/";
  if (!using_proxy_)
    connection_group.append(request_->url.GetOrigin().spec());

  DCHECK(!connection_group.empty());
  return connection_.Init(connection_group, request_->priority, &io_callback_);
}

int HttpNetworkTransaction::DoInitConnectionComplete(int result) {
  if (result < 0)
    return result;

  DCHECK(connection_.is_initialized());

  // Set the reused_socket_ flag to indicate that we are using a keep-alive
  // connection.  This flag is used to handle errors that occur while we are
  // trying to reuse a keep-alive connection.
  reused_socket_ = (connection_.socket() != NULL);
  if (reused_socket_) {
    next_state_ = STATE_WRITE_HEADERS;
  } else {
    next_state_ = STATE_RESOLVE_HOST;
  }
  return OK;
}

int HttpNetworkTransaction::DoResolveHost() {
  next_state_ = STATE_RESOLVE_HOST_COMPLETE;

  std::string host;
  int port;

  // Determine the host and port to connect to.
  if (using_proxy_ || using_tunnel_) {
    ProxyServer proxy_server = proxy_info_.proxy_server();
    host = proxy_server.host();
    port = proxy_server.port();
  } else {
    // Direct connection
    host = request_->url.host();
    port = request_->url.EffectiveIntPort();
  }

  DidStartDnsResolution(host, this);
  return resolver_.Resolve(host, port, &addresses_, &io_callback_);
}

int HttpNetworkTransaction::DoResolveHostComplete(int result) {
  bool ok = (result == OK);
  DidFinishDnsResolutionWithStatus(ok, request_->referrer, this);
  if (ok) {
    next_state_ = STATE_CONNECT;
  } else {
    result = ReconsiderProxyAfterError(result);
  }
  return result;
}

int HttpNetworkTransaction::DoConnect() {
  next_state_ = STATE_CONNECT_COMPLETE;

  DCHECK(!connection_.socket());

  ClientSocket* s = socket_factory_->CreateTCPClientSocket(addresses_);

  // If we are using a direct SSL connection, then go ahead and create the SSL
  // wrapper socket now.  Otherwise, we need to first issue a CONNECT request.
  if (using_ssl_ && !using_tunnel_)
    s = socket_factory_->CreateSSLClientSocket(s, request_->url.host(),
                                               ssl_config_);

  connection_.set_socket(s);
  return connection_.socket()->Connect(&io_callback_);
}

int HttpNetworkTransaction::DoConnectComplete(int result) {
  if (IsCertificateError(result))
    result = HandleCertificateError(result);

  if (result == OK) {
    next_state_ = STATE_WRITE_HEADERS;
    if (using_tunnel_)
      establishing_tunnel_ = true;
  } else {
    result = HandleSSLHandshakeError(result);
    if (result != OK)
      result = ReconsiderProxyAfterError(result);
  }
  return result;
}

int HttpNetworkTransaction::DoSSLConnectOverTunnel() {
  next_state_ = STATE_SSL_CONNECT_OVER_TUNNEL_COMPLETE;

  // Add a SSL socket on top of our existing transport socket.
  ClientSocket* s = connection_.release_socket();
  s = socket_factory_->CreateSSLClientSocket(s, request_->url.host(),
                                             ssl_config_);
  connection_.set_socket(s);
  return connection_.socket()->Connect(&io_callback_);
}

int HttpNetworkTransaction::DoSSLConnectOverTunnelComplete(int result) {
  if (IsCertificateError(result))
    result = HandleCertificateError(result);

  if (result == OK) {
    next_state_ = STATE_WRITE_HEADERS;
  } else {
    result = HandleSSLHandshakeError(result);
  }
  return result;
}

int HttpNetworkTransaction::DoWriteHeaders() {
  next_state_ = STATE_WRITE_HEADERS_COMPLETE;

  // This is constructed lazily (instead of within our Start method), so that
  // we have proxy info available.
  if (request_headers_.empty()) {
    if (establishing_tunnel_) {
      BuildTunnelRequest();
    } else {
      BuildRequestHeaders();
    }
  }

  // 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();
  }

  const char* buf = request_headers_.data() + request_headers_bytes_sent_;
  int buf_len = static_cast<int>(request_headers_.size() -
                                 request_headers_bytes_sent_);
  DCHECK(buf_len > 0);

  return connection_.socket()->Write(buf, buf_len, &io_callback_);
}

int HttpNetworkTransaction::DoWriteHeadersComplete(int result) {
  if (result < 0)
    return HandleIOError(result);

  request_headers_bytes_sent_ += result;
  if (request_headers_bytes_sent_ < request_headers_.size()) {
    next_state_ = STATE_WRITE_HEADERS;
  } else if (!establishing_tunnel_ && request_->upload_data) {
    next_state_ = STATE_WRITE_BODY;
  } else {
    next_state_ = STATE_READ_HEADERS;
  }
  return OK;
}

int HttpNetworkTransaction::DoWriteBody() {
  next_state_ = STATE_WRITE_BODY_COMPLETE;

  DCHECK(request_->upload_data);
  DCHECK(request_body_stream_.get());

  const char* buf = request_body_stream_->buf();
  int buf_len = static_cast<int>(request_body_stream_->buf_len());

  return connection_.socket()->Write(buf, buf_len, &io_callback_);
}

int HttpNetworkTransaction::DoWriteBodyComplete(int result) {
  if (result < 0)
    return HandleIOError(result);

  request_body_stream_->DidConsume(result);
<commit_msg>After r12485, every POST request has an UploadData, even if no data needs to be uploaded, so that we can set the identifier field in UploadData.<commit_after>// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/http/http_network_transaction.h"

#include "base/scoped_ptr.h"
#include "base/compiler_specific.h"
#include "base/field_trial.h"
#include "base/string_util.h"
#include "base/trace_event.h"
#include "build/build_config.h"
#include "net/base/client_socket_factory.h"
#include "net/base/connection_type_histograms.h"
#include "net/base/dns_resolution_observer.h"
#include "net/base/host_resolver.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/base/net_util.h"
#include "net/base/ssl_client_socket.h"
#include "net/base/upload_data_stream.h"
#include "net/http/http_auth.h"
#include "net/http/http_auth_handler.h"
#include "net/http/http_chunked_decoder.h"
#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_util.h"

using base::Time;

namespace net {

//-----------------------------------------------------------------------------

HttpNetworkTransaction::HttpNetworkTransaction(HttpNetworkSession* session,
                                               ClientSocketFactory* csf)
    : pending_auth_target_(HttpAuth::AUTH_NONE),
      ALLOW_THIS_IN_INITIALIZER_LIST(
          io_callback_(this, &HttpNetworkTransaction::OnIOComplete)),
      user_callback_(NULL),
      session_(session),
      request_(NULL),
      pac_request_(NULL),
      socket_factory_(csf),
      connection_(session->connection_pool()),
      reused_socket_(false),
      using_ssl_(false),
      using_proxy_(false),
      using_tunnel_(false),
      establishing_tunnel_(false),
      request_headers_bytes_sent_(0),
      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) {
#if defined(OS_WIN)
  // TODO(port): Port the SSLConfigService class to Linux and Mac OS X.
  session->ssl_config_service()->GetSSLConfig(&ssl_config_);
#endif
}

int HttpNetworkTransaction::Start(const HttpRequestInfo* request_info,
                                  CompletionCallback* callback) {
  UpdateConnectionTypeHistograms(CONNECTION_ANY);

  request_ = request_info;
  start_time_ = base::Time::Now();

  next_state_ = STATE_RESOLVE_PROXY;
  int rv = DoLoop(OK);
  if (rv == ERR_IO_PENDING)
    user_callback_ = callback;
  return rv;
}

int HttpNetworkTransaction::RestartIgnoringLastError(
    CompletionCallback* callback) {
  // TODO(wtc): If the connection is no longer alive, call
  // connection_.socket()->ReconnectIgnoringLastError().
  next_state_ = STATE_WRITE_HEADERS;
  int rv = DoLoop(OK);
  if (rv == ERR_IO_PENDING)
    user_callback_ = callback;
  return rv;
}

int HttpNetworkTransaction::RestartWithAuth(
    const std::wstring& username,
    const std::wstring& password,
    CompletionCallback* callback) {
  HttpAuth::Target target = pending_auth_target_;
  if (target == HttpAuth::AUTH_NONE) {
    NOTREACHED();
    return ERR_UNEXPECTED;
  }

  pending_auth_target_ = HttpAuth::AUTH_NONE;

  DCHECK(auth_identity_[target].invalid ||
         (username.empty() && password.empty()));

  if (auth_identity_[target].invalid) {
    // Update the username/password.
    auth_identity_[target].source = HttpAuth::IDENT_SRC_EXTERNAL;
    auth_identity_[target].invalid = false;
    auth_identity_[target].username = username;
    auth_identity_[target].password = password;
  }

  PrepareForAuthRestart(target);

  DCHECK(user_callback_ == NULL);
  int rv = DoLoop(OK);
  if (rv == ERR_IO_PENDING)
    user_callback_ = callback;

  return rv;
}

void HttpNetworkTransaction::PrepareForAuthRestart(HttpAuth::Target target) {
  DCHECK(HaveAuth(target));
  DCHECK(auth_identity_[target].source != HttpAuth::IDENT_SRC_PATH_LOOKUP);

  // Add the auth entry to the cache before restarting. We don't know whether
  // the identity is valid yet, but if it is valid we want other transactions
  // to know about it. If an entry for (origin, handler->realm()) already
  // exists, we update it.
  //
  // If auth_identity_[target].source is HttpAuth::IDENT_SRC_NONE,
  // auth_identity_[target] contains no identity because identity is not
  // required yet.
  if (auth_identity_[target].source != HttpAuth::IDENT_SRC_NONE) {
    session_->auth_cache()->Add(AuthOrigin(target), auth_handler_[target],
        auth_identity_[target].username, auth_identity_[target].password,
        AuthPath(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()) {
      next_state_ = STATE_DRAIN_BODY_FOR_AUTH_RESTART;
      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.
  }

  // If the auth scheme is connection-based but the proxy/server mistakenly
  // marks the connection as not keep-alive, the auth is going to fail, so log
  // an error message.
  if (!keep_alive && auth_handler_[target]->is_connection_based() &&
      auth_identity_[target].source != HttpAuth::IDENT_SRC_NONE) {
    std::string auth_target(target == HttpAuth::AUTH_PROXY ?
                            "proxy" : "server");
    LOG(ERROR) << "Can't perform " << auth_handler_[target]->scheme()
               << " auth to the " << auth_target << " "
               << AuthOrigin(target).spec()
               << " over a non-keep-alive connection";

    HttpVersion http_version = response_.headers->GetHttpVersion();
    LOG(ERROR) << "  HTTP version is " << http_version.major_value() << "."
               << http_version.minor_value();

    std::string connection_val;
    void* iter = NULL;
    while (response_.headers->EnumerateHeader(&iter, "connection",
                                              &connection_val)) {
      LOG(ERROR) << "  Has header Connection: " << connection_val;
    }

    iter = NULL;
    while (response_.headers->EnumerateHeader(&iter, "proxy-connection",
                                              &connection_val)) {
      LOG(ERROR) << "  Has header Proxy-Connection: " << connection_val;
    }
  }

  // We don't need to drain the response body, so we act as if we had drained
  // the response body.
  DidDrainBodyForAuthRestart(keep_alive);
}

void HttpNetworkTransaction::DidDrainBodyForAuthRestart(bool keep_alive) {
  if (keep_alive) {
    next_state_ = STATE_WRITE_HEADERS;
    reused_socket_ = true;
  } else {
    next_state_ = STATE_INIT_CONNECTION;
    connection_.set_socket(NULL);
    connection_.Reset();
  }

  // Reset the other member variables.
  ResetStateForRestart();
}

int HttpNetworkTransaction::Read(IOBuffer* buf, int buf_len,
                                 CompletionCallback* callback) {
  DCHECK(response_.headers);
  DCHECK(buf);
  DCHECK(buf_len > 0);

  if (!connection_.is_initialized())
    return 0;  // connection_ has been reset.  Treat like EOF.

  if (establishing_tunnel_) {
    // We're trying to read the body of the response but we're still trying to
    // establish an SSL tunnel through the proxy.  We can't read these bytes
    // when establishing a tunnel because they might be controlled by an active
    // network attacker.  We don't worry about this for HTTP because an active
    // network attacker can already control HTTP sessions.
    // We reach this case when the user cancels a 407 proxy auth prompt.
    // See http://crbug.com/8473
    DCHECK(response_.headers->response_code() == 407);
    LogBlockedTunnelResponse(response_.headers->response_code());
    return ERR_TUNNEL_CONNECTION_FAILED;
  }

  read_buf_ = buf;
  read_buf_len_ = buf_len;

  next_state_ = STATE_READ_BODY;
  int rv = DoLoop(OK);
  if (rv == ERR_IO_PENDING)
    user_callback_ = callback;
  return rv;
}

const HttpResponseInfo* HttpNetworkTransaction::GetResponseInfo() const {
  return (response_.headers || response_.ssl_info.cert) ? &response_ : NULL;
}

LoadState HttpNetworkTransaction::GetLoadState() const {
  // TODO(wtc): Define a new LoadState value for the
  // STATE_INIT_CONNECTION_COMPLETE state, which delays the HTTP request.
  switch (next_state_) {
    case STATE_RESOLVE_PROXY_COMPLETE:
      return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
    case STATE_RESOLVE_HOST_COMPLETE:
      return LOAD_STATE_RESOLVING_HOST;
    case STATE_CONNECT_COMPLETE:
      return LOAD_STATE_CONNECTING;
    case STATE_WRITE_HEADERS_COMPLETE:
    case STATE_WRITE_BODY_COMPLETE:
      return LOAD_STATE_SENDING_REQUEST;
    case STATE_READ_HEADERS_COMPLETE:
      return LOAD_STATE_WAITING_FOR_RESPONSE;
    case STATE_READ_BODY_COMPLETE:
      return LOAD_STATE_READING_RESPONSE;
    default:
      return LOAD_STATE_IDLE;
  }
}

uint64 HttpNetworkTransaction::GetUploadProgress() const {
  if (!request_body_stream_.get())
    return 0;

  return request_body_stream_->position();
}

HttpNetworkTransaction::~HttpNetworkTransaction() {
  // If we still have an open socket, then make sure to close it so we don't
  // try to reuse it later on.
  if (connection_.is_initialized())
    connection_.set_socket(NULL);

  if (pac_request_)
    session_->proxy_service()->CancelPacRequest(pac_request_);
}

void HttpNetworkTransaction::BuildRequestHeaders() {
  // For proxy use the full url. Otherwise just the absolute path.
  // This strips out any reference/username/password.
  std::string path = using_proxy_ ?
      HttpUtil::SpecForRequest(request_->url) :
      HttpUtil::PathForRequest(request_->url);

  request_headers_ = request_->method + " " + path +
      " HTTP/1.1\r\nHost: " + request_->url.host();
  if (request_->url.IntPort() != -1)
    request_headers_ += ":" + request_->url.port();
  request_headers_ += "\r\n";

  // For compat with HTTP/1.0 servers and proxies:
  if (using_proxy_)
    request_headers_ += "Proxy-";
  request_headers_ += "Connection: keep-alive\r\n";

  if (!request_->user_agent.empty())
    request_headers_ += "User-Agent: " + request_->user_agent + "\r\n";

  // Our consumer should have made sure that this is a safe referrer.  See for
  // instance WebCore::FrameLoader::HideReferrer.
  if (request_->referrer.is_valid())
    request_headers_ += "Referer: " + request_->referrer.spec() + "\r\n";

  // Add a content length header?
  if (request_->upload_data) {
    request_body_stream_.reset(new UploadDataStream(request_->upload_data));
    request_headers_ +=
        "Content-Length: " + Uint64ToString(request_body_stream_->size()) +
        "\r\n";
  } 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_ += "Content-Length: 0\r\n";
  }

  // Honor load flags that impact proxy caches.
  if (request_->load_flags & LOAD_BYPASS_CACHE) {
    request_headers_ += "Pragma: no-cache\r\nCache-Control: no-cache\r\n";
  } else if (request_->load_flags & LOAD_VALIDATE_CACHE) {
    request_headers_ += "Cache-Control: max-age=0\r\n";
  }

  ApplyAuth();

  // TODO(darin): Need to prune out duplicate headers.

  request_headers_ += request_->extra_headers;
  request_headers_ += "\r\n";
}

// The HTTP CONNECT method for establishing a tunnel connection is documented
// in draft-luotonen-web-proxy-tunneling-01.txt and RFC 2817, Sections 5.2 and
// 5.3.
void HttpNetworkTransaction::BuildTunnelRequest() {
  // RFC 2616 Section 9 says the Host request-header field MUST accompany all
  // HTTP/1.1 requests.
  request_headers_ = StringPrintf("CONNECT %s:%d HTTP/1.1\r\n",
      request_->url.host().c_str(), request_->url.EffectiveIntPort());
  request_headers_ += "Host: " + request_->url.host();
  if (request_->url.has_port())
    request_headers_ += ":" + request_->url.port();
  request_headers_ += "\r\n";

  if (!request_->user_agent.empty())
    request_headers_ += "User-Agent: " + request_->user_agent + "\r\n";

  ApplyAuth();

  request_headers_ += "\r\n";
}

void HttpNetworkTransaction::DoCallback(int rv) {
  DCHECK(rv != ERR_IO_PENDING);
  DCHECK(user_callback_);

  // Since Run may result in Read being called, clear user_callback_ up front.
  CompletionCallback* c = user_callback_;
  user_callback_ = NULL;
  c->Run(rv);
}

void HttpNetworkTransaction::OnIOComplete(int result) {
  int rv = DoLoop(result);
  if (rv != ERR_IO_PENDING)
    DoCallback(rv);
}

int HttpNetworkTransaction::DoLoop(int result) {
  DCHECK(next_state_ != STATE_NONE);

  int rv = result;
  do {
    State state = next_state_;
    next_state_ = STATE_NONE;
    switch (state) {
      case STATE_RESOLVE_PROXY:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.resolve_proxy", request_, request_->url.spec());
        rv = DoResolveProxy();
        break;
      case STATE_RESOLVE_PROXY_COMPLETE:
        rv = DoResolveProxyComplete(rv);
        TRACE_EVENT_END("http.resolve_proxy", request_, request_->url.spec());
        break;
      case STATE_INIT_CONNECTION:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.init_conn", request_, request_->url.spec());
        rv = DoInitConnection();
        break;
      case STATE_INIT_CONNECTION_COMPLETE:
        rv = DoInitConnectionComplete(rv);
        TRACE_EVENT_END("http.init_conn", request_, request_->url.spec());
        break;
      case STATE_RESOLVE_HOST:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.resolve_host", request_, request_->url.spec());
        rv = DoResolveHost();
        break;
      case STATE_RESOLVE_HOST_COMPLETE:
        rv = DoResolveHostComplete(rv);
        TRACE_EVENT_END("http.resolve_host", request_, request_->url.spec());
        break;
      case STATE_CONNECT:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.connect", request_, request_->url.spec());
        rv = DoConnect();
        break;
      case STATE_CONNECT_COMPLETE:
        rv = DoConnectComplete(rv);
        TRACE_EVENT_END("http.connect", request_, request_->url.spec());
        break;
      case STATE_SSL_CONNECT_OVER_TUNNEL:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.ssl_tunnel", request_, request_->url.spec());
        rv = DoSSLConnectOverTunnel();
        break;
      case STATE_SSL_CONNECT_OVER_TUNNEL_COMPLETE:
        rv = DoSSLConnectOverTunnelComplete(rv);
        TRACE_EVENT_END("http.ssl_tunnel", request_, request_->url.spec());
        break;
      case STATE_WRITE_HEADERS:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.write_headers", request_, request_->url.spec());
        rv = DoWriteHeaders();
        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());
        break;
      case STATE_READ_HEADERS:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.read_headers", request_, request_->url.spec());
        rv = DoReadHeaders();
        break;
      case STATE_READ_HEADERS_COMPLETE:
        rv = DoReadHeadersComplete(rv);
        TRACE_EVENT_END("http.read_headers", request_, request_->url.spec());
        break;
      case STATE_READ_BODY:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.read_body", request_, request_->url.spec());
        rv = DoReadBody();
        break;
      case STATE_READ_BODY_COMPLETE:
        rv = DoReadBodyComplete(rv);
        TRACE_EVENT_END("http.read_body", request_, request_->url.spec());
        break;
      case STATE_DRAIN_BODY_FOR_AUTH_RESTART:
        DCHECK_EQ(OK, rv);
        TRACE_EVENT_BEGIN("http.drain_body_for_auth_restart",
                          request_, request_->url.spec());
        rv = DoDrainBodyForAuthRestart();
        break;
      case STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE:
        rv = DoDrainBodyForAuthRestartComplete(rv);
        TRACE_EVENT_END("http.drain_body_for_auth_restart",
                        request_, request_->url.spec());
        break;
      default:
        NOTREACHED() << "bad state";
        rv = ERR_FAILED;
        break;
    }
  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);

  return rv;
}

int HttpNetworkTransaction::DoResolveProxy() {
  DCHECK(!pac_request_);

  next_state_ = STATE_RESOLVE_PROXY_COMPLETE;

  if (request_->load_flags & LOAD_BYPASS_PROXY) {
    proxy_info_.UseDirect();
    return OK;
  }

  return session_->proxy_service()->ResolveProxy(
      request_->url, &proxy_info_, &io_callback_, &pac_request_);
}

int HttpNetworkTransaction::DoResolveProxyComplete(int result) {
  next_state_ = STATE_INIT_CONNECTION;

  // Since we only support HTTP proxies or DIRECT connections, remove
  // any other type of proxy from the list (i.e. SOCKS).
  // Supporting SOCKS is issue http://crbug.com/469.
  proxy_info_.RemoveProxiesWithoutScheme(
      ProxyServer::SCHEME_DIRECT | ProxyServer::SCHEME_HTTP);

  pac_request_ = NULL;

  if (result != OK) {
    DLOG(ERROR) << "Failed to resolve proxy: " << result;
    proxy_info_.UseDirect();
  }
  return OK;
}

int HttpNetworkTransaction::DoInitConnection() {
  DCHECK(!connection_.is_initialized());

  next_state_ = STATE_INIT_CONNECTION_COMPLETE;

  using_ssl_ = request_->url.SchemeIs("https");
  using_proxy_ = !proxy_info_.is_direct() && !using_ssl_;
  using_tunnel_ = !proxy_info_.is_direct() && using_ssl_;

  // Build the string used to uniquely identify connections of this type.
  std::string connection_group;
  if (using_proxy_ || using_tunnel_)
    connection_group = "proxy/" + proxy_info_.proxy_server().ToURI() + "/";
  if (!using_proxy_)
    connection_group.append(request_->url.GetOrigin().spec());

  DCHECK(!connection_group.empty());
  return connection_.Init(connection_group, request_->priority, &io_callback_);
}

int HttpNetworkTransaction::DoInitConnectionComplete(int result) {
  if (result < 0)
    return result;

  DCHECK(connection_.is_initialized());

  // Set the reused_socket_ flag to indicate that we are using a keep-alive
  // connection.  This flag is used to handle errors that occur while we are
  // trying to reuse a keep-alive connection.
  reused_socket_ = (connection_.socket() != NULL);
  if (reused_socket_) {
    next_state_ = STATE_WRITE_HEADERS;
  } else {
    next_state_ = STATE_RESOLVE_HOST;
  }
  return OK;
}

int HttpNetworkTransaction::DoResolveHost() {
  next_state_ = STATE_RESOLVE_HOST_COMPLETE;

  std::string host;
  int port;

  // Determine the host and port to connect to.
  if (using_proxy_ || using_tunnel_) {
    ProxyServer proxy_server = proxy_info_.proxy_server();
    host = proxy_server.host();
    port = proxy_server.port();
  } else {
    // Direct connection
    host = request_->url.host();
    port = request_->url.EffectiveIntPort();
  }

  DidStartDnsResolution(host, this);
  return resolver_.Resolve(host, port, &addresses_, &io_callback_);
}

int HttpNetworkTransaction::DoResolveHostComplete(int result) {
  bool ok = (result == OK);
  DidFinishDnsResolutionWithStatus(ok, request_->referrer, this);
  if (ok) {
    next_state_ = STATE_CONNECT;
  } else {
    result = ReconsiderProxyAfterError(result);
  }
  return result;
}

int HttpNetworkTransaction::DoConnect() {
  next_state_ = STATE_CONNECT_COMPLETE;

  DCHECK(!connection_.socket());

  ClientSocket* s = socket_factory_->CreateTCPClientSocket(addresses_);

  // If we are using a direct SSL connection, then go ahead and create the SSL
  // wrapper socket now.  Otherwise, we need to first issue a CONNECT request.
  if (using_ssl_ && !using_tunnel_)
    s = socket_factory_->CreateSSLClientSocket(s, request_->url.host(),
                                               ssl_config_);

  connection_.set_socket(s);
  return connection_.socket()->Connect(&io_callback_);
}

int HttpNetworkTransaction::DoConnectComplete(int result) {
  if (IsCertificateError(result))
    result = HandleCertificateError(result);

  if (result == OK) {
    next_state_ = STATE_WRITE_HEADERS;
    if (using_tunnel_)
      establishing_tunnel_ = true;
  } else {
    result = HandleSSLHandshakeError(result);
    if (result != OK)
      result = ReconsiderProxyAfterError(result);
  }
  return result;
}

int HttpNetworkTransaction::DoSSLConnectOverTunnel() {
  next_state_ = STATE_SSL_CONNECT_OVER_TUNNEL_COMPLETE;

  // Add a SSL socket on top of our existing transport socket.
  ClientSocket* s = connection_.release_socket();
  s = socket_factory_->CreateSSLClientSocket(s, request_->url.host(),
                                             ssl_config_);
  connection_.set_socket(s);
  return connection_.socket()->Connect(&io_callback_);
}

int HttpNetworkTransaction::DoSSLConnectOverTunnelComplete(int result) {
  if (IsCertificateError(result))
    result = HandleCertificateError(result);

  if (result == OK) {
    next_state_ = STATE_WRITE_HEADERS;
  } else {
    result = HandleSSLHandshakeError(result);
  }
  return result;
}

int HttpNetworkTransaction::DoWriteHeaders() {
  next_state_ = STATE_WRITE_HEADERS_COMPLETE;

  // This is constructed lazily (instead of within our Start method), so that
  // we have proxy info available.
  if (request_headers_.empty()) {
    if (establishing_tunnel_) {
      BuildTunnelRequest();
    } else {
      BuildRequestHeaders();
    }
  }

  // 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();
  }

  const char* buf = request_headers_.data() + request_headers_bytes_sent_;
  int buf_len = static_cast<int>(request_headers_.size() -
                                 request_headers_bytes_sent_);
  DCHECK(buf_len > 0);

  return connection_.socket()->Write(buf, buf_len, &io_callback_);
}

int HttpNetworkTransaction::DoWriteHeadersComplete(int result) {
  if (result < 0)
    return HandleIOError(result);

  request_headers_bytes_sent_ += result;
  if (request_headers_bytes_sent_ < request_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());

  const char* buf = request_body_stream_->buf();
  int buf_len = static_cast<int>(request_body_stream_->buf_len());

  return connection_.socket()->Write(buf, buf_len, &io_callback_);
}

int HttpNetworkTransaction::DoWriteBodyComplete(int result) {
  if (result < 0)
    return HandleIOError(result);

  request_body_stream_->DidConsume(result);

{'fraction_non_alphanumeric': 0.11537979443469541, 'fraction_numerical': 0.0030709450990223115, 'mean_word_length': 3.4875785131714636, 'pattern_counts': {'":': 4, '<': 87, '<?xml version=': 0, '>': 191, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 26, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1195758', 'n_tokens_mistral': 15065, 'n_tokens_neox': 14717, 'n_words': 3320}