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

#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h"
#include "chrome/browser/policy/policy_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "storage/browser/blob/features.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/switches.h"
#include "url/gurl.h"

namespace policy {

namespace {
GURL CreateBlobUrl(content::RenderFrameHost* rfh) {
  std::string blob_url_string =
      EvalJs(rfh,
             "const blob_url = URL.createObjectURL(new "
             "  Blob(['<!doctype html><body>potato</body>'], {type: "
             "  'text/html'}));"
             "blob_url;")
          .ExtractString();
  return GURL(blob_url_string);
}

bool FetchAndReadBlobUrl(content::RenderFrameHost* rfh, const GURL& blob_url) {
  return content::EvalJs(rfh,
                         content::JsReplace(
                             "async function test() {"
                             "  try {"
                             "    const blob = await "
                             "    fetch($1).then(response => response.blob());"
                             "    await blob.text();"
                             "    return true;"
                             "  } catch (e) {"
                             "    return false;"
                             "  }"
                             "}"
                             "test();",
                             blob_url))
      .ExtractBool();
}
}  // namespace

class PartitionedBlobUrlUsagePolicyBrowserTestP
    : public policy::PolicyTest,
      public testing::WithParamInterface<bool> {
 public:
  PartitionedBlobUrlUsagePolicyBrowserTestP() {
    scoped_feature_list_.InitWithFeatures(
        {features::kBlockCrossPartitionBlobUrlFetching,
         blink::features::kEnforceNoopenerOnBlobURLNavigation},
        {});
  }
  void SetUpOnMainThread() override {
    PolicyTest::SetUpOnMainThread();
    host_resolver()->AddRule("*", "127.0.0.1");

    embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
    content::SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  void SetUpInProcessBrowserTestFixture() override {
    PolicyTest::SetUpInProcessBrowserTestFixture();
    // The policy must be set in this function because if the policy is set in
    // the test body the test fails because it seems that an existing renderer
    // is re-used for which the command line flag hasn't been set.
    SetEnterprisePolicies(GetParam());
  }

  void SetEnterprisePolicies(bool enabled) {
    policy::PolicyMap policies;
    policies.Set(policy::key::kPartitionedBlobUrlUsage,
                 policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
                 policy::POLICY_SOURCE_CLOUD, base::Value(enabled), nullptr);

    UpdateProviderPolicy(policies);
  }

  bool IsPolicyEnabled() { return GetParam(); }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_P(PartitionedBlobUrlUsagePolicyBrowserTestP,
                       FetchingPartitionedBlobUrlUsageEnterprisePolicy) {
  EXPECT_TRUE(base::FeatureList::IsEnabled(
      features::kBlockCrossPartitionBlobUrlFetching));

  GURL main_url = embedded_test_server()->GetURL(
      "c.com", "/cross_site_iframe_factory.html?c(b(c))");

  content::WebContents* web_contents =
      chrome_test_utils::GetActiveWebContents(this);

  EXPECT_TRUE(content::NavigateToURL(web_contents, main_url));
  content::RenderFrameHost* rfh_c = web_contents->GetPrimaryMainFrame();

  GURL blob_url(CreateBlobUrl(rfh_c));

  content::RenderFrameHost* rfh_b = content::ChildFrameAt(rfh_c, 0);
  content::RenderFrameHost* rfh_c_2 = content::ChildFrameAt(rfh_b, 0);

  // The default cookie setting to BLOCK here to ensure that the
  // cross-origin blob URL fetch will be blocked due to lack of storage
  // access. If cookies are allowed, storage access might be granted, and the
  // fetch would succeed even if the blob URL is cross-origin and
  // kBlockCrossPartitionBlobUrlFetching is enabled.
  content_settings::CookieSettings* settings =
      CookieSettingsFactory::GetForProfile(browser()->GetProfile()).get();
  settings->SetDefaultCookieSetting(CONTENT_SETTING_BLOCK);

  bool fetch_results = FetchAndReadBlobUrl(rfh_c_2, blob_url);

  if (IsPolicyEnabled()) {
    EXPECT_FALSE(fetch_results);
  } else {
    EXPECT_TRUE(fetch_results);
  }
}

IN_PROC_BROWSER_TEST_P(
    PartitionedBlobUrlUsagePolicyBrowserTestP,
    EnforcingNoopenerOnPartitionedBlobUrlUsageEnterprisePolicy) {
  EXPECT_TRUE(base::FeatureList::IsEnabled(
      blink::features::kEnforceNoopenerOnBlobURLNavigation));
  // 1. navigate to c.com
  GURL main_url = embedded_test_server()->GetURL("c.com", "/title1.html");

  content::WebContents* web_contents =
      chrome_test_utils::GetActiveWebContents(this);
  EXPECT_TRUE(content::NavigateToURL(web_contents, main_url));
  content::RenderFrameHost* rfh_c = web_contents->GetPrimaryMainFrame();

  // 2. create blob_url in c.com (blob url origin is c.com)
  GURL blob_url(CreateBlobUrl(rfh_c));

  // 3. window.open b.com with c.com embedded
  // 3a. navigate to b.com
  GURL b_url = embedded_test_server()->GetURL(
      "b.com", "/cross_site_iframe_factory.html?b(c)");

  // 3b. open new tab from b.com context
  ui_test_utils::TabAddedWaiter tab_waiter(browser());
  EXPECT_TRUE(
      content::ExecJs(rfh_c, content::JsReplace("window.open($1)", b_url)));
  tab_waiter.Wait();
  content::WebContents* tab_b =
      browser()->tab_strip_model()->GetWebContentsAt(1);
  ASSERT_NE(nullptr, tab_b) << "No tab found at index 1.";

  content::TestNavigationObserver nav_observer(tab_b);
  nav_observer.Wait();

  content::RenderFrameHost* rfh_b = tab_b->GetPrimaryMainFrame();
  content::RenderFrameHost* rfh_c_in_b = content::ChildFrameAt(rfh_b, 0);

  // 4. do the window.open of blob url from c.com
  ui_test_utils::TabAddedWaiter tab_waiter2(browser());
  EXPECT_TRUE(rfh_c_in_b->IsRenderFrameLive());
  EXPECT_TRUE(content::ExecJs(
      rfh_c_in_b, content::JsReplace("handle = window.open($1);", blob_url)));
  tab_waiter2.Wait();

  bool handle_null = EvalJs(rfh_c_in_b, "handle === null;").ExtractBool();

  tab_b->Close();
  EXPECT_EQ(GetParam(), handle_null);
}

INSTANTIATE_TEST_SUITE_P(All,
                         PartitionedBlobUrlUsagePolicyBrowserTestP,
                         testing::Values(true, false));

}  // namespace policy
