// Copyright 2019 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/fetch/resource_fetcher_properties.h"

#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/frame/policy_container.mojom.h"
#include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom-blink.h"
#include "third_party/blink/public/mojom/service_worker/controller_service_worker_mode.mojom-blink.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object_snapshot.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request_utils.h"
#include "third_party/blink/renderer/platform/loader/testing/test_resource_fetcher_properties.h"
#include "third_party/blink/renderer/platform/weborigin/referrer.h"

namespace blink {

namespace {

class DetachableResourceFetcherPropertiesTest : public testing::Test {
 public:
  const FetchClientSettingsObjectSnapshot& CreateFetchClientSettingsObject() {
    return *MakeGarbageCollected<FetchClientSettingsObjectSnapshot>(
        KURL("https://example.com/foo.html"),
        KURL("https://example.com/foo.html"),
        SecurityOrigin::Create(KURL("https://example.com/")),
        []() {
          auto policies = mojom::blink::PolicyContainerPolicies::New();
          policies->referrer_policy = network::mojom::ReferrerPolicy::kDefault;
          return policies;
        }(),
        "https://example.com/foo.html", HttpsState::kModern,
        AllowedByNosniff::MimeTypeCheck::kStrict,
        mojom::blink::InsecureRequestPolicy::kLeaveInsecureRequestsAlone,
        FetchClientSettingsObject::InsecureNavigationsSet());
  }
};

TEST_F(DetachableResourceFetcherPropertiesTest, DetachWithDefaultValues) {
  const auto& original_client_settings_object =
      CreateFetchClientSettingsObject();
  auto& properties = *MakeGarbageCollected<DetachableResourceFetcherProperties>(
      *MakeGarbageCollected<TestResourceFetcherProperties>(
          original_client_settings_object));

  const auto& client_settings_object =
      properties.GetFetchClientSettingsObject();
  EXPECT_EQ(&original_client_settings_object, &client_settings_object);
  EXPECT_FALSE(properties.IsOutermostMainFrame());
  EXPECT_EQ(properties.GetControllerServiceWorkerMode(),
            mojom::ControllerServiceWorkerMode::kNoController);
  // We cannot call ServiceWorkerId as the service worker mode is kNoController.
  EXPECT_FALSE(properties.IsPaused());
  EXPECT_FALSE(properties.IsDetached());
  EXPECT_FALSE(properties.IsLoadComplete());
  EXPECT_FALSE(properties.ShouldBlockLoadingSubResource());
  EXPECT_FALSE(properties.IsSubframeDeprioritizationEnabled());
  EXPECT_EQ(scheduler::FrameStatus::kNone, properties.GetFrameStatus());

  properties.Detach();

  EXPECT_NE(&client_settings_object,
            &properties.GetFetchClientSettingsObject());
  EXPECT_EQ(properties.GetFetchClientSettingsObject().BaseUrl(),
            KURL("https://example.com/foo.html"));
  EXPECT_FALSE(properties.IsOutermostMainFrame());
  EXPECT_EQ(properties.GetControllerServiceWorkerMode(),
            mojom::ControllerServiceWorkerMode::kNoController);
  // We cannot call ServiceWorkerId as the service worker mode is kNoController.
  EXPECT_FALSE(properties.IsPaused());
  EXPECT_TRUE(properties.IsDetached());
  EXPECT_FALSE(properties.IsLoadComplete());
  EXPECT_TRUE(properties.ShouldBlockLoadingSubResource());
  EXPECT_FALSE(properties.IsSubframeDeprioritizationEnabled());
  EXPECT_EQ(scheduler::FrameStatus::kNone, properties.GetFrameStatus());
}

TEST_F(DetachableResourceFetcherPropertiesTest, DetachWithNonDefaultValues) {
  const auto& original_client_settings_object =
      CreateFetchClientSettingsObject();
  auto& original_properties =
      *MakeGarbageCollected<TestResourceFetcherProperties>(
          original_client_settings_object);
  auto& properties = *MakeGarbageCollected<DetachableResourceFetcherProperties>(
      original_properties);

  original_properties.SetIsOutermostMainFrame(true);
  original_properties.SetControllerServiceWorkerMode(
      mojom::ControllerServiceWorkerMode::kControlled);
  original_properties.SetServiceWorkerId(133);
  original_properties.SetIsPaused(true);
  original_properties.SetIsLoadComplete(true);
  original_properties.SetShouldBlockLoadingSubResource(true);
  original_properties.SetIsSubframeDeprioritizationEnabled(true);
  original_properties.SetFrameStatus(scheduler::FrameStatus::kMainFrameVisible);

  const auto& client_settings_object =
      properties.GetFetchClientSettingsObject();
  EXPECT_EQ(&original_client_settings_object, &client_settings_object);
  EXPECT_TRUE(properties.IsOutermostMainFrame());
  EXPECT_EQ(properties.GetControllerServiceWorkerMode(),
            mojom::ControllerServiceWorkerMode::kControlled);
  EXPECT_EQ(properties.ServiceWorkerId(), 133);
  EXPECT_TRUE(properties.IsPaused());
  EXPECT_FALSE(properties.IsDetached());
  EXPECT_TRUE(properties.IsLoadComplete());
  EXPECT_TRUE(properties.ShouldBlockLoadingSubResource());
  EXPECT_TRUE(properties.IsSubframeDeprioritizationEnabled());
  EXPECT_EQ(scheduler::FrameStatus::kMainFrameVisible,
            properties.GetFrameStatus());

  properties.Detach();

  EXPECT_NE(&client_settings_object,
            &properties.GetFetchClientSettingsObject());
  EXPECT_EQ(properties.GetFetchClientSettingsObject().BaseUrl(),
            KURL("https://example.com/foo.html"));
  EXPECT_TRUE(properties.IsOutermostMainFrame());
  EXPECT_EQ(properties.GetControllerServiceWorkerMode(),
            mojom::ControllerServiceWorkerMode::kNoController);
  // We cannot call ServiceWorkerId as the service worker mode is kNoController.
  EXPECT_TRUE(properties.IsPaused());
  EXPECT_TRUE(properties.IsDetached());
  EXPECT_TRUE(properties.IsLoadComplete());
  EXPECT_TRUE(properties.ShouldBlockLoadingSubResource());
  EXPECT_TRUE(properties.IsSubframeDeprioritizationEnabled());
  EXPECT_EQ(scheduler::FrameStatus::kNone, properties.GetFrameStatus());
}

TEST(FetchClientSettingsObjectSnapshotTest, OutgoingReferrerUrlStripping) {
  // Test that credentials and fragments are stripped
  auto* snapshot_with_credentials =
      MakeGarbageCollected<FetchClientSettingsObjectSnapshot>(
          KURL("https://example.com/foo.html"),
          KURL("https://example.com/foo.html"),
          SecurityOrigin::Create(KURL("https://example.com/")),
          mojom::blink::PolicyContainerPolicies::New(),
          "https://user:pass@example.com/foo.html#fragment",
          HttpsState::kModern, AllowedByNosniff::MimeTypeCheck::kStrict,
          mojom::blink::InsecureRequestPolicy::kLeaveInsecureRequestsAlone,
          FetchClientSettingsObject::InsecureNavigationsSet());

  EXPECT_EQ(KURL("https://example.com/foo.html"),
            snapshot_with_credentials->GetOutgoingReferrerUrl());

  // Test that disallowed schemes (like ftp) are resolved to an empty/invalid
  // URL
  auto* snapshot_with_ftp =
      MakeGarbageCollected<FetchClientSettingsObjectSnapshot>(
          KURL("https://example.com/foo.html"),
          KURL("https://example.com/foo.html"),
          SecurityOrigin::Create(KURL("https://example.com/")),
          mojom::blink::PolicyContainerPolicies::New(),
          "ftp://example.com/foo.html", HttpsState::kModern,
          AllowedByNosniff::MimeTypeCheck::kStrict,
          mojom::blink::InsecureRequestPolicy::kLeaveInsecureRequestsAlone,
          FetchClientSettingsObject::InsecureNavigationsSet());

  EXPECT_TRUE(snapshot_with_ftp->GetOutgoingReferrerUrl().IsEmpty());
}

TEST(ResourceFetcherPropertiesTest, SetReferrerFromFetchClientSettingsObject) {
  auto* snapshot = MakeGarbageCollected<FetchClientSettingsObjectSnapshot>(
      KURL("https://example.com/foo.html"),
      KURL("https://example.com/foo.html"),
      SecurityOrigin::Create(KURL("https://example.com/")),
      []() {
        auto policies = mojom::blink::PolicyContainerPolicies::New();
        policies->referrer_policy = network::mojom::ReferrerPolicy::kAlways;
        return policies;
      }(),
      "https://example.com/parent.html", HttpsState::kModern,
      AllowedByNosniff::MimeTypeCheck::kStrict,
      mojom::blink::InsecureRequestPolicy::kLeaveInsecureRequestsAlone,
      FetchClientSettingsObject::InsecureNavigationsSet());

  // Verify that request referrers set to "about:client" are routed to the
  // snapshot's GetOutgoingReferrerUrl() and have their referrer and policy
  // properly updated.
  ResourceRequest client_referrer_request;
  client_referrer_request.SetUrl(KURL("https://example.com/subresource.html"));
  client_referrer_request.SetReferrerString(Referrer::ClientReferrerString());
  client_referrer_request.SetReferrerPolicy(
      network::mojom::ReferrerPolicy::kDefault);

  SetReferrer(client_referrer_request, *snapshot);

  EXPECT_EQ(KURL("https://example.com/parent.html"),
            client_referrer_request.ReferrerString());
  EXPECT_EQ(network::mojom::ReferrerPolicy::kAlways,
            client_referrer_request.GetReferrerPolicy());

  // Verify that custom request referrers are resolved directly and correctly
  // stripped of fragments.
  ResourceRequest custom_referrer_request;
  custom_referrer_request.SetUrl(KURL("https://example.com/subresource.html"));
  custom_referrer_request.SetReferrerString(
      "https://example.com/custom.html#fragment");
  custom_referrer_request.SetReferrerPolicy(
      network::mojom::ReferrerPolicy::kDefault);

  SetReferrer(custom_referrer_request, *snapshot);

  EXPECT_EQ(KURL("https://example.com/custom.html"),
            custom_referrer_request.ReferrerString());
  EXPECT_EQ(network::mojom::ReferrerPolicy::kAlways,
            custom_referrer_request.GetReferrerPolicy());
}

}  // namespace

}  // namespace blink
