chromium/ios/components/security_interstitials/safe_browsing/safe_browsing_service_impl.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/file_path.h"
#import "base/functional/bind.h"
#import "base/path_service.h"
#import "build/branding_buildflags.h"
#import "components/prefs/pref_change_registrar.h"
#import "components/prefs/pref_service.h"
#import "components/safe_browsing/core/browser/db/v4_local_database_manager.h"
#import "components/safe_browsing/core/browser/realtime/url_lookup_service.h"
#import "components/safe_browsing/core/browser/safe_browsing_metrics_collector.h"
#import "components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h"
#import "components/safe_browsing/core/browser/url_checker_delegate.h"
#import "components/safe_browsing/core/common/features.h"
#import "components/safe_browsing/core/common/safebrowsing_constants.h"
#import "components/sessions/core/session_id.h"
#import "ios/components/cookie_util/cookie_util.h"
#import "ios/components/security_interstitials/safe_browsing/safe_browsing_client.h"
#import "ios/components/security_interstitials/safe_browsing/url_checker_delegate_impl.h"
#import "ios/net/cookies/system_cookie_store.h"
#import "ios/web/common/user_agent.h"
#import "ios/web/public/browser_state.h"
#import "ios/web/public/thread/web_task_traits.h"
#import "ios/web/public/thread/web_thread.h"
#import "ios/web/public/web_client.h"
#import "ios/web/public/web_state.h"
#import "net/cookies/cookie_store.h"
#import "net/url_request/url_request_context.h"
#import "net/url_request/url_request_context_builder.h"
#import "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"

#pragma mark - SafeBrowsingServiceImpl

namespace {

void StartSafeBrowsingDBManagerInternal(
    scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager>
        safe_browsing_db_manager,
    scoped_refptr<network::WeakWrapperSharedURLLoaderFactory>
        shared_url_loader_factory) {
  std::string client_name;
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
  client_name = "googlechrome";
#else
  client_name = "chromium";
#endif

  safe_browsing::V4ProtocolConfig config =
      safe_browsing::GetV4ProtocolConfig(client_name,
                                         /*disable_auto_update=*/false);

  safe_browsing_db_manager->StartOnUIThread(shared_url_loader_factory, config);
}

}  // namespace

SafeBrowsingServiceImpl::SafeBrowsingServiceImpl() {
  url_loader_factory_pending_receiver_ =
      url_loader_factory_.BindNewPipeAndPassReceiver();
  shared_url_loader_factory_ =
      base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
          url_loader_factory_.get());
}

SafeBrowsingServiceImpl::~SafeBrowsingServiceImpl() = default;

void SafeBrowsingServiceImpl::Initialize(
    PrefService* prefs,
    const base::FilePath& user_data_path,
    safe_browsing::SafeBrowsingMetricsCollector*
        safe_browsing_metrics_collector) {
  DCHECK_CURRENTLY_ON(web::WebThread::UI);

  if (io_thread_enabler_) {
    // Already initialized.
    return;
  }

  base::FilePath safe_browsing_data_path =
      user_data_path.Append(safe_browsing::kSafeBrowsingBaseFilename);
  safe_browsing_db_manager_ = safe_browsing::V4LocalDatabaseManager::Create(
      safe_browsing_data_path, web::GetUIThreadTaskRunner({}),
      web::GetIOThreadTaskRunner({}),
      safe_browsing::ExtendedReportingLevelCallback(),
      safe_browsing::V4LocalDatabaseManager::RecordMigrationMetricsCallback());

  io_thread_enabler_ = base::MakeRefCounted<IOThreadEnabler>();

  web::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&IOThreadEnabler::Initialize, io_thread_enabler_,
                     network_context_client_.BindNewPipeAndPassReceiver(),
                     safe_browsing_data_path));

  auto url_loader_factory_params =
      network::mojom::URLLoaderFactoryParams::New();
  url_loader_factory_params->process_id = network::mojom::kBrowserProcessId;
  url_loader_factory_params->is_orb_enabled = false;
  network_context_client_->CreateURLLoaderFactory(
      std::move(url_loader_factory_pending_receiver_),
      std::move(url_loader_factory_params));

  // Watch for changes to the Safe Browsing opt-out preference.
  pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  pref_change_registrar_->Init(prefs);
  pref_change_registrar_->Add(
      prefs::kSafeBrowsingEnabled,
      base::BindRepeating(
          &SafeBrowsingServiceImpl::UpdateSafeBrowsingEnabledState,
          base::Unretained(this)));
  UMA_HISTOGRAM_BOOLEAN(
      safe_browsing::kSafeBrowsingEnabledHistogramName,
      pref_change_registrar_->prefs()->GetBoolean(prefs::kSafeBrowsingEnabled));
  // TODO(crbug.com/40886668): Deprecate SafeBrowsing.Pref.Enhanced.
  UMA_HISTOGRAM_BOOLEAN("SafeBrowsing.Pref.Enhanced",
                        prefs->GetBoolean(prefs::kSafeBrowsingEnhanced));
  // TODO(crbug.com/332512508): We will need to update the
  // SafeBrowsing.Pref.Enhanced.RegularProfile metric to support multi-profile.
  UMA_HISTOGRAM_BOOLEAN("SafeBrowsing.Pref.Enhanced.RegularProfile",
                        prefs->GetBoolean(prefs::kSafeBrowsingEnhanced));
  safe_browsing::RecordExtendedReportingMetrics(*prefs);
  if (base::FeatureList::IsEnabled(
          safe_browsing::kExtendedReportingRemovePrefDependency)) {
    prefs->SetBoolean(
        prefs::kSafeBrowsingScoutReportingEnabledWhenDeprecated,
        prefs->GetBoolean(prefs::kSafeBrowsingScoutReportingEnabled));
  } else {
    prefs->SetBoolean(prefs::kSafeBrowsingScoutReportingEnabledWhenDeprecated,
                      false);
  }
  UpdateSafeBrowsingEnabledState();
  if (safe_browsing_metrics_collector)
    safe_browsing_metrics_collector->StartLogging();
}

void SafeBrowsingServiceImpl::ShutDown() {
  DCHECK_CURRENTLY_ON(web::WebThread::UI);

  pref_change_registrar_.reset();
  web::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&IOThreadEnabler::ShutDown, io_thread_enabler_));
  if (enabled_) {
    enabled_ = false;
    safe_browsing_db_manager_->StopOnUIThread(true);
  }
  network_context_client_.reset();
}

std::unique_ptr<safe_browsing::SafeBrowsingUrlCheckerImpl>
SafeBrowsingServiceImpl::CreateUrlChecker(
    network::mojom::RequestDestination request_destination,
    web::WebState* web_state,
    SafeBrowsingClient* client) {
  safe_browsing::RealTimeUrlLookupService* url_lookup_service =
      client->GetRealTimeUrlLookupService();
  bool can_perform_full_url_lookup =
      url_lookup_service && url_lookup_service->CanPerformFullURLLookup();
  scoped_refptr<safe_browsing::UrlCheckerDelegate> url_checker_delegate =
      base::MakeRefCounted<UrlCheckerDelegateImpl>(safe_browsing_db_manager_,
                                                   client->AsWeakPtr());
  safe_browsing::HashRealTimeService* hash_real_time_service =
      client->GetHashRealTimeService();

  safe_browsing::hash_realtime_utils::HashRealTimeSelection
      hash_real_time_selection =
          safe_browsing::hash_realtime_utils::DetermineHashRealTimeSelection(
              web_state->GetBrowserState()->IsOffTheRecord(),
              pref_change_registrar_->prefs(),
              safe_browsing::hash_realtime_utils::GetCountryCode(
                  client->GetVariationsService()),
              /*log_usage_histograms=*/true);

  return std::make_unique<safe_browsing::SafeBrowsingUrlCheckerImpl>(
      /*headers=*/net::HttpRequestHeaders(), /*load_flags=*/0,
      /*has_user_gesture=*/false, url_checker_delegate,
      /*web_contents_getter=*/
      base::RepeatingCallback<content::WebContents*()>(),
      web_state->GetWeakPtr(),
      /*render_process_id=*/
      security_interstitials::UnsafeResource::kNoRenderProcessId,
      /*render_frame_token=*/std::nullopt,
      /*frame_tree_node_id=*/
      security_interstitials::UnsafeResource::kNoFrameTreeNodeId,
      /*navigation_id=*/std::nullopt, can_perform_full_url_lookup,
      /*can_check_db=*/true, /*can_check_high_confidence_allowlist=*/true,
      /*url_lookup_service_metric_suffix=*/"", web::GetUIThreadTaskRunner({}),
      url_lookup_service ? url_lookup_service->GetWeakPtr() : nullptr,
      hash_real_time_service ? hash_real_time_service->GetWeakPtr() : nullptr,
      hash_real_time_selection,
      /*is_async_check=*/false, SessionID::InvalidValue());
}

std::unique_ptr<safe_browsing::SafeBrowsingUrlCheckerImpl>
SafeBrowsingServiceImpl::CreateAsyncChecker(
    network::mojom::RequestDestination request_destination,
    web::WebState* web_state,
    SafeBrowsingClient* client) {
  safe_browsing::RealTimeUrlLookupService* url_lookup_service =
      client->GetRealTimeUrlLookupService();
  bool can_perform_full_url_lookup =
      url_lookup_service && url_lookup_service->CanPerformFullURLLookup();
  scoped_refptr<safe_browsing::UrlCheckerDelegate> url_checker_delegate =
      base::MakeRefCounted<UrlCheckerDelegateImpl>(safe_browsing_db_manager_,
                                                   client->AsWeakPtr());
  safe_browsing::HashRealTimeService* hash_real_time_service =
      client->GetHashRealTimeService();

  safe_browsing::hash_realtime_utils::HashRealTimeSelection
      hash_real_time_selection =
          safe_browsing::hash_realtime_utils::DetermineHashRealTimeSelection(
              web_state->GetBrowserState()->IsOffTheRecord(),
              pref_change_registrar_->prefs(),
              safe_browsing::hash_realtime_utils::GetCountryCode(
                  client->GetVariationsService()),
              /*log_usage_histograms=*/true);

  return std::make_unique<safe_browsing::SafeBrowsingUrlCheckerImpl>(
      /*headers=*/net::HttpRequestHeaders(), /*load_flags=*/0,
      /*has_user_gesture=*/false, url_checker_delegate,
      /*web_contents_getter=*/
      base::RepeatingCallback<content::WebContents*()>(),
      web_state->GetWeakPtr(),
      /*render_process_id=*/
      security_interstitials::UnsafeResource::kNoRenderProcessId,
      /*render_frame_token=*/std::nullopt,
      /*frame_tree_node_id=*/
      security_interstitials::UnsafeResource::kNoFrameTreeNodeId,
      /*navigation_id=*/std::nullopt, can_perform_full_url_lookup,
      /*can_check_db=*/true, /*can_check_high_confidence_allowlist=*/true,
      /*url_lookup_service_metric_suffix=*/"", web::GetUIThreadTaskRunner({}),
      url_lookup_service ? url_lookup_service->GetWeakPtr() : nullptr,
      hash_real_time_service ? hash_real_time_service->GetWeakPtr() : nullptr,
      hash_real_time_selection,
      /*is_async_check=*/true, SessionID::InvalidValue());
}

std::unique_ptr<safe_browsing::SafeBrowsingUrlCheckerImpl>
SafeBrowsingServiceImpl::CreateSyncChecker(
    network::mojom::RequestDestination request_destination,
    web::WebState* web_state,
    SafeBrowsingClient* client) {
  scoped_refptr<safe_browsing::UrlCheckerDelegate> url_checker_delegate =
      base::MakeRefCounted<UrlCheckerDelegateImpl>(safe_browsing_db_manager_,
                                                   client->AsWeakPtr());

  return std::make_unique<safe_browsing::SafeBrowsingUrlCheckerImpl>(
      /*headers=*/net::HttpRequestHeaders(), /*load_flags=*/0,
      /*has_user_gesture=*/false, url_checker_delegate,
      /*web_contents_getter=*/
      base::RepeatingCallback<content::WebContents*()>(),
      web_state->GetWeakPtr(),
      /*render_process_id=*/
      security_interstitials::UnsafeResource::kNoRenderProcessId,
      /*render_frame_token=*/std::nullopt,
      /*frame_tree_node_id=*/
      security_interstitials::UnsafeResource::kNoFrameTreeNodeId,
      /*navigation_id=*/std::nullopt, /*url_real_time_lookup_enabled=*/false,
      /*can_check_db=*/true, /*can_check_high_confidence_allowlist=*/true,
      /*url_lookup_service_metric_suffix=*/"", web::GetUIThreadTaskRunner({}),
      /*url_lookup_service=*/nullptr,
      /*hash_realtime_service=*/nullptr,
      /*hash_realtime_selection=*/
      safe_browsing::hash_realtime_utils::HashRealTimeSelection::kNone,
      /*is_async_check=*/false, SessionID::InvalidValue());
}

// Checks if async check should be created.
bool SafeBrowsingServiceImpl::ShouldCreateAsyncChecker(
    web::WebState* web_state,
    SafeBrowsingClient* client) {
  if (!base::FeatureList::IsEnabled(
          safe_browsing::kSafeBrowsingAsyncRealTimeCheck)) {
    return false;
  }

  if (!web_state) {
    return false;
  }

  safe_browsing::RealTimeUrlLookupService* url_lookup_service =
      client->GetRealTimeUrlLookupService();
  bool can_perform_full_url_lookup =
      url_lookup_service && url_lookup_service->CanPerformFullURLLookup();

  safe_browsing::hash_realtime_utils::HashRealTimeSelection
      hash_real_time_selection =
          safe_browsing::hash_realtime_utils::DetermineHashRealTimeSelection(
              web_state->GetBrowserState()->IsOffTheRecord(),
              pref_change_registrar_->prefs(),
              safe_browsing::hash_realtime_utils::GetCountryCode(
                  client->GetVariationsService()),
              /*log_usage_histograms=*/true);

  if (!can_perform_full_url_lookup &&
      hash_real_time_selection ==
          safe_browsing::hash_realtime_utils::HashRealTimeSelection::kNone) {
    return false;
  }

  return true;
}

bool SafeBrowsingServiceImpl::CanCheckUrl(const GURL& url) const {
  return safe_browsing_db_manager_->CanCheckUrl(url);
}

scoped_refptr<network::SharedURLLoaderFactory>
SafeBrowsingServiceImpl::GetURLLoaderFactory() {
  return shared_url_loader_factory_;
}

scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager>
SafeBrowsingServiceImpl::GetDatabaseManager() {
  return safe_browsing_db_manager_;
}

network::mojom::NetworkContext* SafeBrowsingServiceImpl::GetNetworkContext() {
  return network_context_client_.get();
}

void SafeBrowsingServiceImpl::ClearCookies(
    const net::CookieDeletionInfo::TimeRange& creation_range,
    base::OnceClosure callback) {
  if (creation_range.start() == base::Time() &&
      creation_range.end() == base::Time::Max()) {
    web::GetIOThreadTaskRunner({base::TaskShutdownBehavior::BLOCK_SHUTDOWN})
        ->PostTask(FROM_HERE,
                   base::BindOnce(&IOThreadEnabler::ClearAllCookies,
                                  io_thread_enabler_, std::move(callback)));
  } else {
    web::GetIOThreadTaskRunner({})->PostTask(FROM_HERE, std::move(callback));
  }
}

void SafeBrowsingServiceImpl::SetUpURLLoaderFactory(
    mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver) {
  DCHECK_CURRENTLY_ON(web::WebThread::UI);
  url_loader_factory_->Clone(std::move(receiver));
}

void SafeBrowsingServiceImpl::UpdateSafeBrowsingEnabledState() {
  bool enabled =
      pref_change_registrar_->prefs()->GetBoolean(prefs::kSafeBrowsingEnabled);
  if (enabled_ == enabled) {
    return;
  }

  enabled_ = enabled;
  if (enabled_) {
    StartSafeBrowsingDBManagerInternal(safe_browsing_db_manager_,
                                       shared_url_loader_factory_);
  } else {
    safe_browsing_db_manager_->StopOnUIThread(false);
  }
}

#pragma mark - SafeBrowsingServiceImpl::IOThreadEnabler

SafeBrowsingServiceImpl::IOThreadEnabler::IOThreadEnabler() = default;

SafeBrowsingServiceImpl::IOThreadEnabler::~IOThreadEnabler() = default;

void SafeBrowsingServiceImpl::IOThreadEnabler::Initialize(
    mojo::PendingReceiver<network::mojom::NetworkContext>
        network_context_receiver,
    const base::FilePath& safe_browsing_data_path) {
  SetUpURLRequestContext(safe_browsing_data_path);
  std::vector<std::string> cors_exempt_header_list;
  network_context_ = std::make_unique<network::NetworkContext>(
      /*network_service=*/nullptr, std::move(network_context_receiver),
      url_request_context_.get(), cors_exempt_header_list);
}

void SafeBrowsingServiceImpl::IOThreadEnabler::ShutDown() {
  DCHECK_CURRENTLY_ON(web::WebThread::IO);
  network_context_.reset();
  url_request_context_.reset();
}

void SafeBrowsingServiceImpl::IOThreadEnabler::ClearAllCookies(
    base::OnceClosure callback) {
  DCHECK_CURRENTLY_ON(web::WebThread::IO);
  net::CookieStore* cookie_store = url_request_context_->cookie_store();
  cookie_store->DeleteAllAsync(base::BindOnce(
      [](base::OnceClosure callback, uint32_t) { std::move(callback).Run(); },
      std::move(callback)));
}

void SafeBrowsingServiceImpl::IOThreadEnabler::SetUpURLRequestContext(
    const base::FilePath& safe_browsing_data_path) {
  DCHECK_CURRENTLY_ON(web::WebThread::IO);

  net::URLRequestContextBuilder builder;
  base::FilePath cookie_file_path(safe_browsing_data_path.value() +
                                  safe_browsing::kCookiesFile);
  std::unique_ptr<net::CookieStore> cookie_store =
      cookie_util::CreateCookieStore(
          cookie_util::CookieStoreConfig(
              cookie_file_path,
              cookie_util::CookieStoreConfig::RESTORED_SESSION_COOKIES,
              cookie_util::CookieStoreConfig::COOKIE_MONSTER),
          /*system_cookie_store=*/nullptr, net::NetLog::Get());

  builder.SetCookieStore(std::move(cookie_store));
  builder.set_user_agent(
      web::GetWebClient()->GetUserAgent(web::UserAgentType::MOBILE));
  url_request_context_ = builder.Build();
}