// Copyright 2016 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_util.h"

#include <stdint.h>

#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "base/version.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h"
#include "components/data_reduction_proxy/core/common/lofi_decider.h"
#include "components/data_reduction_proxy/core/common/version.h"
#include "net/base/net_errors.h"
#include "net/base/url_util.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "net/proxy_resolution/proxy_config.h"
#include "net/proxy_resolution/proxy_info.h"
#include "net/url_request/url_request.h"

#if defined(USE_GOOGLE_API_KEYS)
#include "google_apis/google_api_keys.h"
#endif

namespace data_reduction_proxy {

namespace {

#if defined(USE_GOOGLE_API_KEYS)
// Used in all Data Reduction Proxy URLs to specify API Key.
const char kApiKeyName[] = "key";
#endif

// Hostname used for the other bucket which consists of chrome-services traffic.
// This should be in sync with the same in DataReductionSiteBreakdownView.java
const char kOtherHostName[] = "Other";

// Scales |byte_count| by the ratio of |numerator|:|denomenator|.
int64_t ScaleByteCountByRatio(int64_t byte_count,
                              int64_t numerator,
                              int64_t denomenator) {
  DCHECK_LE(0, byte_count);
  DCHECK_LE(0, numerator);
  DCHECK_LT(0, denomenator);

  // As an optimization, use integer arithmetic if it won't overflow.
  if (byte_count <= std::numeric_limits<int32_t>::max() &&
      numerator <= std::numeric_limits<int32_t>::max()) {
    return byte_count * numerator / denomenator;
  }

  double scaled_byte_count = static_cast<double>(byte_count) *
                             static_cast<double>(numerator) /
                             static_cast<double>(denomenator);
  if (scaled_byte_count >
      static_cast<double>(std::numeric_limits<int64_t>::max())) {
    // If this ever triggers, then byte counts can no longer be safely stored in
    // 64-bit ints.
    NOTREACHED();
    return byte_count;
  }
  return static_cast<int64_t>(scaled_byte_count);
}

// Estimate the size of the original headers of |request|. If |used_drp| is
// true, then it's assumed that the original request would have used HTTP/1.1,
// otherwise it assumes that the original request would have used the same
// protocol as |request| did. This is to account for stuff like HTTP/2 header
// compression.
int64_t EstimateOriginalHeaderBytes(const net::URLRequest& request,
                                    const LoFiDecider* lofi_decider) {
  // If this is an auto-reload of an image, then this request would ordinarily
  // not be issued, so return 0.
  if (lofi_decider && lofi_decider->IsClientLoFiAutoReloadRequest(request))
    return 0;
  data_reduction_proxy::DataReductionProxyData* data =
      data_reduction_proxy::DataReductionProxyData::GetData(request);
  if (data && data->used_data_reduction_proxy()) {
    // TODO(sclittle): Remove headers added by Data Reduction Proxy when
    // computing original size. https://crbug.com/535701.
    return request.response_headers()->raw_headers().size();
  }
  return std::max<int64_t>(0, request.GetTotalReceivedBytes() -
                                  request.received_response_content_length());
}

}  // namespace

namespace util {

const char* ChromiumVersion() {
  // Assert at compile time that the Chromium version is at least somewhat
  // properly formed, e.g. the version string is at least as long as "0.0.0.0",
  // and starts and ends with numeric digits. This is to prevent another
  // regression like http://crbug.com/595471.
  static_assert(arraysize(PRODUCT_VERSION) >= arraysize("0.0.0.0") &&
                    '0' <= PRODUCT_VERSION[0] && PRODUCT_VERSION[0] <= '9' &&
                    '0' <= PRODUCT_VERSION[arraysize(PRODUCT_VERSION) - 2] &&
                    PRODUCT_VERSION[arraysize(PRODUCT_VERSION) - 2] <= '9',
                "PRODUCT_VERSION must be a string of the form "
                "'MAJOR.MINOR.BUILD.PATCH', e.g. '1.2.3.4'. "
                "PRODUCT_VERSION='" PRODUCT_VERSION "' is badly formed.");

  return PRODUCT_VERSION;
}

void GetChromiumBuildAndPatch(const std::string& version_string,
                              std::string* build,
                              std::string* patch) {
  uint32_t build_number;
  uint32_t patch_number;
  GetChromiumBuildAndPatchAsInts(version_string, &build_number, &patch_number);
  *build = base::NumberToString(build_number);
  *patch = base::NumberToString(patch_number);
}

void GetChromiumBuildAndPatchAsInts(const std::string& version_string,
                                    uint32_t* build,
                                    uint32_t* patch) {
  base::Version version(version_string);
  DCHECK(version.IsValid());
  DCHECK_EQ(4U, version.components().size());
  *build = version.components()[2];
  *patch = version.components()[3];
}

const char* GetStringForClient(Client client) {
  switch (client) {
    case Client::UNKNOWN:
      return "";
    case Client::CRONET_ANDROID:
      return "cronet";
    case Client::WEBVIEW_ANDROID:
      return "webview";
    case Client::CHROME_ANDROID:
      return "android";
    case Client::CHROME_IOS:
      return "ios";
    case Client::CHROME_MAC:
      return "mac";
    case Client::CHROME_CHROMEOS:
      return "chromeos";
    case Client::CHROME_LINUX:
      return "linux";
    case Client::CHROME_WINDOWS:
      return "win";
    case Client::CHROME_FREEBSD:
      return "freebsd";
    case Client::CHROME_OPENBSD:
      return "openbsd";
    case Client::CHROME_SOLARIS:
      return "solaris";
    case Client::CHROME_QNX:
      return "qnx";
    default:
      NOTREACHED();
      return "";
  }
}

GURL AddApiKeyToUrl(const GURL& url) {
  GURL new_url = url;
#if defined(USE_GOOGLE_API_KEYS)
  std::string api_key = google_apis::GetAPIKey();
  if (google_apis::HasAPIKeyConfigured() && !api_key.empty()) {
    new_url = net::AppendOrReplaceQueryParameter(url, kApiKeyName, api_key);
  }
#endif
  return net::AppendOrReplaceQueryParameter(new_url, "alt", "proto");
}

bool EligibleForDataReductionProxy(const net::ProxyInfo& proxy_info,
                                   const GURL& url,
                                   const std::string& method) {
  return proxy_info.is_direct() && proxy_info.proxy_list().size() == 1 &&
         !url.SchemeIsWSOrWSS() && net::HttpUtil::IsMethodIdempotent(method);
}

bool ApplyProxyConfigToProxyInfo(const net::ProxyConfig& proxy_config,
                                 const net::ProxyRetryInfoMap& proxy_retry_info,
                                 const GURL& url,
                                 net::ProxyInfo* data_reduction_proxy_info) {
  DCHECK(data_reduction_proxy_info);
  if (proxy_config.proxy_rules().empty())
    return false;
  proxy_config.proxy_rules().Apply(url, data_reduction_proxy_info);
  data_reduction_proxy_info->DeprioritizeBadProxies(proxy_retry_info);
  return !data_reduction_proxy_info->proxy_server().is_direct();
}

int64_t CalculateOCLFromOFCL(const net::URLRequest& request) {
  const net::HttpResponseHeaders* response_headers = request.response_headers();
  if (!response_headers)
    return request.received_response_content_length();

  int64_t original_content_length = GetDataReductionProxyOFCL(response_headers);

  if (response_headers->response_code() == net::HTTP_PARTIAL_CONTENT) {
    int64_t first, last, range_content_length;
    if (response_headers->GetContentRangeFor206(&first, &last,
                                                &range_content_length) &&
        range_content_length > 0 && original_content_length > 0) {
      // For a range request, OFCL indicates the original content length of the
      // entire resource. The received response content length should be scaled
      // by the compression ratio given by OFCL / range_content_length.
      original_content_length =
          ScaleByteCountByRatio(request.received_response_content_length(),
                                original_content_length, range_content_length);
    }
  }
  return original_content_length;
}

int64_t EstimateOriginalBodySize(const net::URLRequest& request,
                                 const LoFiDecider* lofi_decider) {
  if (lofi_decider) {
    // If this is an auto-reload of an image, then this request would ordinarily
    // not be issued, so return 0.
    if (lofi_decider->IsClientLoFiAutoReloadRequest(request))
      return 0;

    int64_t first, last, length;
    if (!request.was_cached() &&
        lofi_decider->IsClientLoFiImageRequest(request) &&
        request.response_headers() &&
        request.response_headers()->GetContentRangeFor206(&first, &last,
                                                          &length) &&
        length > request.received_response_content_length()) {
      return length;
    }
  }

  data_reduction_proxy::DataReductionProxyData* data =
      data_reduction_proxy::DataReductionProxyData::GetData(request);
  if (!data || !data->used_data_reduction_proxy() || request.was_cached() ||
      !request.response_headers()) {
    return std::min<int64_t>(request.GetTotalReceivedBytes(),
                             request.received_response_content_length());
  }

  int64_t original_content_length_from_header = CalculateOCLFromOFCL(request);

  if (original_content_length_from_header < 0)
    return request.received_response_content_length();
  if (request.status().error() == net::OK)
    return original_content_length_from_header;

  int64_t content_length_from_header =
      request.response_headers()->GetContentLength();

  if (content_length_from_header < 0)
    return request.received_response_content_length();
  if (content_length_from_header == 0)
    return original_content_length_from_header;

  return ScaleByteCountByRatio(request.received_response_content_length(),
                               original_content_length_from_header,
                               content_length_from_header);
}

int64_t EstimateOriginalReceivedBytes(const net::URLRequest& request,
                                      const LoFiDecider* lofi_decider) {
  if (request.was_cached() || !request.response_headers())
    return request.GetTotalReceivedBytes();

  return EstimateOriginalHeaderBytes(request, lofi_decider) +
         EstimateOriginalBodySize(request, lofi_decider);
}

ProxyScheme ConvertNetProxySchemeToProxyScheme(
    net::ProxyServer::Scheme scheme) {
  switch (scheme) {
    case net::ProxyServer::SCHEME_HTTP:
      return PROXY_SCHEME_HTTP;
    case net::ProxyServer::SCHEME_HTTPS:
      return PROXY_SCHEME_HTTPS;
    case net::ProxyServer::SCHEME_QUIC:
      return PROXY_SCHEME_QUIC;
    case net::ProxyServer::SCHEME_DIRECT:
      return PROXY_SCHEME_DIRECT;
    default:
      NOTREACHED() << scheme;
      return PROXY_SCHEME_UNKNOWN;
  }
}

const char* GetSiteBreakdownOtherHostName() {
  return kOtherHostName;
}

}  // namespace util

namespace protobuf_parser {

PageloadMetrics_EffectiveConnectionType
ProtoEffectiveConnectionTypeFromEffectiveConnectionType(
    net::EffectiveConnectionType effective_connection_type) {
  switch (effective_connection_type) {
    case net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN:
      return PageloadMetrics_EffectiveConnectionType_EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
    case net::EFFECTIVE_CONNECTION_TYPE_OFFLINE:
      return PageloadMetrics_EffectiveConnectionType_EFFECTIVE_CONNECTION_TYPE_OFFLINE;
    case net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G:
      return PageloadMetrics_EffectiveConnectionType_EFFECTIVE_CONNECTION_TYPE_SLOW_2G;
    case net::EFFECTIVE_CONNECTION_TYPE_2G:
      return PageloadMetrics_EffectiveConnectionType_EFFECTIVE_CONNECTION_TYPE_2G;
    case net::EFFECTIVE_CONNECTION_TYPE_3G:
      return PageloadMetrics_EffectiveConnectionType_EFFECTIVE_CONNECTION_TYPE_3G;
    case net::EFFECTIVE_CONNECTION_TYPE_4G:
      return PageloadMetrics_EffectiveConnectionType_EFFECTIVE_CONNECTION_TYPE_4G;
    default:
      NOTREACHED();
      return PageloadMetrics_EffectiveConnectionType_EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
  }
}

PageloadMetrics_ConnectionType ProtoConnectionTypeFromConnectionType(
    net::NetworkChangeNotifier::ConnectionType connection_type) {
  switch (connection_type) {
    case net::NetworkChangeNotifier::CONNECTION_UNKNOWN:
      return PageloadMetrics_ConnectionType_CONNECTION_UNKNOWN;
    case net::NetworkChangeNotifier::CONNECTION_ETHERNET:
      return PageloadMetrics_ConnectionType_CONNECTION_ETHERNET;
    case net::NetworkChangeNotifier::CONNECTION_WIFI:
      return PageloadMetrics_ConnectionType_CONNECTION_WIFI;
    case net::NetworkChangeNotifier::CONNECTION_2G:
      return PageloadMetrics_ConnectionType_CONNECTION_2G;
    case net::NetworkChangeNotifier::CONNECTION_3G:
      return PageloadMetrics_ConnectionType_CONNECTION_3G;
    case net::NetworkChangeNotifier::CONNECTION_4G:
      return PageloadMetrics_ConnectionType_CONNECTION_4G;
    case net::NetworkChangeNotifier::CONNECTION_NONE:
      return PageloadMetrics_ConnectionType_CONNECTION_NONE;
    case net::NetworkChangeNotifier::CONNECTION_BLUETOOTH:
      return PageloadMetrics_ConnectionType_CONNECTION_BLUETOOTH;
  }
}

RequestInfo_Protocol ProtoRequestInfoProtocolFromRequestInfoProtocol(
    DataReductionProxyData::RequestInfo::Protocol protocol) {
  switch (protocol) {
    case DataReductionProxyData::RequestInfo::Protocol::HTTP:
      return RequestInfo_Protocol_HTTP;
    case DataReductionProxyData::RequestInfo::Protocol::HTTPS:
      return RequestInfo_Protocol_HTTPS;
    case DataReductionProxyData::RequestInfo::Protocol::QUIC:
      return RequestInfo_Protocol_QUIC;
    case DataReductionProxyData::RequestInfo::Protocol::UNKNOWN:
      return RequestInfo_Protocol_UNKNOWN;
  }
}

net::ProxyServer::Scheme SchemeFromProxyScheme(
    ProxyServer_ProxyScheme proxy_scheme) {
  switch (proxy_scheme) {
    case ProxyServer_ProxyScheme_HTTP:
      return net::ProxyServer::SCHEME_HTTP;
    case ProxyServer_ProxyScheme_HTTPS:
      return net::ProxyServer::SCHEME_HTTPS;
    default:
      return net::ProxyServer::SCHEME_INVALID;
  }
}

ProxyServer_ProxyScheme ProxySchemeFromScheme(net::ProxyServer::Scheme scheme) {
  switch (scheme) {
    case net::ProxyServer::SCHEME_HTTP:
      return ProxyServer_ProxyScheme_HTTP;
    case net::ProxyServer::SCHEME_HTTPS:
      return ProxyServer_ProxyScheme_HTTPS;
    default:
      return ProxyServer_ProxyScheme_UNSPECIFIED;
  }
}

void TimeDeltaToDuration(const base::TimeDelta& time_delta,
                         Duration* duration) {
  duration->set_seconds(time_delta.InSeconds());
  base::TimeDelta partial_seconds =
      time_delta - base::TimeDelta::FromSeconds(time_delta.InSeconds());
  duration->set_nanos(partial_seconds.InMicroseconds() *
                      base::Time::kNanosecondsPerMicrosecond);
}

base::TimeDelta DurationToTimeDelta(const Duration& duration) {
  return base::TimeDelta::FromSeconds(duration.seconds()) +
         base::TimeDelta::FromMicroseconds(
             duration.nanos() / base::Time::kNanosecondsPerMicrosecond);
}

void TimeToTimestamp(const base::Time& time, Timestamp* timestamp) {
  timestamp->set_seconds((time - base::Time::UnixEpoch()).InSeconds());
  timestamp->set_nanos(((time - base::Time::UnixEpoch()).InMicroseconds() %
                        base::Time::kMicrosecondsPerSecond) *
                       base::Time::kNanosecondsPerMicrosecond);
}

base::Time TimestampToTime(const Timestamp& timestamp) {
  base::Time t = base::Time::UnixEpoch();
  t += base::TimeDelta::FromSeconds(timestamp.seconds());
  t += base::TimeDelta::FromMicroseconds(
      timestamp.nanos() / base::Time::kNanosecondsPerMicrosecond);
  return t;
}

std::unique_ptr<Duration> CreateDurationFromTimeDelta(
    const base::TimeDelta& time_delta) {
  std::unique_ptr<Duration> duration(new Duration);
  TimeDeltaToDuration(time_delta, duration.get());
  return duration;
}

std::unique_ptr<Timestamp> CreateTimestampFromTime(const base::Time& time) {
  std::unique_ptr<Timestamp> timestamp(new Timestamp);
  TimeToTimestamp(time, timestamp.get());
  return timestamp;
}

}  // namespace protobuf_parser

}  // namespace data_reduction_proxy
