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

#include <map>
#include "base/base64.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/web_package/signed_exchange_consts.h"
#include "content/browser/web_package/signed_exchange_utils.h"
#include "crypto/sha2.h"

namespace content {

namespace {

// This covers the characters allowed in Integers and Identifiers.
// https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-04#section-4.5
// https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-04#section-4.8
constexpr char kTokenChars[] = "0123456789abcdefghijklmnopqrstuvwxyz_-*/";

struct ParameterisedIdentifier {
  std::string identifier;
  std::map<std::string, std::string> params;
};

// Parser for (a subset of) Structured Headers defined in [SH].
// [SH] https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-04
class StructuredHeaderParser {
 public:
  explicit StructuredHeaderParser(const base::StringPiece& str)
      : input_(str), failed_(false) {}

  // Callers should call this after ParseSomething(), to check if parser has
  // consumed all the input successfully.
  bool ParsedSuccessfully() const { return !failed_ && input_.empty(); }

  // Parses a Parameterised List ([SH] 4.3).
  void ParseParameterisedList(std::vector<ParameterisedIdentifier>* values) {
    values->push_back(ParameterisedIdentifier());
    ParseParameterisedIdentifier(&values->back());
    while (!failed_) {
      SkipWhitespaces();
      if (!ConsumeChar(','))
        break;
      SkipWhitespaces();
      values->push_back(ParameterisedIdentifier());
      ParseParameterisedIdentifier(&values->back());
    }
  }

  // Parses a Parameterised Identifier ([SH] 4.3.2).
  void ParseParameterisedIdentifier(ParameterisedIdentifier* out) {
    std::string identifier = ReadToken();
    if (identifier.empty()) {
      DVLOG(1) << "ParseParameterisedIdentifier: Identifier expected, got '"
               << input_.front() << "'";
      failed_ = true;
      return;
    }
    out->identifier = identifier;

    while (!failed_) {
      SkipWhitespaces();
      if (!ConsumeChar(';'))
        break;
      SkipWhitespaces();

      std::string name = ReadToken();
      if (name.empty()) {
        DVLOG(1) << "ParseParameterisedIdentifier: Identifier expected, got '"
                 << input_.front() << "'";
        failed_ = true;
        return;
      }
      std::string value;
      if (ConsumeChar('='))
        value = ReadItem();
      if (!out->params.insert(std::make_pair(name, value)).second) {
        DVLOG(1) << "ParseParameterisedIdentifier: duplicated parameter: "
                 << name;
        failed_ = true;
        return;
      }
    }
  }

 private:
  void SkipWhitespaces() {
    input_ = base::TrimWhitespaceASCII(input_, base::TRIM_LEADING);
  }

  std::string ReadToken() {
    size_t len = input_.find_first_not_of(kTokenChars);
    if (len == base::StringPiece::npos)
      len = input_.size();
    std::string token(input_.substr(0, len));
    input_.remove_prefix(len);
    return token;
  }

  bool ConsumeChar(char expected) {
    if (!input_.empty() && input_.front() == expected) {
      input_.remove_prefix(1);
      return true;
    }
    return false;
  }

  // [SH] 4.7. Strings
  std::string ReadString() {
    std::string s;
    if (!ConsumeChar('"')) {
      DVLOG(1) << "ReadString: '\"' expected, got '" << input_.front() << "'";
      failed_ = true;
      return s;
    }
    while (!ConsumeChar('"')) {
      size_t len = input_.find_first_of("\"\\");
      if (len == base::StringPiece::npos) {
        DVLOG(1) << "ReadString: missing closing '\"'";
        failed_ = true;
        return s;
      }
      s.append(std::string(input_.substr(0, len)));
      input_.remove_prefix(len);
      if (ConsumeChar('\\')) {
        if (input_.empty()) {
          DVLOG(1) << "ReadString: backslash at string end";
          failed_ = true;
          return s;
        }
        s.push_back(input_.front());
        input_.remove_prefix(1);
      }
    }
    return s;
  }

  // [SH] 4.9. Binary Content
  std::string ReadBinary() {
    if (!ConsumeChar('*')) {
      DVLOG(1) << "ReadBinary: '*' expected, got '" << input_.front() << "'";
      failed_ = true;
      return std::string();
    }
    size_t len = input_.find('*');
    if (len == base::StringPiece::npos) {
      DVLOG(1) << "ReadBinary: missing closing '*'";
      failed_ = true;
      return std::string();
    }
    base::StringPiece base64 = input_.substr(0, len);
    std::string binary;
    if (!base::Base64Decode(base64, &binary)) {
      DVLOG(1) << "ReadBinary: failed to decode base64: " << base64;
      failed_ = true;
    }
    input_.remove_prefix(len);
    ConsumeChar('*');
    return binary;
  }

  // [SH] 4.4. Items
  std::string ReadItem() {
    if (input_.empty()) {
      DVLOG(1) << "ReadItem: unexpected EOF";
      failed_ = true;
      return std::string();
    }
    switch (input_.front()) {
      case '"':
        return ReadString();
      case '*':
        return ReadBinary();
      default:  // identifier or integer
        return ReadToken();
    }
  }

  base::StringPiece input_;
  bool failed_;
};

}  // namespace

// static
base::Optional<std::vector<SignedExchangeSignatureHeaderField::Signature>>
SignedExchangeSignatureHeaderField::ParseSignature(
    base::StringPiece signature_str,
    SignedExchangeDevToolsProxy* devtools_proxy) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
               "SignedExchangeSignatureHeaderField::ParseSignature");

  StructuredHeaderParser parser(signature_str);
  std::vector<ParameterisedIdentifier> values;
  parser.ParseParameterisedList(&values);
  if (!parser.ParsedSuccessfully()) {
    signed_exchange_utils::ReportErrorAndTraceEvent(
        devtools_proxy, "Failed to parse signature header.");
    return base::nullopt;
  }

  std::vector<Signature> signatures;
  signatures.reserve(values.size());
  for (auto& value : values) {
    signatures.push_back(Signature());
    Signature& sig = signatures.back();
    sig.label = value.identifier;
    sig.sig = value.params[kSig];
    if (sig.sig.empty()) {
      signed_exchange_utils::ReportErrorAndTraceEvent(
          devtools_proxy, "'sig' parameter is not set,");
      return base::nullopt;
    }
    sig.integrity = value.params[kIntegrity];
    if (sig.integrity.empty()) {
      signed_exchange_utils::ReportErrorAndTraceEvent(
          devtools_proxy, "'integrity' parameter is not set.");
      return base::nullopt;
    }
    sig.cert_url = GURL(value.params[kCertUrl]);
    if (!sig.cert_url.is_valid() || sig.cert_url.has_ref()) {
      // TODO(https://crbug.com/819467) : When we will support "ed25519Key", the
      // params may not have "cert-url".
      signed_exchange_utils::ReportErrorAndTraceEvent(
          devtools_proxy, "'cert-url' parameter is not a valid URL.");
      return base::nullopt;
    }
    if (!sig.cert_url.SchemeIs("https") && !sig.cert_url.SchemeIs("data")) {
      signed_exchange_utils::ReportErrorAndTraceEvent(
          devtools_proxy, "'cert-url' should have 'https' or 'data' scheme.");
      return base::nullopt;
    }
    const std::string cert_sha256_string = value.params[kCertSha256Key];
    if (cert_sha256_string.size() != crypto::kSHA256Length) {
      // TODO(https://crbug.com/819467) : When we will support "ed25519Key", the
      // params may not have "cert-sha256".
      signed_exchange_utils::ReportErrorAndTraceEvent(
          devtools_proxy, "'cert-sha256' parameter is not a SHA-256 digest.");
      return base::nullopt;
    }
    net::SHA256HashValue cert_sha256;
    memcpy(&cert_sha256.data, cert_sha256_string.data(), crypto::kSHA256Length);
    sig.cert_sha256 = std::move(cert_sha256);
    // TODO(https://crbug.com/819467): Support ed25519key.
    // sig.ed25519_key = value.params["ed25519Key"];
    sig.validity_url = GURL(value.params[kValidityUrlKey]);
    if (!sig.validity_url.is_valid()) {
      signed_exchange_utils::ReportErrorAndTraceEvent(
          devtools_proxy, "'validity-url' parameter is not a valid URL.");
      return base::nullopt;
    }
    if (sig.validity_url.has_ref()) {
      signed_exchange_utils::ReportErrorAndTraceEvent(
          devtools_proxy, "'validity-url' parameter can't have a fragment.");
      return base::nullopt;
    }
    if (!sig.validity_url.SchemeIs("https")) {
      signed_exchange_utils::ReportErrorAndTraceEvent(
          devtools_proxy, "'validity-url' should have 'https' scheme.");
      return base::nullopt;
    }
    if (!base::StringToUint64(value.params[kDateKey], &sig.date)) {
      signed_exchange_utils::ReportErrorAndTraceEvent(
          devtools_proxy, "'date' parameter is not a number.");
      return base::nullopt;
    }
    if (!base::StringToUint64(value.params[kExpiresKey], &sig.expires)) {
      signed_exchange_utils::ReportErrorAndTraceEvent(
          devtools_proxy, "'expires' parameter is not a number.");
      return base::nullopt;
    }
  }
  return signatures;
}

// static
bool SignedExchangeSignatureHeaderField::GetVersionParamFromContentType(
    base::StringPiece content_type,
    base::Optional<SignedExchangeVersion>* version_param) {
  DCHECK(version_param);
  StructuredHeaderParser parser(content_type);
  ParameterisedIdentifier parameterised_identifier;
  parser.ParseParameterisedIdentifier(&parameterised_identifier);
  if (!parser.ParsedSuccessfully())
    return false;
  const auto it = parameterised_identifier.params.find("v");
  if (it == parameterised_identifier.params.end()) {
    *version_param = base::nullopt;
  } else {
    if (it->second == "b1")
      *version_param = SignedExchangeVersion::kB1;
    else
      return false;
  }
  return true;
}

SignedExchangeSignatureHeaderField::Signature::Signature() = default;
SignedExchangeSignatureHeaderField::Signature::Signature(
    const Signature& other) = default;
SignedExchangeSignatureHeaderField::Signature::~Signature() = default;

}  // namespace content
