// Copyright 2020 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/functional/callback_helpers.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/features.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "services/network/public/mojom/network_context.mojom.h"

namespace {

class CookieStoreSameSiteTest : public InProcessBrowserTest,
                                public ::testing::WithParamInterface<bool> {
 protected:
  CookieStoreSameSiteTest()
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}

  CookieStoreSameSiteTest(const CookieStoreSameSiteTest&) = delete;
  CookieStoreSameSiteTest& operator=(const CookieStoreSameSiteTest&) = delete;

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    base::FilePath path;
    base::PathService::Get(content::DIR_TEST_DATA, &path);
    https_server_.ServeFilesFromDirectory(path);
    https_server_.AddDefaultHandlers(GetChromeTestDataDir());
    https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
    ASSERT_TRUE(https_server_.Start());

    // If SameSite access semantics is "legacy", add content settings to allow
    // legacy access for all sites.
    if (!HasNonLegacySameSiteAccessSemantics()) {
      browser()
          ->GetProfile()
          ->GetDefaultStoragePartition()
          ->GetNetworkContext()
          ->GetCookieManager(
              cookie_manager_remote_.BindNewPipeAndPassReceiver());
      cookie_manager_remote_->SetContentSettings(
          ContentSettingsType::LEGACY_COOKIE_ACCESS,
          {ContentSettingPatternSource(
              ContentSettingsPattern::Wildcard(),
              ContentSettingsPattern::Wildcard(),
              base::Value(ContentSetting::CONTENT_SETTING_ALLOW),
              content_settings::ProviderType::kNone, false /* incognito */)},
          base::NullCallback());
      cookie_manager_remote_.FlushForTesting();
    }
  }

  void NavigateToPageWithFrame(const std::string& host) {
    GURL main_url(https_server_.GetURL(host, "/iframe.html"));
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
  }

  content::RenderFrameHost* GetFrame() {
    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    return ChildFrameAt(web_contents->GetPrimaryMainFrame(), 0);
  }

  std::string GetCookieSameSite(const std::string& cookie_name) {
    std::string script = base::StringPrintf(R"(
      (async () => {
          const cookie = await cookieStore.get('%s');
          return cookie ? cookie.sameSite : '';
      }) ();
    )",
                                            cookie_name.c_str());
    return content::EvalJs(GetFrame(), script).ExtractString();
  }

 protected:
  bool HasNonLegacySameSiteAccessSemantics() { return GetParam(); }

  mojo::Remote<network::mojom::CookieManager> cookie_manager_remote_;
  net::test_server::EmbeddedTestServer https_server_;
};

IN_PROC_BROWSER_TEST_P(CookieStoreSameSiteTest,
                       CookieStoreUsesEffectiveSameSiteForUnspecifiedCookies) {
  NavigateToPageWithFrame("a.test");

  // Create unspecified sameSite cookie with document.cookie because CookieStore
  // doesn't allow unspecified sameSite.
  EXPECT_TRUE(content::ExecJs(GetFrame(),
                              "document.cookie='unspecified-cookie=value'"));

  std::string sameSite = GetCookieSameSite("unspecified-cookie");
  if (HasNonLegacySameSiteAccessSemantics()) {
    EXPECT_EQ("lax", sameSite);
  } else {
    EXPECT_EQ("none", sameSite);
  }
}

IN_PROC_BROWSER_TEST_P(CookieStoreSameSiteTest,
                       CookieStoreSameSiteDefaultsToStrict) {
  NavigateToPageWithFrame("a.test");

  // Create cookie with CookieStore with no sameSite.
  EXPECT_TRUE(content::ExecJs(GetFrame(),
                              "cookieStore.set('default-cookie', 'value')"));

  EXPECT_EQ("strict", GetCookieSameSite("default-cookie"));
}

IN_PROC_BROWSER_TEST_P(CookieStoreSameSiteTest,
                       CookieStoreUsesSpecifiedSameSite) {
  NavigateToPageWithFrame("a.test");

  // Create cookie with CookieStore with none sameSite. CookieStore defaults to
  // Secure when written from a secure web origin, therefore allows for sameSite
  // none.
  EXPECT_TRUE(content::ExecJs(GetFrame(),
                              "cookieStore.set({name: 'none-cookie', value: "
                              "'value', sameSite: 'none'})"));

  EXPECT_EQ("none", GetCookieSameSite("none-cookie"));
}

INSTANTIATE_TEST_SUITE_P(All, CookieStoreSameSiteTest, ::testing::Bool());
}  // namespace
