1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/safe_browsing/client_side_detection_host.h"
6 
7 #include <memory>
8 #include <utility>
9 #include <vector>
10 
11 #include "base/bind.h"
12 #include "base/check_op.h"
13 #include "base/macros.h"
14 #include "base/memory/ptr_util.h"
15 #include "base/memory/ref_counted.h"
16 #include "base/metrics/histogram_functions.h"
17 #include "base/metrics/histogram_macros.h"
18 #include "base/sequenced_task_runner_helpers.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/time/default_tick_clock.h"
21 #include "base/time/tick_clock.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/safe_browsing/client_side_detection_service.h"
25 #include "chrome/browser/safe_browsing/client_side_detection_service_factory.h"
26 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
27 #include "chrome/browser/safe_browsing/user_interaction_observer.h"
28 #include "chrome/common/pref_names.h"
29 #include "components/prefs/pref_service.h"
30 #include "components/safe_browsing/content/common/safe_browsing.mojom-shared.h"
31 #include "components/safe_browsing/content/common/safe_browsing.mojom.h"
32 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
33 #include "components/safe_browsing/core/db/allowlist_checker_client.h"
34 #include "components/safe_browsing/core/db/database_manager.h"
35 #include "components/safe_browsing/core/proto/csd.pb.h"
36 #include "components/security_interstitials/content/unsafe_resource_util.h"
37 #include "content/public/browser/browser_task_traits.h"
38 #include "content/public/browser/browser_thread.h"
39 #include "content/public/browser/navigation_controller.h"
40 #include "content/public/browser/navigation_entry.h"
41 #include "content/public/browser/navigation_handle.h"
42 #include "content/public/browser/render_frame_host.h"
43 #include "content/public/browser/render_process_host.h"
44 #include "content/public/browser/web_contents.h"
45 #include "content/public/common/url_constants.h"
46 #include "net/base/ip_endpoint.h"
47 #include "net/http/http_response_headers.h"
48 #include "services/service_manager/public/cpp/interface_provider.h"
49 #include "third_party/blink/public/mojom/loader/referrer.mojom.h"
50 #include "url/gurl.h"
51 
52 using content::BrowserThread;
53 using content::NavigationEntry;
54 using content::WebContents;
55 
56 namespace safe_browsing {
57 
58 typedef base::OnceCallback<void(bool)> ShouldClassifyUrlCallback;
59 
60 // This class is instantiated each time a new toplevel URL loads, and
61 // asynchronously checks whether the phishing classifier should run
62 // for this URL.  If so, it notifies the host class by calling the provided
63 // callback form the UI thread.  Objects of this class are ref-counted and will
64 // be destroyed once nobody uses it anymore.  If |web_contents|, |csd_service|
65 // or |host| go away you need to call Cancel().  We keep the |database_manager|
66 // alive in a ref pointer for as long as it takes.
67 class ClientSideDetectionHost::ShouldClassifyUrlRequest
68     : public base::RefCountedThreadSafe<
69           ClientSideDetectionHost::ShouldClassifyUrlRequest> {
70  public:
ShouldClassifyUrlRequest(content::NavigationHandle * navigation_handle,ShouldClassifyUrlCallback start_phishing_classification,WebContents * web_contents,ClientSideDetectionService * csd_service,SafeBrowsingDatabaseManager * database_manager,ClientSideDetectionHost * host)71   ShouldClassifyUrlRequest(
72       content::NavigationHandle* navigation_handle,
73       ShouldClassifyUrlCallback start_phishing_classification,
74       WebContents* web_contents,
75       ClientSideDetectionService* csd_service,
76       SafeBrowsingDatabaseManager* database_manager,
77       ClientSideDetectionHost* host)
78       : web_contents_(web_contents),
79         csd_service_(csd_service),
80         database_manager_(database_manager),
81         host_(host),
82         start_phishing_classification_cb_(
83             std::move(start_phishing_classification)) {
84     DCHECK_CURRENTLY_ON(BrowserThread::UI);
85     DCHECK(web_contents_);
86     DCHECK(csd_service_);
87     DCHECK(database_manager_.get());
88     DCHECK(host_);
89     url_ = navigation_handle->GetURL();
90     if (navigation_handle->GetResponseHeaders())
91       navigation_handle->GetResponseHeaders()->GetMimeType(&mime_type_);
92     remote_endpoint_ = navigation_handle->GetSocketAddress();
93   }
94 
Start()95   void Start() {
96     DCHECK_CURRENTLY_ON(BrowserThread::UI);
97 
98     // We start by doing some simple checks that can run on the UI thread.
99     base::UmaHistogramBoolean("SBClientPhishing.ClassificationStart", true);
100 
101     // Only classify [X]HTML documents.
102     if (mime_type_ != "text/html" && mime_type_ != "application/xhtml+xml") {
103       DontClassifyForPhishing(NO_CLASSIFY_UNSUPPORTED_MIME_TYPE);
104     }
105 
106     if (csd_service_->IsPrivateIPAddress(
107             remote_endpoint_.ToStringWithoutPort())) {
108       DontClassifyForPhishing(NO_CLASSIFY_PRIVATE_IP);
109     }
110 
111     // For phishing we only classify HTTP or HTTPS pages.
112     if (!url_.SchemeIsHTTPOrHTTPS()) {
113       DontClassifyForPhishing(NO_CLASSIFY_SCHEME_NOT_SUPPORTED);
114     }
115 
116     // Don't run any classifier if the tab is incognito.
117     if (web_contents_->GetBrowserContext()->IsOffTheRecord()) {
118       DontClassifyForPhishing(NO_CLASSIFY_OFF_THE_RECORD);
119     }
120 
121     // Don't start classification if |url_| is whitelisted by enterprise policy.
122     Profile* profile =
123         Profile::FromBrowserContext(web_contents_->GetBrowserContext());
124     if (profile && IsURLWhitelistedByPolicy(url_, *profile->GetPrefs())) {
125       DontClassifyForPhishing(NO_CLASSIFY_WHITELISTED_BY_POLICY);
126     }
127 
128     // If the tab has a delayed warning, ignore this second verdict. We don't
129     // want to immediately undelay a page that's already blocked as phishy.
130     if (SafeBrowsingUserInteractionObserver::FromWebContents(web_contents_)) {
131       DontClassifyForPhishing(NO_CLASSIFY_HAS_DELAYED_WARNING);
132     }
133 
134     // We lookup the csd-whitelist before we lookup the cache because
135     // a URL may have recently been whitelisted.  If the URL matches
136     // the csd-whitelist we won't start phishing classification.  The
137     // csd-whitelist check has to be done on the IO thread because it
138     // uses the SafeBrowsing service class.
139     if (ShouldClassifyForPhishing()) {
140       content::GetIOThreadTaskRunner({})->PostTask(
141           FROM_HERE,
142           base::BindOnce(&ShouldClassifyUrlRequest::CheckSafeBrowsingDatabase,
143                          this, url_));
144     }
145   }
146 
Cancel()147   void Cancel() {
148     DontClassifyForPhishing(NO_CLASSIFY_CANCEL);
149     // Just to make sure we don't do anything stupid we reset all these
150     // pointers except for the safebrowsing service class which may be
151     // accessed by CheckSafeBrowsingDatabase().
152     web_contents_ = nullptr;
153     csd_service_ = nullptr;
154     host_ = nullptr;
155   }
156 
157  private:
158   friend class base::RefCountedThreadSafe<
159       ClientSideDetectionHost::ShouldClassifyUrlRequest>;
160 
161   // Enum used to keep stats about why the pre-classification check failed.
162   enum PreClassificationCheckResult {
163     OBSOLETE_NO_CLASSIFY_PROXY_FETCH = 0,
164     NO_CLASSIFY_PRIVATE_IP = 1,
165     NO_CLASSIFY_OFF_THE_RECORD = 2,
166     NO_CLASSIFY_MATCH_CSD_WHITELIST = 3,
167     NO_CLASSIFY_TOO_MANY_REPORTS = 4,
168     NO_CLASSIFY_UNSUPPORTED_MIME_TYPE = 5,
169     NO_CLASSIFY_NO_DATABASE_MANAGER = 6,
170     NO_CLASSIFY_KILLSWITCH = 7,
171     NO_CLASSIFY_CANCEL = 8,
172     NO_CLASSIFY_RESULT_FROM_CACHE = 9,
173     DEPRECATED_NO_CLASSIFY_NOT_HTTP_URL = 10,
174     NO_CLASSIFY_SCHEME_NOT_SUPPORTED = 11,
175     NO_CLASSIFY_WHITELISTED_BY_POLICY = 12,
176     CLASSIFY = 13,
177     NO_CLASSIFY_HAS_DELAYED_WARNING = 14,
178 
179     NO_CLASSIFY_MAX  // Always add new values before this one.
180   };
181 
182   // The destructor can be called either from the UI or the IO thread.
~ShouldClassifyUrlRequest()183   virtual ~ShouldClassifyUrlRequest() { }
184 
ShouldClassifyForPhishing() const185   bool ShouldClassifyForPhishing() const {
186     DCHECK_CURRENTLY_ON(BrowserThread::UI);
187     return !start_phishing_classification_cb_.is_null();
188   }
189 
DontClassifyForPhishing(PreClassificationCheckResult reason)190   void DontClassifyForPhishing(PreClassificationCheckResult reason) {
191     DCHECK_CURRENTLY_ON(BrowserThread::UI);
192     if (ShouldClassifyForPhishing()) {
193       // Track the first reason why we stopped classifying for phishing.
194       base::UmaHistogramEnumeration(
195           "SBClientPhishing.PreClassificationCheckResult", reason,
196           NO_CLASSIFY_MAX);
197       std::move(start_phishing_classification_cb_).Run(false);
198     }
199     start_phishing_classification_cb_.Reset();
200   }
201 
CheckSafeBrowsingDatabase(const GURL & url)202   void CheckSafeBrowsingDatabase(const GURL& url) {
203     DCHECK_CURRENTLY_ON(BrowserThread::IO);
204     PreClassificationCheckResult phishing_reason = NO_CLASSIFY_MAX;
205     if (!database_manager_.get()) {
206       // We cannot check the Safe Browsing whitelists so we stop here
207       // for safety.
208       OnWhitelistCheckDoneOnIO(url, NO_CLASSIFY_NO_DATABASE_MANAGER,
209                                /*match_whitelist=*/false);
210       return;
211     }
212 
213     // Query the CSD Whitelist asynchronously. We're already on the IO thread so
214     // can call AllowlistCheckerClient directly.
215     base::OnceCallback<void(bool)> result_callback =
216         base::BindOnce(&ClientSideDetectionHost::ShouldClassifyUrlRequest::
217                            OnWhitelistCheckDoneOnIO,
218                        this, url, phishing_reason);
219     AllowlistCheckerClient::StartCheckCsdWhitelist(database_manager_, url,
220                                                    std::move(result_callback));
221   }
222 
OnWhitelistCheckDoneOnIO(const GURL & url,PreClassificationCheckResult phishing_reason,bool match_whitelist)223   void OnWhitelistCheckDoneOnIO(const GURL& url,
224                                 PreClassificationCheckResult phishing_reason,
225                                 bool match_whitelist) {
226     DCHECK_CURRENTLY_ON(BrowserThread::IO);
227     // We don't want to call the classification callbacks from the IO
228     // thread so we simply pass the results of this method to CheckCache()
229     // which is called on the UI thread;
230     if (match_whitelist) {
231       phishing_reason = NO_CLASSIFY_MATCH_CSD_WHITELIST;
232     }
233     content::GetUIThreadTaskRunner({})->PostTask(
234         FROM_HERE, base::BindOnce(&ShouldClassifyUrlRequest::CheckCache, this,
235                                   phishing_reason));
236   }
237 
CheckCache(PreClassificationCheckResult phishing_reason)238   void CheckCache(PreClassificationCheckResult phishing_reason) {
239     DCHECK_CURRENTLY_ON(BrowserThread::UI);
240     if (phishing_reason != NO_CLASSIFY_MAX)
241       DontClassifyForPhishing(phishing_reason);
242     if (!ShouldClassifyForPhishing()) {
243       return;  // No point in doing anything else.
244     }
245     // If result is cached, we don't want to run classification again.
246     // In that case we're just trying to show the warning.
247     bool is_phishing;
248     if (csd_service_->GetValidCachedResult(url_, &is_phishing)) {
249       base::UmaHistogramBoolean("SBClientPhishing.RequestSatisfiedFromCache",
250                                 true);
251       // Since we are already on the UI thread, this is safe.
252       host_->MaybeShowPhishingWarning(/*is_from_cache=*/true, url_,
253                                       is_phishing);
254       DontClassifyForPhishing(NO_CLASSIFY_RESULT_FROM_CACHE);
255     }
256 
257     // We want to limit the number of requests, though we will ignore the
258     // limit for urls in the cache.  We don't want to start classifying
259     // too many pages as phishing, but for those that we already think are
260     // phishing we want to send a request to the server to give ourselves
261     // a chance to fix misclassifications.
262     if (csd_service_->IsInCache(url_)) {
263       base::UmaHistogramBoolean("SBClientPhishing.ReportLimitSkipped", true);
264     } else if (csd_service_->OverPhishingReportLimit()) {
265       DontClassifyForPhishing(NO_CLASSIFY_TOO_MANY_REPORTS);
266     }
267 
268     // Everything checks out, so start classification.
269     // |web_contents_| is safe to call as we will be destructed
270     // before it is.
271     if (ShouldClassifyForPhishing()) {
272       base::UmaHistogramEnumeration(
273           "SBClientPhishing.PreClassificationCheckResult", CLASSIFY,
274           NO_CLASSIFY_MAX);
275       std::move(start_phishing_classification_cb_).Run(true);
276       // Reset the callback to make sure ShouldClassifyForPhishing()
277       // returns false.
278       start_phishing_classification_cb_.Reset();
279     }
280   }
281 
282   GURL url_;
283   std::string mime_type_;
284   net::IPEndPoint remote_endpoint_;
285   WebContents* web_contents_;
286   ClientSideDetectionService* csd_service_;
287   // We keep a ref pointer here just to make sure the safe browsing
288   // database manager stays alive long enough.
289   scoped_refptr<SafeBrowsingDatabaseManager> database_manager_;
290   ClientSideDetectionHost* host_;
291 
292   ShouldClassifyUrlCallback start_phishing_classification_cb_;
293 
294   DISALLOW_COPY_AND_ASSIGN(ShouldClassifyUrlRequest);
295 };
296 
297 // static
Create(WebContents * tab)298 std::unique_ptr<ClientSideDetectionHost> ClientSideDetectionHost::Create(
299     WebContents* tab) {
300   return base::WrapUnique(new ClientSideDetectionHost(tab));
301 }
302 
ClientSideDetectionHost(WebContents * tab)303 ClientSideDetectionHost::ClientSideDetectionHost(WebContents* tab)
304     : content::WebContentsObserver(tab),
305       csd_service_(nullptr),
306       tab_(tab),
307       classification_request_(nullptr),
308       pageload_complete_(false),
309       tick_clock_(base::DefaultTickClock::GetInstance()) {
310   DCHECK(tab);
311   // Note: csd_service_ and sb_service will be nullptr here in testing.
312   csd_service_ = ClientSideDetectionServiceFactory::GetForProfile(
313       Profile::FromBrowserContext(tab->GetBrowserContext()));
314 
315   scoped_refptr<SafeBrowsingService> sb_service =
316       g_browser_process->safe_browsing_service();
317   if (sb_service.get()) {
318     ui_manager_ = sb_service->ui_manager();
319     database_manager_ = sb_service->database_manager();
320   }
321 }
322 
~ClientSideDetectionHost()323 ClientSideDetectionHost::~ClientSideDetectionHost() {
324   if (csd_service_)
325     csd_service_->RemoveClientSideDetectionHost(this);
326 }
327 
DidFinishNavigation(content::NavigationHandle * navigation_handle)328 void ClientSideDetectionHost::DidFinishNavigation(
329     content::NavigationHandle* navigation_handle) {
330   if (!navigation_handle->IsInMainFrame() || !navigation_handle->HasCommitted())
331     return;
332 
333   // TODO(noelutz): move this DCHECK to WebContents and fix all the unit tests
334   // that don't call this method on the UI thread.
335   // DCHECK_CURRENTLY_ON(BrowserThread::UI);
336   if (navigation_handle->IsSameDocument()) {
337     // If the navigation is within the same document, the user isn't really
338     // navigating away.  We don't need to cancel a pending callback or
339     // begin a new classification.
340     return;
341   }
342   // Cancel any pending classification request.
343   if (classification_request_.get()) {
344     classification_request_->Cancel();
345   }
346   // If we navigate away and there currently is a pending phishing
347   // report request we have to cancel it to make sure we don't display
348   // an interstitial for the wrong page.  Note that this won't cancel
349   // the server ping back but only cancel the showing of the
350   // interstitial.
351   weak_factory_.InvalidateWeakPtrs();
352 
353   if (!csd_service_) {
354     return;
355   }
356 
357   current_url_ = navigation_handle->GetURL();
358 
359   pageload_complete_ = false;
360 
361   // Check whether we can cassify the current URL for phishing.
362   classification_request_ = new ShouldClassifyUrlRequest(
363       navigation_handle,
364       base::BindOnce(&ClientSideDetectionHost::OnPhishingPreClassificationDone,
365                      weak_factory_.GetWeakPtr()),
366       web_contents(), csd_service_, database_manager_.get(), this);
367   classification_request_->Start();
368 }
369 
SendModelToRenderFrame()370 void ClientSideDetectionHost::SendModelToRenderFrame() {
371   DCHECK_CURRENTLY_ON(BrowserThread::UI);
372   if (!web_contents() || web_contents() != tab_ || !csd_service_)
373     return;
374 
375   for (content::RenderFrameHost* frame : web_contents()->GetAllFrames()) {
376     if (phishing_detector_)
377       phishing_detector_.reset();
378     frame->GetRemoteInterfaces()->GetInterface(
379         phishing_detector_.BindNewPipeAndPassReceiver());
380     phishing_detector_->SetPhishingModel(csd_service_->GetModelStr());
381   }
382 }
383 
WebContentsDestroyed()384 void ClientSideDetectionHost::WebContentsDestroyed() {
385   // Tell any pending classification request that it is being canceled.
386   if (classification_request_.get()) {
387     classification_request_->Cancel();
388   }
389   if (csd_service_)
390     csd_service_->RemoveClientSideDetectionHost(this);
391 }
392 
RenderFrameCreated(content::RenderFrameHost * render_frame_host)393 void ClientSideDetectionHost::RenderFrameCreated(
394     content::RenderFrameHost* render_frame_host) {
395   if (phishing_detector_)
396     phishing_detector_.reset();
397   render_frame_host->GetRemoteInterfaces()->GetInterface(
398       phishing_detector_.BindNewPipeAndPassReceiver());
399   phishing_detector_->SetPhishingModel(csd_service_->GetModelStr());
400 }
401 
OnPhishingPreClassificationDone(bool should_classify)402 void ClientSideDetectionHost::OnPhishingPreClassificationDone(
403     bool should_classify) {
404   DCHECK_CURRENTLY_ON(BrowserThread::UI);
405   if (should_classify) {
406     content::RenderFrameHost* rfh = web_contents()->GetMainFrame();
407     phishing_detector_.reset();
408     rfh->GetRemoteInterfaces()->GetInterface(
409         phishing_detector_.BindNewPipeAndPassReceiver());
410     phishing_detection_start_time_ = tick_clock_->NowTicks();
411     phishing_detector_->StartPhishingDetection(
412         current_url_,
413         base::BindOnce(&ClientSideDetectionHost::PhishingDetectionDone,
414                        weak_factory_.GetWeakPtr()));
415   }
416 }
417 
PhishingDetectionDone(mojom::PhishingDetectorResult result,const std::string & verdict_str)418 void ClientSideDetectionHost::PhishingDetectionDone(
419     mojom::PhishingDetectorResult result,
420     const std::string& verdict_str) {
421   DCHECK_CURRENTLY_ON(BrowserThread::UI);
422   // There is something seriously wrong if there is no service class but
423   // this method is called.  The renderer should not start phishing detection
424   // if there isn't any service class in the browser.
425   DCHECK(csd_service_);
426 
427   UmaHistogramMediumTimes(
428       "SBClientPhishing.PhishingDetectionDuration",
429       base::TimeTicks::Now() - phishing_detection_start_time_);
430   base::UmaHistogramEnumeration("SBClientPhishing.PhishingDetectorResult",
431                                 result);
432   if (result == mojom::PhishingDetectorResult::CLASSIFIER_NOT_READY) {
433     base::UmaHistogramEnumeration("SBClientPhishing.ClassifierNotReadyReason",
434                                   csd_service_->GetLastModelStatus());
435   }
436   if (result != mojom::PhishingDetectorResult::SUCCESS)
437     return;
438 
439   // We parse the protocol buffer here.  If we're unable to parse it we won't
440   // send the verdict further.
441   std::unique_ptr<ClientPhishingRequest> verdict(new ClientPhishingRequest);
442   if (csd_service_ &&
443       verdict->ParseFromString(verdict_str) &&
444       verdict->IsInitialized()) {
445     VLOG(2) << "Phishing classification score: " << verdict->client_score();
446     Profile* profile =
447         Profile::FromBrowserContext(web_contents()->GetBrowserContext());
448     if (!IsExtendedReportingEnabled(*profile->GetPrefs()) &&
449         !IsEnhancedProtectionEnabled(*profile->GetPrefs())) {
450       // These fields should only be set for SBER users.
451       verdict->clear_screenshot_digest();
452       verdict->clear_screenshot_phash();
453       verdict->clear_phash_dimension_size();
454     }
455 
456     base::UmaHistogramBoolean("SBClientPhishing.LocalModelDetectsPhishing",
457                               verdict->is_phishing());
458 
459     // We only send phishing verdict to the server if the verdict is phishing.
460     if (verdict->is_phishing()) {
461       ClientSideDetectionService::ClientReportPhishingRequestCallback callback =
462           base::BindOnce(&ClientSideDetectionHost::MaybeShowPhishingWarning,
463                          weak_factory_.GetWeakPtr(),
464                          /*is_from_cache=*/false);
465       Profile* profile =
466           Profile::FromBrowserContext(web_contents()->GetBrowserContext());
467       csd_service_->SendClientReportPhishingRequest(
468           std::move(verdict), IsExtendedReportingEnabled(*profile->GetPrefs()),
469           IsEnhancedProtectionEnabled(*profile->GetPrefs()),
470           std::move(callback));
471     }
472   }
473 }
474 
MaybeShowPhishingWarning(bool is_from_cache,GURL phishing_url,bool is_phishing)475 void ClientSideDetectionHost::MaybeShowPhishingWarning(bool is_from_cache,
476                                                        GURL phishing_url,
477                                                        bool is_phishing) {
478   DCHECK_CURRENTLY_ON(BrowserThread::UI);
479   if (is_from_cache) {
480     base::UmaHistogramBoolean("SBClientPhishing.CacheDetectsPhishing",
481                               is_phishing);
482   } else {
483     base::UmaHistogramBoolean("SBClientPhishing.ServerModelDetectsPhishing",
484                               is_phishing);
485   }
486 
487   if (is_phishing) {
488     DCHECK(web_contents());
489     if (ui_manager_.get()) {
490       security_interstitials::UnsafeResource resource;
491       resource.url = phishing_url;
492       resource.original_url = phishing_url;
493       resource.is_subresource = false;
494       resource.threat_type = SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING;
495       resource.threat_source =
496           safe_browsing::ThreatSource::CLIENT_SIDE_DETECTION;
497       resource.web_contents_getter =
498           security_interstitials::GetWebContentsGetter(
499               web_contents()->GetMainFrame()->GetProcess()->GetID(),
500               web_contents()->GetMainFrame()->GetRoutingID());
501       if (!ui_manager_->IsWhitelisted(resource)) {
502         // We need to stop any pending navigations, otherwise the interstitial
503         // might not get created properly.
504         web_contents()->GetController().DiscardNonCommittedEntries();
505       }
506       ui_manager_->DisplayBlockingPage(resource);
507     }
508     // If there is true phishing verdict, invalidate weakptr so that no longer
509     // consider the malware vedict.
510     weak_factory_.InvalidateWeakPtrs();
511   }
512 }
513 
set_client_side_detection_service(ClientSideDetectionService * service)514 void ClientSideDetectionHost::set_client_side_detection_service(
515     ClientSideDetectionService* service) {
516   csd_service_ = service;
517 }
518 
set_ui_manager(SafeBrowsingUIManager * ui_manager)519 void ClientSideDetectionHost::set_ui_manager(
520     SafeBrowsingUIManager* ui_manager) {
521   ui_manager_ = ui_manager;
522 }
523 
set_database_manager(SafeBrowsingDatabaseManager * database_manager)524 void ClientSideDetectionHost::set_database_manager(
525     SafeBrowsingDatabaseManager* database_manager) {
526   database_manager_ = database_manager;
527 }
528 
529 }  // namespace safe_browsing
530