// Copyright 2017 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 "services/network/public/cpp/content_security_policy/csp_source_list.h"
#include "base/memory/raw_ptr.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/cpp/content_security_policy/content_security_policy.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"

namespace network {

namespace {

// Allow() is an abbreviation of CheckCSPSourceList. Useful for writing
// test expectations on one line.
bool Allow(const mojom::CSPSourceListPtr& source_list,
           const GURL& url,
           const mojom::CSPSource& self,
           bool is_redirect = false,
           bool is_response_check = false,
           mojom::CSPDirectiveName directive_name =
               mojom::CSPDirectiveName::FrameSrc) {
  return CheckCSPSourceList(directive_name, *source_list, url, self,
                            is_redirect, is_response_check);
}

std::vector<mojom::ContentSecurityPolicyPtr> Parse(
    const std::vector<std::string>& policies) {
  auto headers =
      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
  for (const auto& policy : policies) {
    headers->AddHeader("Content-Security-Policy", policy);
  }
  std::vector<mojom::ContentSecurityPolicyPtr> parsed_policies;
  AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"),
                                      &parsed_policies);
  return parsed_policies;
}

mojom::CSPSourceListPtr ParseToSourceList(mojom::CSPDirectiveName directive,
                                          const std::string& value) {
  return std::move(
      Parse({ToString(directive) + " " + value})[0]->directives[directive]);
}

std::vector<mojom::CSPSourceListPtr> ParseToVectorOfSourceLists(
    mojom::CSPDirectiveName directive,
    const std::vector<std::string>& values) {
  std::vector<std::string> csp_values(values.size());
  std::transform(values.begin(), values.end(), csp_values.begin(),
                 [directive](const std::string& s) -> std::string {
                   return ToString(directive) + " " + s;
                 });
  std::vector<mojom::ContentSecurityPolicyPtr> policies = Parse(csp_values);
  std::vector<mojom::CSPSourceListPtr> sources(policies.size());
  std::transform(policies.begin(), policies.end(), sources.begin(),
                 [directive](mojom::ContentSecurityPolicyPtr& p)
                     -> mojom::CSPSourceListPtr {
                   return std::move(p->directives[directive]);
                 });
  return sources;
}

std::vector<const mojom::CSPSourceList*> ToRawPointers(
    const std::vector<mojom::CSPSourceListPtr>& list) {
  std::vector<const mojom::CSPSourceList*> out(list.size());
  std::transform(
      list.begin(), list.end(), out.begin(),
      [](const mojom::CSPSourceListPtr& item) -> const mojom::CSPSourceList* {
        return item.get();
      });
  return out;
}

}  // namespace

TEST(CSPSourceList, MultipleSource) {
  auto self = network::mojom::CSPSource::New("http", "example.com", 80, "",
                                             false, false);
  std::vector<mojom::CSPSourcePtr> sources;
  sources.push_back(mojom::CSPSource::New("", "a.com", url::PORT_UNSPECIFIED,
                                          "", false, false));
  sources.push_back(mojom::CSPSource::New("", "b.com", url::PORT_UNSPECIFIED,
                                          "", false, false));
  auto source_list = mojom::CSPSourceList::New();
  source_list->sources = std::move(sources);
  EXPECT_TRUE(Allow(source_list, GURL("http://a.com"), *self));
  EXPECT_TRUE(Allow(source_list, GURL("http://b.com"), *self));
  EXPECT_FALSE(Allow(source_list, GURL("http://c.com"), *self));
}

TEST(CSPSourceList, AllowStar) {
  auto self = network::mojom::CSPSource::New("http", "example.com", 80, "",
                                             false, false);
  auto source_list = mojom::CSPSourceList::New();
  source_list->allow_star = true;
  EXPECT_TRUE(Allow(source_list, GURL("http://not-example.com"), *self));
  EXPECT_TRUE(Allow(source_list, GURL("https://not-example.com"), *self));
  EXPECT_TRUE(Allow(source_list, GURL("ws://not-example.com"), *self));
  EXPECT_TRUE(Allow(source_list, GURL("wss://not-example.com"), *self));
  EXPECT_TRUE(Allow(source_list, GURL("ftp://not-example.com"), *self));

  EXPECT_FALSE(Allow(source_list, GURL("file://not-example.com"), *self));
  EXPECT_FALSE(Allow(source_list, GURL("applewebdata://a.test"), *self));

  {
    // With a protocol of 'file', '*' allow 'file:'
    auto file = network::mojom::CSPSource::New(
        "file", "example.com", url::PORT_UNSPECIFIED, "", false, false);
    EXPECT_TRUE(Allow(source_list, GURL("file://not-example.com"), *file));
    EXPECT_FALSE(Allow(source_list, GURL("applewebdata://a.test"), *file));
  }
}

TEST(CSPSourceList, AllowSelf) {
  auto self = network::mojom::CSPSource::New("http", "example.com", 80, "",
                                             false, false);
  auto source_list = mojom::CSPSourceList::New();
  source_list->allow_self = true;
  EXPECT_TRUE(Allow(source_list, GURL("http://example.com"), *self));
  EXPECT_FALSE(Allow(source_list, GURL("http://not-example.com"), *self));
  EXPECT_TRUE(Allow(source_list, GURL("https://example.com"), *self));
  EXPECT_FALSE(Allow(source_list, GURL("ws://example.com"), *self));
}

TEST(CSPSourceList, AllowStarAndSelf) {
  auto self =
      network::mojom::CSPSource::New("https", "a.com", 443, "", false, false);
  auto source_list = mojom::CSPSourceList::New();

  // If the request is allowed by {*} and not by {'self'} then it should be
  // allowed by the union {*,'self'}.
  source_list->allow_self = true;
  source_list->allow_star = false;
  EXPECT_FALSE(Allow(source_list, GURL("http://b.com"), *self));
  source_list->allow_self = false;
  source_list->allow_star = true;
  EXPECT_TRUE(Allow(source_list, GURL("http://b.com"), *self));
  source_list->allow_self = true;
  source_list->allow_star = true;
  EXPECT_TRUE(Allow(source_list, GURL("http://b.com"), *self));
}

TEST(CSPSourceList, AllowSelfWithUnspecifiedPort) {
  auto self = network::mojom::CSPSource::New("https", "example.com", 443, "",
                                             false, false);
  auto source_list = mojom::CSPSourceList::New();
  source_list->allow_self = true;

  EXPECT_TRUE(Allow(source_list, GURL("https://example.com/print.pdf"), *self));
}

TEST(CSPSourceList, AllowNone) {
  auto self = network::mojom::CSPSource::New("http", "example.com", 80, "",
                                             false, false);
  auto source_list = mojom::CSPSourceList::New();
  EXPECT_FALSE(Allow(source_list, GURL("http://example.com"), *self));
  EXPECT_FALSE(Allow(source_list, GURL("https://example.test/"), *self));
}

TEST(CSPSourceTest, SelfIsUnique) {
  // Policy: 'self'
  auto source_list = mojom::CSPSourceList::New();
  source_list->allow_self = true;

  auto self =
      network::mojom::CSPSource::New("http", "a.com", 80, "", false, false);
  EXPECT_TRUE(Allow(source_list, GURL("http://a.com"), *self));
  EXPECT_FALSE(Allow(source_list, GURL("data:text/html,hello"), *self));

  // Self doesn't match anything.
  auto no_self_source = network::mojom::CSPSource::New(
      "", "", url::PORT_UNSPECIFIED, "", false, false);
  EXPECT_FALSE(Allow(source_list, GURL("http://a.com"), *no_self_source));
  EXPECT_FALSE(
      Allow(source_list, GURL("data:text/html,hello"), *no_self_source));
}

// Test that 'unsafe-allow-redirects' is only applied to navigate-to.
TEST(CSPSourceList, UnsafeAllowRedirects) {
  auto self = network::mojom::CSPSource::New("http", "example.com", 80, "",
                                             false, false);
  std::vector<mojom::CSPSourcePtr> sources;
  sources.push_back(mojom::CSPSource::New("", "a.com", url::PORT_UNSPECIFIED,
                                          "", false, false));
  auto source_list = mojom::CSPSourceList::New();
  source_list->sources = std::move(sources);
  source_list->allow_response_redirects = true;

  EXPECT_TRUE(Allow(source_list, GURL("http://a.com"), *self,
                    /*is_redirect=*/false, /*is_response_check=*/false,
                    mojom::CSPDirectiveName::NavigateTo));
  EXPECT_TRUE(Allow(source_list, GURL("http://b.com"), *self,
                    /*is_redirect=*/false, /*is_response_check=*/false,
                    mojom::CSPDirectiveName::NavigateTo));
  EXPECT_FALSE(Allow(source_list, GURL("http://b.com"), *self,
                     /*is_redirect=*/false, /*is_response_check=*/true,
                     mojom::CSPDirectiveName::NavigateTo));

  EXPECT_TRUE(Allow(source_list, GURL("http://a.com"), *self,
                    /*is_redirect=*/false, /*is_response_check=*/false,
                    mojom::CSPDirectiveName::FrameSrc));
  EXPECT_FALSE(Allow(source_list, GURL("http://b.com"), *self,
                     /*is_redirect=*/false, /*is_response_check=*/false,
                     mojom::CSPDirectiveName::FrameSrc));
  EXPECT_FALSE(Allow(source_list, GURL("http://b.com"), *self,
                     /*is_redirect=*/false, /*is_response_check=*/true,
                     mojom::CSPDirectiveName::FrameSrc));

  EXPECT_TRUE(Allow(source_list, GURL("http://a.com"), *self,
                    /*is_redirect=*/false, /*is_response_check=*/false,
                    mojom::CSPDirectiveName::FormAction));
  EXPECT_FALSE(Allow(source_list, GURL("http://b.com"), *self,
                     /*is_redirect=*/false, /*is_response_check=*/false,
                     mojom::CSPDirectiveName::FormAction));
  EXPECT_FALSE(Allow(source_list, GURL("http://b.com"), *self,
                     /*is_redirect=*/false, /*is_response_check=*/true,
                     mojom::CSPDirectiveName::FormAction));
}

TEST(CSPSourceList, Subsume) {
  std::string required =
      "http://example1.com/foo/ https://*.example2.com/bar/ "
      "http://*.example3.com:*/bar/";
  mojom::CSPSourceListPtr required_sources =
      ParseToSourceList(mojom::CSPDirectiveName::ScriptSrc, required);

  struct TestCase {
    std::vector<std::string> response_csp;
    bool expected;
  } cases[] = {
      // Non-intersecting source lists give an effective policy of 'none', which
      // is always subsumed.
      {{"http://example1.com/bar/", "http://*.example3.com:*/bar/"}, true},
      {{"http://example1.com/bar/",
        "http://*.example3.com:*/bar/ https://*.example2.com/bar/"},
       true},
      // Lists that intersect into one of the required sources are subsumed.
      {{"http://example1.com/foo/"}, true},
      {{"https://*.example2.com/bar/"}, true},
      {{"http://*.example3.com:*/bar/"}, true},
      {{"https://example1.com/foo/",
        "http://*.example1.com/foo/ https://*.example2.com/bar/"},
       true},
      {{"https://example2.com/bar/",
        "http://*.example3.com:*/bar/ https://*.example2.com/bar/"},
       true},
      {{"http://example3.com:100/bar/",
        "http://*.example3.com:*/bar/ https://*.example2.com/bar/"},
       true},
      // Lists that intersect into two of the required sources are subsumed.
      {{"http://example1.com/foo/ https://*.example2.com/bar/"}, true},
      {{"http://example1.com/foo/ https://a.example2.com/bar/",
        "https://a.example2.com/bar/ http://example1.com/foo/"},
       true},
      {{"http://example1.com/foo/ https://a.example2.com/bar/",
        "http://*.example2.com/bar/ http://example1.com/foo/"},
       true},
      // Ordering should not matter.
      {{"https://example1.com/foo/ https://a.example2.com/bar/",
        "http://a.example2.com/bar/ http://example1.com/foo/"},
       true},
      // Lists that intersect into a policy identical to the required list are
      // subsumed.
      {{"http://example1.com/foo/ https://*.example2.com/bar/ "
        "http://*.example3.com:*/bar/ http://example1.com/foo/"},
       true},
      {{"http://example1.com/foo/ https://*.example2.com/bar/ "
        "http://*.example3.com:*/bar/"},
       true},
      {{"http://example1.com/foo/ https://*.example2.com/bar/ "
        "http://*.example3.com:*/bar/",
        "http://example1.com/foo/ https://*.example2.com/bar/ "
        "http://*.example3.com:*/bar/ http://example4.com/foo/"},
       true},
      {{"http://example1.com/foo/ https://*.example2.com/bar/ "
        "http://*.example3.com:*/bar/",
        "http://example1.com/foo/ https://*.example2.com/bar/ "
        "http://*.example3.com:*/bar/ http://example1.com/foo/"},
       true},
      // Lists that include sources which are not subsumed by the required list
      // are not subsumed.
      {{"http://example1.com/foo/ https://*.example2.com/bar/ "
        "http://*.example3.com:*/bar/ http://*.example4.com:*/bar/"},
       false},
      {{"http://example1.com/foo/ http://example2.com/foo/"}, false},
      {{"http://*.com/bar/", "http://example1.com/bar/"}, false},
      {{"http://*.example1.com/bar/"}, false},
      {{"http://example1.com/bar/"}, false},
      {{"http://*.example1.com/foo/"}, false},
      {{"wss://example2.com/bar/"}, false},
      {{"http://*.non-example3.com:*/bar/"}, false},
      {{"http://example3.com/foo/"}, false},
      {{"http://not-example1.com", "http://not-example1.com"}, false},
      // Lists that intersect into sources which are not subsumed by the
      // required
      // list are not subsumed.
      {{"http://not-example1.com/foo/", "https:"}, false},
      {{"http://not-example1.com/foo/ http://example1.com/foo/", "https:"},
       false},
      {{"http://*", "http://*.com http://*.example3.com:*/bar/"}, false},
  };

  auto origin_b =
      mojom::CSPSource::New("https", "frame.test", 443, "", false, false);
  for (const auto& test : cases) {
    auto response_sources = ParseToVectorOfSourceLists(
        mojom::CSPDirectiveName::ScriptSrc, test.response_csp);

    EXPECT_EQ(test.expected,
              CSPSourceListSubsumes(
                  *required_sources, ToRawPointers(response_sources),
                  mojom::CSPDirectiveName::ScriptSrc, origin_b.get()))
        << required << " should " << (test.expected ? "" : "not ") << "subsume "
        << base::JoinString(test.response_csp, ", ");
  }
}

TEST(CSPSourceList, SubsumeWithSelf) {
  std::string required =
      "http://example1.com/foo/ http://*.example2.com/bar/ "
      "http://*.example3.com:*/bar/ 'self'";
  mojom::CSPSourceListPtr required_sources =
      ParseToSourceList(mojom::CSPDirectiveName::ScriptSrc, required);

  struct TestCase {
    std::vector<std::string> response_csp;
    const char* origin;
    bool expected;
  } cases[] = {
      // "https://example.test/" is a secure origin for both `required` and
      // `response_csp`.
      {{"'self'"}, "https://example.test/", true},
      {{"https://example.test"}, "https://example.test/", true},
      {{"https://example.test/"}, "https://example.test/", true},
      {{"'self' 'self' 'self'"}, "https://example.test/", true},
      {{"'self'", "'self'", "'self'"}, "https://example.test/", true},
      {{"'self'", "'self'", "https://*.example.test/"},
       "https://example.test/",
       true},
      {{"'self'", "'self'", "https://*.example.test/bar/"},
       "https://example.test/",
       true},
      {{"'self' https://another.test/bar", "'self' http://*.example.test/bar",
        "https://*.example.test/bar/"},
       "https://example.test/",
       true},
      {{"http://example1.com/foo/ 'self'"}, "https://example.test/", true},
      {{"http://example1.com/foo/ https://example.test/"},
       "https://example.test/",
       true},
      {{"http://example1.com/foo/ http://*.example2.com/bar/"},
       "https://example.test/",
       true},
      {{"http://example1.com/foo/ http://*.example2.com/bar/ "
        "http://*.example3.com:*/bar/ https://example.test/"},
       "https://example.test/",
       true},
      {{"http://example1.com/foo/ http://*.example2.com/bar/ "
        "http://*.example3.com:*/bar/ 'self'"},
       "https://example.test/",
       true},
      {{"'self'", "'self'", "https://example.test/"},
       "https://example.test/",
       true},
      {{"'self'", "https://example.test/folder/"},
       "https://example.test/",
       true},
      {{"'self'", "http://example.test/folder/"},
       "https://example.test/",
       true},
      {{"'self' https://example.com/", "https://example.com/"},
       "https://example.test/",
       false},
      {{"http://example1.com/foo/ http://*.example2.com/bar/",
        "http://example1.com/foo/ http://*.example2.com/bar/ 'self'"},
       "https://example.test/",
       true},
      {{"http://*.example1.com/foo/", "http://*.example1.com/foo/ 'self'"},
       "https://example.test/",
       false},
      {{"https://*.example.test/", "https://*.example.test/ 'self'"},
       "https://example.test/",
       false},
      {{"http://example.test/"}, "https://example.test/", false},
      {{"https://example.test/"}, "https://example.test/", true},
      // Origins of `required` and `response_csp` do not match.
      {{"https://example.test/"}, "https://other-origin.test/", false},
      {{"'self'"}, "https://other-origin.test/", true},
      {{"http://example1.com/foo/ http://*.example2.com/bar/ "
        "http://*.example3.com:*/bar/ 'self'"},
       "https://other-origin.test/",
       true},
      {{"http://example1.com/foo/ http://*.example2.com/bar/ "
        "http://*.example3.com:*/bar/ https://other-origin.test/"},
       "https://other-origin.test/",
       true},
      {{"http://example1.com/foo/ 'self'"}, "https://other-origin.test/", true},
      {{"'self'", "https://example.test/"}, "https://other-origin.test/", true},
      {{"'self' https://example.test/", "https://example.test/"},
       "https://other-origin.test/",
       false},
      {{"https://example.test/", "http://example.test/"},
       "https://other-origin.test/",
       false},
      {{"'self'", "http://other-origin.test/"},
       "https://other-origin.test/",
       true},
      {{"'self'", "https://non-example.test/"},
       "https://other-origin.test/",
       true},
      // `response_csp`'s origin matches one of the sources in the source list
      // of `required`.
      {{"'self'", "http://*.example1.com/foo/"}, "http://example1.com/", true},
      {{"http://*.example2.com/bar/", "'self'"},
       "http://example2.com/bar/",
       true},
      {{"'self' http://*.example1.com/foo/", "http://*.example1.com/foo/"},
       "http://example1.com/",
       false},
      {{"http://*.example2.com/bar/ http://example1.com/",
        "'self' http://example1.com/"},
       "http://example2.com/bar/",
       false},
  };

  for (const auto& test : cases) {
    auto response_sources = ParseToVectorOfSourceLists(
        mojom::CSPDirectiveName::ScriptSrc, test.response_csp);

    GURL parsed_test_origin(test.origin);
    auto origin_b = mojom::CSPSource::New(
        parsed_test_origin.scheme(), parsed_test_origin.host(),
        parsed_test_origin.EffectiveIntPort(), "", false, false);
    EXPECT_EQ(test.expected,
              CSPSourceListSubsumes(
                  *required_sources, ToRawPointers(response_sources),
                  mojom::CSPDirectiveName::ScriptSrc, origin_b.get()))
        << required << "from origin " << test.origin << " should "
        << (test.expected ? "" : "not ") << "subsume "
        << base::JoinString(test.response_csp, ", ");
  }
}

TEST(CSPSourceList, SubsumeAllowAllInline) {
  struct TestCase {
    mojom::CSPDirectiveName directive;
    std::string required;
    std::vector<std::string> response_csp;
    bool expected;
  } cases[] = {
      // `required` allows all inline behavior.
      {mojom::CSPDirectiveName::StyleSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
       {"'unsafe-inline' http://example1.com/foo/bar.html"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline'",
       {"http://example1.com/foo/ 'unsafe-inline'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline'",
       {"'unsafe-inline' 'nonce-yay'", "'unsafe-inline'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline'",
       {"'unsafe-inline' 'nonce-yay'", "'unsafe-inline'", "'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline'",
       {"'unsafe-inline' 'nonce-yay'", "'unsafe-inline'",
        "'strict-dynamic' 'nonce-yay'"},
       true},
      // `required` does not allow all inline behavior.
      {mojom::CSPDirectiveName::StyleSrc,
       "http://example1.com/foo/ 'self' 'strict-dynamic'",
       {"'unsafe-inline' http://example1.com/foo/bar.html"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self'",
       {"'unsafe-inline'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline'",
       {"'unsafe-inline' 'nonce-yay'", "'nonce-abc'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self'",
       {"'unsafe-inline' https://example.test/"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
       {"'unsafe-inline' https://example.test/"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
       {"'unsafe-inline' 'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'nonce-yay'",
       {"'unsafe-inline' 'nonce-yay'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic' "
       "'nonce-yay'",
       {"'unsafe-inline' 'nonce-yay'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic' "
       "'nonce-yay'",
       {"http://example1.com/foo/ 'unsafe-inline' 'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba' "
       "'strict-dynamic'",
       {"'unsafe-inline' 'sha512-321cba'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic' "
       "'sha512-321cba'",
       {"http://example1.com/foo/ 'unsafe-inline' 'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba'",
       {"http://example1.com/foo/ 'unsafe-inline'",
        "http://example1.com/foo/ 'sha512-321cba'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba'",
       {"http://example1.com/foo/ 'unsafe-inline'",
        "http://example1.com/foo/ 'unsafe-inline' 'sha512-321cba'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba'",
       {"http://example1.com/foo/ 'unsafe-inline' 'nonce-yay'",
        "http://example1.com/foo/ 'unsafe-inline' 'sha512-321cba'"},
       true},
  };

  auto origin_b =
      mojom::CSPSource::New("https", "frame.test", 443, "", false, false);
  for (const auto& test : cases) {
    mojom::CSPSourceListPtr required_sources =
        ParseToSourceList(test.directive, test.required);
    auto response_sources =
        ParseToVectorOfSourceLists(test.directive, test.response_csp);

    EXPECT_EQ(test.expected,
              CSPSourceListSubsumes(*required_sources,
                                    ToRawPointers(response_sources),
                                    test.directive, origin_b.get()))
        << test.required << " should " << (test.expected ? "" : "not ")
        << "subsume " << base::JoinString(test.response_csp, ", ");
  }
}

TEST(CSPSourceList, SubsumeUnsafeAttributes) {
  struct TestCase {
    mojom::CSPDirectiveName directive;
    std::string required;
    std::vector<std::string> response_csp;
    bool expected;
  } cases[] = {
      // `required` or `response_csp` contain `unsafe-eval`.
      {mojom::CSPDirectiveName::StyleSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic' "
       "'unsafe-eval'",
       {"http://example1.com/foo/bar.html 'unsafe-eval'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-eval'",
       {"http://example1.com/foo/ 'unsafe-inline'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-eval'",
       {"http://example1.com/foo/ 'unsafe-inline' 'unsafe-eval'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-eval'",
       {"http://example1.com/foo/ 'unsafe-eval'",
        "http://example1.com/foo/bar 'self' unsafe-eval'",
        "http://non-example.com/foo/ 'unsafe-eval' 'self'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self'",
       {"http://example1.com/foo/ 'unsafe-eval'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline'",
       {"http://example1.com/foo/ 'unsafe-eval'",
        "http://example1.com/foo/bar 'self' 'unsafe-eval'",
        "http://non-example.com/foo/ 'unsafe-eval' 'self'"},
       false},
      // `required` or `response_csp` contain `unsafe-hashes`.
      {mojom::CSPDirectiveName::StyleSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'unsafe-eval' "
       "'strict-dynamic' "
       "'unsafe-hashes'",
       {"http://example1.com/foo/bar.html 'unsafe-hashes'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-hashes'",
       {"http://example1.com/foo/ 'unsafe-inline'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-hashes'",
       {"http://example1.com/foo/ 'unsafe-inline' 'unsafe-hashes'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-eval' "
       "'unsafe-hashes'",
       {"http://example1.com/foo/ 'unsafe-eval' 'unsafe-hashes'",
        "http://example1.com/foo/bar 'self' 'unsafe-hashes'",
        "http://non-example.com/foo/ 'unsafe-hashes' 'self'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self'",
       {"http://example1.com/foo/ 'unsafe-hashes'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline'",
       {"http://example1.com/foo/ 'unsafe-hashes'",
        "http://example1.com/foo/bar 'self' 'unsafe-hashes'",
        "https://example1.com/foo/bar 'unsafe-hashes' 'self'"},
       false},
  };

  auto origin_b =
      mojom::CSPSource::New("https", "frame.test", 443, "", false, false);
  for (const auto& test : cases) {
    mojom::CSPSourceListPtr required_sources =
        ParseToSourceList(test.directive, test.required);
    auto response_sources =
        ParseToVectorOfSourceLists(test.directive, test.response_csp);

    EXPECT_EQ(test.expected,
              CSPSourceListSubsumes(*required_sources,
                                    ToRawPointers(response_sources),
                                    test.directive, origin_b.get()))
        << test.required << " should " << (test.expected ? "" : "not ")
        << "subsume " << base::JoinString(test.response_csp, ", ");
  }
}

TEST(CSPSourceList, SubsumeNoncesAndHashes) {
  // For |required| to subsume |response_csp|:
  // - If |response_csp| enforces some nonce, then |required| must contain some
  // nonce, but they do not need to match.
  // - On the other side, all hashes enforced by |response_csp| must be
  // contained in |required|.
  struct TestCase {
    mojom::CSPDirectiveName directive;
    std::string required;
    std::vector<std::string> response_csp;
    bool expected;
  } cases[] = {
      // Check nonces.
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'unsafe-inline' 'nonce-abc'",
       {"'unsafe-inline'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'nonce-abc'",
       {"'nonce-abc'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline'",
       {"'unsafe-inline' 'nonce-yay'", "'nonce-yay'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'nonce-yay'",
       {"'unsafe-inline' 'nonce-yay'", "'nonce-yay'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'nonce-abc' 'nonce-yay'",
       {"'unsafe-inline' https://example.test/"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'nonce-abc' 'nonce-yay'",
       {"'nonce-abc' https://example1.com/foo/"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'nonce-yay' "
       "'strict-dynamic'",
       {"https://example.test/ 'nonce-yay'"},
       false},
      {mojom::CSPDirectiveName::StyleSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'nonce-yay' "
       "'strict-dynamic'",
       {"'nonce-yay' https://example1.com/foo/"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'nonce-abc'",
       {"http://example1.com/foo/ 'nonce-xyz'"},
       true},
      {mojom::CSPDirectiveName::StyleSrc,
       "http://example1.com/foo/ 'nonce-abc'",
       {"http://example1.com/foo/ 'nonce-xyz'"},
       true},
      // Check hashes.
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba'",
       {"http://example1.com/foo/page.html 'strict-dynamic'",
        "https://example1.com/foo/ 'sha512-321cba'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba'",
       {"http://some-other.com/ 'strict-dynamic' 'sha512-321cba'",
        "http://example1.com/foo/ 'unsafe-inline' 'sha512-321cba'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba'",
       {"http://example1.com/foo/ 'sha512-321abc' 'sha512-321cba'",
        "http://example1.com/foo/ 'sha512-321abc' 'sha512-321cba'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321cba'",
       {"http://example1.com/foo/ 'unsafe-inline'",
        "http://example1.com/foo/ 'sha512-321cba'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc'",
       {"http://example1.com/foo/ 'unsafe-inline' 'sha512-321abc'",
        "http://example1.com/foo/ 'sha512-321abc'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc'",
       {"'unsafe-inline' 'sha512-321abc'",
        "http://example1.com/foo/ 'sha512-321abc'"},
       true},
      // Nonces and hashes together.
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc' "
       "'nonce-abc'",
       {"'unsafe-inline' 'sha512-321abc' 'self'",
        "'unsafe-inline''sha512-321abc' https://example.test/"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc' "
       "'nonce-abc'",
       {"'unsafe-inline' 'sha512-321abc' 'self' 'nonce-abc'",
        "'sha512-321abc' https://example.test/"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc' "
       "'nonce-abc'",
       {"'unsafe-inline' 'sha512-321abc' 'self'",
        " 'sha512-321abc' https://example.test/ 'nonce-abc'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc' "
       "'nonce-abc'",
       {"'unsafe-inline' 'sha512-321abc' 'self' 'nonce-xyz'",
        "unsafe-inline' 'sha512-321abc' https://example.test/ 'nonce-xyz'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc' "
       "'nonce-abc'",
       {"'unsafe-inline' 'sha512-321abc' 'self' 'sha512-xyz'",
        "unsafe-inline' 'sha512-321abc' https://example.test/ 'sha512-xyz'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'nonce-abc' 'sha512-321abc'",
       {"http://example1.com/foo/ 'nonce-xyz' 'sha512-321abc'"},
       true},
      {mojom::CSPDirectiveName::StyleSrc,
       "http://example1.com/foo/ 'nonce-abc' 'sha512-321abc'",
       {"http://example1.com/foo/ 'nonce-xyz' 'sha512-321abc'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'nonce-abc' 'sha512-321abc'",
       {"http://example1.com/foo/ 'nonce-xyz' 'sha512-xyz'"},
       false},
      {mojom::CSPDirectiveName::StyleSrc,
       "http://example1.com/foo/ 'nonce-abc' 'sha512-321abc'",
       {"http://example1.com/foo/ 'nonce-xyz' 'sha512-xyz'",
        "http://example1.com/foo/ 'nonce-zyx' 'nonce-xyz' 'sha512-xyz'"},
       false},
  };

  auto origin_b =
      mojom::CSPSource::New("https", "frame.test", 443, "", false, false);
  for (const auto& test : cases) {
    mojom::CSPSourceListPtr required_sources =
        ParseToSourceList(test.directive, test.required);
    auto response_sources =
        ParseToVectorOfSourceLists(test.directive, test.response_csp);

    EXPECT_EQ(test.expected,
              CSPSourceListSubsumes(*required_sources,
                                    ToRawPointers(response_sources),
                                    test.directive, origin_b.get()))
        << test.required << " should " << (test.expected ? "" : "not ")
        << "subsume " << base::JoinString(test.response_csp, ", ");
  }
}

TEST(CSPSourceList, SubsumeStrictDynamic) {
  struct TestCase {
    mojom::CSPDirectiveName directive;
    std::string required;
    std::vector<std::string> response_csp;
    bool expected;
  } cases[] = {
      // Neither `required` nor effective policy of `response_csp` has
      // `strict-dynamic`.
      {mojom::CSPDirectiveName::StyleSrc,
       "http://example1.com/foo/ 'self' 'nonce-yay' 'strict-dynamic'",
       {"'strict-dynamic' 'nonce-yay'", "'nonce-yay' 'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::StyleSrc,
       "http://example1.com/foo/ 'self' 'nonce-yay' 'strict-dynamic'",
       {"'strict-dynamic' 'nonce-yay'", "'nonce-abc' 'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::StyleSrc,
       "http://example1.com/foo/ 'self' 'sha512-321abc'",
       {"'strict-dynamic' 'nonce-yay' 'sha512-321abc'",
        "'sha512-321abc' 'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::StyleSrc,
       "http://example1.com/foo/ 'self' 'sha512-321abc'",
       {"'strict-dynamic' 'nonce-yay' 'sha512-321abc'",
        "'sha512-321abc' 'strict-dynamic'", "'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::StyleSrc,
       "http://example1.com/foo/ 'self' 'sha512-321abc'",
       {"'strict-dynamic' 'nonce-yay' http://example1.com/",
        "http://example1.com/ 'strict-dynamic'"},
       false},
      // `required` has `strict-dynamic`, effective policy of `response_csp`
      // does not.
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'nonce-yay' 'strict-dynamic'",
       {"'strict-dynamic' 'nonce-yay'", "'nonce-yay'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
       {"'strict-dynamic' 'sha512-321abc'", "'unsafe-inline' 'sha512-321abc'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
       {"'sha512-321abc'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
       {"http://example1.com/foo/ 'sha512-321abc'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
       {"'self' 'sha512-321abc'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
       {"'strict-dynamic' 'nonce-yay'", "'nonce-yay'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
       {"http://example1.com/ 'sha512-321abc'",
        "http://example1.com/ 'sha512-321abc'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'sha512-321abc' 'strict-dynamic'",
       {"https://example1.com/foo/ 'sha512-321abc'",
        "http://example1.com/foo/ 'sha512-321abc'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'nonce-yay' 'strict-dynamic'",
       {"'strict-dynamic' 'nonce-yay'", "'nonce-yay'", "'sha512-321abc'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-hashes' "
       "'strict-dynamic'",
       {"'strict-dynamic' 'unsafe-hashes'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'nonce-yay' 'strict-dynamic'",
       {"'strict-dynamic' 'nonce-yay' 'unsafe-hashes'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-eval' 'strict-dynamic'",
       {"'strict-dynamic' 'unsafe-eval'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'nonce-yay' 'strict-dynamic'",
       {"'strict-dynamic' 'nonce-yay' 'unsafe-eval'"},
       false},
      // `required` does not have `strict-dynamic`, but effective policy of
      // `response_csp` does.
      // Note that any subsumption in this set-up should be `false`.
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'nonce-yay'",
       {"'strict-dynamic' 'nonce-yay'", "'sha512-321abc' 'strict-dynamic'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'sha512-321abc'",
       {"'strict-dynamic' 'sha512-321abc'", "'strict-dynamic' 'sha512-321abc'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline'",
       {"'strict-dynamic'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'sha512-321abc'",
       {"'strict-dynamic'"},
       false},
      // Both `required` and effective policy of `response_csp` has
      // `strict-dynamic`.
      {mojom::CSPDirectiveName::ScriptSrc,
       "'strict-dynamic'",
       {"'strict-dynamic'", "'strict-dynamic'", "'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "'strict-dynamic'",
       {"'strict-dynamic'", "'strict-dynamic' 'nonce-yay'",
        "'strict-dynamic' 'nonce-yay'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "'strict-dynamic' 'nonce-yay'",
       {"http://example.com 'strict-dynamic' 'nonce-yay'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
       {"'strict-dynamic' 'nonce-yay'", "'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
       {"'strict-dynamic' http://another.com/",
        "http://another.com/ 'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'unsafe-inline' 'strict-dynamic'",
       {"'self' 'sha512-321abc' 'strict-dynamic'",
        "'self' 'strict-dynamic' 'sha512-321abc'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'sha512-321abc' 'strict-dynamic'",
       {"'self' 'sha512-321abc' 'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
       {"'unsafe-inline' 'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
       {"'unsafe-inline' 'sha512-123xyz' 'strict-dynamic'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "'unsafe-eval' 'self' 'sha512-321abc' 'strict-dynamic'",
       {"'unsafe-eval' 'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
       {"'unsafe-eval' 'strict-dynamic'"},
       false},
      {mojom::CSPDirectiveName::ScriptSrc,
       "'unsafe-hashes' 'self' 'sha512-321abc' 'strict-dynamic'",
       {"'unsafe-hashes' 'strict-dynamic'"},
       true},
      {mojom::CSPDirectiveName::ScriptSrc,
       "http://example1.com/foo/ 'self' 'sha512-321abc' 'strict-dynamic'",
       {"'unsafe-hashes' 'strict-dynamic'"},
       false},
  };

  auto origin_b =
      mojom::CSPSource::New("https", "frame.test", 443, "", false, false);
  for (const auto& test : cases) {
    mojom::CSPSourceListPtr required_sources =
        ParseToSourceList(test.directive, test.required);
    auto response_sources =
        ParseToVectorOfSourceLists(test.directive, test.response_csp);

    EXPECT_EQ(test.expected,
              CSPSourceListSubsumes(*required_sources,
                                    ToRawPointers(response_sources),
                                    test.directive, origin_b.get()))
        << test.required << " should " << (test.expected ? "" : "not ")
        << "subsume " << base::JoinString(test.response_csp, ", ");
  }
}

TEST(CSPSourceList, SubsumeListWildcard) {
  struct TestCase {
    std::string required;
    std::vector<std::string> response_csp;
    bool expected;
  } cases[] = {
      // `required` subsumes `response_csp`.
      {"*", {""}, true},
      {"*", {"'none'"}, true},
      {"*", {"*"}, true},
      {"*", {"*", "*", "*"}, true},
      {"*", {"*", "* https: http: ftp: ws: wss:"}, true},
      {"*", {"*", "https: http: ftp: ws: wss:"}, true},
      {"https: http: ftp: ws: wss:", {"*", "https: http: ftp: ws: wss:"}, true},
      {"http: ftp: ws:", {"*", "https: http: ftp: ws: wss:"}, true},
      {"http: ftp: ws:", {"*", "https: 'strict-dynamic'"}, true},
      {"http://another.test", {"*", "'self'"}, true},
      {"http://another.test/", {"*", "'self'"}, true},
      {"http://another.test", {"https:", "'self'"}, true},
      {"'self'", {"*", "'self'"}, true},
      {"'unsafe-eval' * ", {"'unsafe-eval'"}, true},
      {"'unsafe-hashes' * ", {"'unsafe-hashes'"}, true},
      {"'unsafe-inline' * ", {"'unsafe-inline'"}, true},
      {"*", {"*", "http://a.com ws://b.com ftp://c.com"}, true},
      {"*", {"* data: blob:", "http://a.com ws://b.com ftp://c.com"}, true},
      {"*", {"data: blob:", "http://a.com ws://b.com ftp://c.com"}, true},
      {"*", {"*", "data://a.com ws://b.com ftp://c.com"}, true},
      {"* data:",
       {"data: blob: *", "data://a.com ws://b.com ftp://c.com"},
       true},
      {"http://a.com ws://b.com ftp://c.com",
       {"*", "http://a.com ws://b.com ftp://c.com"},
       true},
      // `required` does not subsume `response_csp`.
      {"*", std::vector<std::string>(), false},
      {"", {"*"}, false},
      {"'none'", {"*"}, false},
      {"*", {"data:"}, false},
      {"*", {"blob:"}, false},
      {"http: ftp: ws:",
       {"* 'strict-dynamic'", "https: 'strict-dynamic'"},
       false},
      {"https://another.test", {"*"}, false},
      {"*", {"* 'unsafe-eval'"}, false},
      {"*", {"* 'unsafe-hashes'"}, false},
      {"*", {"* 'unsafe-inline'"}, false},
      {"'unsafe-eval'", {"* 'unsafe-eval'"}, false},
      {"'unsafe-hashes'", {"* 'unsafe-hashes'"}, false},
      {"'unsafe-inline'", {"* 'unsafe-inline'"}, false},
      {"*", {"data: blob:", "data://a.com ws://b.com ftp://c.com"}, false},
      {"* data:",
       {"data: blob:", "blob://a.com ws://b.com ftp://c.com"},
       false},
  };

  auto origin_b =
      mojom::CSPSource::New("https", "another.test", 443, "", false, false);
  for (const auto& test : cases) {
    mojom::CSPSourceListPtr required_sources =
        ParseToSourceList(mojom::CSPDirectiveName::ScriptSrc, test.required);
    auto response_sources = ParseToVectorOfSourceLists(
        mojom::CSPDirectiveName::ScriptSrc, test.response_csp);

    EXPECT_EQ(test.expected,
              CSPSourceListSubsumes(
                  *required_sources, ToRawPointers(response_sources),
                  mojom::CSPDirectiveName::ScriptSrc, origin_b.get()))
        << test.required << " should " << (test.expected ? "" : "not ")
        << "subsume " << base::JoinString(test.response_csp, ", ");
  }
}

TEST(CSPSourceList, SubsumeListNoScheme) {
  auto origin_http =
      mojom::CSPSource::New("http", "example.org", 80, "", false, false);
  auto origin_https =
      mojom::CSPSource::New("https", "example.org", 443, "", false, false);
  struct TestCase {
    std::string required;
    std::vector<std::string> response_csp;
    raw_ptr<mojom::CSPSource> origin;
    bool expected;
  } cases[] = {
      {"http://a.com", {"a.com"}, origin_https.get(), true},
      {"https://a.com", {"a.com"}, origin_https.get(), true},
      {"https://a.com", {"a.com"}, origin_http.get(), false},
      {"data://a.com", {"a.com"}, origin_https.get(), false},
      {"a.com", {"a.com"}, origin_https.get(), true},
      {"a.com", {"a.com"}, origin_http.get(), true},
      {"a.com", {"https://a.com"}, origin_http.get(), true},
      {"a.com", {"https://a.com"}, origin_https.get(), true},
      {"a.com", {"http://a.com"}, origin_https.get(), false},
      {"https:", {"a.com"}, origin_http.get(), false},
      {"http:", {"a.com"}, origin_http.get(), true},
      {"https:", {"a.com", "https:"}, origin_http.get(), true},
      {"https:", {"a.com"}, origin_https.get(), true},
  };

  for (const auto& test : cases) {
    mojom::CSPSourceListPtr required_sources =
        ParseToSourceList(mojom::CSPDirectiveName::ScriptSrc, test.required);
    auto response_sources = ParseToVectorOfSourceLists(
        mojom::CSPDirectiveName::ScriptSrc, test.response_csp);

    EXPECT_EQ(test.expected,
              CSPSourceListSubsumes(
                  *required_sources, ToRawPointers(response_sources),
                  mojom::CSPDirectiveName::ScriptSrc, test.origin))
        << test.required << " on origin with scheme " << test.origin->scheme
        << " should " << (test.expected ? "" : "not ") << "subsume "
        << base::JoinString(test.response_csp, ", ");
  }
}

}  // namespace network
