// Copyright 2012 The Chromium Authors
// 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"

#include <set>
#include <string_view>
#include <utility>

#include "base/functional/bind.h"
#include "net/http/http_network_session.h"
#include "net/http/http_raw_request_headers.h"
#include "net/http/http_request_info.h"
#include "net/http/http_response_body_drainer.h"
#include "net/http/http_stream_parser.h"
#include "net/socket/client_socket_handle.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/ssl/ssl_info.h"

namespace net {

HttpBasicStream::HttpBasicStream(std::unique_ptr<ClientSocketHandle> connection,
                                 bool is_for_get_to_http_proxy)
    : state_(std::move(connection), is_for_get_to_http_proxy) {}

HttpBasicStream::~HttpBasicStream() = default;

void HttpBasicStream::RegisterRequest(const HttpRequestInfo* request_info) {
  DCHECK(request_info);
  DCHECK(request_info->traffic_annotation.is_valid());
  request_info_ = request_info;
}

int HttpBasicStream::InitializeStream(bool can_send_early,
                                      RequestPriority priority,
                                      const NetLogWithSource& net_log,
                                      CompletionOnceCallback callback) {
  DCHECK(request_info_);
  state_.Initialize(request_info_, priority, net_log);
  int ret = OK;
  if (!can_send_early) {
    // parser() cannot outlive |this|, so we can use base::Unretained().
    ret = parser()->ConfirmHandshake(
        base::BindOnce(&HttpBasicStream::OnHandshakeConfirmed,
                       base::Unretained(this), std::move(callback)));
  }
  // RequestInfo is no longer needed after this point.
  request_info_ = nullptr;
  return ret;
}

int HttpBasicStream::SendRequest(const HttpRequestHeaders& headers,
                                 HttpResponseInfo* response,
                                 CompletionOnceCallback callback) {
  DCHECK(parser());
  if (request_headers_callback_) {
    HttpRawRequestHeaders raw_headers;
    raw_headers.set_request_line(state_.GenerateRequestLine());
    for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();)
      raw_headers.Add(it.name(), it.value());
    request_headers_callback_.Run(std::move(raw_headers));
  }
  return parser()->SendRequest(
      state_.GenerateRequestLine(), headers,
      NetworkTrafficAnnotationTag(state_.traffic_annotation()), response,
      std::move(callback));
}

int HttpBasicStream::ReadResponseHeaders(CompletionOnceCallback callback) {
  return parser()->ReadResponseHeaders(std::move(callback));
}

int HttpBasicStream::ReadResponseBody(IOBuffer* buf,
                                      int buf_len,
                                      CompletionOnceCallback callback) {
  return parser()->ReadResponseBody(buf, buf_len, std::move(callback));
}

void HttpBasicStream::Close(bool not_reusable) {
  // parser() is null if |this| is created by an orphaned HttpStreamFactory::Job
  // in which case InitializeStream() will not have been called. This also
  // protects against null dereference in the case where
  // state_.ReleaseConnection() has been called.
  //
  // TODO(mmenke):  Can these cases be handled a bit more cleanly?
  // WebSocketHandshakeStream will need to be updated as well.
  if (!parser())
    return;
  StreamSocket* socket = state_.connection()->socket();
  if (not_reusable && socket)
    socket->Disconnect();
  parser()->OnConnectionClose();
  state_.connection()->Reset();
}

std::unique_ptr<HttpStream> HttpBasicStream::RenewStreamForAuth() {
  DCHECK(IsResponseBodyComplete());
  DCHECK(!parser()->IsMoreDataBuffered());
  // The HttpStreamParser object still has a pointer to the connection. Just to
  // be extra-sure it doesn't touch the connection again, delete it here rather
  // than leaving it until the destructor is called.
  state_.DeleteParser();
  return std::make_unique<HttpBasicStream>(state_.ReleaseConnection(),
                                           state_.is_for_get_to_http_proxy());
}

bool HttpBasicStream::IsResponseBodyComplete() const {
  return parser()->IsResponseBodyComplete();
}

bool HttpBasicStream::IsConnectionReused() const {
  return state_.IsConnectionReused();
}

void HttpBasicStream::SetConnectionReused() {
  state_.connection()->set_reuse_type(ClientSocketHandle::REUSED_IDLE);
}

bool HttpBasicStream::CanReuseConnection() const {
  return parser() && state_.connection()->socket() &&
         parser()->CanReuseConnection();
}

int64_t HttpBasicStream::GetTotalReceivedBytes() const {
  if (parser())
    return parser()->received_bytes();
  return 0;
}

int64_t HttpBasicStream::GetTotalSentBytes() const {
  if (parser())
    return parser()->sent_bytes();
  return 0;
}

bool HttpBasicStream::GetLoadTimingInfo(
    LoadTimingInfo* load_timing_info) const {
  if (!state_.connection()->GetLoadTimingInfo(IsConnectionReused(),
                                              load_timing_info) ||
      !parser()) {
    return false;
  }

  // If the request waited for handshake confirmation, shift |ssl_end| to
  // include that time.
  if (!load_timing_info->connect_timing.ssl_end.is_null() &&
      !confirm_handshake_end_.is_null()) {
    load_timing_info->connect_timing.ssl_end = confirm_handshake_end_;
    load_timing_info->connect_timing.connect_end = confirm_handshake_end_;
  }

  load_timing_info->receive_headers_start =
      parser()->first_response_start_time();
  load_timing_info->receive_non_informational_headers_start =
      parser()->non_informational_response_start_time();
  load_timing_info->first_early_hints_time = parser()->first_early_hints_time();
  return true;
}

bool HttpBasicStream::GetAlternativeService(
    AlternativeService* alternative_service) const {
  return false;
}

void HttpBasicStream::GetSSLInfo(SSLInfo* ssl_info) {
  if (!state_.connection()->socket() ||
      !state_.connection()->socket()->GetSSLInfo(ssl_info)) {
    ssl_info->Reset();
  }
}

int HttpBasicStream::GetRemoteEndpoint(IPEndPoint* endpoint) {
  if (!state_.connection() || !state_.connection()->socket())
    return ERR_SOCKET_NOT_CONNECTED;

  return state_.connection()->socket()->GetPeerAddress(endpoint);
}

void HttpBasicStream::Drain(HttpNetworkSession* session) {
  session->StartResponseDrainer(
      std::make_unique<HttpResponseBodyDrainer>(this));
  // |drainer| will delete itself.
}

void HttpBasicStream::PopulateNetErrorDetails(NetErrorDetails* details) {
  // TODO(mmenke):  Consumers don't actually care about HTTP version, but seems
  // like the right version should be reported, if headers were received.
  details->connection_info = HttpConnectionInfo::kHTTP1_1;
  return;
}

void HttpBasicStream::SetPriority(RequestPriority priority) {
  // TODO(akalin): Plumb this through to |connection_|.
}

void HttpBasicStream::SetRequestHeadersCallback(
    RequestHeadersCallback callback) {
  request_headers_callback_ = std::move(callback);
}

const std::set<std::string>& HttpBasicStream::GetDnsAliases() const {
  return state_.GetDnsAliases();
}

std::string_view HttpBasicStream::GetAcceptChViaAlps() const {
  return {};
}

void HttpBasicStream::OnHandshakeConfirmed(CompletionOnceCallback callback,
                                           int rv) {
  if (rv == OK) {
    // Note this time is only recorded if ConfirmHandshake() completed
    // asynchronously. If it was synchronous, GetLoadTimingInfo() assumes the
    // handshake was already confirmed or there was nothing to confirm.
    confirm_handshake_end_ = base::TimeTicks::Now();
  }
  std::move(callback).Run(rv);
}

}  // namespace net
