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

#include "services/network/prefetch_matches.h"

#include "base/memory/scoped_refptr.h"
#include "base/unguessable_token.h"
#include "services/network/public/cpp/resource_request.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/navigation/preloading_headers.h"

namespace network {

namespace {

TEST(PrefetchMatchesTest, EmptyMatchesEmpty) {
  EXPECT_TRUE(PrefetchMatches(ResourceRequest(), ResourceRequest()));
}

// We do not test every field, as we assume if the generic logic works for one
// field it will work for another.

TEST(PrefetchMatchesTest, Method) {
  ResourceRequest prefetch;
  ResourceRequest real;
  prefetch.method = "GET";
  real.method = "POST";
  EXPECT_FALSE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, URL) {
  ResourceRequest prefetch;
  ResourceRequest real;
  prefetch.url = GURL("https://example.com/a");
  real.url = GURL("https://example.com/a");
  EXPECT_TRUE(PrefetchMatches(prefetch, real));
  real.url = GURL("https://example.com/b");
  EXPECT_FALSE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, SiteForCookies) {
  ResourceRequest prefetch;
  ResourceRequest real;
  prefetch.site_for_cookies =
      net::SiteForCookies::FromUrl(GURL("https://example.com/"));
  real.site_for_cookies = net::SiteForCookies();
  EXPECT_FALSE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, RequestInitiator) {
  ResourceRequest prefetch;
  ResourceRequest real;
  prefetch.request_initiator =
      url::Origin::Create(GURL("https://example.com/"));
  real.request_initiator = url::Origin::Create(GURL("http://example.com/"));
  EXPECT_FALSE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, ReferrerPolicy) {
  ResourceRequest prefetch;
  ResourceRequest real;
  real.referrer_policy = net::ReferrerPolicy::ORIGIN;
  EXPECT_FALSE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, HeadersSecPurposeDiffers) {
  ResourceRequest prefetch;
  ResourceRequest real;
  prefetch.headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
                             "Mozilla/1.0");
  prefetch.headers.SetHeader(net::HttpRequestHeaders::kReferer,
                             "https://www.example.com/");
  prefetch.headers.SetHeader(blink::kSecPurposeHeaderName,
                             blink::kSecPurposePrefetchHeaderValue);
  real.headers.SetHeader(net::HttpRequestHeaders::kUserAgent, "Mozilla/1.0");
  real.headers.SetHeader(net::HttpRequestHeaders::kReferer,
                         "https://www.example.com/");
  EXPECT_TRUE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, HeadersOrderDoesntMatter) {
  ResourceRequest prefetch;
  ResourceRequest real;
  prefetch.headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
                             "Mozilla/1.0");
  prefetch.headers.SetHeader(net::HttpRequestHeaders::kReferer,
                             "https://www.example.com/");
  prefetch.headers.SetHeader(blink::kSecPurposeHeaderName,
                             blink::kSecPurposePrefetchHeaderValue);
  real.headers.SetHeader(net::HttpRequestHeaders::kReferer,
                         "https://www.example.com/");
  real.headers.SetHeader(net::HttpRequestHeaders::kUserAgent, "Mozilla/1.0");
  EXPECT_TRUE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, HeadersOriginDiffers) {
  ResourceRequest prefetch;
  ResourceRequest real;
  prefetch.headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
                             "Mozilla/1.0");
  prefetch.headers.SetHeader(net::HttpRequestHeaders::kOrigin,
                             "https://www.example.com/");
  real.headers.SetHeader(net::HttpRequestHeaders::kUserAgent, "Mozilla/1.0");
  real.headers.SetHeader(net::HttpRequestHeaders::kReferer,
                         "https://www.example.com/");
  real.headers.SetHeader(net::HttpRequestHeaders::kOrigin,
                         "https://www2.example/");
  EXPECT_FALSE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, RealRequestHasExtraHeader) {
  ResourceRequest prefetch;
  ResourceRequest real;
  prefetch.headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
                             "Mozilla/1.0");
  real.headers.SetHeader(net::HttpRequestHeaders::kUserAgent, "Mozilla/1.0");
  real.headers.SetHeader(net::HttpRequestHeaders::kReferer,
                         "https://www.example.com/");
  EXPECT_FALSE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, PrefetchRequestHasExtraHeader) {
  ResourceRequest prefetch;
  ResourceRequest real;
  prefetch.headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
                             "Mozilla/1.0");
  prefetch.headers.SetHeader(net::HttpRequestHeaders::kReferer,
                             "https://www.example.com/");
  real.headers.SetHeader(net::HttpRequestHeaders::kUserAgent, "Mozilla/1.0");
  EXPECT_FALSE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, CorsExemptHeadersSecPurposeDiffers) {
  ResourceRequest prefetch;
  ResourceRequest real;
  // The "Sec-Purpose" header is not ignored when it is a field other than
  // "headers".
  prefetch.cors_exempt_headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
                                         "Mozilla/1.0");
  prefetch.cors_exempt_headers.SetHeader(net::HttpRequestHeaders::kReferer,
                                         "https://www.example.com/");
  prefetch.cors_exempt_headers.SetHeader(blink::kSecPurposeHeaderName,
                                         blink::kSecPurposePrefetchHeaderValue);
  real.cors_exempt_headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
                                     "Mozilla/1.0");
  real.cors_exempt_headers.SetHeader(net::HttpRequestHeaders::kReferer,
                                     "https://www.example.com/");
  EXPECT_FALSE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, RequestBodySameBytes) {
  constexpr char kBytes[] = "Some bytes";
  ResourceRequest prefetch;
  ResourceRequest real;
  // Set the bodies to different objects with the same contents.
  prefetch.request_body = ResourceRequestBody::CreateFromCopyOfBytes(
      base::byte_span_from_cstring(kBytes));
  real.request_body = ResourceRequestBody::CreateFromCopyOfBytes(
      base::byte_span_from_cstring(kBytes));
  EXPECT_TRUE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, RequestBodyDifferentBytes) {
  constexpr char kPrefetchBytes[] = "Some bytes";
  constexpr char kRealBytes[] = "Some different bytes";
  ResourceRequest prefetch;
  ResourceRequest real;
  // Set the bodies to different objects.
  prefetch.request_body = ResourceRequestBody::CreateFromCopyOfBytes(
      base::byte_span_from_cstring(kPrefetchBytes));
  real.request_body = ResourceRequestBody::CreateFromCopyOfBytes(
      base::byte_span_from_cstring(kRealBytes));
  EXPECT_FALSE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, RequestBodyDifferentType) {
  constexpr char kBytes[] = "Some bytes";
  ResourceRequest prefetch;
  ResourceRequest real;
  // Set the bodies to different objects with the same contents.
  prefetch.request_body = ResourceRequestBody::CreateFromCopyOfBytes(
      base::byte_span_from_cstring(kBytes));
  real.request_body = base::MakeRefCounted<ResourceRequestBody>();
  real.request_body->AppendFileRange(base::FilePath::FromASCII("path"), 0, 7,
                                     base::Time::Now());
  EXPECT_FALSE(PrefetchMatches(prefetch, real));
}

// A ResourceRequestBody consists of zero or more DataElements. If the number is
// different, they should not compare equal.
TEST(PrefetchMatchesTest, RequestBodyDifferentLength) {
  constexpr std::string_view kBytes = "Some bytes";
  constexpr std::string_view kSplit1 = kBytes.substr(0, 5);
  constexpr std::string_view kSplit2 = kBytes.substr(5);
  ResourceRequest prefetch;
  ResourceRequest real;
  // Set the bodies to different objects with the same contents.
  prefetch.request_body =
      ResourceRequestBody::CreateFromCopyOfBytes(base::as_byte_span(kBytes));
  real.request_body =
      ResourceRequestBody::CreateFromCopyOfBytes(base::as_byte_span(kSplit1));
  real.request_body->AppendCopyOfBytes(base::as_byte_span(kSplit2));
  EXPECT_FALSE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, FetchWindowIdIgnored) {
  ResourceRequest prefetch;
  ResourceRequest real;
  real.fetch_window_id = base::UnguessableToken::Create();
  EXPECT_TRUE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, WebBundleTokenParamsMatch) {
  ResourceRequest prefetch;
  ResourceRequest real;
  const GURL bundle_url("https://example.com/bundle");
  const auto token = base::UnguessableToken::Create();
  constexpr int kProcessId1 = 1;
  constexpr int kProcessId2 = 2;
  prefetch.web_bundle_token_params =
      ResourceRequest::WebBundleTokenParams(bundle_url, token, kProcessId1);
  real.web_bundle_token_params =
      ResourceRequest::WebBundleTokenParams(bundle_url, token, kProcessId2);
  EXPECT_TRUE(PrefetchMatches(prefetch, real));
}

TEST(PrefetchMatchesTest, WebBundleTokenParamsMismatch) {
  ResourceRequest prefetch;
  ResourceRequest real;
  const GURL bundle_url("https://example.com/bundle");
  const auto token1 = base::UnguessableToken::Create();
  const auto token2 = base::UnguessableToken::Create();
  constexpr int kProcessId1 = 1;
  constexpr int kProcessId2 = 2;
  prefetch.web_bundle_token_params =
      ResourceRequest::WebBundleTokenParams(bundle_url, token1, kProcessId1);
  real.web_bundle_token_params =
      ResourceRequest::WebBundleTokenParams(bundle_url, token2, kProcessId2);
  EXPECT_FALSE(PrefetchMatches(prefetch, real));
}

}  // namespace

}  // namespace network
