// 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 <optional>
#include <string>

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/strcat.h"
#include "base/test/bind.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "content/browser/bad_message.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/security_principal.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_devtools_protocol_client.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/base/features.h"
#include "net/base/filename_util.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/canonical_cookie_test_helpers.h"
#include "net/cookies/cookie_access_result.h"
#include "net/cookies/cookie_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/alternative_service.h"
#include "net/storage_access_api/status.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/mojom/restricted_cookie_manager.mojom-test-utils.h"
#include "services/network/public/mojom/restricted_cookie_manager.mojom.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using testing::IsEmpty;
using testing::Key;
using testing::UnorderedElementsAre;

namespace content {

namespace {

void EnableDevtoolsThirdPartyCookieRestriction(
    TestDevToolsProtocolClient& frame_devtools_client) {
  base::DictValue command_params;
  frame_devtools_client.SendCommandSync("Network.enable");
  command_params.Set("enableThirdPartyCookieRestriction", true);
  frame_devtools_client.SendCommandAsync("Network.setCookieControls",
                                         std::move(command_params));
}

void SetCookieFromJS(RenderFrameHost* frame, std::string cookie) {
  EvalJsResult result = EvalJs(frame, "document.cookie = '" + cookie + "'");
  EXPECT_TRUE(result.is_ok()) << result;
}

std::string GetCookieFromJS(RenderFrameHost* frame) {
  return EvalJs(frame, "document.cookie;").ExtractString();
}

void SetCookieDirect(WebContentsImpl* tab,
                     const GURL& url,
                     const std::string& cookie_line) {
  net::CookieOptions options;
  // Allow setting SameSite cookies.
  options.set_same_site_cookie_context(
      net::CookieOptions::SameSiteCookieContext::MakeInclusive());

  auto cookie_obj = net::CanonicalCookie::CreateForTesting(
      url, cookie_line, base::Time::Now(), net::CookieSourceType::kOther);

  base::RunLoop run_loop;
  tab->GetBrowserContext()
      ->GetDefaultStoragePartition()
      ->GetCookieManagerForBrowserProcess()
      ->SetCanonicalCookie(
          *cookie_obj, url, options,
          base::BindLambdaForTesting(
              [&](net::CookieAccessResult status) { run_loop.Quit(); }));
  run_loop.Run();
}

std::string GetCookiesDirect(WebContentsImpl* tab, const GURL& url) {
  net::CookieOptions options;
  // Allow setting SameSite cookies.
  options.set_same_site_cookie_context(
      net::CookieOptions::SameSiteCookieContext::MakeInclusive());
  net::CookieList result;
  base::RunLoop run_loop;
  tab->GetBrowserContext()
      ->GetDefaultStoragePartition()
      ->GetCookieManagerForBrowserProcess()
      ->GetCookieList(
          url, options, net::CookiePartitionKeyCollection(),
          base::BindLambdaForTesting(
              [&](const net::CookieAccessResultList& cookie_list,
                  const net::CookieAccessResultList& excluded_cookies) {
                result = net::cookie_util::StripAccessResults(cookie_list);
                run_loop.Quit();
              }));
  run_loop.Run();
  return net::CanonicalCookie::BuildCookieLine(result);
}

class UseCounterTrackingContentBrowserClient
    : public ContentBrowserTestContentBrowserClient {
 public:
  ~UseCounterTrackingContentBrowserClient() override = default;

  void LogWebFeatureForCurrentPage(content::RenderFrameHost* render_frame_host,
                                   blink::mojom::WebFeature feature) override {
    if (feature == blink::mojom::WebFeature::kSetCookieWithEmptyName) {
      ++cookie_set_has_empty_name_;
    } else if (feature == blink::mojom::WebFeature::
                              kSetCookieWithEmptyNameAndAmbiguousValue) {
      ++cookie_set_has_empty_name_and_ambiguous_value_;
    } else if (feature ==
               blink::mojom::WebFeature::kCookieInsecureAndSameSiteNone) {
      ++cookie_same_site_none_insecure_;
    }
  }

  int cookie_set_has_empty_name() const { return cookie_set_has_empty_name_; }
  int cookie_set_has_empty_name_and_ambiguous_value() const {
    return cookie_set_has_empty_name_and_ambiguous_value_;
  }
  int cookie_same_site_none_insecure() const {
    return cookie_same_site_none_insecure_;
  }

 private:
  int cookie_set_has_empty_name_ = 0;
  int cookie_set_has_empty_name_and_ambiguous_value_ = 0;
  int cookie_same_site_none_insecure_ = 0;
};

}  // namespace

class CookieBrowserTest : public ContentBrowserTest {
 public:
  CookieBrowserTest()
      : https_server_(net::test_server::EmbeddedTestServer::TYPE_HTTPS) {}
  ~CookieBrowserTest() override = default;

 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);
  }

  void SetUpOnMainThread() override {
    // Support multiple sites on the test server.
    host_resolver()->AddRule("*", "127.0.0.1");
    https_server_.AddDefaultHandlers(GetTestDataFilePath());
    https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
    SetupCrossSiteRedirector(&https_server_);
    ASSERT_TRUE(https_server_.Start());
  }

  net::test_server::EmbeddedTestServer https_server_;
};

// Exercises basic cookie operations via javascript, including an http page
// interacting with secure cookies.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, Cookies) {
  SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());

  // The server sends a HttpOnly cookie. The RestrictedCookieManager should
  // never allow this to be sent to any renderer process.
  GURL https_url =
      https_server_.GetURL("a.test", "/set-cookie?notforjs=1;HttpOnly");
  GURL http_url =
      embedded_test_server()->GetURL("a.test", "/frame_with_load_event.html");

  Shell* shell2 = CreateBrowser();
  EXPECT_TRUE(NavigateToURL(shell(), http_url));
  EXPECT_TRUE(NavigateToURL(shell2, https_url));

  WebContentsImpl* web_contents_https =
      static_cast<WebContentsImpl*>(shell2->web_contents());
  WebContentsImpl* web_contents_http =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  if (AreStrictSiteInstancesEnabled()) {
    EXPECT_EQ("http://a.test/", web_contents_http->GetSiteInstance()
                                    ->GetSecurityPrincipal()
                                    .GetDeprecatedSiteURL()
                                    .spec());
    // Create expected site url, including port if origin isolation is enabled.
    std::string expected_site_url =
        SiteIsolationPolicy::AreOriginKeyedProcessesEnabledByDefault(
            shell()->web_contents()->GetBrowserContext())
            ? url::Origin::Create(https_url).GetURL().spec()
            : std::string("https://a.test/");
    EXPECT_EQ(expected_site_url, web_contents_https->GetSiteInstance()
                                     ->GetSecurityPrincipal()
                                     .GetDeprecatedSiteURL()
                                     .spec());
  } else {
    // Note: Both use the default SiteInstance because the URLs don't require
    // a dedicated process, but these default SiteInstances are not the same
    // object because they come from different BrowsingInstances.
    EXPECT_TRUE(web_contents_http->GetSiteInstance()->IsDefaultSiteInstance());
    EXPECT_TRUE(web_contents_https->GetSiteInstance()->IsDefaultSiteInstance());
    EXPECT_NE(web_contents_http->GetSiteInstance(),
              web_contents_https->GetSiteInstance());
    EXPECT_FALSE(web_contents_http->GetSiteInstance()->IsRelatedSiteInstance(
        web_contents_https->GetSiteInstance()));
  }

  EXPECT_NE(web_contents_http->GetSiteInstance()->GetProcess(),
            web_contents_https->GetSiteInstance()->GetProcess());

  EXPECT_EQ("", GetCookieFromJS(web_contents_https->GetPrimaryMainFrame()));
  EXPECT_EQ("", GetCookieFromJS(web_contents_http->GetPrimaryMainFrame()));

  // Non-TLS page writes secure cookie.
  EXPECT_TRUE(ExecJs(web_contents_http->GetPrimaryMainFrame(),
                     "document.cookie = 'A=1; secure;';"));
  EXPECT_EQ("", GetCookieFromJS(web_contents_https->GetPrimaryMainFrame()));
  EXPECT_EQ("", GetCookieFromJS(web_contents_http->GetPrimaryMainFrame()));

  // Non-TLS page writes not-secure cookie.
  EXPECT_TRUE(ExecJs(web_contents_http->GetPrimaryMainFrame(),
                     "document.cookie = 'B=2';"));
  EXPECT_EQ("B=2", GetCookieFromJS(web_contents_http->GetPrimaryMainFrame()));
  EXPECT_EQ("B=2", GetCookieFromJS(web_contents_https->GetPrimaryMainFrame()));

  // TLS page writes secure cookie.
  EXPECT_TRUE(ExecJs(web_contents_https->GetPrimaryMainFrame(),
                     "document.cookie = 'C=3;secure;';"));
  EXPECT_EQ("B=2; C=3",
            GetCookieFromJS(web_contents_https->GetPrimaryMainFrame()));
  EXPECT_EQ("B=2", GetCookieFromJS(web_contents_http->GetPrimaryMainFrame()));

  // TLS page writes not-secure cookie.
  EXPECT_TRUE(ExecJs(web_contents_https->GetPrimaryMainFrame(),
                     "document.cookie = 'D=4';"));
  EXPECT_EQ("B=2; C=3; D=4",
            GetCookieFromJS(web_contents_https->GetPrimaryMainFrame()));
  EXPECT_EQ("B=2; D=4",
            GetCookieFromJS(web_contents_http->GetPrimaryMainFrame()));
}

// Ensure "priority" cookie option is settable via document.cookie.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, CookiePriority) {
  ASSERT_TRUE(embedded_test_server()->Start());

  struct {
    std::string param;
    net::CookiePriority priority;
  } cases[] = {{"name=value", net::COOKIE_PRIORITY_DEFAULT},
               {"name=value;priority=Low", net::COOKIE_PRIORITY_LOW},
               {"name=value;priority=Medium", net::COOKIE_PRIORITY_MEDIUM},
               {"name=value;priority=High", net::COOKIE_PRIORITY_HIGH}};

  for (auto test_case : cases) {
    GURL url = embedded_test_server()->GetURL("/set_document_cookie.html?" +
                                              test_case.param);
    EXPECT_TRUE(NavigateToURL(shell(), url));
    // Ensure that the RestrictedCookieManager processed the SetCookieFromString
    // IPCs by reading document.cookie from the renderer.
    EXPECT_EQ("name=value",
              GetCookieFromJS(shell()->web_contents()->GetPrimaryMainFrame()));

    std::vector<net::CanonicalCookie> cookies =
        GetCanonicalCookies(shell()->web_contents()->GetBrowserContext(), url);

    ASSERT_EQ(1u, cookies.size());
    EXPECT_EQ("name", cookies[0].Name());
    EXPECT_EQ("value", cookies[0].Value());
    EXPECT_EQ(test_case.priority, cookies[0].Priority());
  }
}

// SameSite cookies (that aren't marked as http-only) should be available to
// JavaScript.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, SameSiteCookies) {
  // Must use HTTPS because SameSite=None cookies must be Secure.

  // The server sets eight cookies on 'a.test' and on 'b.test', then loads
  // a page that frames both 'a.test' and 'b.test' under 'a.test'.
  std::string cookies_to_set =
      "/set-cookie?none=1;SameSite=None;Secure"  // SameSite=None must be
                                                 // Secure.
      "&none-insecure=1;SameSite=None"
      "&strict=1;SameSite=Strict"
      "&unspecified=1"  // unspecified SameSite should be treated as Lax.
      "&lax=1;SameSite=Lax"
      "&none-http=1;SameSite=None;Secure;httponly"
      "&strict-http=1;SameSite=Strict;httponly"
      "&unspecified-http=1;httponly"
      "&lax-http=1;SameSite=Lax;httponly";

  GURL url = https_server_.GetURL("a.test", cookies_to_set);
  EXPECT_TRUE(NavigateToURL(shell(), url));
  url = https_server_.GetURL("b.test", cookies_to_set);
  EXPECT_TRUE(NavigateToURL(shell(), url));
  url = https_server_.GetURL(
      "a.test", "/cross_site_iframe_factory.html?a.test(a.test(),b.test())");
  EXPECT_TRUE(NavigateToURL(shell(), url));

  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
  RenderFrameHost* a_iframe = web_contents->GetPrimaryFrameTree()
                                  .root()
                                  ->child_at(0)
                                  ->current_frame_host();
  RenderFrameHost* b_iframe = web_contents->GetPrimaryFrameTree()
                                  .root()
                                  ->child_at(1)
                                  ->current_frame_host();

  // The top-level frame should get all same-site cookies.
  EXPECT_EQ("none=1; strict=1; unspecified=1; lax=1",
            GetCookieFromJS(main_frame));

  // Same-site cookies will be delievered to the 'a.com' frame, as it is same-
  // site with its ancestors.
  EXPECT_EQ("none=1; strict=1; unspecified=1; lax=1",
            GetCookieFromJS(a_iframe));

  // Same-site cookies should not be delievered to the 'b.com' frame, as it
  // isn't same-site with its ancestors. The SameSite=None but insecure cookie
  // is rejected.
  EXPECT_EQ("none=1", GetCookieFromJS(b_iframe));
}

// Prefixed cookies (that aren't marked as http-only) should be available to
// JavaScript.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, PrefixedCookies_Read) {
  // Must use HTTPS because prefixed cookies must be Secure.

  ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(),
                        https_server_.GetURL("a.test", "/"),
                        "__Host-cookie=1;Secure;Path=/"));
  ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(),
                        https_server_.GetURL("a.test", "/"),
                        "__Secure-cookie=1;Secure"));
  ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(),
                        https_server_.GetURL("a.test", "/"),
                        "__Secure-http-cookie=1;Secure;HttpOnly"));

  EXPECT_TRUE(
      NavigateToURL(shell(), https_server_.GetURL("a.test", "/empty.html")));

  EXPECT_THAT(EvalJs(shell(), "document.cookie").ExtractString(),
              net::CookieStringIs(UnorderedElementsAre(
                  Key("__Host-cookie"), Key("__Secure-cookie"))));
}

IN_PROC_BROWSER_TEST_F(CookieBrowserTest, PrefixedCookies_Read_Insecure) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(),
                        https_server_.GetURL("a.test", "/"),
                        "__Host-cookie=1;Secure;Path=/"));
  ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(),
                        https_server_.GetURL("a.test", "/"),
                        "__Secure-cookie=1;Secure"));
  ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(),
                        https_server_.GetURL("a.test", "/"),
                        "__Secure-http-cookie=1;Secure;HttpOnly"));

  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("a.test", "/empty.html")));

  EXPECT_EQ(EvalJs(shell(), "document.cookie"), "");
}

// Prefixed cookies should be writable by JavaScript.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, PrefixedCookies_Write) {
  // Must use HTTPS because prefixed cookies must be Secure.

  EXPECT_TRUE(
      NavigateToURL(shell(), https_server_.GetURL("a.test", "/empty.html")));

  EXPECT_TRUE(ExecJs(shell(), R"js(
    // Valid cookies:
    document.cookie = "__Host-cookie=1;Secure;Path=/";
    document.cookie = "__Secure-cookie=1;Secure";
    // Invalid cookies:
    document.cookie = "__Secure-http-cookie=1;Secure;HttpOnly";
    document.cookie = "__Secure-missing-attr=1";
    document.cookie = "__Host-wrong-path=1;Secure;";
    document.cookie = "__Host-wrong-domain=1;Secure;Domain=a.test";
    document.cookie = "__Host-wrong-secure=1;Path=/";
    )js"));

  // Ensure that the RestrictedCookieManager processed the SetCookieFromString
  // IPCs by reading document.cookie from the renderer.
  EXPECT_THAT(GetCookieFromJS(shell()->web_contents()->GetPrimaryMainFrame()),
              net::CookieStringIs(UnorderedElementsAre(
                  Key("__Host-cookie"), Key("__Secure-cookie"))));

  EXPECT_THAT(GetCanonicalCookies(shell()->web_contents()->GetBrowserContext(),
                                  https_server_.GetURL("a.test", "/")),
              UnorderedElementsAre(
                  net::MatchesCookieNameValue("__Host-cookie", "1"),
                  net::MatchesCookieNameValue("__Secure-cookie", "1")));
}

IN_PROC_BROWSER_TEST_F(CookieBrowserTest, PrefixedCookies_Write_Insecure) {
  ASSERT_TRUE(embedded_test_server()->Start());
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("a.test", "/empty.html")));

  EXPECT_TRUE(ExecJs(shell(), R"js(
    document.cookie = "__Host-cookie=1;Secure;Path=/";
    document.cookie = "__Secure-cookie=1;Secure";
    document.cookie = "__Secure-http-cookie=1;Secure;HttpOnly";
    document.cookie = "__Secure-missing-attr=1";
    document.cookie = "__Host-wrong-path=1;Secure;";
    document.cookie = "__Host-wrong-domain=1;Secure;Domain=a.test";
    document.cookie = "__Host-wrong-secure=1;Path=/";
    )js"));

  // Ensure that the RestrictedCookieManager processed the SetCookieFromString
  // IPCs by reading document.cookie from the renderer.
  EXPECT_EQ("",
            GetCookieFromJS(shell()->web_contents()->GetPrimaryMainFrame()));

  EXPECT_THAT(
      GetCanonicalCookies(shell()->web_contents()->GetBrowserContext(),
                          embedded_test_server()->GetURL("a.test", "/")),
      IsEmpty());
}

// embedded_test_server() uses http, which is insecure, but localhost is
// allowed to set prefixed cookies anyway.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, PrefixedCookies_Write_Localhost) {
  ASSERT_TRUE(embedded_test_server()->Start());
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("localhost", "/empty.html")));

  EXPECT_TRUE(ExecJs(shell(), R"js(
    document.cookie = "__Host-cookie=1;Secure;Path=/";
    document.cookie = "__Secure-cookie=1;Secure";
    document.cookie = "__Secure-http-cookie=1;Secure;HttpOnly";
    document.cookie = "__Secure-missing-attr=1";
    document.cookie = "__Host-wrong-path=1;Secure;";
    document.cookie = "__Host-wrong-domain=1;Secure;Domain=a.test";
    document.cookie = "__Host-wrong-secure=1;Path=/";
    )js"));

  // Ensure that the RestrictedCookieManager processed the SetCookieFromString
  // IPCs by reading document.cookie from the renderer.
  EXPECT_THAT(GetCookieFromJS(shell()->web_contents()->GetPrimaryMainFrame()),
              net::CookieStringIs(UnorderedElementsAre(
                  Key("__Host-cookie"), Key("__Secure-cookie"))));

  EXPECT_THAT(
      GetCanonicalCookies(shell()->web_contents()->GetBrowserContext(),
                          embedded_test_server()->GetURL("localhost", "/")),
      UnorderedElementsAre(
          net::MatchesCookieNameValue("__Host-cookie", "1"),
          net::MatchesCookieNameValue("__Secure-cookie", "1")));
}

IN_PROC_BROWSER_TEST_F(CookieBrowserTest,
                       CookieJarInvalidatesCacheWithNewDevtoolsControls) {
  // Must use HTTPS because SameSite=None cookies must be Secure.

  // Set a single cookie that we'll access from a third-party context
  std::string cookies_to_set =
      "/set-cookie?none=1;SameSite=None;Secure";  // SameSite=None must be
                                                  // Secure

  GURL url = https_server_.GetURL("b.test", cookies_to_set);
  EXPECT_TRUE(NavigateToURL(shell(), url));

  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  // Turn on third-party cookie restriction from devtools. This needs to happen
  // from a top level client
  TestDevToolsProtocolClient devtools_client;
  devtools_client.AttachToWebContents(web_contents);
  EnableDevtoolsThirdPartyCookieRestriction(devtools_client);

  url = https_server_.GetURL(
      "a.test", "/cross_site_iframe_factory.html?a.test(b.test())");
  EXPECT_TRUE(NavigateToURL(shell(), url));

  RenderFrameHost* oop_iframe = web_contents->GetPrimaryFrameTree()
                                    .root()
                                    ->child_at(0)
                                    ->current_frame_host();

  // Attach devtools client to the sub frame, but disable the controls at first
  devtools_client.DetachProtocolClient();
  devtools_client.AttachToFrameTreeHost(oop_iframe);
  devtools_client.SendCommandSync("Network.disable");

  // Check Get->Get
  // Overrides should not apply after disabling the controls
  EXPECT_EQ("none=1", GetCookieFromJS(oop_iframe));

  // Confirm cache is invalidated by observing new value from document.cookie
  // when re-enabling devtools
  devtools_client.SendCommandSync("Network.enable");
  EXPECT_EQ("", GetCookieFromJS(oop_iframe));

  // Check Set->Get
  // Set a cookie with devtools disabled
  devtools_client.SendCommandSync("Network.disable");
  SetCookieFromJS(oop_iframe, "none=2; SameSite=None; Secure");

  // Confirm cache is invalidated by observing no cookie from document.cookie
  // when re-enabling devtools
  devtools_client.SendCommandSync("Network.enable");
  EXPECT_EQ("", GetCookieFromJS(oop_iframe));

  devtools_client.DetachProtocolClient();
}

IN_PROC_BROWSER_TEST_F(CookieBrowserTest, CookieTruncatingCharFromJavascript) {
  ASSERT_TRUE(embedded_test_server()->Start());

  ASSERT_TRUE(
      NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));

  WebContentsImpl* tab = static_cast<WebContentsImpl*>(shell()->web_contents());
  RenderFrameHost* frame = tab->GetPrimaryMainFrame();

  // Test scenarios where a control char may appear at start, middle and end of
  // a cookie line. Control char array with NULL (\x0), CR (\xD), and LF (xA).
  const std::string kTestChars[] = {"\\x00", "\\x0D", "\\x0A"};

  for (const std::string& ctl_string : kTestChars) {
    // Control char at the start of the string.
    // Note that when truncation of this cookie string occurs, no histogram
    // entries get recorded because the code bails out early on the resulting
    // empty cookie string.
    std::string cookie_string = base::StrCat({ctl_string, "foo1=bar"});
    SetCookieFromJS(frame, cookie_string);

    // Control char in the middle of the string.
    cookie_string = base::StrCat({"foo2=bar;", ctl_string, "httponly"});
    SetCookieFromJS(frame, cookie_string);

    cookie_string = base::StrCat({"foo3=ba", ctl_string, "r; httponly"});
    SetCookieFromJS(frame, cookie_string);

    // Control char at the end of the string.
    cookie_string = base::StrCat({"foo4=bar;", ctl_string});
    SetCookieFromJS(frame, cookie_string);
  }

  EXPECT_EQ("", GetCookieFromJS(frame));
}

IN_PROC_BROWSER_TEST_F(CookieBrowserTest, CookieTruncatingCharFromHeaders) {
  std::string cookie_string;
  embedded_test_server()->RegisterRequestHandler(base::BindLambdaForTesting(
      [&](const net::test_server::HttpRequest& request)
          -> std::unique_ptr<net::test_server::HttpResponse> {
        auto response = std::make_unique<net::test_server::BasicHttpResponse>();
        response->AddCustomHeader("Set-Cookie", cookie_string);
        return std::move(response);
      }));

  ASSERT_TRUE(embedded_test_server()->Start());

  GURL http_url = embedded_test_server()->GetURL("/");

  // Test scenarios where a control char may appear at start, middle and end of
  // a cookie line. Control char array with NULL (\x0), CR (\xD), and LF (xA)
  char kTestChars[] = {'\x0', '\xD', '\xA'};

  for (const auto& test : kTestChars) {
    std::string ctl_string(1, test);

    // ctrl char at start of string
    cookie_string = base::StrCat({ctl_string, "foo=bar"});
    EXPECT_TRUE(NavigateToURL(shell(), http_url));

    // ctrl char at middle of string
    cookie_string = base::StrCat({"foo=bar;", ctl_string, "httponly"});
    EXPECT_TRUE(NavigateToURL(shell(), http_url));

    cookie_string = base::StrCat({"foo=ba", ctl_string, "r; httponly"});
    EXPECT_TRUE(NavigateToURL(shell(), http_url));

    // ctrl char at end of string
    cookie_string = base::StrCat({"foo=bar;", "httponly;", ctl_string});
    EXPECT_TRUE(NavigateToURL(shell(), http_url));
  }
  // Test if there are multiple control characters that terminate.
  cookie_string = "foo=bar;\xA\xDhttponly";
  EXPECT_TRUE(NavigateToURL(shell(), http_url));
}

class RestrictedCookieManagerInterceptor
    : public network::mojom::RestrictedCookieManagerInterceptorForTesting {
 public:
  RestrictedCookieManagerInterceptor(
      mojo::PendingReceiver<network::mojom::RestrictedCookieManager> receiver,
      mojo::PendingRemote<network::mojom::RestrictedCookieManager> real_rcm)
      : receiver_(this, std::move(receiver)), real_rcm_(std::move(real_rcm)) {}

  void set_override_url(std::optional<std::string> maybe_url) {
    override_url_ = std::move(maybe_url);
  }

  void SetCookieFromString(
      const GURL& url,
      const net::SiteForCookies& site_for_cookies,
      const url::Origin& top_frame_origin,
      net::StorageAccessApiStatus storage_access_api_status,
      bool is_ad_tagged,
      bool apply_devtools_overrides,
      const std::string& cookie) override {
    GetForwardingInterface()->SetCookieFromString(
        URLToUse(url), site_for_cookies, top_frame_origin,
        storage_access_api_status, is_ad_tagged, apply_devtools_overrides,
        std::move(cookie));
  }

  void GetCookiesString(const GURL& url,
                        const net::SiteForCookies& site_for_cookies,
                        const url::Origin& top_frame_origin,
                        net::StorageAccessApiStatus storage_access_api_status,
                        bool get_version_shared_memory,
                        bool is_ad_tagged,
                        bool force_disable_third_party_cookies,
                        bool apply_devtools_overrides,
                        GetCookiesStringCallback callback) override {
    GetForwardingInterface()->GetCookiesString(
        URLToUse(url), site_for_cookies, top_frame_origin,
        storage_access_api_status, get_version_shared_memory, is_ad_tagged,
        force_disable_third_party_cookies, apply_devtools_overrides,
        std::move(callback));
  }

 private:
  network::mojom::RestrictedCookieManager* GetForwardingInterface() override {
    return real_rcm_.get();
  }

  GURL URLToUse(const GURL& url_in) {
    return override_url_ ? GURL(override_url_.value()) : url_in;
  }

  std::optional<std::string> override_url_;

  mojo::Receiver<network::mojom::RestrictedCookieManager> receiver_;
  mojo::Remote<network::mojom::RestrictedCookieManager> real_rcm_;
};

// For tests of the UseCounters about nameless cookies, we disable the
// base::Feature that forbids some of these cookies from being created.
class CookieBrowserTestAllowingNamelessAmbiguous : public CookieBrowserTest {
 public:
  CookieBrowserTestAllowingNamelessAmbiguous() {
    features_.InitAndDisableFeature(
        net::features::kCookieParseRejectEmptyNameAmbiguous);
  }

 private:
  base::test::ScopedFeatureList features_;
};

IN_PROC_BROWSER_TEST_F(CookieBrowserTestAllowingNamelessAmbiguous,
                       NoNameCookieMetrics) {
  UseCounterTrackingContentBrowserClient content_browser_client;

  // Same-Site:none w/o Secure cookies are used to count cookie sets.
  GURL https_url =
      https_server_.GetURL("a.test", "/set-cookie?a=b;SameSite=none");
  EXPECT_TRUE(NavigateToURL(shell(), https_url));

  EXPECT_TRUE(base::test::RunUntil([&]() {
    return content_browser_client.cookie_same_site_none_insecure() == 1;
  }));
  EXPECT_EQ(content_browser_client.cookie_set_has_empty_name(), 0);
  EXPECT_EQ(
      content_browser_client.cookie_set_has_empty_name_and_ambiguous_value(),
      0);

  https_url = https_server_.GetURL("a.test", "/set-cookie?a;SameSite=none");
  EXPECT_TRUE(NavigateToURL(shell(), https_url));
  EXPECT_TRUE(base::test::RunUntil([&]() {
    return content_browser_client.cookie_same_site_none_insecure() == 2;
  }));
  EXPECT_EQ(content_browser_client.cookie_set_has_empty_name(), 1);
  EXPECT_EQ(
      content_browser_client.cookie_set_has_empty_name_and_ambiguous_value(),
      0);

  https_url = https_server_.GetURL("a.test", "/set-cookie?=a=b;SameSite=none");
  EXPECT_TRUE(NavigateToURL(shell(), https_url));
  EXPECT_TRUE(base::test::RunUntil([&]() {
    return content_browser_client.cookie_same_site_none_insecure() == 3;
  }));
  EXPECT_EQ(content_browser_client.cookie_set_has_empty_name(), 2);
  EXPECT_EQ(
      content_browser_client.cookie_set_has_empty_name_and_ambiguous_value(),
      1);
}

class CookieStoreContentBrowserClient
    : public ContentBrowserTestContentBrowserClient {
 public:
  ~CookieStoreContentBrowserClient() override = default;

  bool WillCreateRestrictedCookieManager(
      network::mojom::RestrictedCookieManagerRole role,
      content::BrowserContext* browser_context,
      const url::Origin& origin,
      const net::IsolationInfo& isolation_info,
      bool is_service_worker,
      int process_id,
      int routing_id,
      bool prefer_bound_cookie_context,
      mojo::PendingReceiver<network::mojom::RestrictedCookieManager>* receiver)
      override {
    mojo::PendingReceiver<network::mojom::RestrictedCookieManager>
        orig_receiver = std::move(*receiver);

    mojo::PendingRemote<network::mojom::RestrictedCookieManager> real_rcm;
    *receiver = real_rcm.InitWithNewPipeAndPassReceiver();

    rcm_interceptor_ = std::make_unique<RestrictedCookieManagerInterceptor>(
        std::move(orig_receiver), std::move(real_rcm));
    rcm_interceptor_->set_override_url(override_url_);

    return false;  // only made a proxy, still need the actual impl to be made.
  }

  void set_override_url(std::optional<std::string> maybe_url) {
    override_url_ = maybe_url;
    if (rcm_interceptor_)
      rcm_interceptor_->set_override_url(override_url_);
  }

 private:
  std::optional<std::string> override_url_;
  std::unique_ptr<RestrictedCookieManagerInterceptor> rcm_interceptor_;
};

// Cookie access in loader is locked to a particular origin, so messages
// for wrong URLs are rejected.
// TODO(crbug.com/41453892): This should actually result in renderer
// kills.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, CrossSiteCookieSecurityEnforcement) {
  // The code under test is only active under site isolation.
  if (!AreAllSitesIsolatedForTesting()) {
    return;
  }

  SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());
  EXPECT_TRUE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("/frame_with_load_event.html")));

  WebContentsImpl* tab = static_cast<WebContentsImpl*>(shell()->web_contents());

  // The iframe on the http page should get its own process.
  FrameTreeVisualizer v;
  EXPECT_EQ(
      " Site A ------------ proxies for B\n"
      "   +--Site B ------- proxies for A\n"
      "Where A = http://127.0.0.1/\n"
      "      B = http://baz.com/",
      v.DepictFrameTree(tab->GetPrimaryFrameTree().root()));

  RenderFrameHost* main_frame = tab->GetPrimaryMainFrame();
  RenderFrameHost* iframe =
      tab->GetPrimaryFrameTree().root()->child_at(0)->current_frame_host();

  EXPECT_NE(iframe->GetProcess(), main_frame->GetProcess());

  SetCookieDirect(tab, GURL("http://127.0.0.1/"), "A_cookie = parent");
  SetCookieDirect(tab, GURL("http://baz.com/"), "B_cookie = child");
  EXPECT_EQ("A_cookie=parent",
            GetCookiesDirect(tab, GURL("http://127.0.0.1/")));

  EXPECT_EQ("B_cookie=child", GetCookiesDirect(tab, GURL("http://baz.com/")));

  // Try to get cross-site cookies from the subframe's process.
  {
    CookieStoreContentBrowserClient browser_client;
    browser_client.set_override_url("http://127.0.0.1/");
    EXPECT_EQ("", GetCookieFromJS(iframe));
  }

  EXPECT_EQ(
      " Site A ------------ proxies for B\n"
      "   +--Site B ------- proxies for A\n"
      "Where A = http://127.0.0.1/\n"
      "      B = http://baz.com/",
      v.DepictFrameTree(tab->GetPrimaryFrameTree().root()));

  // Now set a cross-site cookie from the main frame's process.
  {
    CookieStoreContentBrowserClient browser_client;

    browser_client.set_override_url("https://baz.com/");
    SetCookieFromJS(iframe, "pwn=ed");

    EXPECT_EQ("B_cookie=child", GetCookiesDirect(tab, GURL("http://baz.com/")));
  }

  EXPECT_EQ(
      " Site A ------------ proxies for B\n"
      "   +--Site B ------- proxies for A\n"
      "Where A = http://127.0.0.1/\n"
      "      B = http://baz.com/",
      v.DepictFrameTree(tab->GetPrimaryFrameTree().root()));
}

// Verifies that a frame committed in a PDF-isolated process cannot bind a
// RestrictedCookieManager for the committed origin. The SendCommitNavigation
// path already skips the bind for PDF processes, so this exercises the
// BrowserInterfaceBroker fallback used when no cookie manager was supplied at
// commit time.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, CookiesBlockedForPdfProcess) {
  ASSERT_TRUE(embedded_test_server()->Start());

  WebContentsImpl* tab = static_cast<WebContentsImpl*>(shell()->web_contents());
  GURL url = embedded_test_server()->GetURL("a.test", "/empty.html");

  SetCookieDirect(tab, url, "A=1");
  ASSERT_EQ("A=1", GetCookiesDirect(tab, url));

  // Commit `url` as PDF content so that the resulting frame runs in a process
  // whose SiteInfo has `is_pdf` set.
  NavigationController::LoadURLParams params(url);
  params.transition_type = ui::PageTransitionFromInt(
      ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
  params.is_pdf = true;
  NavigateToURLBlockUntilNavigationsComplete(
      tab, params, 1, /*ignore_uncommitted_navigations=*/false);
  ASSERT_TRUE(IsLastCommittedEntryOfPageType(tab, PAGE_TYPE_NORMAL));
  ASSERT_EQ(url, tab->GetLastCommittedURL());

  RenderFrameHost* frame = tab->GetPrimaryMainFrame();

  // The PDF process must not be able to read or write cookies for the committed
  // origin.
  EXPECT_EQ("", GetCookieFromJS(frame));
  std::ignore = EvalJs(frame, "document.cookie = 'B=2'");
  EXPECT_EQ("A=1", GetCookiesDirect(tab, url));
}

IN_PROC_BROWSER_TEST_F(CookieBrowserTest, CookieNotReadableAfterExpiry) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL http_url = embedded_test_server()->GetURL("example.test", "/empty.html");
  EXPECT_TRUE(NavigateToURL(shell(), http_url));

  WebContentsImpl* web_contents_http =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  RenderFrameHost* frame = web_contents_http->GetPrimaryMainFrame();

  SetCookieFromJS(frame, "c=1;Max-Age=1");
  SetCookieFromJS(frame, "d=1;Max-Age=7200");
  EXPECT_EQ("c=1; d=1", GetCookieFromJS(frame));

  // If cookies properly expire and become unavailable this test will terminate.
  // If they do not the test will time out. The earliest expiry from the cookies
  // is used so the short expiry from c is expected to be used.
  std::string cookie;
  do {
    cookie = GetCookieFromJS(frame);
    base::PlatformThread::Sleep(base::Milliseconds(100));
  } while (cookie != "d=1");
}

// Cookies for an eTLD should be stored (via JS) if they match the URL host,
// even if they begin with `.` or have non-canonical capitalization.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, ETldDomainCookies) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // This test uses `gov.br` as an example of an eTLD.
  GURL http_url = embedded_test_server()->GetURL("gov.br", "/empty.html");
  EXPECT_TRUE(NavigateToURL(shell(), http_url));

  WebContentsImpl* web_contents_http =
      static_cast<WebContentsImpl*>(shell()->web_contents());
  RenderFrameHost* frame = web_contents_http->GetPrimaryMainFrame();

  const char* kCases[] = {
      // A host cookie.
      "c=1",
      // A cookie for this domain.
      "c=1; domain=gov.br",
      // Same, but with a preceding dot. This dot should be ignored.
      "c=1; domain=.gov.br",
      // Same, but with non-canonical case. This should be canonicalized.
      "c=1; domain=gOv.bR",
  };

  for (const char* set_cookie : kCases) {
    SCOPED_TRACE(set_cookie);
    SetCookieFromJS(frame, set_cookie);
    EXPECT_EQ("c=1", GetCookieFromJS(frame));
    SetCookieFromJS(frame, "c=;expires=Thu, 01 Jan 1970 00:00:00 GMT");
    EXPECT_EQ("", GetCookieFromJS(frame));
  }
}

// Cookies for an eTLD should be stored (via header) if they match the URL host,
// even if they begin with `.` or have non-canonical capitalization.
IN_PROC_BROWSER_TEST_F(CookieBrowserTest, ETldDomainCookiesHeader) {
  std::string got_cookie_on_request;
  std::string set_cookie_on_response;
  embedded_test_server()->RegisterRequestHandler(base::BindLambdaForTesting(
      [&](const net::test_server::HttpRequest& request)
          -> std::unique_ptr<net::test_server::HttpResponse> {
        auto response = std::make_unique<net::test_server::BasicHttpResponse>();
        if (request.headers.contains("Cookie")) {
          got_cookie_on_request = request.headers.at("Cookie");
        } else {
          got_cookie_on_request = "";
        }
        if (set_cookie_on_response.size() != 0) {
          response->AddCustomHeader("Set-Cookie", set_cookie_on_response);
          set_cookie_on_response = "";
        }
        return std::move(response);
      }));

  ASSERT_TRUE(embedded_test_server()->Start());

  // This test uses `gov.br` as an example of an eTLD.
  GURL http_url = embedded_test_server()->GetURL("gov.br", "/empty.html");

  const char* kCases[] = {
      // A host cookie.
      "c=1",
      // A cookie for this domain.
      "c=1; domain=gov.br",
      // Same, but with a preceding dot. This dot should be ignored.
      "c=1; domain=.gov.br",
      // Same, but with non-canonical case. This should be canonicalized.
      "c=1; domain=gOv.bR",
  };

  for (const char* set_cookie : kCases) {
    SCOPED_TRACE(set_cookie);

    set_cookie_on_response = set_cookie;
    EXPECT_TRUE(NavigateToURL(shell(), http_url));

    EXPECT_TRUE(NavigateToURL(shell(), http_url));
    EXPECT_EQ("c=1", got_cookie_on_request);

    set_cookie_on_response = "c=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
    EXPECT_TRUE(NavigateToURL(shell(), http_url));

    EXPECT_TRUE(NavigateToURL(shell(), http_url));
    EXPECT_EQ("", got_cookie_on_request);
  }
}

enum class CookieFileMode { kDefault, kEnabled, kDisabled };

class CookieFileBrowserTest
    : public ContentBrowserTest,
      public ::testing::WithParamInterface<CookieFileMode> {
 protected:
  void SetUpOnMainThread() override {
    // Setup file url.
    base::ScopedAllowBlockingForTesting allow_blocking;
    EXPECT_TRUE(file_directory_.CreateUniqueTempDir());
    base::FilePath file_path =
        file_directory_.GetPath().AppendASCII("index.html");
    EXPECT_TRUE(base::WriteFile(file_path, ""));
    file_url_ = net::FilePathToFileURL(file_path);

    // Setup cookie manager.
    bool file_cookie_enabled;
    switch (GetParam()) {
      case CookieFileMode::kDefault:
        // Nothing to do.
        return;
      case CookieFileMode::kEnabled:
        file_cookie_enabled = true;
        break;
      case CookieFileMode::kDisabled:
        file_cookie_enabled = false;
        break;
    }
    base::RunLoop run_loop;
    shell()
        ->web_contents()
        ->GetBrowserContext()
        ->GetDefaultStoragePartition()
        ->GetCookieManagerForBrowserProcess()
        ->AllowFileSchemeCookies(file_cookie_enabled,
                                 base::BindLambdaForTesting([&](bool success) {
                                   EXPECT_TRUE(success);
                                   run_loop.Quit();
                                 }));
    run_loop.Run();
  }

  GURL file_url_;

 private:
  base::ScopedTempDir file_directory_;
};

INSTANTIATE_TEST_SUITE_P(,
                         CookieFileBrowserTest,
                         ::testing::Values(CookieFileMode::kDefault,
                                           CookieFileMode::kEnabled,
                                           CookieFileMode::kDisabled));

// Try to set and get cookies on a file URL.
IN_PROC_BROWSER_TEST_P(CookieFileBrowserTest, SetAndGetCookie) {
  // Navigate to file.
  EXPECT_TRUE(NavigateToURL(shell(), file_url_));
  RenderFrameHost* frame = shell()->web_contents()->GetPrimaryMainFrame();

  // File cookies always appear to be writable. On non-Android platforms a
  // warning is printed when this occurs.
#if !BUILDFLAG(IS_ANDROID)
  WebContentsConsoleObserver console_observer(shell()->web_contents());
  console_observer.SetPattern(
      "While navigator.cookieEnabled does return true for this file:// "
      "URL, this is done for web compatability reasons. Cookies will not "
      "actually be stored for file:// URLs. If you want this to change "
      "please leave feedback on crbug.com/378604901.");
#endif
  EXPECT_TRUE(EvalJs(frame, "navigator.cookieEnabled").ExtractBool());
#if !BUILDFLAG(IS_ANDROID)
  ASSERT_TRUE(console_observer.Wait());
#endif

  // File cookies can only be set if they are enabled.
  bool can_set_cookies;
  switch (GetParam()) {
    case CookieFileMode::kDefault:
      // TODO(crbug.com/378604901): Perhapse this should be allowed by default.
      can_set_cookies = false;
      return;
    case CookieFileMode::kEnabled:
      can_set_cookies = true;
      break;
    case CookieFileMode::kDisabled:
      can_set_cookies = false;
      break;
  }
  SetCookieFromJS(frame, "test=1");
  EXPECT_EQ(can_set_cookies ? "test=1" : "", GetCookieFromJS(frame));
}

}  // namespace content
