// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/platform/loader/link_header.h"

#include "testing/gtest/include/gtest/gtest.h"

namespace blink {
namespace {

TEST(LinkHeaderTest, Empty) {
  String null_string;
  LinkHeaderSet null_header_set(null_string);
  ASSERT_EQ(null_header_set.size(), unsigned(0));
  String empty_string("");
  LinkHeaderSet empty_header_set(empty_string);
  ASSERT_EQ(empty_header_set.size(), unsigned(0));
}

struct SingleTestCase {
  const char* header_value;
  bool valid;
  const char* url;
  const char* rel;
  const char* as;
  const char* media;
  const char* fetch_priority;
} g_single_test_cases[] = {
    {"</images/cat.jpg>; rel=prefetch", true, "/images/cat.jpg", "prefetch", "",
     "", ""},
    {"</images/cat.jpg>;rel=prefetch", true, "/images/cat.jpg", "prefetch", "",
     "", ""},
    {"</images/cat.jpg>   ;rel=prefetch", true, "/images/cat.jpg", "prefetch",
     "", "", ""},
    {"</images/cat.jpg>   ;   rel=prefetch", true, "/images/cat.jpg",
     "prefetch", "", "", ""},
    {"< /images/cat.jpg>   ;   rel=prefetch", true, "/images/cat.jpg",
     "prefetch", "", "", ""},
    {"</images/cat.jpg >   ;   rel=prefetch", true, "/images/cat.jpg",
     "prefetch", "", "", ""},
    {"</images/cat.jpg wutwut>   ;   rel=prefetch", true,
     "/images/cat.jpg wutwut", "prefetch", "", "", ""},
    {"</images/cat.jpg wutwut  \t >   ;   rel=prefetch", true,
     "/images/cat.jpg wutwut", "prefetch", "", "", ""},
    {"</images/cat.jpg>; rel=prefetch   ", true, "/images/cat.jpg", "prefetch",
     "", "", ""},
    {"</images/cat.jpg>; Rel=prefetch   ", true, "/images/cat.jpg", "prefetch",
     "", "", ""},
    {"</images/cat.jpg>; Rel=PReFetCh   ", true, "/images/cat.jpg", "prefetch",
     "", "", ""},
    {"</images/cat.jpg>; rel=prefetch; rel=somethingelse", true,
     "/images/cat.jpg", "prefetch", "", "", ""},
    {"  </images/cat.jpg>; rel=prefetch   ", true, "/images/cat.jpg",
     "prefetch", "", "", ""},
    {"\t  </images/cat.jpg>; rel=prefetch   ", true, "/images/cat.jpg",
     "prefetch", "", "", ""},
    {"</images/cat.jpg>\t\t ; \trel=prefetch \t  ", true, "/images/cat.jpg",
     "prefetch", "", "", ""},
    {"\f</images/cat.jpg>\t\t ; \trel=prefetch \t  ", false},
    {"</images/cat.jpg>; rel= prefetch", true, "/images/cat.jpg", "prefetch",
     "", "", ""},
    {"<../images/cat.jpg?dog>; rel= prefetch", true, "../images/cat.jpg?dog",
     "prefetch", "", "", ""},
    {"</images/cat.jpg>; rel =prefetch", true, "/images/cat.jpg", "prefetch",
     "", "", ""},
    {"</images/cat.jpg>; rel pel=prefetch", false},
    {"< /images/cat.jpg>", true, "/images/cat.jpg", "", "", "", ""},
    {"</images/cat.jpg>; rel =", false},
    {"</images/cat.jpg>; wut=sup; rel =prefetch", true, "/images/cat.jpg",
     "prefetch", "", "", ""},
    {"</images/cat.jpg>; wut=sup ; rel =prefetch", true, "/images/cat.jpg",
     "prefetch", "", "", ""},
    {"</images/cat.jpg>; wut=sup ; rel =prefetch  \t  ;", true,
     "/images/cat.jpg", "prefetch", "", "", ""},
    {"</images/cat.jpg> wut=sup ; rel =prefetch  \t  ;", false},
    {"<   /images/cat.jpg", false},
    {"<   http://wut.com/  sdfsdf ?sd>; rel=dns-prefetch", true,
     "http://wut.com/  sdfsdf ?sd", "dns-prefetch", "", "", ""},
    {"<   http://wut.com/%20%20%3dsdfsdf?sd>; rel=dns-prefetch", true,
     "http://wut.com/%20%20%3dsdfsdf?sd", "dns-prefetch", "", "", ""},
    {"<   http://wut.com/dfsdf?sdf=ghj&wer=rty>; rel=prefetch", true,
     "http://wut.com/dfsdf?sdf=ghj&wer=rty", "prefetch", "", "", ""},
    {"<   http://wut.com/dfsdf?sdf=ghj&wer=rty>;;;;; rel=prefetch", true,
     "http://wut.com/dfsdf?sdf=ghj&wer=rty", "prefetch", "", "", ""},
    {"<   http://wut.com/%20%20%3dsdfsdf?sd>; rel=preload;as=image", true,
     "http://wut.com/%20%20%3dsdfsdf?sd", "preload", "image", "", ""},
    {"<   http://wut.com/%20%20%3dsdfsdf?sd>; rel=preload;as=whatever", true,
     "http://wut.com/%20%20%3dsdfsdf?sd", "preload", "whatever", "", ""},
    {"</images/cat.jpg>; anchor=foo; rel=prefetch;", false},
    {"</images/cat.jpg>; rel=prefetch;anchor=foo ", false},
    {"</images/cat.jpg>; anchor='foo'; rel=prefetch;", false},
    {"</images/cat.jpg>; rel=prefetch;anchor='foo' ", false},
    {"</images/cat.jpg>; rel=prefetch;anchor='' ", false},
    {"</images/cat.jpg>; rel=prefetch;", true, "/images/cat.jpg", "prefetch",
     "", "", ""},
    {"</images/cat.jpg>; rel=prefetch    ;", true, "/images/cat.jpg",
     "prefetch", "", "", ""},
    {"</images/ca,t.jpg>; rel=prefetch    ;", true, "/images/ca,t.jpg",
     "prefetch", "", "", ""},
    {"<simple.css>; rel=stylesheet; title=\"title with a DQUOTE and "
     "backslash\"",
     true, "simple.css", "stylesheet", "", "", ""},
    {"<simple.css>; rel=stylesheet; title=\"title with a DQUOTE \\\" and "
     "backslash: \\\"",
     false},
    {"<simple.css>; title=\"title with a DQUOTE \\\" and backslash: \"; "
     "rel=stylesheet; ",
     true, "simple.css", "stylesheet", "", "", ""},
    {"<simple.css>; title=\'title with a DQUOTE \\\' and backslash: \'; "
     "rel=stylesheet; ",
     true, "simple.css", "stylesheet", "", "", ""},
    {"<simple.css>; title=\"title with a DQUOTE \\\" and ;backslash,: \"; "
     "rel=stylesheet; ",
     true, "simple.css", "stylesheet", "", "", ""},
    {"<simple.css>; title=\"title with a DQUOTE \' and ;backslash,: \"; "
     "rel=stylesheet; ",
     true, "simple.css", "stylesheet", "", "", ""},
    {"<simple.css>; title=\"\"; rel=stylesheet; ", true, "simple.css",
     "stylesheet", "", "", ""},
    {"<simple.css>; title=\"\"; rel=\"stylesheet\"; ", true, "simple.css",
     "stylesheet", "", "", ""},
    {"<simple.css>; rel=stylesheet; title=\"", false},
    {"<simple.css>; rel=stylesheet; title=\"\"", true, "simple.css",
     "stylesheet", "", "", ""},
    {"<simple.css>; rel=\"stylesheet\"; title=\"", false},
    {"<simple.css>; rel=\";style,sheet\"; title=\"", false},
    {"<simple.css>; rel=\"bla'sdf\"; title=\"", false},
    {"<simple.css>; rel=\"\"; title=\"\"", true, "simple.css", "", "", "", ""},
    {"<simple.css>; rel=''; title=\"\"", true, "simple.css", "''", "", "", ""},
    {"<simple.css>; rel=''; title=", false},
    {"<simple.css>; rel=''; title", false},
    {"<simple.css>; rel=''; media", false},
    {"<simple.css>; rel=''; hreflang", false},
    {"<simple.css>; rel=''; type", false},
    {"<simple.css>; rel=''; rev", false},
    {"<simple.css>; rel=''; bla", true, "simple.css", "''", "", "", ""},
    {"<simple.css>; rel='prefetch", true, "simple.css", "'prefetch", "", "",
     ""},
    {"<simple.css>; rel=\"prefetch", false},
    {"<simple.css>; rel=\"", false},
    {"<http://whatever.com>; rel=preconnect; valid!", true,
     "http://whatever.com", "preconnect", "", "", ""},
    {"<http://whatever.com>; rel=preconnect; valid$", true,
     "http://whatever.com", "preconnect", "", "", ""},
    {"<http://whatever.com>; rel=preconnect; invalid@", false},
    {"<http://whatever.com>; rel=preconnect; invalid*", false},
    {"</images/cat.jpg>; rel=prefetch;media='(max-width: 5000px)'", true,
     "/images/cat.jpg", "prefetch", "", "'(max-width: 5000px)'", ""},
    {"</images/cat.jpg>; rel=prefetch;media=\"(max-width: 5000px)\"", true,
     "/images/cat.jpg", "prefetch", "", "(max-width: 5000px)", ""},
    {"</images/cat.jpg>; rel=prefetch;media=(max-width:5000px)", true,
     "/images/cat.jpg", "prefetch", "", "(max-width:5000px)", ""},
    {"<simple.css>; rel=preload; fetchpriority=auto", true, "simple.css",
     "preload", "", "", "auto"},
    {"<simple.css>; rel=preload; fetchpriority=low", true, "simple.css",
     "preload", "", "", "low"},
    {"<simple.css>; rel=preload; fetchpriority=high", true, "simple.css",
     "preload", "", "", "high"},
    {"<simple.js>; rel=modulepreload; fetchpriority=auto", true, "simple.js",
     "modulepreload", "", "", "auto"},
    {"<simple.js>; rel=modulepreload; fetchpriority=low", true, "simple.js",
     "modulepreload", "", "", "low"},
    {"<simple.js>; rel=modulepreload; fetchpriority=high", true, "simple.js",
     "modulepreload", "", "", "high"},
};

void PrintTo(const SingleTestCase& test, std::ostream* os) {
  *os << testing::PrintToString(test.header_value);
}

class SingleLinkHeaderTest : public testing::TestWithParam<SingleTestCase> {};

// Test the cases with a single header
TEST_P(SingleLinkHeaderTest, Single) {
  const SingleTestCase test_case = GetParam();
  LinkHeaderSet header_set(test_case.header_value);
  ASSERT_EQ(1u, header_set.size());
  LinkHeader& header = header_set[0];
  EXPECT_EQ(test_case.valid, header.Valid());
  if (test_case.valid) {
    EXPECT_EQ(test_case.url, header.Url().Ascii());
    EXPECT_EQ(test_case.rel, header.Rel().Ascii());
    EXPECT_EQ(test_case.as, header.As().Ascii());
    EXPECT_EQ(test_case.media, header.Media().Ascii());
    EXPECT_EQ(test_case.fetch_priority, header.FetchPriority().Ascii());
  }
}

INSTANTIATE_TEST_SUITE_P(LinkHeaderTest,
                         SingleLinkHeaderTest,
                         testing::ValuesIn(g_single_test_cases));

struct DoubleTestCase {
  const char* header_value;
  const char* url;
  const char* rel;
  bool valid;
  const char* url2;
  const char* rel2;
  bool valid2;
} g_double_test_cases[] = {
    {"<ybg.css>; rel=stylesheet, <simple.css>; rel=stylesheet", "ybg.css",
     "stylesheet", true, "simple.css", "stylesheet", true},
    {"<ybg.css>; rel=stylesheet,<simple.css>; rel=stylesheet", "ybg.css",
     "stylesheet", true, "simple.css", "stylesheet", true},
    {"<ybg.css>; rel=stylesheet;crossorigin,<simple.css>; rel=stylesheet",
     "ybg.css", "stylesheet", true, "simple.css", "stylesheet", true},
    {"<hel,lo.css>; rel=stylesheet; title=\"foo,bar\", <simple.css>; "
     "rel=stylesheet; title=\"foo;bar\"",
     "hel,lo.css", "stylesheet", true, "simple.css", "stylesheet", true},
};

void PrintTo(const DoubleTestCase& test, std::ostream* os) {
  *os << testing::PrintToString(test.header_value);
}

class DoubleLinkHeaderTest : public testing::TestWithParam<DoubleTestCase> {};

TEST_P(DoubleLinkHeaderTest, Double) {
  const DoubleTestCase test_case = GetParam();
  LinkHeaderSet header_set(test_case.header_value);
  ASSERT_EQ(2u, header_set.size());
  LinkHeader& header1 = header_set[0];
  LinkHeader& header2 = header_set[1];
  EXPECT_EQ(test_case.url, header1.Url());
  EXPECT_EQ(test_case.rel, header1.Rel());
  EXPECT_EQ(test_case.valid, header1.Valid());
  EXPECT_EQ(test_case.url2, header2.Url());
  EXPECT_EQ(test_case.rel2, header2.Rel());
  EXPECT_EQ(test_case.valid2, header2.Valid());
}

INSTANTIATE_TEST_SUITE_P(LinkHeaderTest,
                         DoubleLinkHeaderTest,
                         testing::ValuesIn(g_double_test_cases));

struct CrossOriginTestCase {
  const char* header_value;
  const char* url;
  const char* rel;
  const char* crossorigin;
  bool valid;
} g_cross_origin_test_cases[] = {
    {"<http://whatever.com>; rel=preconnect", "http://whatever.com",
     "preconnect", nullptr, true},
    {"<http://whatever.com>; rel=preconnect; crossorigin=", "", "", "", false},
    {"<http://whatever.com>; rel=preconnect; crossorigin",
     "http://whatever.com", "preconnect", "", true},
    {"<http://whatever.com>; rel=preconnect; crossorigin ",
     "http://whatever.com", "preconnect", "", true},
    {"<http://whatever.com>; rel=preconnect; crossorigin;",
     "http://whatever.com", "preconnect", "", true},
    {"<http://whatever.com>; rel=preconnect; crossorigin, "
     "<http://whatever2.com>; rel=preconnect",
     "http://whatever.com", "preconnect", "", true},
    {"<http://whatever.com>; rel=preconnect; crossorigin , "
     "<http://whatever2.com>; rel=preconnect",
     "http://whatever.com", "preconnect", "", true},
    {"<http://whatever.com>; rel=preconnect; "
     "crossorigin,<http://whatever2.com>; rel=preconnect",
     "http://whatever.com", "preconnect", "", true},
    {"<http://whatever.com>; rel=preconnect; crossorigin=anonymous",
     "http://whatever.com", "preconnect", "anonymous", true},
    {"<http://whatever.com>; rel=preconnect; crossorigin=use-credentials",
     "http://whatever.com", "preconnect", "use-credentials", true},
    {"<http://whatever.com>; rel=preconnect; crossorigin=whatever",
     "http://whatever.com", "preconnect", "whatever", true},
    {"<http://whatever.com>; rel=preconnect; crossorig|in=whatever",
     "http://whatever.com", "preconnect", nullptr, true},
    {"<http://whatever.com>; rel=preconnect; crossorigin|=whatever",
     "http://whatever.com", "preconnect", nullptr, true},
};

void PrintTo(const CrossOriginTestCase& test, std::ostream* os) {
  *os << testing::PrintToString(test.header_value);
}

class CrossOriginLinkHeaderTest
    : public testing::TestWithParam<CrossOriginTestCase> {};

TEST_P(CrossOriginLinkHeaderTest, CrossOrigin) {
  const CrossOriginTestCase test_case = GetParam();
  LinkHeaderSet header_set(test_case.header_value);
  ASSERT_GE(header_set.size(), 1u);
  LinkHeader& header = header_set[0];
  EXPECT_EQ(test_case.url, header.Url().Ascii());
  EXPECT_EQ(test_case.rel, header.Rel().Ascii());
  EXPECT_EQ(test_case.valid, header.Valid());
  if (!test_case.crossorigin)
    EXPECT_TRUE(header.CrossOrigin().IsNull());
  else
    EXPECT_EQ(test_case.crossorigin, header.CrossOrigin().Ascii());
}

INSTANTIATE_TEST_SUITE_P(LinkHeaderTest,
                         CrossOriginLinkHeaderTest,
                         testing::ValuesIn(g_cross_origin_test_cases));

}  // namespace
}  // namespace blink
