// Copyright 2014 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 "components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.h"

#include <algorithm>
#include <limits>
#include <utility>

#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_stats.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_configurator.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_request_options.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_util.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
#include "components/data_reduction_proxy/core/common/lofi_decider.h"
#include "net/base/load_flags.h"
#include "net/base/mime_util.h"
#include "net/base/network_change_notifier.h"
#include "net/base/proxy_server.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/nqe/effective_connection_type.h"
#include "net/nqe/network_quality_estimator.h"
#include "net/proxy_resolution/proxy_info.h"
#include "net/proxy_resolution/proxy_resolution_service.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_status.h"
#include "url/gurl.h"

namespace data_reduction_proxy {

namespace {

// Values of the UMA DataReductionProxy.Protocol.AcceptTransform histogram
// defined in metrics/histograms/histograms.xml. This enum must remain
// synchronized with DataReductionProxyProtocolAcceptTransformEvent in
// tools/metrics/histograms/enums.xml.
enum AcceptTransformEvent {
  LITE_PAGE_REQUESTED = 0,
  LITE_PAGE_TRANSFORM_RECEIVED = 1,
  EMPTY_IMAGE_POLICY_DIRECTIVE_RECEIVED = 2,
  EMPTY_IMAGE_REQUESTED = 3,
  EMPTY_IMAGE_TRANSFORM_RECEIVED = 4,
  COMPRESSED_VIDEO_REQUESTED = 5,
  IDENTITY_TRANSFORM_REQUESTED = 6,
  IDENTITY_TRANSFORM_RECEIVED = 7,
  COMPRESSED_VIDEO_RECEIVED = 8,
  UNKNOWN_TRANSFORM_RECEIVED = 9,
  ACCEPT_TRANSFORM_EVENT_BOUNDARY
};

// Records the occurrence of |sample| in |name| histogram. UMA macros are not
// used because the |name| is not static.
void RecordNewContentLengthHistogram(const std::string& name, int64_t sample) {
  base::UmaHistogramCustomCounts(
      name, sample,
      1,          // Minimum sample size in bytes.
      128 << 20,  // Maximum sample size in bytes. 128MB is chosen because some
                  // video requests can be very large.
      50          // Bucket count.
      );
}

void RecordNewContentLengthHistograms(
    const char* prefix,
    bool is_https,
    bool is_video,
    DataReductionProxyRequestType request_type,
    int64_t content_length) {
  const char* connection_type = is_https ? ".Https" : ".Http";
  const char* suffix = ".Other";
  // TODO(crbug.com/726411): Differentiate between a bypass and a disabled
  // proxy config.
  switch (request_type) {
    case VIA_DATA_REDUCTION_PROXY:
      suffix = ".ViaDRP";
      break;
    case HTTPS:
    case DIRECT_HTTP:
      suffix = ".Direct";
      break;
    case SHORT_BYPASS:
    case LONG_BYPASS:
      suffix = ".BypassedDRP";
      break;
    case UPDATE:
    case UNKNOWN_TYPE:
    default:
      // Value already properly initialized to ".Other"
      break;
  }
  // Record a histogram for all traffic, including video.
  RecordNewContentLengthHistogram(
      base::StringPrintf("%s%s%s", prefix, connection_type, suffix),
      content_length);
  if (is_video) {
    RecordNewContentLengthHistogram(
        base::StringPrintf("%s%s%s.Video", prefix, connection_type, suffix),
        content_length);
  }
}

// |received_content_length| is the number of prefilter bytes received.
// |original_content_length| is the length of resource if accessed directly
// without data saver proxy. |freshness_lifetime| specifies how long the
// resource will be fresh for.
void RecordContentLengthHistograms(bool is_https,
                                   bool is_video,
                                   int64_t received_content_length,
                                   int64_t original_content_length,
                                   const base::TimeDelta& freshness_lifetime,
                                   DataReductionProxyRequestType request_type) {
  // Add the current resource to these histograms only when the content length
  // is valid.
  if (original_content_length >= 0) {
    // This is only used locally in integration testing.
    LOCAL_HISTOGRAM_COUNTS_1000000("Net.HttpOriginalContentLengthWithValidOCL",
                                   original_content_length);
    UMA_HISTOGRAM_COUNTS_1M("Net.HttpContentLengthDifferenceWithValidOCL",
                            original_content_length - received_content_length);
  } else {
    // Presume the original content length is the same as the received content
    // length.
    original_content_length = received_content_length;
  }
  UMA_HISTOGRAM_COUNTS_1M("Net.HttpContentLength", received_content_length);

  // Record the new histograms broken down by HTTP/HTTPS and video/non-video
  RecordNewContentLengthHistograms("Net.HttpContentLengthV2", is_https,
                                   is_video, request_type,
                                   received_content_length);
  RecordNewContentLengthHistograms("Net.HttpOriginalContentLengthV2", is_https,
                                   is_video, request_type,
                                   original_content_length);

  UMA_HISTOGRAM_CUSTOM_COUNTS("Net.HttpContentFreshnessLifetime",
                              freshness_lifetime.InSeconds(),
                              base::TimeDelta::FromHours(1).InSeconds(),
                              base::TimeDelta::FromDays(30).InSeconds(), 100);
}

void RecordAcceptTransformEvent(AcceptTransformEvent event) {
  UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.Protocol.AcceptTransform",
                            event, ACCEPT_TRANSFORM_EVENT_BOUNDARY);
}

void RecordAcceptTransformSentUMA(
    const net::HttpRequestHeaders& request_headers) {
  switch (ParseRequestTransform(request_headers)) {
    case TRANSFORM_LITE_PAGE:
      RecordAcceptTransformEvent(LITE_PAGE_REQUESTED);
      break;
    case TRANSFORM_EMPTY_IMAGE:
      RecordAcceptTransformEvent(EMPTY_IMAGE_REQUESTED);
      break;
    case TRANSFORM_COMPRESSED_VIDEO:
      RecordAcceptTransformEvent(COMPRESSED_VIDEO_REQUESTED);
      break;
    case TRANSFORM_IDENTITY:
      RecordAcceptTransformEvent(IDENTITY_TRANSFORM_REQUESTED);
      break;
    case TRANSFORM_NONE:
      break;
    case TRANSFORM_PAGE_POLICIES_EMPTY_IMAGE:
    case TRANSFORM_UNKNOWN:
      NOTREACHED();
      break;
  }
}

void RecordAcceptTransformReceivedUMA(const net::URLRequest& request) {
  net::HttpResponseHeaders* response_headers = request.response_headers();
  if (!response_headers) {
    return;
  }

  switch (ParseResponseTransform(*response_headers)) {
    case TRANSFORM_UNKNOWN:
      RecordAcceptTransformEvent(UNKNOWN_TRANSFORM_RECEIVED);
      break;
    case TRANSFORM_LITE_PAGE:
      RecordAcceptTransformEvent(LITE_PAGE_TRANSFORM_RECEIVED);
      break;
    case TRANSFORM_PAGE_POLICIES_EMPTY_IMAGE:
      RecordAcceptTransformEvent(EMPTY_IMAGE_POLICY_DIRECTIVE_RECEIVED);
      break;
    case TRANSFORM_EMPTY_IMAGE:
      RecordAcceptTransformEvent(EMPTY_IMAGE_TRANSFORM_RECEIVED);
      break;
    case TRANSFORM_IDENTITY:
      RecordAcceptTransformEvent(IDENTITY_TRANSFORM_RECEIVED);
      break;
    case TRANSFORM_COMPRESSED_VIDEO:
      RecordAcceptTransformEvent(COMPRESSED_VIDEO_RECEIVED);
      break;
    case TRANSFORM_NONE:
      break;
  }
}

// Verifies that the chrome proxy related request headers are set correctly.
// |via_chrome_proxy| is true if the request is being fetched via Chrome Data
// Saver proxy.
void VerifyHttpRequestHeaders(bool via_chrome_proxy,
                              const net::HttpRequestHeaders& headers) {
  // If holdback is enabled, then |via_chrome_proxy| should be false.
  DCHECK(!params::IsIncludedInHoldbackFieldTrial() || !via_chrome_proxy);

  if (via_chrome_proxy) {
    DCHECK(headers.HasHeader(chrome_proxy_ect_header()));
    std::string chrome_proxy_header_value;
    DCHECK(
        headers.GetHeader(chrome_proxy_header(), &chrome_proxy_header_value));
    // Check that only 1 "exp" directive is sent.
    DCHECK_GT(3u, base::SplitStringUsingSubstr(chrome_proxy_header_value,
                                               "exp=", base::TRIM_WHITESPACE,
                                               base::SPLIT_WANT_ALL)
                      .size());
    // Silence unused variable warning in release builds.
    (void)chrome_proxy_header_value;
  } else {
    DCHECK(!headers.HasHeader(chrome_proxy_header()));
    DCHECK(!headers.HasHeader(chrome_proxy_accept_transform_header()));
    DCHECK(!headers.HasHeader(chrome_proxy_ect_header()));
  }
}

// If the response is the entire resource, then the renderer won't show a
// placeholder. This should match the behavior in blink::ImageResource.
bool IsEntireResource(const net::HttpResponseHeaders* response_headers) {
  if (!response_headers || response_headers->response_code() != 206)
    return true;

  int64_t first, last, length;
  return response_headers->GetContentRangeFor206(&first, &last, &length) &&
         first == 0 && last + 1 == length;
}

}  // namespace

DataReductionProxyNetworkDelegate::DataReductionProxyNetworkDelegate(
    std::unique_ptr<net::NetworkDelegate> network_delegate,
    DataReductionProxyConfig* config,
    DataReductionProxyRequestOptions* request_options,
    const DataReductionProxyConfigurator* configurator)
    : LayeredNetworkDelegate(std::move(network_delegate)),
      data_reduction_proxy_config_(config),
      data_reduction_proxy_bypass_stats_(nullptr),
      data_reduction_proxy_request_options_(request_options),
      data_reduction_proxy_io_data_(nullptr),
      configurator_(configurator) {
  DCHECK(data_reduction_proxy_config_);
  DCHECK(data_reduction_proxy_request_options_);
  DCHECK(configurator_);
}

DataReductionProxyNetworkDelegate::~DataReductionProxyNetworkDelegate() {
}

void DataReductionProxyNetworkDelegate::InitIODataAndUMA(
    DataReductionProxyIOData* io_data,
    DataReductionProxyBypassStats* bypass_stats) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(bypass_stats);
  data_reduction_proxy_io_data_ = io_data;
  data_reduction_proxy_bypass_stats_ = bypass_stats;
}

void DataReductionProxyNetworkDelegate::OnBeforeStartTransactionInternal(
    net::URLRequest* request,
    net::HttpRequestHeaders* headers) {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (!data_reduction_proxy_io_data_)
    return;
  if (!data_reduction_proxy_io_data_->IsEnabled())
    return;

  if (request->url().SchemeIsCryptographic() ||
      !request->url().SchemeIsHTTPOrHTTPS()) {
    return;
  }

  if (data_reduction_proxy_io_data_->resource_type_provider()) {
    // Sets content type of |request| in the resource type provider, so it can
    // be later used for determining the proxy that should be used for fetching
    // |request|.
    data_reduction_proxy_io_data_->resource_type_provider()->SetContentType(
        *request);
  }

  if (data_reduction_proxy_io_data_->lofi_decider()) {
    data_reduction_proxy_io_data_->lofi_decider()
        ->MaybeSetAcceptTransformHeader(*request, headers);
  }

  MaybeAddChromeProxyECTHeader(headers, *request);
}

void DataReductionProxyNetworkDelegate::OnBeforeSendHeadersInternal(
    net::URLRequest* request,
    const net::ProxyInfo& proxy_info,
    const net::ProxyRetryInfoMap& proxy_retry_info,
    net::HttpRequestHeaders* headers) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(data_reduction_proxy_config_);
  DCHECK(request);

  // If there was a redirect or request bypass, use the same page ID for both
  // requests. As long as the session ID has not changed. Re-issued requests
  // and client redirects will be assigned a new page ID as they are different
  // URLRequests.
  DataReductionProxyData* data = DataReductionProxyData::GetData(*request);
  base::Optional<uint64_t> page_id;
  if (data && data->session_key() ==
                  data_reduction_proxy_request_options_->GetSecureSession()) {
    page_id = data->page_id();
  }
  // Always persist data's |request_info| since it tracks connection pingback
  // data for redirects on main frame requests. It should include re-issued
  // requests and client redirects.
  std::vector<DataReductionProxyData::RequestInfo> request_info;
  if (data)
    request_info = data->TakeRequestInfo();

  // Reset |request|'s DataReductionProxyData.
  DataReductionProxyData::ClearData(request);
  data = nullptr;

  bool using_data_reduction_proxy = true;
  // The following checks rule out direct, invalid, and other connection types.
  if (!proxy_info.is_http() && !proxy_info.is_https() &&
      !proxy_info.is_quic()) {
    using_data_reduction_proxy = false;
  } else if (proxy_info.proxy_server().host_port_pair().IsEmpty()) {
    using_data_reduction_proxy = false;
  } else if (!data_reduction_proxy_config_->FindConfiguredDataReductionProxy(
                 proxy_info.proxy_server())) {
    using_data_reduction_proxy = false;
  }

  bool is_holdback_eligible = false;

  if (params::IsIncludedInHoldbackFieldTrial() &&
      WasEligibleWithoutHoldback(*request, proxy_info, proxy_retry_info)) {
    is_holdback_eligible = true;
  }
  // If holdback is enabled, |using_data_reduction_proxy| must be false.
  DCHECK(!params::IsIncludedInHoldbackFieldTrial() ||
         !using_data_reduction_proxy);

  // For the holdback field trial, still log UMA and send the pingback as if
  // the proxy were used.
  if (is_holdback_eligible || using_data_reduction_proxy) {
    // Retrieves DataReductionProxyData from a request, creating a new instance
    // if needed.
    data = DataReductionProxyData::GetDataAndCreateIfNecessary(request);
    data->set_used_data_reduction_proxy(true);
    // Only set GURL, NQE and session key string for main frame requests since
    // they are not needed for sub-resources.
    if (request->load_flags() & net::LOAD_MAIN_FRAME_DEPRECATED) {
      data->set_session_key(
          data_reduction_proxy_request_options_->GetSecureSession());
      data->set_request_url(request->url());
      if (request->context()->network_quality_estimator()) {
        data->set_effective_connection_type(request->context()
                                                ->network_quality_estimator()
                                                ->GetEffectiveConnectionType());
      }
      data->set_connection_type(
          net::NetworkChangeNotifier::GetConnectionType());
      // Generate a page ID for main frame requests that don't already have one.
      // TODO(ryansturm): remove LOAD_MAIN_FRAME_DEPRECATED from d_r_p.
      // crbug.com/709621
      if (!page_id) {
        page_id = data_reduction_proxy_request_options_->GeneratePageId();
      }
      data->set_page_id(page_id.value());
      data->set_request_info(std::move(request_info));
    }
  }

  LoFiDecider* lofi_decider = nullptr;
  if (data_reduction_proxy_io_data_)
    lofi_decider = data_reduction_proxy_io_data_->lofi_decider();

  if (!using_data_reduction_proxy) {
    if (lofi_decider) {
      // If not using the data reduction proxy, strip the
      // Chrome-Proxy-Accept-Transform header.
      lofi_decider->RemoveAcceptTransformHeader(headers);
    }
    RemoveChromeProxyECTHeader(headers);
    headers->RemoveHeader(chrome_proxy_header());
    VerifyHttpRequestHeaders(false, *headers);
    return;
  }

  DCHECK(data);
  data->set_lofi_requested(
      lofi_decider ? lofi_decider->ShouldRecordLoFiUMA(*request) : false);
  MaybeAddBrotliToAcceptEncodingHeader(proxy_info, headers, *request);

  data_reduction_proxy_request_options_->AddRequestHeader(headers, page_id);

  VerifyHttpRequestHeaders(true, *headers);
  RecordAcceptTransformSentUMA(*headers);
}

void DataReductionProxyNetworkDelegate::OnBeforeRedirectInternal(
    net::URLRequest* request,
    const GURL& new_location) {
  // Since this is after a redirect response, reset |request|'s
  // DataReductionProxyData, but keep page ID and session.
  // TODO(ryansturm): Change ClearData logic to have persistent and
  // non-persistent (WRT redirects) data.
  // crbug.com/709564
  DataReductionProxyData* data = DataReductionProxyData::GetData(*request);
  base::Optional<uint64_t> page_id;
  if (data && data->session_key() ==
                  data_reduction_proxy_request_options_->GetSecureSession()) {
    page_id = data->page_id();
  }

  // Persist data's |request_info| since it tracks connection pingback data for
  // redirects on main frame requests.
  std::vector<DataReductionProxyData::RequestInfo> request_info;
  if (data)
    request_info = data->TakeRequestInfo();

  DataReductionProxyData::ClearData(request);

  if (page_id) {
    data = DataReductionProxyData::GetDataAndCreateIfNecessary(request);
    data->set_page_id(page_id.value());
    data->set_session_key(
        data_reduction_proxy_request_options_->GetSecureSession());
    data->set_request_info(std::move(request_info));
  }
}

void DataReductionProxyNetworkDelegate::OnCompletedInternal(
    net::URLRequest* request,
    bool started,
    int net_error) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(request);
  DCHECK_NE(net::ERR_IO_PENDING, net_error);
  if (data_reduction_proxy_bypass_stats_)
    data_reduction_proxy_bypass_stats_->OnUrlRequestCompleted(request, started,
                                                              net_error);

  net::HttpRequestHeaders request_headers;
  bool server_lofi = request->response_headers() &&
                     IsEmptyImagePreview(*(request->response_headers()));
  bool will_show_client_lofi_placeholder =
      data_reduction_proxy_io_data_ &&
      data_reduction_proxy_io_data_->lofi_decider() &&
      data_reduction_proxy_io_data_->lofi_decider()->IsClientLoFiImageRequest(
          *request) &&
      // If the response contains the entire resource, then the renderer won't
      // show a placeholder for this image, so don't bother triggering an
      // infobar.
      !IsEntireResource(request->response_headers());

  if ((server_lofi || will_show_client_lofi_placeholder) &&
      data_reduction_proxy_io_data_ &&
      data_reduction_proxy_io_data_->lofi_ui_service()) {
    data_reduction_proxy_io_data_->lofi_ui_service()->OnLoFiReponseReceived(
        *request);
  } else if (data_reduction_proxy_io_data_ && request->response_headers() &&
             IsLitePagePreview(*(request->response_headers()))) {
    RecordLitePageTransformationType(LITE_PAGE);
  } else if (request->GetFullRequestHeaders(&request_headers)) {
    // TODO(bengr): transform processing logic should happen elsewhere.
    std::string header_value;
    request_headers.GetHeader(chrome_proxy_accept_transform_header(),
                              &header_value);
    if (header_value == lite_page_directive())
      RecordLitePageTransformationType(NO_TRANSFORMATION_LITE_PAGE_REQUESTED);
  }

  if (!request->response_info().network_accessed ||
      !request->url().SchemeIsHTTPOrHTTPS() ||
      request->GetTotalReceivedBytes() == 0) {
    return;
  }

  DataReductionProxyRequestType request_type = GetDataReductionProxyRequestType(
      *request, configurator_->GetProxyConfig(), *data_reduction_proxy_config_);

  CalculateAndRecordDataUsage(*request, request_type);
  RecordContentLength(*request, request_type,
                      util::CalculateOCLFromOFCL(*request));
  RecordAcceptTransformReceivedUMA(*request);
}

void DataReductionProxyNetworkDelegate::OnHeadersReceivedInternal(
    net::URLRequest* request,
    const net::HttpResponseHeaders* original_response_headers,
    scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
    GURL* allowed_unsafe_redirect_url) {
  if (!original_response_headers ||
      original_response_headers->IsRedirect(nullptr))
    return;

  switch (ParseResponseTransform(*original_response_headers)) {
    case TRANSFORM_LITE_PAGE:
      DataReductionProxyData::GetDataAndCreateIfNecessary(request)
          ->set_lite_page_received(true);
      break;
    case TRANSFORM_PAGE_POLICIES_EMPTY_IMAGE:
      DataReductionProxyData::GetDataAndCreateIfNecessary(request)
          ->set_lofi_policy_received(true);
      break;
    case TRANSFORM_EMPTY_IMAGE:
      DataReductionProxyData::GetDataAndCreateIfNecessary(request)
          ->set_lofi_received(true);
      break;
    case TRANSFORM_IDENTITY:
    case TRANSFORM_COMPRESSED_VIDEO:
    case TRANSFORM_NONE:
    case TRANSFORM_UNKNOWN:
      break;
  }
  if (data_reduction_proxy_io_data_ &&
      data_reduction_proxy_io_data_->lofi_decider() &&
      data_reduction_proxy_io_data_->lofi_decider()->IsClientLoFiImageRequest(
          *request)) {
    DataReductionProxyData* data =
        DataReductionProxyData::GetDataAndCreateIfNecessary(request);
    data->set_client_lofi_requested(true);
  }
}

void DataReductionProxyNetworkDelegate::CalculateAndRecordDataUsage(
    const net::URLRequest& request,
    DataReductionProxyRequestType request_type) {
  DCHECK(thread_checker_.CalledOnValidThread());
  int64_t data_used = request.GetTotalReceivedBytes();

  // Estimate how many bytes would have been used if the DataReductionProxy was
  // not used, and record the data usage.
  int64_t original_size = util::EstimateOriginalReceivedBytes(
      request, data_reduction_proxy_io_data_
                   ? data_reduction_proxy_io_data_->lofi_decider()
                   : nullptr);

  std::string mime_type;
  if (request.response_headers())
    request.response_headers()->GetMimeType(&mime_type);

  AccumulateDataUsage(
      data_used, original_size, request_type, mime_type,
      data_use_measurement::DataUseMeasurement::IsUserRequest(request),
      data_use_measurement::DataUseMeasurement::GetContentTypeForRequest(
          request),
      request.traffic_annotation().unique_id_hash_code);

  if (params::IsDataSaverSiteBreakdownUsingPLMEnabled() &&
      data_reduction_proxy_io_data_ &&
      data_reduction_proxy_io_data_->resource_type_provider() &&
      data_reduction_proxy_io_data_->resource_type_provider()
          ->IsNonContentInitiatedRequest(request)) {
    // Record non-content initiated traffic to the Other bucket for data saver
    // site-breakdown.
    data_reduction_proxy_io_data_->UpdateDataUseForHost(
        data_used, original_size, util::GetSiteBreakdownOtherHostName());
  }
}

void DataReductionProxyNetworkDelegate::AccumulateDataUsage(
    int64_t data_used,
    int64_t original_size,
    DataReductionProxyRequestType request_type,
    const std::string& mime_type,
    bool is_user_traffic,
    data_use_measurement::DataUseUserData::DataUseContentType content_type,
    int32_t service_hash_code) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK_GE(data_used, 0);
  DCHECK_GE(original_size, 0);
  if (data_reduction_proxy_io_data_) {
    data_reduction_proxy_io_data_->UpdateContentLengths(
        data_used, original_size, data_reduction_proxy_io_data_->IsEnabled(),
        request_type, mime_type, is_user_traffic, content_type,
        service_hash_code);
  }
}

void DataReductionProxyNetworkDelegate::RecordContentLength(
    const net::URLRequest& request,
    DataReductionProxyRequestType request_type,
    int64_t original_content_length) {
  DCHECK(thread_checker_.CalledOnValidThread());
  if (!request.response_headers() || request.was_cached() ||
      request.received_response_content_length() == 0) {
    return;
  }

  // Record content length histograms for the request.
  base::TimeDelta freshness_lifetime =
      request.response_headers()
          ->GetFreshnessLifetimes(request.response_info().response_time)
          .freshness;

  bool is_https = request.url().SchemeIs("https");
  bool is_video = false;
  std::string mime_type;
  if (request.response_headers()->GetMimeType(&mime_type)) {
    is_video = net::MatchesMimeType("video/*", mime_type);
  }

  RecordContentLengthHistograms(
      is_https, is_video, request.received_response_content_length(),
      original_content_length, freshness_lifetime, request_type);

  if (data_reduction_proxy_io_data_ && data_reduction_proxy_bypass_stats_) {
    // Record BypassedBytes histograms for the request.
    data_reduction_proxy_bypass_stats_->RecordBypassedBytesHistograms(
        request, data_reduction_proxy_io_data_->IsEnabled(),
        configurator_->GetProxyConfig());
  }
}

void DataReductionProxyNetworkDelegate::RecordLitePageTransformationType(
    LitePageTransformationType type) {
  DCHECK(thread_checker_.CalledOnValidThread());
  UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.LoFi.TransformationType", type,
                            LITE_PAGE_TRANSFORMATION_TYPES_INDEX_BOUNDARY);
}

bool DataReductionProxyNetworkDelegate::WasEligibleWithoutHoldback(
    const net::URLRequest& request,
    const net::ProxyInfo& proxy_info,
    const net::ProxyRetryInfoMap& proxy_retry_info) const {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(proxy_info.is_empty() || proxy_info.is_direct() ||
         !data_reduction_proxy_config_->FindConfiguredDataReductionProxy(
             proxy_info.proxy_server()));
  if (!util::EligibleForDataReductionProxy(proxy_info, request.url(),
                                           request.method())) {
    return false;
  }
  net::ProxyConfig proxy_config =
      data_reduction_proxy_config_->ProxyConfigIgnoringHoldback();
  net::ProxyInfo data_reduction_proxy_info;
  return util::ApplyProxyConfigToProxyInfo(proxy_config, proxy_retry_info,
                                           request.url(),
                                           &data_reduction_proxy_info);
}

void DataReductionProxyNetworkDelegate::MaybeAddBrotliToAcceptEncodingHeader(
    const net::ProxyInfo& proxy_info,
    net::HttpRequestHeaders* request_headers,
    const net::URLRequest& request) const {
  DCHECK(thread_checker_.CalledOnValidThread());

  // This method should be called only when the resolved proxy was a data
  // saver proxy.
  DCHECK(data_reduction_proxy_config_->FindConfiguredDataReductionProxy(
      proxy_info.proxy_server()));
  DCHECK(request.url().is_valid());
  DCHECK(!request.url().SchemeIsCryptographic());
  DCHECK(request.url().SchemeIsHTTPOrHTTPS());

  static const char kBrotli[] = "br";

  if (!request.context()->enable_brotli()) {
    // Verify that Brotli is enabled globally.
    return;
  }

  if (!params::IsBrotliAcceptEncodingEnabled()) {
    // Verify that Brotli is enabled for data reduction proxy.
    return;
  }

  if (!proxy_info.proxy_server().is_https() &&
      !proxy_info.proxy_server().is_quic()) {
    // Brotli encoding can be used only when the proxy server is a secure proxy
    // server.
    return;
  }

  if (!request_headers->HasHeader(net::HttpRequestHeaders::kAcceptEncoding))
    return;

  std::string header_value;
  request_headers->GetHeader(net::HttpRequestHeaders::kAcceptEncoding,
                             &header_value);

  // Brotli should not be already present in the header since the URL is non-
  // cryptographic. This is an approximate check, and would trigger even if the
  // accept-encoding header contains an encoding that has prefix |kBrotli|.
  DCHECK_EQ(std::string::npos, header_value.find(kBrotli));

  request_headers->RemoveHeader(net::HttpRequestHeaders::kAcceptEncoding);
  if (!header_value.empty())
    header_value += ", ";
  header_value += kBrotli;
  request_headers->SetHeader(net::HttpRequestHeaders::kAcceptEncoding,
                             header_value);
}

void DataReductionProxyNetworkDelegate::MaybeAddChromeProxyECTHeader(
    net::HttpRequestHeaders* request_headers,
    const net::URLRequest& request) const {
  DCHECK(thread_checker_.CalledOnValidThread());

  // This method should be called only when the resolved proxy was a data
  // saver proxy.
  DCHECK(request.url().is_valid());
  DCHECK(!request.url().SchemeIsCryptographic());
  DCHECK(request.url().SchemeIsHTTPOrHTTPS());

  if (request_headers->HasHeader(chrome_proxy_ect_header()))
    request_headers->RemoveHeader(chrome_proxy_ect_header());

  if (request.context()->network_quality_estimator()) {
    net::EffectiveConnectionType type = request.context()
                                            ->network_quality_estimator()
                                            ->GetEffectiveConnectionType();
    if (type > net::EFFECTIVE_CONNECTION_TYPE_OFFLINE) {
      DCHECK_NE(net::EFFECTIVE_CONNECTION_TYPE_LAST, type);
      request_headers->SetHeader(chrome_proxy_ect_header(),
                                 net::GetNameForEffectiveConnectionType(type));
      return;
    }
  }
  request_headers->SetHeader(chrome_proxy_ect_header(),
                             net::GetNameForEffectiveConnectionType(
                                 net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN));

  static_assert(net::EFFECTIVE_CONNECTION_TYPE_OFFLINE + 1 ==
                    net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G,
                "ECT enum value is not handled.");
  static_assert(net::EFFECTIVE_CONNECTION_TYPE_4G + 1 ==
                    net::EFFECTIVE_CONNECTION_TYPE_LAST,
                "ECT enum value is not handled.");
}

void DataReductionProxyNetworkDelegate::RemoveChromeProxyECTHeader(
    net::HttpRequestHeaders* request_headers) const {
  DCHECK(thread_checker_.CalledOnValidThread());

  request_headers->RemoveHeader(chrome_proxy_ect_header());
}

}  // namespace data_reduction_proxy
