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

#include "chrome/browser/net/system_network_context_manager.h"

#include <optional>
#include <string>
#include <vector>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/process/process.h"
#include "base/run_loop.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/test/test_future.h"
#include "base/test/values_test_util.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/stub_resolver_config_reader.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/test_launcher_utils.h"
#include "components/component_updater/installer_policies/first_party_sets_component_installer_policy.h"
#include "components/prefs/pref_service.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/cookie_access_details.h"
#include "content/public/browser/first_party_sets_handler.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/network_service_util.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/service_process_info.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
#include "net/base/features.h"
#include "net/base/schemeful_site.h"
#include "net/cookies/canonical_cookie_test_helpers.h"
#include "net/cookies/cookie_access_result.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_inclusion_status.h"
#include "net/dns/mock_host_resolver.h"
#include "net/net_buildflags.h"
#include "sandbox/policy/features.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/network_service_buildflags.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "services/network/public/mojom/network_service_test.mojom.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"

#if BUILDFLAG(IS_WIN)
#include "base/process/process_info.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/browser_features.h"
#include "chrome/common/chrome_version.h"
#include "content/public/browser/storage_partition.h"
#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
#include "sandbox/policy/linux/sandbox_seccomp_bpf_linux.h"
#endif  // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_pref_names.h"
#endif  // BUILDFLAG(IS_CHROMEOS)

using SystemNetworkContextManagerBrowsertest = InProcessBrowserTest;

const char* kCookieName = "Cookie";
const char* kHostA = "a.test";
const char* kHostB = "b.test";

IN_PROC_BROWSER_TEST_F(SystemNetworkContextManagerBrowsertest,
                       StaticAuthParams) {
  // Test defaults.
  network::mojom::HttpAuthStaticParamsPtr static_params =
      SystemNetworkContextManager::GetHttpAuthStaticParamsForTesting();
  EXPECT_EQ("", static_params->gssapi_library_name);
#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
  // Test that prefs are reflected in params.

  PrefService* local_state = g_browser_process->local_state();
  const char dev_null[] = "/dev/null";
  local_state->SetString(prefs::kGSSAPILibraryName, dev_null);
  static_params =
      SystemNetworkContextManager::GetHttpAuthStaticParamsForTesting();
  EXPECT_EQ(dev_null, static_params->gssapi_library_name);
#endif  // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID) &&
        // !BUILDFLAG(IS_CHROMEOS)
}

IN_PROC_BROWSER_TEST_F(SystemNetworkContextManagerBrowsertest, AuthParams) {
  // Test defaults.
  network::mojom::HttpAuthDynamicParamsPtr dynamic_params =
      SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
  EXPECT_THAT(*dynamic_params->allowed_schemes,
              testing::ElementsAre("basic", "digest", "ntlm", "negotiate"));
  EXPECT_FALSE(dynamic_params->negotiate_disable_cname_lookup);
  EXPECT_FALSE(dynamic_params->enable_negotiate_port);
  EXPECT_TRUE(dynamic_params->basic_over_http_enabled);
  EXPECT_EQ("", dynamic_params->server_allowlist);
  EXPECT_EQ("", dynamic_params->delegate_allowlist);
  EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy);
  EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty());

  PrefService* local_state = g_browser_process->local_state();

  local_state->SetBoolean(prefs::kDisableAuthNegotiateCnameLookup, true);
  dynamic_params =
      SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
  EXPECT_THAT(*dynamic_params->allowed_schemes,
              testing::ElementsAre("basic", "digest", "ntlm", "negotiate"));
  EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup);
  EXPECT_FALSE(dynamic_params->enable_negotiate_port);
  EXPECT_TRUE(dynamic_params->basic_over_http_enabled);
  EXPECT_EQ("", dynamic_params->server_allowlist);
  EXPECT_EQ("", dynamic_params->delegate_allowlist);
  EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy);
  EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty());

  local_state->SetBoolean(prefs::kEnableAuthNegotiatePort, true);
  dynamic_params =
      SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
  EXPECT_THAT(*dynamic_params->allowed_schemes,
              testing::ElementsAre("basic", "digest", "ntlm", "negotiate"));
  EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup);
  EXPECT_TRUE(dynamic_params->enable_negotiate_port);
  EXPECT_TRUE(dynamic_params->basic_over_http_enabled);
  EXPECT_EQ("", dynamic_params->server_allowlist);
  EXPECT_EQ("", dynamic_params->delegate_allowlist);
  EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy);
  EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty());

  local_state->SetBoolean(prefs::kBasicAuthOverHttpEnabled, false);
  dynamic_params =
      SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
  EXPECT_THAT(*dynamic_params->allowed_schemes,
              testing::ElementsAre("basic", "digest", "ntlm", "negotiate"));
  EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup);
  EXPECT_TRUE(dynamic_params->enable_negotiate_port);
  EXPECT_FALSE(dynamic_params->basic_over_http_enabled);
  EXPECT_EQ("", dynamic_params->server_allowlist);
  EXPECT_EQ("", dynamic_params->delegate_allowlist);
  EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy);
  EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty());

  const char kServerAllowList[] = "foo";
  local_state->SetString(prefs::kAuthServerAllowlist, kServerAllowList);
  dynamic_params =
      SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
  EXPECT_THAT(*dynamic_params->allowed_schemes,
              testing::ElementsAre("basic", "digest", "ntlm", "negotiate"));
  EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup);
  EXPECT_TRUE(dynamic_params->enable_negotiate_port);
  EXPECT_FALSE(dynamic_params->basic_over_http_enabled);
  EXPECT_EQ(kServerAllowList, dynamic_params->server_allowlist);
  EXPECT_EQ("", dynamic_params->delegate_allowlist);
  EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty());

  const char kDelegateAllowList[] = "bar, baz";
  local_state->SetString(prefs::kAuthNegotiateDelegateAllowlist,
                         kDelegateAllowList);
  dynamic_params =
      SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
  EXPECT_THAT(*dynamic_params->allowed_schemes,
              testing::ElementsAre("basic", "digest", "ntlm", "negotiate"));
  EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup);
  EXPECT_TRUE(dynamic_params->enable_negotiate_port);
  EXPECT_EQ(kServerAllowList, dynamic_params->server_allowlist);
  EXPECT_FALSE(dynamic_params->basic_over_http_enabled);
  EXPECT_EQ(kDelegateAllowList, dynamic_params->delegate_allowlist);
  EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy);
  EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty());

  local_state->SetString(prefs::kAuthSchemes, "basic");
  dynamic_params =
      SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
  EXPECT_THAT(*dynamic_params->allowed_schemes, testing::ElementsAre("basic"));
  EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup);
  EXPECT_TRUE(dynamic_params->enable_negotiate_port);
  EXPECT_FALSE(dynamic_params->basic_over_http_enabled);
  EXPECT_EQ(kServerAllowList, dynamic_params->server_allowlist);
  EXPECT_EQ(kDelegateAllowList, dynamic_params->delegate_allowlist);
  EXPECT_FALSE(dynamic_params->delegate_by_kdc_policy);

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS)
  local_state->SetString(prefs::kAuthSchemes, "basic");
  local_state->SetBoolean(prefs::kAuthNegotiateDelegateByKdcPolicy, true);
  dynamic_params =
      SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
  EXPECT_THAT(*dynamic_params->allowed_schemes, testing::ElementsAre("basic"));
  EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup);
  EXPECT_TRUE(dynamic_params->enable_negotiate_port);
  EXPECT_FALSE(dynamic_params->basic_over_http_enabled);
  EXPECT_EQ(kServerAllowList, dynamic_params->server_allowlist);
  EXPECT_EQ(kDelegateAllowList, dynamic_params->delegate_allowlist);
  EXPECT_TRUE(dynamic_params->delegate_by_kdc_policy);
  EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty());
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(IS_CHROMEOS)
  // The kerberos.enabled pref is false and the device is not Active Directory
  // managed by default.
  EXPECT_FALSE(dynamic_params->allow_gssapi_library_load);
  local_state->SetBoolean(ash::prefs::kKerberosEnabled, true);
  dynamic_params =
      SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();
  EXPECT_TRUE(dynamic_params->allow_gssapi_library_load);
  EXPECT_TRUE(dynamic_params->patterns_allowed_to_use_all_schemes.empty());
#endif  // BUILDFLAG(IS_CHROMEOS)

  base::ListValue patterns_allowed_to_use_all_schemes;
  patterns_allowed_to_use_all_schemes.Append("*.allowed.google.com");
  patterns_allowed_to_use_all_schemes.Append("*.youtube.com");
  local_state->SetList(prefs::kAllHttpAuthSchemesAllowedForOrigins,
                       std::move(patterns_allowed_to_use_all_schemes));
  dynamic_params =
      SystemNetworkContextManager::GetHttpAuthDynamicParamsForTesting();

  EXPECT_TRUE(dynamic_params->negotiate_disable_cname_lookup);
  EXPECT_TRUE(dynamic_params->enable_negotiate_port);
  EXPECT_EQ(kServerAllowList, dynamic_params->server_allowlist);
  EXPECT_FALSE(dynamic_params->basic_over_http_enabled);
  EXPECT_EQ(kDelegateAllowList, dynamic_params->delegate_allowlist);
  EXPECT_EQ((std::vector<std::string>{"*.allowed.google.com", "*.youtube.com"}),
            dynamic_params->patterns_allowed_to_use_all_schemes);
}

#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
// GSSAPI is currently incompatible with the network service sandbox
// (crbug.com/40070096). It isn't known until the browser is already started
// whether GSSAPI is desired, so if Chrome detects that GSSAPI is desired after
// the network service has already started sandboxed, the network
// service must be restarted so the sandbox can be removed.
class SystemNetworkContextManagerNetworkServiceSandboxBrowsertest
    : public SystemNetworkContextManagerBrowsertest,
      public content::NetworkServiceProcessObserver,
      public testing::WithParamInterface<bool> {
 public:
  // On both ChromeOS and Linux, a pref determines whether GSSAPI is desired in
  // the network service. This pref will determine whether the network service
  // is sandboxed, and when it changes from false to true this should trigger a
  // network service restart to remove the sandbox.
  const char* kGssapiDesiredPref =
#if BUILDFLAG(IS_CHROMEOS)
      ash::prefs::kKerberosEnabled;
#elif BUILDFLAG(IS_LINUX)
      prefs::kReceivedHttpAuthNegotiateHeader;
#endif

  SystemNetworkContextManagerNetworkServiceSandboxBrowsertest() {
    sandbox_desired_ = GetParam();
    scoped_feature_list_.InitWithFeatureState(
        sandbox::policy::features::kNetworkServiceSandbox, sandbox_desired_);
  }

  void SetUpOnMainThread() override {
    // If the sandbox or the seccomp policy is disabled, these tests are
    // meaningless.
    if (!sandbox::policy::SandboxSeccompBPF::IsSeccompBPFDesired()) {
      GTEST_SKIP();
    }

    SystemNetworkContextManagerBrowsertest::SetUpOnMainThread();

    content::AddNetworkServiceProcessObserver(this);
  }

  void TearDownOnMainThread() override {
    content::RemoveNetworkServiceProcessObserver(this);
    SystemNetworkContextManagerBrowsertest::TearDownOnMainThread();
  }

  void WaitForNextLaunch() {
    launch_run_loop_.emplace();
    launch_run_loop_->Run();
  }

  void ExpectNetworkService(bool seccomp_sandboxed,
                            bool allows_gssapi_library_load) {
    // The network service may have been launched but has not yet sandboxed
    // itself. So, wait for the Mojo endpoints to start accepting messages.
    mojo::Remote<network::mojom::NetworkServiceTest> network_service_test;
    content::GetNetworkService()->BindTestInterfaceForTesting(
        network_service_test.BindNewPipeAndPassReceiver());
    mojo::ScopedAllowSyncCallForTesting allow_sync_call;
    // Log() is sync so this thread will wait for this call to succeed.
    network_service_test->Log(
        "Logging in network service to ensure it's ready.");

    // Now the test can check if the seccomp sandbox has been applied.
    EXPECT_EQ(seccomp_sandboxed,
              GetNetworkServiceProcess().IsSeccompSandboxed());

    bool network_service_allows_gssapi_library_load;
    ASSERT_TRUE(network_service_test->AllowsGSSAPILibraryLoad(
        &network_service_allows_gssapi_library_load));
    EXPECT_EQ(allows_gssapi_library_load,
              network_service_allows_gssapi_library_load);
  }

  base::Process GetNetworkServiceProcess() {
    CHECK(content::IsOutOfProcessNetworkService());
    return network_process_.Duplicate();
  }

 protected:
  bool sandbox_desired_;

 private:
  void OnServiceLaunched(const content::ServiceProcessInfo& info) override {
    network_process_ = info.GetProcess().Duplicate();
    if (launch_run_loop_) {
      launch_run_loop_->Quit();
    }
  }

  void OnServiceTerminatedNormally(
      const content::ServiceProcessInfo& info) override {}

  void OnServiceCrashed(const content::ServiceProcessInfo& info) override {}

  base::test::ScopedFeatureList scoped_feature_list_;
  base::Process network_process_;
  std::optional<base::RunLoop> launch_run_loop_;
};

IN_PROC_BROWSER_TEST_P(
    SystemNetworkContextManagerNetworkServiceSandboxBrowsertest,
    NetworkServiceRestartsUnsandboxedOnGssapiDesired) {
  PrefService* local_state = g_browser_process->local_state();

  // Ensure GSSAPI starts as "undesired".
  EXPECT_FALSE(local_state->GetBoolean(kGssapiDesiredPref));
  // Ensure the network service starts sandboxed (if desired) and cannot load
  // GSSAPI libraries.
  ExpectNetworkService(/*seccomp_sandboxed=*/sandbox_desired_,
                       /*allows_gssapi_library_load=*/false);

  // Now signal that GSSAPI is desired.
  local_state->SetBoolean(kGssapiDesiredPref, true);
  EXPECT_TRUE(local_state->GetBoolean(kGssapiDesiredPref));
  // If the network service was sandboxed it should automatically restart and
  // be unsandboxed. In any case it should now respect the pref and allow GSSAPI
  // library loads.
  if (sandbox_desired_) {
    WaitForNextLaunch();
  }
  ExpectNetworkService(/*seccomp_sandboxed=*/false,
                       /*allows_gssapi_library_load=*/true);

  // After killing the network service, it should still restart unsandboxed and
  // allow GSSAPI library loads.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(base::IgnoreResult(&content::RestartNetworkService)));
  WaitForNextLaunch();
  ExpectNetworkService(/*seccomp_sandboxed=*/false,
                       /*allows_gssapi_library_load=*/true);
}

IN_PROC_BROWSER_TEST_P(
    SystemNetworkContextManagerNetworkServiceSandboxBrowsertest,
    PRE_NetworkServiceStartsUnsandboxedWithGssapiDesired) {
  PrefService* local_state = g_browser_process->local_state();
  // Signal that GSSAPI is desired. This should persist across browser restarts
  // like any pref.
  local_state->SetBoolean(kGssapiDesiredPref, true);
  EXPECT_TRUE(local_state->GetBoolean(kGssapiDesiredPref));
}

IN_PROC_BROWSER_TEST_P(
    SystemNetworkContextManagerNetworkServiceSandboxBrowsertest,
    NetworkServiceStartsUnsandboxedWithGssapiDesired) {
  // Ensure the network service starts sandboxed and allows GSSAPI library
  // loads.
  ExpectNetworkService(/*seccomp_sandboxed=*/false,
                       /*allows_gssapi_library_load=*/true);
}

INSTANTIATE_TEST_SUITE_P(
    All,
    SystemNetworkContextManagerNetworkServiceSandboxBrowsertest,
    testing::Bool(),
    [](const testing::TestParamInfo<bool>& info) {
      return info.param ? "NetworkSandboxDesired"
                        : "NetworkSandboxFullyDisabled";
    });

#endif  // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)

#if BUILDFLAG(IS_WIN)

class SystemNetworkContextManagerNetworkServiceSandboxBrowsertest
    : public SystemNetworkContextManagerBrowsertest,
      public content::NetworkServiceProcessObserver {
 public:
  SystemNetworkContextManagerNetworkServiceSandboxBrowsertest() {
    scoped_feature_list_.InitWithFeatures(
        {sandbox::policy::features::kNetworkServiceSandbox,
         features::kRestartNetworkServiceUnsandboxedForFailedLaunch},
        {});
  }

  void SetUpOnMainThread() override {
    SystemNetworkContextManagerBrowsertest::SetUpOnMainThread();
    launch_run_loop_.emplace();
    content::AddNetworkServiceProcessObserver(this);
  }

  void TearDownOnMainThread() override {
    content::RemoveNetworkServiceProcessObserver(this);
  }

 protected:
  size_t launches_seen_ = 0;
  bool network_service_sandboxed_;
  std::optional<base::RunLoop> launch_run_loop_;

 private:
  void RecordProcessSandboxState(const base::Process& process) {
    const auto integrity_level = base::GetProcessIntegrityLevel(process.Pid());
    CHECK_NE(base::INTEGRITY_UNKNOWN, integrity_level);
    network_service_sandboxed_ = integrity_level < base::MEDIUM_INTEGRITY;
  }

  void OnServiceLaunched(const content::ServiceProcessInfo& info) override {
    RecordProcessSandboxState(info.GetProcess());
    // Expect two launches, first for the restart due to intentional crash, then
    // for the restart after the intentional crash at startup.
    if (++launches_seen_ >= 2u) {
      launch_run_loop_->Quit();
    }
  }

  base::test::ScopedFeatureList scoped_feature_list_;
};

// This is a four stage test to verify the early launch crash behavior for the
// network service sandbox on Windows.
//
// In the first stage, the network service is already started as normal during
// browser launch. The test verifies that the service is currently sandboxed.
//
// In the second stage, the artificial early crash is added, then the service is
// crashed to cause a restart. This results in the service starting, crashing
// immediately then starting again unsandboxed. The test verifies that the
// service is now unsandboxed and the pref has been set to mark the sandbox as
// being disabled.
//
// In the third stage, a crash is induced again and it's checked that the
// unsandboxed state is correctly persisted and used on the second restart.
//
// In the fourth stage, the crashing milestone is set to one milestone previous
// to current, and then the service then crashed again to cause a restart. This
// time the service will start sandboxed again because the early crash was in a
// previous milestone.
#if BUILDFLAG(IS_WIN)
// TODO(crbug.com/532718529): Re-enable this test on Windows.
#define MAYBE_NetworkServiceRestartsFailingLaunches \
  DISABLED_NetworkServiceRestartsFailingLaunches
#else
#define MAYBE_NetworkServiceRestartsFailingLaunches \
  NetworkServiceRestartsFailingLaunches
#endif
IN_PROC_BROWSER_TEST_F(
    SystemNetworkContextManagerNetworkServiceSandboxBrowsertest,
    MAYBE_NetworkServiceRestartsFailingLaunches) {
  if (!sandbox::policy::features::IsNetworkSandboxSupported()) {
    GTEST_SKIP() << "This test requires platform sandbox support.";
  }

  // Check network service is sandboxed initially.
  EXPECT_TRUE(network_service_sandboxed_);

  {
    base::HistogramTester histograms;

    // The next restart will fail to bootstrap correctly.
    content::SetNetworkServiceCrashOnNextStartupForTesting();
    // Cause a crash in the network service, which will cause it to restart,
    // then fail to start, then restart again unsandboxed.
    SimulateNetworkServiceCrash();
    // The second restart (unsandboxed) must be ignored otherwise the test will
    // fail.
    IgnoreNetworkServiceCrashes();
    // Make sure the network service is fully up and running after having to
    // restart twice.
    browser()
        ->GetProfile()
        ->GetDefaultStoragePartition()
        ->FlushNetworkInterfaceForTesting();

    launch_run_loop_->Run();
    EXPECT_FALSE(network_service_sandboxed_);
    EXPECT_TRUE(SystemNetworkContextManager::GetInstance()
                    ->HasFailedPreviousRecentLaunch());
    int crashing_major_version = g_browser_process->local_state()->GetInteger(
        prefs::kNetworkServiceFailedLaunchMajorVersion);
    CHECK_EQ(crashing_major_version, CHROME_VERSION_MAJOR);
    histograms.ExpectUniqueSample(
        "Chrome.SystemNetworkContextManager.NetworkSandboxEarlyLaunchCrashed",
        true, 1u);
    // Two sets of histograms are emitted in this part: first the network
    // service is launched with sandbox enabled, then the second re-launch is
    // with sandbox disabled because of failed launch. Note these histograms
    // have varied counts. This is because
    // Chrome.SystemNetworkContextManager.NetworkSandboxState is logged from
    // SystemNetworkContextManager::IsNetworkSandboxEnabled which is called from
    // various places in mojo and service manager. This test merely has to
    // verify that there exist only entries in the two buckets and no other
    // bucket.
    const auto enabled_count = histograms.GetBucketCount(
        "Chrome.SystemNetworkContextManager.NetworkSandboxState",
        /*kEnabledByPlatform*/ 1);
    const auto disabled_because_of_failed_launch_count =
        histograms.GetBucketCount(
            "Chrome.SystemNetworkContextManager.NetworkSandboxState",
            /*kDisabledBecauseOfFailedLaunch*/ 4);
    CHECK_GT(disabled_because_of_failed_launch_count, 1u);
    CHECK_GT(enabled_count, 1u);
    histograms.ExpectTotalCount(
        "Chrome.SystemNetworkContextManager.NetworkSandboxState",
        enabled_count + disabled_because_of_failed_launch_count);
  }

  {
    base::HistogramTester histograms;
    // Crash again, this time to verify that the pref persists and causes the
    // network service to remain unsandboxed even on a second restart.
    SimulateNetworkServiceCrash();

    // Make sure the network service is fully up and running after having to
    // restart.
    browser()
        ->GetProfile()
        ->GetDefaultStoragePartition()
        ->FlushNetworkInterfaceForTesting();

    // Sandbox should still be disabled as the pref stored the state.
    EXPECT_FALSE(network_service_sandboxed_);
    EXPECT_TRUE(SystemNetworkContextManager::GetInstance()
                    ->HasFailedPreviousRecentLaunch());
    histograms.ExpectTotalCount(
        "Chrome.SystemNetworkContextManager.NetworkSandboxEarlyLaunchCrashed",
        0);
    const auto disabled_because_of_failed_launch_count =
        histograms.GetBucketCount(
            "Chrome.SystemNetworkContextManager.NetworkSandboxState",
            /*kDisabledBecauseOfFailedLaunch*/ 4);
    CHECK_GT(disabled_because_of_failed_launch_count, 1u);
    histograms.ExpectTotalCount(
        "Chrome.SystemNetworkContextManager.NetworkSandboxState",
        disabled_because_of_failed_launch_count);
  }

  {
    base::HistogramTester histograms;
    // Simulate that a previous milestone was crashing.
    g_browser_process->local_state()->SetInteger(
        prefs::kNetworkServiceFailedLaunchMajorVersion,
        CHROME_VERSION_MAJOR - 1);

    // Crash again, this time to get a sandboxed service again.
    SimulateNetworkServiceCrash();

    // Make sure the network service is fully up and running after having to
    // restart.
    browser()
        ->GetProfile()
        ->GetDefaultStoragePartition()
        ->FlushNetworkInterfaceForTesting();

    // Sandbox should now be engaged again because the major version
    // incremented.
    EXPECT_TRUE(network_service_sandboxed_);
    EXPECT_FALSE(SystemNetworkContextManager::GetInstance()
                     ->HasFailedPreviousRecentLaunch());
    histograms.ExpectTotalCount(
        "Chrome.SystemNetworkContextManager.NetworkSandboxEarlyLaunchCrashed",
        0);
    const auto enabled_count = histograms.GetBucketCount(
        "Chrome.SystemNetworkContextManager.NetworkSandboxState",
        /*kEnabledByPlatform*/ 1);
    CHECK_GT(enabled_count, 1u);
    histograms.ExpectTotalCount(
        "Chrome.SystemNetworkContextManager.NetworkSandboxState",
        enabled_count);
  }
}

#endif  // BUILDFLAG(IS_WIN)

#if BUILDFLAG(IS_LINUX)
class SystemNetworkContextManagerHttpNegotiateHeader
    : public SystemNetworkContextManagerBrowsertest {
 public:
  static constexpr char kHttpsNegotiateAuthPath[] = "/http_negotiate_auth";

  SystemNetworkContextManagerHttpNegotiateHeader()
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}

  void SetUpOnMainThread() override {
    SystemNetworkContextManagerBrowsertest::SetUpOnMainThread();

    https_server_.AddDefaultHandlers(
        base::FilePath(FILE_PATH_LITERAL("content/test/data")));
    https_server_.RegisterRequestHandler(
        base::BindRepeating(&SystemNetworkContextManagerHttpNegotiateHeader::
                                SendBackHttpNegotiateHeader,
                            base::Unretained(this)));
    ASSERT_TRUE(https_server_.Start());
  }

  std::unique_ptr<net::test_server::HttpResponse> SendBackHttpNegotiateHeader(
      const net::test_server::HttpRequest& request) {
    if (request.relative_url != kHttpsNegotiateAuthPath) {
      return nullptr;
    }

    auto http_response =
        std::make_unique<net::test_server::BasicHttpResponse>();
    http_response->set_code(net::HTTP_UNAUTHORIZED);
    http_response->AddCustomHeader("WWW-Authenticate", "Negotiate");
    return http_response;
  }

  content::WebContents* web_contents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

 protected:
  net::test_server::EmbeddedTestServer https_server_;
};

IN_PROC_BROWSER_TEST_F(SystemNetworkContextManagerHttpNegotiateHeader,
                       DISABLED_SetsPrefOnHttpNegotiateHeader) {
  PrefService* local_state = g_browser_process->local_state();

  // Ensure the pref starts false.
  EXPECT_FALSE(
      local_state->GetBoolean(prefs::kReceivedHttpAuthNegotiateHeader));

  PrefChangeRegistrar pref_change_registrar;
  pref_change_registrar.Init(local_state);

  base::RunLoop wait_for_set_pref_loop;
  pref_change_registrar.Add(prefs::kReceivedHttpAuthNegotiateHeader,
                            wait_for_set_pref_loop.QuitClosure());

  // Navigate to a URL that requests negotiate authentication.
  EXPECT_FALSE(NavigateToURL(web_contents(),
                             https_server_.GetURL(kHttpsNegotiateAuthPath)));
  wait_for_set_pref_loop.Run();

  // Ensure the pref is now true.
  EXPECT_TRUE(local_state->GetBoolean(prefs::kReceivedHttpAuthNegotiateHeader));
}
#endif  // BUILDFLAG(IS_LINUX)

namespace {
struct CookieAccess {
  content::CookieAccessDetails::Type type;
  std::string cookie_name;
  std::string cookie_value;
  net::CookieAccessResult cookie_access_result;

  friend std::ostream& operator<<(std::ostream& o, const CookieAccess& d) {
    o << (d.type == content::CookieAccessDetails::Type::kRead ? "read"
                                                              : "change");
    o << " name=" << d.cookie_name;
    o << " value=" << d.cookie_value;
    o << " access_result=";
    net::PrintTo(d.cookie_access_result, &o);
    return o;
  }

 public:
  bool operator==(const CookieAccess&) const = default;
};

class CookieTracker : public content::WebContentsObserver {
 public:
  explicit CookieTracker(content::WebContents* web_contents)
      : content::WebContentsObserver(web_contents) {}

  void OnCookiesAccessed(content::NavigationHandle* navigation,
                         const content::CookieAccessDetails& details) override {
    OnCookiesAccessed(details);
  }

  void OnCookiesAccessed(content::RenderFrameHost* rfh,
                         const content::CookieAccessDetails& details) override {
    OnCookiesAccessed(details);
  }

  void WaitForCookies(size_t count) {
    waiting_for_cookies_count_ = count;

    base::RunLoop run_loop;
    quit_closure_ = run_loop.QuitClosure();
    QuitIfReady();
    run_loop.Run();
  }

  std::vector<CookieAccess>& cookie_accesses() { return cookie_accesses_; }

 private:
  void OnCookiesAccessed(const content::CookieAccessDetails& details) {
    for (const auto& cookie_with_access_result :
         details.cookie_access_result_list) {
      cookie_accesses_.emplace_back(details.type,
                                    cookie_with_access_result.cookie.Name(),
                                    cookie_with_access_result.cookie.Value(),
                                    cookie_with_access_result.access_result);
    }

    QuitIfReady();
  }

  void QuitIfReady() {
    if (quit_closure_.is_null()) {
      return;
    }
    if (cookie_accesses_.size() < waiting_for_cookies_count_) {
      return;
    }
    std::move(quit_closure_).Run();
  }

  std::vector<CookieAccess> cookie_accesses_;
  size_t waiting_for_cookies_count_ = 0;
  base::OnceClosure quit_closure_;
};

}  // namespace

class SystemNetworkContextManagerWithFirstPartySetComponentBrowserTest
    : public SystemNetworkContextManagerBrowsertest {
 public:
  SystemNetworkContextManagerWithFirstPartySetComponentBrowserTest()
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}

  void WaitForFirstPartySetsInit() {
    base::test::TestFuture<net::FirstPartySetMetadata> future;
    net::SchemefulSite site(GURL("https://a.test"));
    content::FirstPartySetsHandler::GetInstance()->ComputeFirstPartySetMetadata(
        site, site, net::FirstPartySetsContextConfig(), future.GetCallback());
    ASSERT_TRUE(future.Wait());
  }

  void SetUpOnMainThread() override {
    SystemNetworkContextManagerBrowsertest::SetUpOnMainThread();
    host_resolver()->AddRule("*", "127.0.0.1");
    https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
    https_server()->AddDefaultHandlers(
        base::FilePath(FILE_PATH_LITERAL("content/test/data")));
    ASSERT_TRUE(https_server()->Start());
  }

  void SetUpInProcessBrowserTestFixture() override {
    SystemNetworkContextManagerBrowsertest::SetUpInProcessBrowserTestFixture();
    // Since we set kWaitForFirstPartySetsInit, all cookie-carrying network
    // requests are blocked until FPS is initialized.
    feature_list_.InitWithFeatures(
        {net::features::kForceThirdPartyCookieBlocking}, {});
    CHECK(component_dir_.CreateUniqueTempDir());
    base::ScopedAllowBlockingForTesting allow_blocking;

    component_updater::FirstPartySetsComponentInstallerPolicy::
        WriteComponentForTesting(base::Version("1.2.3"),
                                 component_dir_.GetPath(),
                                 GetComponentContents());
  }

 protected:
  void SetUpDefaultCommandLine(base::CommandLine* command_line) override {
    SystemNetworkContextManagerBrowsertest::SetUpDefaultCommandLine(
        command_line);
    command_line->RemoveSwitch(switches::kDisableComponentUpdate);
  }

  net::test_server::EmbeddedTestServer* https_server() {
    return &https_server_;
  }

  content::WebContents* web_contents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

 private:
  std::string GetComponentContents() const {
    return "{\"primary\": \"https://a.test\", \"associatedSites\": [ "
           "\"https://b.test\", \"https://associatedsite1.test\"]}\n"
           "{\"primary\": \"https://c.test\", \"associatedSites\": [ "
           "\"https://d.test\", \"https://associatedsite2.test\"]}";
  }

  base::test::ScopedFeatureList feature_list_;
  base::ScopedTempDir component_dir_;
  net::test_server::EmbeddedTestServer https_server_;
};

IN_PROC_BROWSER_TEST_F(
    SystemNetworkContextManagerWithFirstPartySetComponentBrowserTest,
    PRE_ReloadsFirstPartySetsAfterCrash) {
  // Network service is not running out of process, so cannot be crashed.
  if (!content::IsOutOfProcessNetworkService()) {
    return;
  }

  // Set a persistent cookie that will still be there after the network service
  // is crashed. We don't use the system network context here (which wouldn't
  // persist the cookie to disk), but that's ok - this test only cares that the
  // NetworkService gets reconfigured after a crash, and that that
  // reconfiguration includes setting up First-Party Sets.
  const GURL host_root = https_server()->GetURL(kHostA, "/");
  ASSERT_TRUE(content::SetCookie(
      browser()->GetProfile(), host_root,
      base::StrCat(
          {kCookieName, "=1; SameSite=None; secure; max-age=2147483647"})));
  ASSERT_THAT(content::GetCookies(browser()->GetProfile(), host_root),
              net::CookieStringIs(
                  testing::UnorderedElementsAre(testing::Key(kCookieName))));
}

IN_PROC_BROWSER_TEST_F(
    SystemNetworkContextManagerWithFirstPartySetComponentBrowserTest,
    ReloadsFirstPartySetsAfterCrash) {
  // Network service is not running out of process, so cannot be crashed.
  if (!content::IsOutOfProcessNetworkService()) {
    return;
  }

  WaitForFirstPartySetsInit();

  {
    CookieTracker cookie_tracker(web_contents());

    const GURL url_a = https_server()->GetURL(kHostA, "/title1.html");
    ASSERT_TRUE(content::NavigateToURL(web_contents(), url_a));
    cookie_tracker.WaitForCookies(2);
    const CookieAccess expected_first_party_access{
        content::CookieAccessDetails::Type::kRead, "Cookie", "1",
        net::CookieAccessResult(
            net::CookieEffectiveSameSite::NO_RESTRICTION,
            net::CookieInclusionStatus::MakeFromReasonsForTesting(
                /*exclusions=*/{},
                /*warnings=*/{net::CookieInclusionStatus::WarningReason::
                                  WARN_PORT_MISMATCH}),
            net::CookieAccessSemantics::NONLEGACY,
            net::CookieScopeSemantics::NONLEGACY, true)};
    EXPECT_THAT(cookie_tracker.cookie_accesses(),
                testing::ElementsAre(
                    // a.test/title1.html
                    expected_first_party_access,
                    // a.test/favicon.ico
                    expected_first_party_access));
  }

  const GURL url_b_cross_site(https_server()->GetURL(
      kHostB, "/cross_site_iframe_factory.html?b.test(a.test)"));
  // Since the sites are in the same Related Website Sets, we're expecting the
  // EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET exclusion reason.
  const net::CookieInclusionStatus expected_third_party_inclusion_status =
      net::CookieInclusionStatus::MakeFromReasonsForTesting(
          {net::CookieInclusionStatus::ExclusionReason::
               EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET,
           net::CookieInclusionStatus::ExclusionReason::
               EXCLUDE_THIRD_PARTY_PHASEOUT},
          {net::CookieInclusionStatus::WarningReason::WARN_PORT_MISMATCH});
  const CookieAccess expected_third_party_access{
      content::CookieAccessDetails::Type::kRead, "Cookie", "1",
      net::CookieAccessResult(net::CookieEffectiveSameSite::NO_RESTRICTION,
                              expected_third_party_inclusion_status,
                              net::CookieAccessSemantics::NONLEGACY,
                              net::CookieScopeSemantics::NONLEGACY, true)};

  ASSERT_TRUE(base::test::RunUntil([&] {
    CookieTracker cookie_tracker(web_contents());
    EXPECT_TRUE(NavigateToURL(web_contents(), url_b_cross_site));
    cookie_tracker.WaitForCookies(2);
    return testing::Value(
        cookie_tracker.cookie_accesses(),
        testing::ElementsAre(
            // a.test iframe under b.test
            expected_third_party_access,
            // a.test/tree_parser_util.js in an iframe under b.test
            expected_third_party_access));
  }));

  SimulateNetworkServiceCrash();

  ASSERT_TRUE(base::test::RunUntil([&] {
    CookieTracker cookie_tracker(web_contents());
    EXPECT_TRUE(NavigateToURL(web_contents(), url_b_cross_site));
    cookie_tracker.WaitForCookies(2);
    return testing::Value(
        cookie_tracker.cookie_accesses(),
        testing::ElementsAre(
            // a.test iframe under b.test
            expected_third_party_access,
            // a.test/tree_parser_util.js in an iframe under b.test
            expected_third_party_access));
  }));
}

class SystemNetworkContextManagerReferrersFeatureBrowsertest
    : public SystemNetworkContextManagerBrowsertest,
      public testing::WithParamInterface<bool> {
 public:
  SystemNetworkContextManagerReferrersFeatureBrowsertest() {
    scoped_feature_list_.InitWithFeatureState(features::kNoReferrers,
                                              GetParam());
  }
  ~SystemNetworkContextManagerReferrersFeatureBrowsertest() override = default;

  void SetUpOnMainThread() override {}

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

// Tests that toggling the kNoReferrers feature correctly changes the default
// value of the kEnableReferrers pref.
IN_PROC_BROWSER_TEST_P(SystemNetworkContextManagerReferrersFeatureBrowsertest,
                       TestDefaultReferrerReflectsFeatureValue) {
  ASSERT_TRUE(g_browser_process);
  PrefService* local_state = g_browser_process->local_state();
  ASSERT_TRUE(local_state);
  EXPECT_NE(local_state->GetBoolean(prefs::kEnableReferrers), GetParam());
}

INSTANTIATE_TEST_SUITE_P(All,
                         SystemNetworkContextManagerReferrersFeatureBrowsertest,
                         ::testing::Bool());

class SystemNetworkContextManagerFreezeQUICUaBrowsertest
    : public SystemNetworkContextManagerBrowsertest {
 public:
  SystemNetworkContextManagerFreezeQUICUaBrowsertest() = default;
  ~SystemNetworkContextManagerFreezeQUICUaBrowsertest() override = default;

  void SetUpOnMainThread() override {}

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

class SystemNetworkContextManagerWPADQuickCheckBrowsertest
    : public SystemNetworkContextManagerBrowsertest,
      public testing::WithParamInterface<bool> {
 public:
  SystemNetworkContextManagerWPADQuickCheckBrowsertest() = default;
  ~SystemNetworkContextManagerWPADQuickCheckBrowsertest() override = default;
};

IN_PROC_BROWSER_TEST_P(SystemNetworkContextManagerWPADQuickCheckBrowsertest,
                       WPADQuickCheckPref) {
  PrefService* local_state = g_browser_process->local_state();
  local_state->SetBoolean(prefs::kQuickCheckEnabled, GetParam());

  network::mojom::NetworkContextParamsPtr network_context_params =
      g_browser_process->system_network_context_manager()
          ->CreateDefaultNetworkContextParams();
  EXPECT_EQ(GetParam(), network_context_params->pac_quick_check_enabled);
}

INSTANTIATE_TEST_SUITE_P(All,
                         SystemNetworkContextManagerWPADQuickCheckBrowsertest,
                         ::testing::Bool());
