chromium/ios/components/security_interstitials/safe_browsing/safe_browsing_service_unittest.mm

// 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.

#import "ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.h"

#import "base/files/scoped_temp_dir.h"
#import "base/memory/raw_ptr.h"
#import "base/path_service.h"
#import "base/run_loop.h"
#import "base/test/bind.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/scoped_feature_list.h"
#import "components/content_settings/core/browser/host_content_settings_map.h"
#import "components/prefs/pref_service.h"
#import "components/prefs/testing_pref_service.h"
#import "components/safe_browsing/core/browser/db/database_manager.h"
#import "components/safe_browsing/core/browser/db/util.h"
#import "components/safe_browsing/core/browser/db/v4_database.h"
#import "components/safe_browsing/core/browser/db/v4_get_hash_protocol_manager.h"
#import "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#import "components/safe_browsing/core/browser/db/v4_test_util.h"
#import "components/safe_browsing/core/browser/realtime/url_lookup_service.h"
#import "components/safe_browsing/core/browser/safe_browsing_token_fetcher.h"
#import "components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h"
#import "components/safe_browsing/core/browser/verdict_cache_manager.h"
#import "components/safe_browsing/core/common/features.h"
#import "components/safe_browsing/core/common/proto/realtimeapi.pb.h"
#import "components/safe_browsing/core/common/safe_browsing_prefs.h"
#import "components/safe_browsing/core/common/safebrowsing_constants.h"
#import "components/safe_browsing/ios/browser/safe_browsing_url_allow_list.h"
#import "components/sync_preferences/pref_service_syncable.h"
#import "components/sync_preferences/testing_pref_service_syncable.h"
#import "components/unified_consent/pref_names.h"
#import "components/unified_consent/unified_consent_service.h"
#import "ios/components/security_interstitials/safe_browsing/fake_safe_browsing_client.h"
#import "ios/components/security_interstitials/safe_browsing/safe_browsing_query_manager.h"
#import "ios/components/security_interstitials/safe_browsing/safe_browsing_unsafe_resource_container.h"
#import "ios/web/public/test/fakes/fake_browser_state.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "ios/web/public/thread/web_task_traits.h"
#import "ios/web/public/thread/web_thread.h"
#import "net/test/embedded_test_server/default_handlers.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#import "services/network/public/cpp/shared_url_loader_factory.h"
#import "services/network/public/cpp/simple_url_loader.h"
#import "testing/platform_test.h"

namespace {

const char kSafePage[] = "https://example.test/safe.html";
const char kMalwarePage[] = "https://unsafe.test/malware.html";

class TestUrlCheckerClient {
 public:
  TestUrlCheckerClient(SafeBrowsingService* safe_browsing_service,
                       web::BrowserState* browser_state,
                       SafeBrowsingClient* safe_browsing_client)
      : safe_browsing_service_(safe_browsing_service),
        safe_browsing_client_(safe_browsing_client) {
    SafeBrowsingQueryManager::CreateForWebState(&web_state_,
                                                safe_browsing_client_);
    SafeBrowsingUrlAllowList::CreateForWebState(&web_state_);
    SafeBrowsingUnsafeResourceContainer::CreateForWebState(&web_state_);
    web_state_.SetBrowserState(browser_state);
  }

  ~TestUrlCheckerClient() = default;

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

  bool url_is_unsafe() const { return url_is_unsafe_; }

  safe_browsing::SafeBrowsingUrlCheckerImpl::PerformedCheck performed_check()
      const {
    return performed_check_;
  }

  void CheckUrl(const GURL& url) {
    result_pending_ = true;
    url_checker_ = safe_browsing_service_->CreateUrlChecker(
        network::mojom::RequestDestination::kDocument, &web_state_,
        safe_browsing_client_);
    CheckUrlOnUIThread(url);
  }

  void CheckUrlWithSyncChecker(const GURL& url) {
    result_pending_ = true;
    url_checker_ = safe_browsing_service_->CreateSyncChecker(
        network::mojom::RequestDestination::kDocument, &web_state_,
        safe_browsing_client_);
    CheckUrlOnUIThread(url);
  }

  void CheckUrlWithAsyncChecker(const GURL& url) {
    result_pending_ = true;
    url_checker_ = safe_browsing_service_->CreateAsyncChecker(
        network::mojom::RequestDestination::kDocument, &web_state_,
        safe_browsing_client_);
    CheckUrlOnUIThread(url);
  }

  bool result_pending() const { return result_pending_; }

  void WaitForResult() {
    while (result_pending()) {
      base::RunLoop().RunUntilIdle();
    }
  }

 private:
  void CheckUrlOnUIThread(const GURL& url) {
    url_checker_->CheckUrl(
        url, "GET",
        base::BindOnce(&TestUrlCheckerClient::OnCheckUrlResult,
                       base::Unretained(this)));
  }

  void OnCheckUrlResult(
      bool proceed,
      bool showed_interstitial,
      bool has_post_commit_interstitial_skipped,
      safe_browsing::SafeBrowsingUrlCheckerImpl::PerformedCheck
          performed_check) {
    url_is_unsafe_ = !proceed;
    result_pending_ = false;
    performed_check_ = performed_check;
    url_checker_.reset();
  }

  void CheckDone() { result_pending_ = false; }

  bool result_pending_ = false;
  bool url_is_unsafe_ = false;
  safe_browsing::SafeBrowsingUrlCheckerImpl::PerformedCheck performed_check_ =
      safe_browsing::SafeBrowsingUrlCheckerImpl::PerformedCheck::kUnknown;
  raw_ptr<SafeBrowsingService> safe_browsing_service_;
  web::FakeWebState web_state_;
  std::unique_ptr<safe_browsing::SafeBrowsingUrlCheckerImpl> url_checker_;
  raw_ptr<SafeBrowsingClient> safe_browsing_client_;
};

}  // namespace

class SafeBrowsingServiceTest : public PlatformTest {
 public:
  SafeBrowsingServiceTest() : browser_state_(new web::FakeBrowserState()) {
    store_factory_ = new safe_browsing::TestV4StoreFactory();
    safe_browsing::V4Database::RegisterStoreFactoryForTest(
        base::WrapUnique(store_factory_.get()));

    v4_db_factory_ = new safe_browsing::TestV4DatabaseFactory();
    safe_browsing::V4Database::RegisterDatabaseFactoryForTest(
        base::WrapUnique(v4_db_factory_.get()));

    v4_get_hash_factory_ =
        new safe_browsing::TestV4GetHashProtocolManagerFactory();
    safe_browsing::V4GetHashProtocolManager::RegisterFactory(
        base::WrapUnique(v4_get_hash_factory_.get()));

    pref_service_ =
        std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
    HostContentSettingsMap::RegisterProfilePrefs(pref_service_->registry());
    safe_browsing::RegisterProfilePrefs(pref_service_->registry());
    unified_consent::UnifiedConsentService::RegisterPrefs(
        pref_service_->registry());

    safe_browsing_service_ = base::MakeRefCounted<SafeBrowsingServiceImpl>();

    CHECK(temp_dir_.CreateUniqueTempDir());
    safe_browsing_service_->Initialize(
        pref_service_.get(), temp_dir_.GetPath(),
        /*safe_browsing_metrics_collector=*/nullptr);
    base::RunLoop().RunUntilIdle();

    SetupUrlLookupService();
  }

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

  ~SafeBrowsingServiceTest() override {
    safe_browsing_service_->ShutDown();
    if (host_content_settings_map_) {
      host_content_settings_map_->ShutdownOnUIThread();
    }

    safe_browsing::V4GetHashProtocolManager::RegisterFactory(nullptr);
    safe_browsing::V4Database::RegisterDatabaseFactoryForTest(nullptr);
    safe_browsing::V4Database::RegisterStoreFactoryForTest(nullptr);
  }

  void MarkUrlAsMalware(const GURL& bad_url) {
    MarkUrlAsMalwareOnUIThread(bad_url);
  }

  // Adds the given `safe_url` to the allowlist used by real-time checks.
  void MarkUrlAsRealTimeSafe(const GURL& safe_url) {
    MarkUrlAsSafeOnUIThread(safe_url);
  }

  // Caches the given `bad_url` as unsafe in the VerdictCacheManager used by
  // real-time checks.
  void MarkUrlAsRealTimeUnsafe(const GURL& bad_url) {
    safe_browsing::RTLookupResponse response;
    safe_browsing::RTLookupResponse::ThreatInfo* threat_info =
        response.add_threat_info();
    threat_info->set_verdict_type(
        safe_browsing::RTLookupResponse::ThreatInfo::DANGEROUS);
    threat_info->set_threat_type(
        safe_browsing::RTLookupResponse::ThreatInfo::SOCIAL_ENGINEERING);
    threat_info->set_cache_duration_sec(100);
    threat_info->set_cache_expression_using_match_type(bad_url.host() + "/");
    threat_info->set_cache_expression_match_type(
        safe_browsing::RTLookupResponse::ThreatInfo::COVERING_MATCH);
    verdict_cache_manager_->CacheRealTimeUrlVerdict(response,
                                                    base::Time::Now());
  }

 protected:
  void SetUpVerdict(GURL url, bool is_unsafe) {
    verdict_cache_manager_->CacheArtificialHashRealTimeLookupVerdict(url.spec(),
                                                                     is_unsafe);
  }

  web::WebTaskEnvironment task_environment_{
      web::WebTaskEnvironment::MainThreadType::IO};
  scoped_refptr<SafeBrowsingService> safe_browsing_service_;
  std::unique_ptr<web::FakeBrowserState> browser_state_;
  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> pref_service_;
  FakeSafeBrowsingClient safe_browsing_client_;
  safe_browsing::hash_realtime_utils::GoogleChromeBrandingPretenderForTesting
      apply_branding_;
  base::test::ScopedFeatureList scoped_feature_list_;
  web::FakeWebState web_state_;

 private:
  void MarkUrlAsMalwareOnUIThread(const GURL& bad_url) {
    safe_browsing::FullHashInfo full_hash_info =
        safe_browsing::GetFullHashInfoWithMetadata(
            bad_url, safe_browsing::GetUrlMalwareId(),
            safe_browsing::ThreatMetadata());
    v4_db_factory_->MarkPrefixAsBad(safe_browsing::GetUrlMalwareId(),
                                    full_hash_info.full_hash);
    v4_get_hash_factory_->AddToFullHashCache(full_hash_info);
  }

  void MarkUrlAsSafeOnUIThread(const GURL& bad_url) {
    safe_browsing::FullHashInfo full_hash_info =
        safe_browsing::GetFullHashInfoWithMetadata(
            bad_url, safe_browsing::GetUrlMalwareId(),
            safe_browsing::ThreatMetadata());
    v4_db_factory_->MarkPrefixAsBad(
        safe_browsing::GetUrlHighConfidenceAllowlistId(),
        full_hash_info.full_hash);
    v4_get_hash_factory_->AddToFullHashCache(full_hash_info);
  }

  void SetupUrlLookupService() {
    host_content_settings_map_ = base::MakeRefCounted<HostContentSettingsMap>(
        pref_service_.get(), /*is_off_the_record=*/false,
        /*store_last_modified=*/false, /*restore_session=*/false,
        /*should_record_metrics=*/false);
    verdict_cache_manager_ =
        std::make_unique<safe_browsing::VerdictCacheManager>(
            /*history_service=*/nullptr, host_content_settings_map_.get(),
            pref_service_.get(), /*sync_observer=*/nullptr);
    lookup_service_ = std::make_unique<safe_browsing::RealTimeUrlLookupService>(
        safe_browsing_service_->GetURLLoaderFactory(),
        verdict_cache_manager_.get(), base::BindRepeating([] {
          safe_browsing::ChromeUserPopulation population;
          return population;
        }),
        pref_service_.get(),
        /*token_fetcher=*/nullptr,
        base::BindRepeating([](bool) { return false; }),
        /*is_off_the_record=*/false,
        /*variations_service=*/nullptr,
        /*referrer_chain_provider=*/nullptr,
        /*webui_delegate=*/nullptr);
    safe_browsing_client_.set_real_time_url_lookup_service(
        lookup_service_.get());
  }

  base::ScopedTempDir temp_dir_;

  // Owned by V4Database.
  raw_ptr<safe_browsing::TestV4DatabaseFactory> v4_db_factory_;
  // Owned by V4GetHashProtocolManager.
  raw_ptr<safe_browsing::TestV4GetHashProtocolManagerFactory>
      v4_get_hash_factory_;
  // Owned by V4Database.
  raw_ptr<safe_browsing::TestV4StoreFactory> store_factory_;
  scoped_refptr<HostContentSettingsMap> host_content_settings_map_;
  std::unique_ptr<safe_browsing::VerdictCacheManager> verdict_cache_manager_;
  std::unique_ptr<safe_browsing::RealTimeUrlLookupService> lookup_service_;
};

TEST_F(SafeBrowsingServiceTest, SafeAndUnsafePages) {
  // Verify that queries to the Safe Browsing database owned by
  // SafeBrowsingService receive responses.
  TestUrlCheckerClient client(safe_browsing_service_.get(),
                              browser_state_.get(), &safe_browsing_client_);
  GURL safe_url = GURL(kSafePage);
  client.CheckUrl(safe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());

  GURL unsafe_url = GURL(kMalwarePage);
  MarkUrlAsMalware(unsafe_url);
  client.CheckUrl(unsafe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_TRUE(client.url_is_unsafe());

  // Disable Safe Browsing, and ensure that unsafe URLs are no longer flagged.
  pref_service_->SetBoolean(prefs::kSafeBrowsingEnabled, false);
  client.CheckUrl(unsafe_url);
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());
}

TEST_F(SafeBrowsingServiceTest, SafeAndUnsafePagesWithSyncChecker) {
  // Verify that queries to the Safe Browsing database owned by
  // SafeBrowsingService receive responses.
  TestUrlCheckerClient client(safe_browsing_service_.get(),
                              browser_state_.get(), &safe_browsing_client_);
  GURL safe_url = GURL(kSafePage);
  client.CheckUrlWithSyncChecker(safe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());

  GURL unsafe_url = GURL(kMalwarePage);
  MarkUrlAsMalware(unsafe_url);
  client.CheckUrlWithSyncChecker(unsafe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_TRUE(client.url_is_unsafe());

  // Disable Safe Browsing, and ensure that unsafe URLs are no longer flagged.
  pref_service_->SetBoolean(prefs::kSafeBrowsingEnabled, false);
  client.CheckUrlWithSyncChecker(unsafe_url);
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());
}

TEST_F(SafeBrowsingServiceTest, SafeAndUnsafePagesWithAsyncChecker) {
  // Verify that queries to the Safe Browsing database owned by
  // SafeBrowsingService receive responses.
  TestUrlCheckerClient client(safe_browsing_service_.get(),
                              browser_state_.get(), &safe_browsing_client_);
  GURL safe_url = GURL(kSafePage);
  client.CheckUrlWithAsyncChecker(safe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());

  GURL unsafe_url = GURL(kMalwarePage);
  MarkUrlAsMalware(unsafe_url);
  client.CheckUrlWithAsyncChecker(unsafe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_TRUE(client.url_is_unsafe());

  // Disable Safe Browsing, and ensure that unsafe URLs are no longer flagged.
  pref_service_->SetBoolean(prefs::kSafeBrowsingEnabled, false);
  client.CheckUrlWithAsyncChecker(unsafe_url);
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());
}

// Verifies that safe and unsafe URLs are identified correctly when real-time
// lookups are enabled, and that opting out of real-time checks works as
// expected.
TEST_F(SafeBrowsingServiceTest, RealTimeSafeAndUnsafePages) {
  TestUrlCheckerClient client(safe_browsing_service_.get(),
                              browser_state_.get(), &safe_browsing_client_);

  // Wait for an initial result to make sure the Safe Browsing database has
  // been initialized, before calling into functions that mark URLs as safe
  // or unsafe in the database.
  GURL safe_url(kSafePage);
  client.CheckUrl(safe_url);
  client.WaitForResult();

  // Opt into real-time checks.
  pref_service_->SetBoolean(
      unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, true);

  MarkUrlAsRealTimeSafe(safe_url);
  client.CheckUrl(safe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());

  GURL unsafe_url(kMalwarePage);
  MarkUrlAsRealTimeUnsafe(unsafe_url);
  client.CheckUrl(unsafe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_TRUE(client.url_is_unsafe());

  // Opt out of real-time checks, and ensure that unsafe URLs are no longer
  // flagged.
  pref_service_->SetBoolean(
      unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, false);
  client.CheckUrl(unsafe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());
}

// Verifies that safe and unsafe URLs are identified correctly for when a sync
// checker is used. A sync checker shouldn't be able to detect unsafe pages
// related to real time checks.
TEST_F(SafeBrowsingServiceTest, RealTimeSafeAndUnsafePagesWithSyncChecker) {
  TestUrlCheckerClient client(safe_browsing_service_.get(),
                              browser_state_.get(), &safe_browsing_client_);

  // Wait for an initial result to make sure the Safe Browsing database has
  // been initialized, before calling into functions that mark URLs as safe
  // or unsafe in the database.
  GURL safe_url(kSafePage);
  client.CheckUrlWithSyncChecker(safe_url);
  client.WaitForResult();

  // Opt into real-time checks.
  pref_service_->SetBoolean(
      unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, true);

  MarkUrlAsRealTimeSafe(safe_url);
  client.CheckUrlWithSyncChecker(safe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());

  GURL unsafe_url(kMalwarePage);
  MarkUrlAsRealTimeUnsafe(unsafe_url);
  client.CheckUrlWithSyncChecker(unsafe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());

  // Opt out of real-time checks, and ensure that unsafe URLs continue to be
  // unflagged.
  pref_service_->SetBoolean(
      unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, false);
  client.CheckUrlWithSyncChecker(unsafe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());
}

// Verifies that safe and unsafe URLs are identified correctly for when an async
// checker is used. An async checker should detect unsafe pages related to real
// time checks.
TEST_F(SafeBrowsingServiceTest, RealTimeSafeAndUnsafePagesWithAsyncChecker) {
  TestUrlCheckerClient client(safe_browsing_service_.get(),
                              browser_state_.get(), &safe_browsing_client_);

  // Wait for an initial result to make sure the Safe Browsing database has
  // been initialized, before calling into functions that mark URLs as safe
  // or unsafe in the database.
  GURL safe_url(kSafePage);
  client.CheckUrlWithAsyncChecker(safe_url);
  client.WaitForResult();

  // Opt into real-time checks.
  pref_service_->SetBoolean(
      unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, true);

  MarkUrlAsRealTimeSafe(safe_url);
  client.CheckUrlWithAsyncChecker(safe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());

  GURL unsafe_url(kMalwarePage);
  MarkUrlAsRealTimeUnsafe(unsafe_url);
  client.CheckUrlWithAsyncChecker(unsafe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_TRUE(client.url_is_unsafe());

  // Opt out of real-time checks, and ensure that unsafe URLs are no longer
  // flagged.
  pref_service_->SetBoolean(
      unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, false);
  client.CheckUrlWithAsyncChecker(unsafe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());
}

TEST_F(SafeBrowsingServiceTest,
       RealTimeSafeAndUnsafePagesWithEnhancedProtection) {
  TestUrlCheckerClient client(safe_browsing_service_.get(),
                              browser_state_.get(), &safe_browsing_client_);

  // Wait for an initial result to make sure the Safe Browsing database has
  // been initialized, before calling into functions that mark URLs as safe
  // or unsafe in the database.
  GURL safe_url(kSafePage);
  client.CheckUrl(safe_url);
  client.WaitForResult();

  // Opt into real-time checks.
  pref_service_->SetBoolean(prefs::kSafeBrowsingEnhanced, true);

  MarkUrlAsRealTimeSafe(safe_url);
  client.CheckUrl(safe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());

  GURL unsafe_url(kMalwarePage);
  MarkUrlAsRealTimeUnsafe(unsafe_url);
  client.CheckUrl(unsafe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_TRUE(client.url_is_unsafe());

  // Opt out of real-time checks, and ensure that unsafe URLs are no longer
  // flagged.
  pref_service_->SetBoolean(prefs::kSafeBrowsingEnhanced, false);
  client.CheckUrl(unsafe_url);
  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_FALSE(client.url_is_unsafe());
}

// Verifies that cookies are persisted across calls to
// SafeBrowsingServiceImpl::GetURLLoaderFactory.
TEST_F(SafeBrowsingServiceTest, PersistentCookies) {
  net::EmbeddedTestServer server(net::EmbeddedTestServer::TYPE_HTTPS);
  net::test_server::RegisterDefaultHandlers(&server);
  ASSERT_TRUE(server.Start());
  std::string cookie = "test=123";
  std::unique_ptr<network::ResourceRequest> resource_request =
      std::make_unique<network::ResourceRequest>();

  // Set a cookie that expires in an hour.
  resource_request->url = server.GetURL("/set-cookie?" + cookie +
                                        ";max-age=3600;SameSite=None;Secure");
  std::unique_ptr<network::SimpleURLLoader> url_loader =
      network::SimpleURLLoader::Create(std::move(resource_request),
                                       TRAFFIC_ANNOTATION_FOR_TESTS);
  base::RunLoop run_loop1;
  url_loader->DownloadHeadersOnly(
      safe_browsing_service_->GetURLLoaderFactory().get(),
      base::BindLambdaForTesting(
          [&](scoped_refptr<net::HttpResponseHeaders> headers) {
            run_loop1.Quit();
          }));
  run_loop1.Run();

  // Make another request to the same site, and verify that the cookie is still
  // set.
  resource_request = std::make_unique<network::ResourceRequest>();
  resource_request->url = server.GetURL("/echoheader?Cookie");
  url_loader = network::SimpleURLLoader::Create(std::move(resource_request),
                                                TRAFFIC_ANNOTATION_FOR_TESTS);
  base::RunLoop run_loop2;
  url_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
      safe_browsing_service_->GetURLLoaderFactory().get(),
      base::BindLambdaForTesting([&](std::unique_ptr<std::string> body) {
        EXPECT_NE(std::string::npos, body->find(cookie));
        run_loop2.Quit();
      }));
  run_loop2.Run();
}

// Verifies that cookies are cleared when ClearCookies() is called with a
// time range of all-time, but not otherwise.
TEST_F(SafeBrowsingServiceTest, ClearCookies) {
  net::EmbeddedTestServer server(net::EmbeddedTestServer::TYPE_HTTPS);
  net::test_server::RegisterDefaultHandlers(&server);
  ASSERT_TRUE(server.Start());
  std::string cookie = "test=123";
  std::unique_ptr<network::ResourceRequest> resource_request =
      std::make_unique<network::ResourceRequest>();

  // Set a cookie that expires in an hour.
  resource_request->url = server.GetURL("/set-cookie?" + cookie +
                                        ";max-age=3600;SameSite=None;Secure");
  std::unique_ptr<network::SimpleURLLoader> url_loader =
      network::SimpleURLLoader::Create(std::move(resource_request),
                                       TRAFFIC_ANNOTATION_FOR_TESTS);
  base::RunLoop run_loop1;
  url_loader->DownloadHeadersOnly(
      safe_browsing_service_->GetURLLoaderFactory().get(),
      base::BindLambdaForTesting(
          [&](scoped_refptr<net::HttpResponseHeaders> headers) {
            run_loop1.Quit();
          }));
  run_loop1.Run();

  // Call ClearCookies() with a non-all-time time range, and verify that the
  // cookie is still set.
  base::RunLoop run_loop2;
  safe_browsing_service_->ClearCookies(
      net::CookieDeletionInfo::TimeRange(base::Time(), base::Time::Now()),
      base::BindLambdaForTesting([&]() { run_loop2.Quit(); }));
  run_loop2.Run();

  resource_request = std::make_unique<network::ResourceRequest>();
  resource_request->url = server.GetURL("/echoheader?Cookie");
  url_loader = network::SimpleURLLoader::Create(std::move(resource_request),
                                                TRAFFIC_ANNOTATION_FOR_TESTS);
  base::RunLoop run_loop3;
  url_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
      safe_browsing_service_->GetURLLoaderFactory().get(),
      base::BindLambdaForTesting([&](std::unique_ptr<std::string> body) {
        EXPECT_NE(std::string::npos, body->find(cookie));
        run_loop3.Quit();
      }));
  run_loop3.Run();

  // Call ClearCookies() with a time range of all-time, and verify that the
  // cookie is no longer set.
  base::RunLoop run_loop4;
  safe_browsing_service_->ClearCookies(
      net::CookieDeletionInfo::TimeRange(base::Time(), base::Time::Max()),
      base::BindLambdaForTesting([&]() { run_loop4.Quit(); }));
  run_loop4.Run();

  resource_request = std::make_unique<network::ResourceRequest>();
  resource_request->url = server.GetURL("/echoheader?Cookie");
  url_loader = network::SimpleURLLoader::Create(std::move(resource_request),
                                                TRAFFIC_ANNOTATION_FOR_TESTS);
  base::RunLoop run_loop5;
  url_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
      safe_browsing_service_->GetURLLoaderFactory().get(),
      base::BindLambdaForTesting([&](std::unique_ptr<std::string> body) {
        EXPECT_EQ(std::string::npos, body->find(cookie));
        run_loop5.Quit();
      }));
  run_loop5.Run();
}

// Verfies that http requests sent by SafeBrowsingServiceImpl's network context
// have a non-empty User-Agent header.
TEST_F(SafeBrowsingServiceTest, NonEmptyUserAgent) {
  net::EmbeddedTestServer server(net::EmbeddedTestServer::TYPE_HTTPS);
  net::test_server::RegisterDefaultHandlers(&server);
  ASSERT_TRUE(server.Start());
  std::unique_ptr<network::ResourceRequest> resource_request =
      std::make_unique<network::ResourceRequest>();

  // Ask the server to echo the User-Agent header and verify that the echoed
  // value is non-empty.
  resource_request = std::make_unique<network::ResourceRequest>();
  resource_request->url = server.GetURL("/echoheader?User-Agent");
  std::unique_ptr<network::SimpleURLLoader> url_loader =
      network::SimpleURLLoader::Create(std::move(resource_request),
                                       TRAFFIC_ANNOTATION_FOR_TESTS);
  base::RunLoop run_loop;
  url_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
      safe_browsing_service_->GetURLLoaderFactory().get(),
      base::BindLambdaForTesting([&](std::unique_ptr<std::string> body) {
        EXPECT_FALSE(body->empty());
        run_loop.Quit();
      }));
  run_loop.Run();
}

// Verifies that Safe Browsing hash prefix metrics are correctly recorded and
// the performed check is correct when the hash prefix feature is enabled.
TEST_F(SafeBrowsingServiceTest, HashPrefixEnabled) {
  scoped_feature_list_.InitAndEnableFeature(
      safe_browsing::kHashPrefixRealTimeLookups);
  TestUrlCheckerClient client(safe_browsing_service_.get(),
                              browser_state_.get(), &safe_browsing_client_);
  pref_service_->SetBoolean(prefs::kSafeBrowsingEnabled, true);

  base::HistogramTester histogram_tester;
  GURL url = GURL(kMalwarePage);
  SetUpVerdict(url, /*is_unsafe=*/true);
  client.CheckUrl(url);

  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_EQ(safe_browsing::SafeBrowsingUrlCheckerImpl::PerformedCheck::
                kHashRealTimeCheck,
            client.performed_check());
  histogram_tester.ExpectBucketCount(
      "SafeBrowsing.HPRT.Ineligible.IneligibleForSessionOrLocation",
      /*sample=*/false,
      /*expected_bucket_count=*/1);

  task_environment_.RunUntilIdle();
}

// Verifies that Safe Browsing hash prefix metrics are correctly recorded and
// the performed check is correct when the hash prefix feature is disabled.
TEST_F(SafeBrowsingServiceTest, HashPrefixDisabled) {
  scoped_feature_list_.InitAndDisableFeature(
      safe_browsing::kHashPrefixRealTimeLookups);
  TestUrlCheckerClient client(safe_browsing_service_.get(),
                              browser_state_.get(), &safe_browsing_client_);

  pref_service_->SetBoolean(prefs::kSafeBrowsingEnabled, true);

  base::HistogramTester histogram_tester;
  GURL url = GURL(kMalwarePage);
  SetUpVerdict(url, /*is_unsafe=*/true);
  client.CheckUrl(url);

  EXPECT_TRUE(client.result_pending());
  client.WaitForResult();
  EXPECT_FALSE(client.result_pending());
  EXPECT_EQ(safe_browsing::SafeBrowsingUrlCheckerImpl::PerformedCheck::
                kHashDatabaseCheck,
            client.performed_check());
  histogram_tester.ExpectBucketCount(
      "SafeBrowsing.HPRT.Ineligible.IneligibleForSessionOrLocation",
      /*sample=*/true,
      /*expected_bucket_count=*/1);

  task_environment_.RunUntilIdle();
}

// Verifies that Safe Browsing preference metrics are correctly recorded when
// Safe Browsing is disabled.
TEST_F(SafeBrowsingServiceTest, TestShouldCreateAsyncChecker) {
  scoped_feature_list_.InitAndEnableFeature(
      safe_browsing::kSafeBrowsingAsyncRealTimeCheck);
  TestUrlCheckerClient client(safe_browsing_service_.get(),
                              browser_state_.get(), &safe_browsing_client_);
  web_state_.SetBrowserState(browser_state_.get());
  EXPECT_TRUE(safe_browsing_service_->ShouldCreateAsyncChecker(
      &web_state_, &safe_browsing_client_));

  pref_service_->SetBoolean(prefs::kSafeBrowsingEnabled, false);
  pref_service_->SetBoolean(
      unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, false);
  EXPECT_FALSE(safe_browsing_service_->ShouldCreateAsyncChecker(
      &web_state_, &safe_browsing_client_));

  safe_browsing_service_->ShutDown();
}

using SafeBrowsingServiceInitializationTest = PlatformTest;

// Verifies that GetURLLoaderFactory() has a non-null return value when called
// immediately after initialization.
TEST_F(SafeBrowsingServiceInitializationTest, GetURLLoaderFactory) {
  web::WebTaskEnvironment task_environment;

  std::unique_ptr<web::FakeBrowserState> browser_state =
      std::make_unique<web::FakeBrowserState>();
  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> prefs =
      std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
  safe_browsing::RegisterProfilePrefs(prefs->registry());

  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  scoped_refptr<SafeBrowsingService> safe_browsing_service =
      base::MakeRefCounted<SafeBrowsingServiceImpl>();
  safe_browsing_service->Initialize(
      prefs.get(), temp_dir.GetPath(),
      /*safe_browsing_metrics_collector=*/nullptr);

  EXPECT_TRUE(safe_browsing_service->GetURLLoaderFactory());

  safe_browsing_service->ShutDown();
  task_environment.RunUntilIdle();
}

// Verifies that Safe Browsing preference metrics are correctly recorded when
// Safe Browsing is enabled but Enhanced Safe Browsing is not.
TEST_F(SafeBrowsingServiceInitializationTest,
       PreferenceMetricsStandardSafeBrowsing) {
  web::WebTaskEnvironment task_environment;

  std::unique_ptr<web::FakeBrowserState> browser_state =
      std::make_unique<web::FakeBrowserState>();
  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> prefs =
      std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
  safe_browsing::RegisterProfilePrefs(prefs->registry());

  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  scoped_refptr<SafeBrowsingService> safe_browsing_service =
      base::MakeRefCounted<SafeBrowsingServiceImpl>();
  prefs->SetBoolean(prefs::kSafeBrowsingEnabled, true);
  prefs->SetBoolean(prefs::kSafeBrowsingEnhanced, false);
  base::HistogramTester histogram_tester;
  safe_browsing_service->Initialize(
      prefs.get(), temp_dir.GetPath(),
      /*safe_browsing_metrics_collector=*/nullptr);
  histogram_tester.ExpectUniqueSample(
      safe_browsing::kSafeBrowsingEnabledHistogramName, /*sample=*/1,
      /*count=*/1);
  histogram_tester.ExpectUniqueSample("SafeBrowsing.Pref.Enhanced",
                                      /*sample=*/0, /*count=*/1);
  histogram_tester.ExpectUniqueSample(
      "SafeBrowsing.Pref.Enhanced.RegularProfile",
      /*sample=*/0, /*count=*/1);

  safe_browsing_service->ShutDown();
  task_environment.RunUntilIdle();
}

// Verifies that Safe Browsing preference metrics are correctly recorded when
// Enhanced Safe Browsing is enabled.
TEST_F(SafeBrowsingServiceInitializationTest,
       PreferenceMetricsEnhancedSafeBrowsing) {
  web::WebTaskEnvironment task_environment;

  std::unique_ptr<web::FakeBrowserState> browser_state =
      std::make_unique<web::FakeBrowserState>();
  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> prefs =
      std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
  safe_browsing::RegisterProfilePrefs(prefs->registry());

  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  scoped_refptr<SafeBrowsingService> safe_browsing_service =
      base::MakeRefCounted<SafeBrowsingServiceImpl>();
  prefs->SetBoolean(prefs::kSafeBrowsingEnabled, true);
  prefs->SetBoolean(prefs::kSafeBrowsingEnhanced, true);
  base::HistogramTester histogram_tester;
  safe_browsing_service->Initialize(
      prefs.get(), temp_dir.GetPath(),
      /*safe_browsing_metrics_collector=*/nullptr);
  histogram_tester.ExpectUniqueSample(
      safe_browsing::kSafeBrowsingEnabledHistogramName, /*sample=*/1,
      /*count=*/1);
  histogram_tester.ExpectUniqueSample("SafeBrowsing.Pref.Enhanced",
                                      /*sample=*/1, /*count=*/1);
  histogram_tester.ExpectUniqueSample(
      "SafeBrowsing.Pref.Enhanced.RegularProfile",
      /*sample=*/1, /*count=*/1);

  safe_browsing_service->ShutDown();
  task_environment.RunUntilIdle();
}

// Verifies that Safe Browsing preference metrics are correctly recorded when
// Safe Browsing is disabled.
TEST_F(SafeBrowsingServiceInitializationTest, PreferenceMetricsNoSafeBrowsing) {
  web::WebTaskEnvironment task_environment;

  std::unique_ptr<web::FakeBrowserState> browser_state =
      std::make_unique<web::FakeBrowserState>();
  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> prefs =
      std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
  safe_browsing::RegisterProfilePrefs(prefs->registry());

  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  scoped_refptr<SafeBrowsingService> safe_browsing_service =
      base::MakeRefCounted<SafeBrowsingServiceImpl>();
  prefs->SetBoolean(prefs::kSafeBrowsingEnabled, false);
  prefs->SetBoolean(prefs::kSafeBrowsingEnhanced, false);
  base::HistogramTester histogram_tester;
  safe_browsing_service->Initialize(
      prefs.get(), temp_dir.GetPath(),
      /*safe_browsing_metrics_collector=*/nullptr);
  histogram_tester.ExpectUniqueSample(
      safe_browsing::kSafeBrowsingEnabledHistogramName, /*sample=*/0,
      /*count=*/1);
  histogram_tester.ExpectUniqueSample("SafeBrowsing.Pref.Enhanced",
                                      /*sample=*/0, /*count=*/1);
  histogram_tester.ExpectUniqueSample(
      "SafeBrowsing.Pref.Enhanced.RegularProfile",
      /*sample=*/0, /*count=*/1);

  safe_browsing_service->ShutDown();
  task_environment.RunUntilIdle();
}