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

#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "components/cbor/cbor_values.h"
#include "components/cbor/cbor_writer.h"
#include "content/browser/web_package/signed_exchange_consts.h"
#include "content/browser/web_package/signed_exchange_prologue.h"
#include "content/public/common/content_paths.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

namespace {

const char kSignatureString[] =
    "sig1;"
    " sig=*MEUCIQDXlI2gN3RNBlgFiuRNFpZXcDIaUpX6HIEwcZEc0cZYLAIga9DsVOMM+"
    "g5YpwEBdGW3sS+bvnmAJJiSMwhuBdqp5UY=*;"
    " integrity=\"mi-draft2\";"
    " validity-url=\"https://test.example.org/resource.validity.1511128380\";"
    " cert-url=\"https://example.com/oldcerts\";"
    " cert-sha256=*W7uB969dFW3Mb5ZefPS9Tq5ZbH5iSmOILpjv2qEArmI=*;"
    " date=1511128380; expires=1511733180";

cbor::CBORValue CBORByteString(const char* str) {
  return cbor::CBORValue(str, cbor::CBORValue::Type::BYTE_STRING);
}

base::Optional<SignedExchangeEnvelope> GenerateHeaderAndParse(
    const GURL& fallback_url,
    base::StringPiece signature,
    const std::map<const char*, const char*>& request_map,
    const std::map<const char*, const char*>& response_map) {
  cbor::CBORValue::MapValue request_cbor_map;
  cbor::CBORValue::MapValue response_cbor_map;
  for (auto& pair : request_map)
    request_cbor_map[CBORByteString(pair.first)] = CBORByteString(pair.second);
  for (auto& pair : response_map)
    response_cbor_map[CBORByteString(pair.first)] = CBORByteString(pair.second);

  cbor::CBORValue::ArrayValue array;
  array.push_back(cbor::CBORValue(std::move(request_cbor_map)));
  array.push_back(cbor::CBORValue(std::move(response_cbor_map)));

  auto serialized = cbor::CBORWriter::Write(cbor::CBORValue(std::move(array)));
  return SignedExchangeEnvelope::Parse(
      fallback_url, signature,
      base::make_span(serialized->data(), serialized->size()),
      nullptr /* devtools_proxy */);
}

}  // namespace

TEST(SignedExchangeEnvelopeTest, ParseGoldenFile) {
  base::FilePath test_sxg_path;
  base::PathService::Get(content::DIR_TEST_DATA, &test_sxg_path);
  test_sxg_path =
      test_sxg_path.AppendASCII("sxg").AppendASCII("test.example.org_test.sxg");

  std::string contents;
  ASSERT_TRUE(base::ReadFileToString(test_sxg_path, &contents));
  auto* contents_bytes = reinterpret_cast<const uint8_t*>(contents.data());

  ASSERT_GT(contents.size(),
            signed_exchange_prologue::BeforeFallbackUrl::kEncodedSizeInBytes);
  signed_exchange_prologue::BeforeFallbackUrl prologue_a =
      signed_exchange_prologue::BeforeFallbackUrl::Parse(
          base::make_span(
              contents_bytes,
              signed_exchange_prologue::BeforeFallbackUrl::kEncodedSizeInBytes),
          nullptr /* devtools_proxy */);
  ASSERT_GT(contents.size(),
            signed_exchange_prologue::BeforeFallbackUrl::kEncodedSizeInBytes +
                prologue_a.ComputeFallbackUrlAndAfterLength());
  signed_exchange_prologue::FallbackUrlAndAfter prologue_b =
      signed_exchange_prologue::FallbackUrlAndAfter::Parse(
          base::make_span(contents_bytes +
                              signed_exchange_prologue::BeforeFallbackUrl::
                                  kEncodedSizeInBytes,
                          prologue_a.ComputeFallbackUrlAndAfterLength()),
          prologue_a, nullptr /* devtools_proxy */);

  size_t signature_header_field_offset =
      signed_exchange_prologue::BeforeFallbackUrl::kEncodedSizeInBytes +
      prologue_a.ComputeFallbackUrlAndAfterLength();
  base::StringPiece signature_header_field(
      contents.data() + signature_header_field_offset,
      prologue_b.signature_header_field_length());
  const auto cbor_bytes = base::make_span<const uint8_t>(
      contents_bytes + signature_header_field_offset +
          prologue_b.signature_header_field_length(),
      prologue_b.cbor_header_length());
  const base::Optional<SignedExchangeEnvelope> envelope =
      SignedExchangeEnvelope::Parse(prologue_b.fallback_url(),
                                    signature_header_field, cbor_bytes,
                                    nullptr /* devtools_proxy */);
  ASSERT_TRUE(envelope.has_value());
  EXPECT_EQ(envelope->request_url(), GURL("https://test.example.org/test/"));
  EXPECT_EQ(envelope->request_method(), "GET");
  EXPECT_EQ(envelope->response_code(), static_cast<net::HttpStatusCode>(200u));
  EXPECT_EQ(envelope->response_headers().size(), 3u);
  EXPECT_EQ(envelope->response_headers().find("content-encoding")->second,
            "mi-sha256-03");
}

TEST(SignedExchangeEnvelopeTest, ValidHeader) {
  auto header = GenerateHeaderAndParse(
      GURL("https://test.example.org/test/"), kSignatureString,
      {
          {kMethodKey, "GET"},
      },
      {{kStatusKey, "200"}, {"content-type", "text/html"}});
  ASSERT_TRUE(header.has_value());
  EXPECT_EQ(header->request_url(), GURL("https://test.example.org/test/"));
  EXPECT_EQ(header->request_method(), "GET");
  EXPECT_EQ(header->response_code(), static_cast<net::HttpStatusCode>(200u));
  EXPECT_EQ(header->response_headers().size(), 1u);
}

TEST(SignedExchangeEnvelopeTest, UnsafeMethod) {
  auto header = GenerateHeaderAndParse(GURL("https://test.example.org/test/"),
                                       kSignatureString,
                                       {
                                           {kMethodKey, "POST"},
                                       },
                                       {
                                           {kStatusKey, "200"},
                                       });
  ASSERT_FALSE(header.has_value());
}

TEST(SignedExchangeEnvelopeTest, RelativeURL) {
  auto header = GenerateHeaderAndParse(GURL("test/"), kSignatureString,
                                       {
                                           {kMethodKey, "GET"},
                                       },
                                       {
                                           {kStatusKey, "200"},
                                       });
  ASSERT_FALSE(header.has_value());
}

TEST(SignedExchangeEnvelopeTest, HttpURLShouldFail) {
  auto header = GenerateHeaderAndParse(GURL("http://test.example.org/test/"),
                                       kSignatureString,
                                       {
                                           {kMethodKey, "GET"},
                                       },
                                       {
                                           {kStatusKey, "200"},
                                       });
  ASSERT_FALSE(header.has_value());
}

TEST(SignedExchangeEnvelopeTest, RedirectStatusShouldFail) {
  auto header = GenerateHeaderAndParse(GURL("https://test.example.org/test/"),
                                       kSignatureString, {{kMethodKey, "GET"}},
                                       {{kStatusKey, "302"}});
  ASSERT_FALSE(header.has_value());
}

TEST(SignedExchangeEnvelopeTest, Status300ShouldSucceed) {
  auto header = GenerateHeaderAndParse(
      GURL("https://test.example.org/test/"), kSignatureString,
      {{kMethodKey, "GET"}},
      {{kStatusKey, "300"}});  // 300 is not a redirect status.
  ASSERT_TRUE(header.has_value());
  EXPECT_EQ(header->response_code(), static_cast<net::HttpStatusCode>(300u));
}

TEST(SignedExchangeEnvelopeTest, StatefulRequestHeader) {
  auto header = GenerateHeaderAndParse(
      GURL("https://test.example.org/test/"), kSignatureString,
      {
          {kMethodKey, "GET"}, {"authorization", "Basic Zm9vOmJhcg=="},
      },
      {
          {kStatusKey, "200"},
      });
  ASSERT_FALSE(header.has_value());
}

TEST(SignedExchangeEnvelopeTest, StatefulResponseHeader) {
  auto header = GenerateHeaderAndParse(
      GURL("https://test.example.org/test/"), kSignatureString,
      {
          {kMethodKey, "GET"},
      },
      {
          {kStatusKey, "200"}, {"set-cookie", "foo=bar"},
      });
  ASSERT_FALSE(header.has_value());
}

TEST(SignedExchangeEnvelopeTest, UppercaseRequestMap) {
  auto header = GenerateHeaderAndParse(
      GURL("https://test.example.org/test/"), kSignatureString,
      {{kMethodKey, "GET"}, {"Accept-Language", "en-us"}},
      {
          {kStatusKey, "200"},
      });
  ASSERT_FALSE(header.has_value());
}

TEST(SignedExchangeEnvelopeTest, UppercaseResponseMap) {
  auto header = GenerateHeaderAndParse(
      GURL("https://test.example.org/test/"), kSignatureString,
      {
          {kMethodKey, "GET"},
      },
      {{kStatusKey, "200"}, {"Content-Length", "123"}});
  ASSERT_FALSE(header.has_value());
}

TEST(SignedExchangeEnvelopeTest, InvalidValidityURLHeader) {
  auto header = GenerateHeaderAndParse(
      GURL("https://test2.example.org/test/"), kSignatureString,
      {
          {kMethodKey, "GET"},
      },
      {{kStatusKey, "200"}, {"content-type", "text/html"}});
  ASSERT_FALSE(header.has_value());
}

}  // namespace content
