// 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_stream_factory.h"

#include <cstddef>
#include <tuple>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "net/base/host_port_pair.h"
#include "net/base/load_flags.h"
#include "net/base/network_anonymization_key.h"
#include "net/base/network_isolation_key.h"
#include "net/base/parse_number.h"
#include "net/base/port_util.h"
#include "net/base/privacy_mode.h"
#include "net/base/upload_data_stream.h"
#include "net/dns/public/secure_dns_policy.h"
#include "net/http/http_network_session.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_server_properties.h"
#include "net/http/http_stream_factory_job.h"
#include "net/http/http_stream_factory_job_controller.h"
#include "net/http/transport_security_state.h"
#include "net/quic/quic_http_utils.h"
#include "net/socket/socket_tag.h"
#include "net/spdy/bidirectional_stream_spdy_impl.h"
#include "net/spdy/spdy_http_stream.h"
#include "net/ssl/ssl_config.h"
#include "net/third_party/quiche/src/quiche/http2/core/spdy_alt_svc_wire_format.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_packets.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_server_id.h"
#include "url/gurl.h"
#include "url/scheme_host_port.h"
#include "url/url_constants.h"

namespace net {

namespace {
const char kAlternativeServiceHeader[] = "Alt-Svc";
}  // namespace

// static
SpdySessionKey HttpStreamFactory::GetSpdySessionKey(
    const ProxyChain& proxy_chain,
    const StreamRequestInfo& request_info) {
  // In the case that we'll be sending a GET request to the proxy, look for an
  // HTTP/2 proxy session *to* the proxy, instead of to the origin server. The
  // way HTTP over HTTPS proxies work is that the ConnectJob makes a SpdyProxy,
  // and then the HttpStreamFactory detects it when it's added to the
  // SpdySession pool, and uses it directly (completely ignoring the result of
  // the ConnectJob, and in fact cancelling it). So we need to create the same
  // key used by the HttpProxyConnectJob for the last proxy in the chain.
  if (IsGetToProxy(proxy_chain, request_info.url)) {
    // For this to work as expected, the whole chain should be HTTPS.
    for (const auto& proxy_server : proxy_chain.proxy_servers()) {
      CHECK(proxy_server.is_https());
    }
    auto [last_proxy_partial_chain, last_proxy_server] =
        proxy_chain.SplitLast();
    const auto& last_proxy_host_port_pair = last_proxy_server.host_port_pair();
    // Note that `disable_cert_network_fetches` must be true for proxies to
    // avoid deadlock. See comment on
    // `SSLConfig::disable_cert_verification_network_fetches`.
    return SpdySessionKey(
        last_proxy_host_port_pair, PRIVACY_MODE_DISABLED,
        last_proxy_partial_chain, SessionUsage::kProxy, request_info.socket_tag,
        request_info.network_anonymization_key, request_info.secure_dns_policy,
        /*disable_cert_network_fetches=*/true, request_info.target_network);
  }
  return SpdySessionKey(
      HostPortPair::FromURL(request_info.url), request_info.privacy_mode,
      proxy_chain, SessionUsage::kDestination, request_info.socket_tag,
      request_info.network_anonymization_key, request_info.secure_dns_policy,
      request_info.load_flags & LOAD_DISABLE_CERT_NETWORK_FETCHES,
      request_info.target_network);
}

// static
bool HttpStreamFactory::IsGetToProxy(const ProxyChain& proxy_chain,
                                     const GURL& origin_url) {
  // Sending proxied GET requests to the last proxy server in the chain is no
  // longer supported for QUIC.
  return proxy_chain.is_get_to_proxy_allowed() &&
         proxy_chain.Last().is_https() && origin_url.SchemeIs(url::kHttpScheme);
}

HttpStreamFactory::StreamRequestInfo::StreamRequestInfo() = default;

HttpStreamFactory::StreamRequestInfo::StreamRequestInfo(
    const HttpRequestInfo& http_request_info)
    : url(RemoveCredentialsFromUrl(http_request_info.url)),
      method(http_request_info.method),
      network_anonymization_key(http_request_info.network_anonymization_key),
      traffic_annotation(http_request_info.traffic_annotation),
      is_http1_allowed(!http_request_info.upload_data_stream ||
                       http_request_info.upload_data_stream->AllowHTTP1()),
      load_flags(http_request_info.load_flags),
      privacy_mode(http_request_info.privacy_mode),
      secure_dns_policy(http_request_info.secure_dns_policy),
      socket_tag(http_request_info.socket_tag),
      target_network(http_request_info.target_network) {}

HttpStreamFactory::StreamRequestInfo::StreamRequestInfo(
    const StreamRequestInfo& other) = default;
HttpStreamFactory::StreamRequestInfo&
HttpStreamFactory::StreamRequestInfo::operator=(
    const StreamRequestInfo& other) = default;
HttpStreamFactory::StreamRequestInfo::StreamRequestInfo(
    StreamRequestInfo&& other) = default;
HttpStreamFactory::StreamRequestInfo&
HttpStreamFactory::StreamRequestInfo::operator=(StreamRequestInfo&& other) =
    default;

HttpStreamFactory::StreamRequestInfo::~StreamRequestInfo() = default;

HttpStreamFactory::HttpStreamFactory(HttpNetworkSession* session)
    : session_(session), job_factory_(std::make_unique<JobFactory>()) {}

HttpStreamFactory::~HttpStreamFactory() = default;

void HttpStreamFactory::ProcessAlternativeServices(
    HttpNetworkSession* session,
    const NetworkAnonymizationKey& network_anonymization_key,
    const HttpResponseHeaders* headers,
    const url::SchemeHostPort& http_server) {
  if (!headers->HasHeader(kAlternativeServiceHeader))
    return;

  std::string alternative_service_str =
      headers->GetNormalizedHeader(kAlternativeServiceHeader)
          .value_or(std::string());
  spdy::SpdyAltSvcWireFormat::AlternativeServiceVector
      alternative_service_vector;
  if (!spdy::SpdyAltSvcWireFormat::ParseHeaderFieldValue(
          alternative_service_str, &alternative_service_vector)) {
    return;
  }

  session->http_server_properties()->SetAlternativeServices(
      http_server, network_anonymization_key,
      net::ProcessAlternativeServices(
          alternative_service_vector, session->params().enable_http2,
          session->params().enable_quic,
          session->context().quic_context->params()->supported_versions));
}

std::unique_ptr<HttpStreamRequest> HttpStreamFactory::RequestStream(
    const HttpRequestInfo& request_info,
    RequestPriority priority,
    const std::vector<SSLConfig::CertAndStatus>& allowed_bad_certs,
    HttpStreamRequest::Delegate* delegate,
    bool enable_ip_based_pooling_for_h2,
    bool enable_alternative_services,
    const NetLogWithSource& net_log) {
  return RequestStreamInternal(
      request_info, priority, allowed_bad_certs, delegate, nullptr,
      HttpStreamRequest::HTTP_STREAM,
      /*is_websocket=*/false, enable_ip_based_pooling_for_h2,
      enable_alternative_services, net_log);
}

std::unique_ptr<HttpStreamRequest>
HttpStreamFactory::RequestWebSocketHandshakeStream(
    const HttpRequestInfo& request_info,
    RequestPriority priority,
    const std::vector<SSLConfig::CertAndStatus>& allowed_bad_certs,
    HttpStreamRequest::Delegate* delegate,
    WebSocketHandshakeStreamBase::CreateHelper* create_helper,
    bool enable_ip_based_pooling_for_h2,
    bool enable_alternative_services,
    const NetLogWithSource& net_log) {
  DCHECK(create_helper);
  return RequestStreamInternal(
      request_info, priority, allowed_bad_certs, delegate, create_helper,
      HttpStreamRequest::HTTP_STREAM,
      /*is_websocket=*/true, enable_ip_based_pooling_for_h2,
      enable_alternative_services, net_log);
}

std::unique_ptr<HttpStreamRequest>
HttpStreamFactory::RequestBidirectionalStreamImpl(
    const HttpRequestInfo& request_info,
    RequestPriority priority,
    const std::vector<SSLConfig::CertAndStatus>& allowed_bad_certs,
    HttpStreamRequest::Delegate* delegate,
    bool enable_ip_based_pooling_for_h2,
    bool enable_alternative_services,
    const NetLogWithSource& net_log) {
  DCHECK(request_info.url.SchemeIs(url::kHttpsScheme));

  return RequestStreamInternal(
      request_info, priority, allowed_bad_certs, delegate, nullptr,
      HttpStreamRequest::BIDIRECTIONAL_STREAM,
      /*is_websocket=*/false, enable_ip_based_pooling_for_h2,
      enable_alternative_services, net_log);
}

std::unique_ptr<HttpStreamRequest> HttpStreamFactory::RequestStreamInternal(
    const HttpRequestInfo& request_info,
    RequestPriority priority,
    const std::vector<SSLConfig::CertAndStatus>& allowed_bad_certs,
    HttpStreamRequest::Delegate* delegate,
    WebSocketHandshakeStreamBase::CreateHelper*
        websocket_handshake_stream_create_helper,
    HttpStreamRequest::StreamType stream_type,
    bool is_websocket,
    bool enable_ip_based_pooling_for_h2,
    bool enable_alternative_services,
    const NetLogWithSource& net_log) {
  // This is only needed in the non-preconnect path, as preconnects do not
  // require a NetworkIsolationKey.
  DCHECK(request_info.IsConsistent());

  auto job_controller = std::make_unique<JobController>(
      this, delegate, session_, job_factory_.get(), request_info,
      /* is_preconnect = */ false, is_websocket, enable_ip_based_pooling_for_h2,
      enable_alternative_services,
      session_->context()
          .quic_context->params()
          ->delay_main_job_with_available_spdy_session,
      allowed_bad_certs);
  JobController* job_controller_raw_ptr = job_controller.get();
  job_controller_set_.insert(std::move(job_controller));
  return job_controller_raw_ptr->Start(delegate,
                                       websocket_handshake_stream_create_helper,
                                       net_log, stream_type, priority);
}

void HttpStreamFactory::PreconnectStreams(int num_streams,
                                          HttpRequestInfo& request_info,
                                          base::OnceClosure callback) {
  // Ignore invalid URLs. This matches the behavior of
  // URLRequestJobFactory::CreateJob(). Passing very long valid GURLs over Mojo
  // can result in invalid URLs, so can't rely on callers sending only valid
  // URLs.
  if (!request_info.url.is_valid()) {
    if (callback) {
      std::move(callback).Run();
    }
    return;
  }

  auto job_controller = std::make_unique<JobController>(
      this, nullptr, session_, job_factory_.get(), request_info,
      /*is_preconnect=*/true,
      /*is_websocket=*/false,
      /*enable_ip_based_pooling_for_h2=*/true,
      /*enable_alternative_services=*/true,
      session_->context()
          .quic_context->params()
          ->delay_main_job_with_available_spdy_session,
      /*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
  JobController* job_controller_raw_ptr = job_controller.get();
  job_controller_set_.insert(std::move(job_controller));
  job_controller_raw_ptr->Preconnect(num_streams, std::move(callback));
}

void HttpStreamFactory::OnJobControllerComplete(JobController* controller) {
  auto it = job_controller_set_.find(controller);
  if (it != job_controller_set_.end()) {
    job_controller_set_.erase(it);
  } else {
    NOTREACHED();
  }
}

}  // namespace net
