// Copyright 2018 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 "content/browser/web_package/signed_exchange_utils.h"

#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/metrics/field_trial_params.h"
#include "base/no_destructor.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/loader/download_utils_impl.h"
#include "content/browser/web_package/origins_list.h"
#include "content/browser/web_package/signed_exchange_devtools_proxy.h"
#include "content/browser/web_package/signed_exchange_error.h"
#include "content/browser/web_package/signed_exchange_request_handler.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/resource_response.h"
#include "third_party/blink/public/common/origin_trials/trial_token_validator.h"

namespace content {
namespace signed_exchange_utils {

void ReportErrorAndTraceEvent(
    SignedExchangeDevToolsProxy* devtools_proxy,
    const std::string& error_message,
    base::Optional<SignedExchangeError::FieldIndexPair> error_field) {
  TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("loading"),
                       "SignedExchangeError", TRACE_EVENT_SCOPE_THREAD, "error",
                       error_message);
  if (devtools_proxy)
    devtools_proxy->ReportError(error_message, std::move(error_field));
}

namespace {

OriginsList CreateAdvertiseAcceptHeaderOriginsList() {
  std::string param = base::GetFieldTrialParamValueByFeature(
      features::kSignedHTTPExchangeAcceptHeader,
      features::kSignedHTTPExchangeAcceptHeaderFieldTrialParamName);
  if (param.empty())
    DLOG(ERROR) << "The Accept-SXG origins list param is empty.";

  return OriginsList(param);
}

bool IsSignedHTTPExchangeEnabledByFlags() {
  return base::FeatureList::IsEnabled(features::kSignedHTTPExchange) ||
         base::CommandLine::ForCurrentProcess()->HasSwitch(
             switches::kEnableExperimentalWebPlatformFeatures);
}

}  //  namespace

bool NeedToCheckRedirectedURLForAcceptHeader() {
  // When SignedHTTPExchange is enabled, the SignedExchange accept header must
  // be sent to all origins. So we don't need to check the redirected URL.
  return !IsSignedHTTPExchangeEnabledByFlags() &&
         base::FeatureList::IsEnabled(
             features::kSignedHTTPExchangeOriginTrial) &&
         base::FeatureList::IsEnabled(
             features::kSignedHTTPExchangeAcceptHeader);
}

bool ShouldAdvertiseAcceptHeader(const url::Origin& origin) {
  // When SignedHTTPExchange is enabled, we must send the SignedExchange accept
  // header to all origins.
  if (IsSignedHTTPExchangeEnabledByFlags())
    return true;
  // When SignedHTTPExchangeOriginTrial is not enabled or
  // SignedHTTPExchangeAcceptHeader is not enabled, we must not send the
  // SignedExchange accept header.
  if (!base::FeatureList::IsEnabled(features::kSignedHTTPExchangeOriginTrial) ||
      !base::FeatureList::IsEnabled(
          features::kSignedHTTPExchangeAcceptHeader)) {
    return false;
  }

  // |origins_list| is initialized in a thread-safe manner.
  // Querying OriginsList::Match() should be safe since it's read-only access.
  static base::NoDestructor<OriginsList> origins_list(
      CreateAdvertiseAcceptHeaderOriginsList());
  return origins_list->Match(origin);
}

bool IsSignedExchangeHandlingEnabled() {
  return IsSignedHTTPExchangeEnabledByFlags() ||
         base::FeatureList::IsEnabled(features::kSignedHTTPExchangeOriginTrial);
}

bool ShouldHandleAsSignedHTTPExchange(
    const GURL& request_url,
    const network::ResourceResponseHead& head) {
  // Currently we don't support the signed exchange which is returned from a
  // service worker.
  // TODO(crbug/803774): Decide whether we should support it or not.
  if (head.was_fetched_via_service_worker)
    return false;
  if (!SignedExchangeRequestHandler::IsSupportedMimeType(head.mime_type))
    return false;
  // Do not handle responses without HttpResponseHeaders.
  // (Example: data:application/signed-exchange,)
  if (!head.headers.get())
    return false;
  if (download_utils::MustDownload(request_url, head.headers.get(),
                                   head.mime_type)) {
    return false;
  }
  if (IsSignedHTTPExchangeEnabledByFlags())
    return true;
  if (!base::FeatureList::IsEnabled(features::kSignedHTTPExchangeOriginTrial))
    return false;
  std::unique_ptr<blink::TrialTokenValidator> validator =
      std::make_unique<blink::TrialTokenValidator>();
  return validator->RequestEnablesFeature(
      request_url, head.headers.get(),
      features::kSignedHTTPExchangeOriginTrial.name, base::Time::Now());
}

base::Optional<SignedExchangeVersion> GetSignedExchangeVersion(
    const std::string& content_type) {
  // https://wicg.github.io/webpackage/loading.html#signed-exchange-version
  // Step 1. Let mimeType be the supplied MIME type of response. [spec text]
  // |content_type| is the supplied MIME type.
  // Step 2. If mimeType is undefined, return undefined. [spec text]
  // Step 3. If mimeType's essence is not "application/signed-exchange", return
  //         undefined. [spec text]
  const std::string::size_type semicolon = content_type.find(';');
  const std::string essence = base::ToLowerASCII(base::TrimWhitespaceASCII(
      content_type.substr(0, semicolon), base::TRIM_ALL));
  if (essence != "application/signed-exchange")
    return base::nullopt;

  // Step 4.Let params be mimeType's parameters. [spec text]
  std::map<std::string, std::string> params;
  if (semicolon != base::StringPiece::npos) {
    net::HttpUtil::NameValuePairsIterator parser(
        content_type.begin() + semicolon + 1, content_type.end(), ';');
    while (parser.GetNext()) {
      const base::StringPiece name(parser.name_begin(), parser.name_end());
      params[base::ToLowerASCII(name)] = parser.value();
    }
    if (!parser.valid())
      return base::nullopt;
  }
  // Step 5. If params["v"] exists, return it. Otherwise, return undefined.
  //        [spec text]
  auto iter = params.find("v");
  if (iter != params.end()) {
    if (iter->second == "b3")
      return base::make_optional(SignedExchangeVersion::kB3);
    return base::make_optional(SignedExchangeVersion::kUnknown);
  }
  return base::nullopt;
}

}  // namespace signed_exchange_utils
}  // namespace content
