// Copyright 2015 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/domain_reliability/header.h"

#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "components/domain_reliability/config.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace domain_reliability {
namespace {

class DomainReliabilityHeaderTest : public testing::Test {
 protected:
  DomainReliabilityHeaderTest() {}
  ~DomainReliabilityHeaderTest() override {}

  void Parse(std::string value) {
    parsed_ = DomainReliabilityHeader::Parse(value);
  }

  const DomainReliabilityHeader* parsed() const { return parsed_.get(); }

 private:
  std::unique_ptr<DomainReliabilityHeader> parsed_;
};

bool CheckReportUris(const char* pipe_separated_expected_report_uris,
                     const ScopedVector<GURL>& actual_report_uris) {
  if (!pipe_separated_expected_report_uris)
    return actual_report_uris.empty();

  std::vector<std::string> expected_report_uri_strings =
      SplitString(pipe_separated_expected_report_uris,
                  "|",
                  base::KEEP_WHITESPACE,
                  base::SPLIT_WANT_ALL);
  if (expected_report_uri_strings.size() != actual_report_uris.size())
    return false;

  for (size_t i = 0; i < actual_report_uris.size(); ++i) {
    if (actual_report_uris[i]->spec() != expected_report_uri_strings[i])
      return false;
  }

  return true;
}

TEST_F(DomainReliabilityHeaderTest, SetConfig) {
  static const struct {
    const char* header;
    const char* pipe_separated_report_uris;
    int64_t max_age_in_seconds;
    bool include_subdomains;
    const char* description;
  } test_cases[] = {
    { "report-uri=https://a/; max-age=5",
          "https://a/", 5, false,
          "register" },
    { "report-uri=\"https://a/\"; max-age=5",
          "https://a/", 5, false,
          "register with quoted report-uri" },
    { "report-uri=<https://a/>; max-age=5",
          "https://a/", 5, false,
          "register with bracketed report-uri" },
    { "report-uri=https://a/ https://b/; max-age=5",
          "https://a/|https://b/", 5, false,
          "register with two report-uris" },
    { "report-uri=https://a/; max-age=5; includeSubdomains",
          "https://a/", 5, true,
          "register with includeSubdomains" },
  };

  for (const auto& test_case : test_cases) {
    std::string assert_prefix = base::StringPrintf(
        "Valid set-config NEL header \"%s\" (%s) incorrectly parsed as ",
        test_case.header,
        test_case.description);
    Parse(test_case.header);
    EXPECT_FALSE(parsed()->IsParseError()) << assert_prefix << "invalid.";
    EXPECT_FALSE(parsed()->IsClearConfig()) << assert_prefix << "clear-config.";
    if (parsed()->IsParseError() || parsed()->IsClearConfig())
      continue;
    EXPECT_TRUE(parsed()->IsSetConfig());

    std::string assert_message =
        assert_prefix + "\"" + parsed()->ToString() + "\"";
    EXPECT_TRUE(CheckReportUris(test_case.pipe_separated_report_uris,
                                parsed()->config().collectors))
        << assert_message;
    EXPECT_EQ(test_case.max_age_in_seconds,
              parsed()->max_age().InSeconds())
        << assert_message;
    EXPECT_EQ(test_case.include_subdomains,
              parsed()->config().include_subdomains)
        << assert_message;
  }
}

TEST_F(DomainReliabilityHeaderTest, ClearConfig) {
  static const struct {
    const char* header;
    const char* description;
  } test_cases[] = {
    { "max-age=0", "unregister" },
    { "report-uri=https://a/; max-age=0", "unregister with report-uri" },
    { "max-age=0; includeSubdomains", "unregister with includeSubdomains" },
  };

  for (const auto& test_case : test_cases) {
    std::string assert_prefix = base::StringPrintf(
        "Valid clear-config NEL header \"%s\" (%s) incorrectly parsed as ",
        test_case.header,
        test_case.description);
    Parse(test_case.header);
    EXPECT_FALSE(parsed()->IsParseError()) << assert_prefix << "invalid.";
    EXPECT_FALSE(parsed()->IsSetConfig()) << assert_prefix << "set-config.";
  }
}

TEST_F(DomainReliabilityHeaderTest, Error) {
  static const struct {
    const char* header;
    const char* description;
  } test_cases[] = {
    { "", "empty" },
    { "max-age=5", "report-uri missing with non-zero max-age" },
    { "report-uri; max-age=5", "report-uri has no value" },
    { "report-uri=; max-age=5", "report-uri value is empty" },
    { "report-uri=http://a/; max-age=5", "report-uri is insecure" },
    { "report-uri=https://a/ http://b/; max-age=5",
          "one report-uri of two is insecure" },
    { "report-uri=\"https://a/; max-age=5", "report-uri is unbalanced" },
    { "report-uri=https://a/\"; max-age=5", "report-uri is unbalanced" },
    { "report-uri=<https://a/; max-age=5", "report-uri is unbalanced" },
    { "report-uri=https://a/>; max-age=5", "report-uri is unbalanced" },
    { "report-uri=https://a/", "max-age is missing" },
    { "report-uri=https://a/; max-age", "max-age has no value" },
    { "report-uri=https://a/; max-age=", "max-age value is empty" },
    { "report-uri=https://a/; max-age=a", "max-age is entirely non-numeric" },
    { "report-uri=https://a/; max-age=5a", "max-age is partly non-numeric" },
    { "report-uri=https://a/; max-age=5 5", "max-age has multiple values" },
  };

  for (const auto& test_case : test_cases) {
    Parse(test_case.header);
    EXPECT_TRUE(parsed()->IsParseError())
        << "Invalid NEL header \"" << test_case.header << "\" ("
        << test_case.description << ") incorrectly parsed as valid.";
  }
}

}  // namespace
}  // namespace domain_reliability
