• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_service.h"
6 
7 #include <algorithm>
8 
9 #include "base/bind.h"
10 #include "base/command_line.h"
11 #include "base/logging.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/metrics/histogram.h"
15 #include "base/metrics/sparse_histogram.h"
16 #include "base/prefs/pref_service.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string_util.h"
19 #include "base/time/time.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/common/chrome_switches.h"
23 #include "chrome/common/pref_names.h"
24 #include "chrome/common/safe_browsing/client_model.pb.h"
25 #include "chrome/common/safe_browsing/csd.pb.h"
26 #include "chrome/common/safe_browsing/safebrowsing_messages.h"
27 #include "content/public/browser/browser_thread.h"
28 #include "content/public/browser/notification_service.h"
29 #include "content/public/browser/notification_types.h"
30 #include "content/public/browser/render_process_host.h"
31 #include "crypto/sha2.h"
32 #include "google_apis/google_api_keys.h"
33 #include "net/base/escape.h"
34 #include "net/base/load_flags.h"
35 #include "net/base/net_util.h"
36 #include "net/http/http_response_headers.h"
37 #include "net/http/http_status_code.h"
38 #include "net/url_request/url_fetcher.h"
39 #include "net/url_request/url_request_context_getter.h"
40 #include "net/url_request/url_request_status.h"
41 #include "url/gurl.h"
42 
43 using content::BrowserThread;
44 
45 namespace safe_browsing {
46 
47 namespace {
48 
49   // malware report type for UMA histogram counting.
50   enum MalwareReportTypes {
51     REPORT_SENT,
52     REPORT_HIT_LIMIT,
53     REPORT_FAILED_SERIALIZATION,
54 
55     // Always at the end
56     REPORT_RESULT_MAX
57   };
58 
UpdateEnumUMAHistogram(MalwareReportTypes report_type)59   void UpdateEnumUMAHistogram(MalwareReportTypes report_type) {
60     DCHECK(report_type >= 0 && report_type < REPORT_RESULT_MAX);
61     UMA_HISTOGRAM_ENUMERATION("SBClientMalware.SentReports",
62                               report_type, REPORT_RESULT_MAX);
63   }
64 
65 }  // namespace
66 
67 const size_t ClientSideDetectionService::kMaxModelSizeBytes = 90 * 1024;
68 const int ClientSideDetectionService::kMaxReportsPerInterval = 3;
69 // TODO(noelutz): once we know this mechanism works as intended we should fetch
70 // the model much more frequently.  E.g., every 5 minutes or so.
71 const int ClientSideDetectionService::kClientModelFetchIntervalMs = 3600 * 1000;
72 const int ClientSideDetectionService::kInitialClientModelFetchDelayMs = 10000;
73 
74 const int ClientSideDetectionService::kReportsIntervalDays = 1;
75 const int ClientSideDetectionService::kNegativeCacheIntervalDays = 1;
76 const int ClientSideDetectionService::kPositiveCacheIntervalMinutes = 30;
77 
78 const char ClientSideDetectionService::kClientReportPhishingUrl[] =
79     "https://sb-ssl.google.com/safebrowsing/clientreport/phishing";
80 const char ClientSideDetectionService::kClientReportMalwareUrl[] =
81     "https://sb-ssl.google.com/safebrowsing/clientreport/malware-check";
82 const char ClientSideDetectionService::kClientModelUrl[] =
83     "https://ssl.gstatic.com/safebrowsing/csd/client_model_v5.pb";
84 
85 struct ClientSideDetectionService::ClientReportInfo {
86   ClientReportPhishingRequestCallback callback;
87   GURL phishing_url;
88 };
89 
90 struct ClientSideDetectionService::ClientMalwareReportInfo {
91   ClientReportMalwareRequestCallback callback;
92   // This is the original landing url, may not be the malware url.
93   GURL original_url;
94 };
95 
CacheState(bool phish,base::Time time)96 ClientSideDetectionService::CacheState::CacheState(bool phish, base::Time time)
97     : is_phishing(phish),
98       timestamp(time) {}
99 
ClientSideDetectionService(net::URLRequestContextGetter * request_context_getter)100 ClientSideDetectionService::ClientSideDetectionService(
101     net::URLRequestContextGetter* request_context_getter)
102     : enabled_(false),
103       weak_factory_(this),
104       request_context_getter_(request_context_getter) {
105   registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
106                  content::NotificationService::AllBrowserContextsAndSources());
107 }
108 
~ClientSideDetectionService()109 ClientSideDetectionService::~ClientSideDetectionService() {
110   weak_factory_.InvalidateWeakPtrs();
111   STLDeleteContainerPairPointers(client_phishing_reports_.begin(),
112                                  client_phishing_reports_.end());
113   client_phishing_reports_.clear();
114   STLDeleteContainerPairPointers(client_malware_reports_.begin(),
115                                  client_malware_reports_.end());
116   client_malware_reports_.clear();
117 }
118 
119 // static
Create(net::URLRequestContextGetter * request_context_getter)120 ClientSideDetectionService* ClientSideDetectionService::Create(
121     net::URLRequestContextGetter* request_context_getter) {
122   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
123   return new ClientSideDetectionService(request_context_getter);
124 }
125 
SetEnabledAndRefreshState(bool enabled)126 void ClientSideDetectionService::SetEnabledAndRefreshState(bool enabled) {
127   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
128   SendModelToRenderers();  // always refresh the renderer state
129   if (enabled == enabled_)
130     return;
131   enabled_ = enabled;
132   if (enabled_) {
133     // Refresh the model when the service is enabled.  This can happen when the
134     // preference is toggled, or early during startup if the preference is
135     // already enabled. In a lot of cases the model will be in the cache so it
136     // won't actually be fetched from the network.
137     // We delay the first model fetch to avoid slowing down browser startup.
138     ScheduleFetchModel(kInitialClientModelFetchDelayMs);
139   } else {
140     // Cancel pending requests.
141     model_fetcher_.reset();
142     // Invoke pending callbacks with a false verdict.
143     for (std::map<const net::URLFetcher*, ClientReportInfo*>::iterator it =
144              client_phishing_reports_.begin();
145          it != client_phishing_reports_.end(); ++it) {
146       ClientReportInfo* info = it->second;
147       if (!info->callback.is_null())
148         info->callback.Run(info->phishing_url, false);
149     }
150     STLDeleteContainerPairPointers(client_phishing_reports_.begin(),
151                                    client_phishing_reports_.end());
152     client_phishing_reports_.clear();
153     for (std::map<const net::URLFetcher*, ClientMalwareReportInfo*>::iterator it
154              = client_malware_reports_.begin();
155          it != client_malware_reports_.end(); ++it) {
156       ClientMalwareReportInfo* info = it->second;
157       if (!info->callback.is_null())
158         info->callback.Run(info->original_url, info->original_url, false);
159     }
160     STLDeleteContainerPairPointers(client_malware_reports_.begin(),
161                                    client_malware_reports_.end());
162     client_malware_reports_.clear();
163     cache_.clear();
164   }
165 }
166 
SendClientReportPhishingRequest(ClientPhishingRequest * verdict,const ClientReportPhishingRequestCallback & callback)167 void ClientSideDetectionService::SendClientReportPhishingRequest(
168     ClientPhishingRequest* verdict,
169     const ClientReportPhishingRequestCallback& callback) {
170   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
171   base::MessageLoop::current()->PostTask(
172       FROM_HERE,
173       base::Bind(&ClientSideDetectionService::StartClientReportPhishingRequest,
174                  weak_factory_.GetWeakPtr(), verdict, callback));
175 }
176 
SendClientReportMalwareRequest(ClientMalwareRequest * verdict,const ClientReportMalwareRequestCallback & callback)177 void ClientSideDetectionService::SendClientReportMalwareRequest(
178     ClientMalwareRequest* verdict,
179     const ClientReportMalwareRequestCallback& callback) {
180   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
181   base::MessageLoop::current()->PostTask(
182       FROM_HERE,
183       base::Bind(&ClientSideDetectionService::StartClientReportMalwareRequest,
184                  weak_factory_.GetWeakPtr(), verdict, callback));
185 }
186 
IsPrivateIPAddress(const std::string & ip_address) const187 bool ClientSideDetectionService::IsPrivateIPAddress(
188     const std::string& ip_address) const {
189   net::IPAddressNumber ip_number;
190   if (!net::ParseIPLiteralToNumber(ip_address, &ip_number)) {
191     VLOG(2) << "Unable to parse IP address: '" << ip_address << "'";
192     // Err on the side of safety and assume this might be private.
193     return true;
194   }
195 
196   return net::IsIPAddressReserved(ip_number);
197 }
198 
OnURLFetchComplete(const net::URLFetcher * source)199 void ClientSideDetectionService::OnURLFetchComplete(
200     const net::URLFetcher* source) {
201   std::string data;
202   source->GetResponseAsString(&data);
203   if (source == model_fetcher_.get()) {
204     HandleModelResponse(
205         source, source->GetURL(), source->GetStatus(),
206         source->GetResponseCode(), source->GetCookies(), data);
207   } else if (client_phishing_reports_.find(source) !=
208              client_phishing_reports_.end()) {
209     HandlePhishingVerdict(
210         source, source->GetURL(), source->GetStatus(),
211         source->GetResponseCode(), source->GetCookies(), data);
212   } else if (client_malware_reports_.find(source) !=
213              client_malware_reports_.end()) {
214     HandleMalwareVerdict(
215         source, source->GetURL(), source->GetStatus(),
216         source->GetResponseCode(), source->GetCookies(), data);
217   } else {
218     NOTREACHED();
219   }
220 }
221 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)222 void ClientSideDetectionService::Observe(
223     int type,
224     const content::NotificationSource& source,
225     const content::NotificationDetails& details) {
226   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
227   DCHECK(type == content::NOTIFICATION_RENDERER_PROCESS_CREATED);
228   if (!model_.get()) {
229     // Model might not be ready or maybe there was an error.
230     return;
231   }
232   SendModelToProcess(
233       content::Source<content::RenderProcessHost>(source).ptr());
234 }
235 
SendModelToProcess(content::RenderProcessHost * process)236 void ClientSideDetectionService::SendModelToProcess(
237     content::RenderProcessHost* process) {
238   // The ClientSideDetectionService is enabled if _any_ active profile has
239   // SafeBrowsing turned on.  Here we check the profile for each renderer
240   // process and only send the model to those that have SafeBrowsing enabled.
241   Profile* profile = Profile::FromBrowserContext(process->GetBrowserContext());
242   std::string model;
243   if (profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
244     VLOG(2) << "Sending phishing model to RenderProcessHost @" << process;
245     model = model_str_;
246   } else {
247     VLOG(2) << "Disabling client-side phishing detection for "
248             << "RenderProcessHost @" << process;
249   }
250   process->Send(new SafeBrowsingMsg_SetPhishingModel(model));
251 }
252 
SendModelToRenderers()253 void ClientSideDetectionService::SendModelToRenderers() {
254   for (content::RenderProcessHost::iterator i(
255           content::RenderProcessHost::AllHostsIterator());
256        !i.IsAtEnd(); i.Advance()) {
257     SendModelToProcess(i.GetCurrentValue());
258   }
259 }
260 
ScheduleFetchModel(int64 delay_ms)261 void ClientSideDetectionService::ScheduleFetchModel(int64 delay_ms) {
262   if (CommandLine::ForCurrentProcess()->HasSwitch(
263       switches::kSbDisableAutoUpdate))
264     return;
265   base::MessageLoop::current()->PostDelayedTask(
266       FROM_HERE,
267       base::Bind(&ClientSideDetectionService::StartFetchModel,
268                  weak_factory_.GetWeakPtr()),
269       base::TimeDelta::FromMilliseconds(delay_ms));
270 }
271 
StartFetchModel()272 void ClientSideDetectionService::StartFetchModel() {
273   if (enabled_) {
274     // Start fetching the model either from the cache or possibly from the
275     // network if the model isn't in the cache.
276     model_fetcher_.reset(net::URLFetcher::Create(
277         0 /* ID used for testing */, GURL(kClientModelUrl),
278         net::URLFetcher::GET, this));
279     model_fetcher_->SetRequestContext(request_context_getter_.get());
280     model_fetcher_->Start();
281   }
282 }
283 
EndFetchModel(ClientModelStatus status)284 void ClientSideDetectionService::EndFetchModel(ClientModelStatus status) {
285   UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.ClientModelStatus",
286                             status,
287                             MODEL_STATUS_MAX);
288   if (status == MODEL_SUCCESS) {
289     SetBadSubnets(*model_, &bad_subnets_);
290     SendModelToRenderers();
291   }
292   int delay_ms = kClientModelFetchIntervalMs;
293   // If the most recently fetched model had a valid max-age and the model was
294   // valid we're scheduling the next model update for after the max-age expired.
295   if (model_max_age_.get() &&
296       (status == MODEL_SUCCESS || status == MODEL_NOT_CHANGED)) {
297     // We're adding 60s of additional delay to make sure we're past
298     // the model's age.
299     *model_max_age_ += base::TimeDelta::FromMinutes(1);
300     delay_ms = model_max_age_->InMilliseconds();
301   }
302   model_max_age_.reset();
303 
304   // Schedule the next model reload.
305   ScheduleFetchModel(delay_ms);
306 }
307 
StartClientReportPhishingRequest(ClientPhishingRequest * verdict,const ClientReportPhishingRequestCallback & callback)308 void ClientSideDetectionService::StartClientReportPhishingRequest(
309     ClientPhishingRequest* verdict,
310     const ClientReportPhishingRequestCallback& callback) {
311   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
312   scoped_ptr<ClientPhishingRequest> request(verdict);
313 
314   if (!enabled_) {
315     if (!callback.is_null())
316       callback.Run(GURL(request->url()), false);
317     return;
318   }
319 
320   std::string request_data;
321   if (!request->SerializeToString(&request_data)) {
322     UMA_HISTOGRAM_COUNTS("SBClientPhishing.RequestNotSerialized", 1);
323     VLOG(1) << "Unable to serialize the CSD request. Proto file changed?";
324     if (!callback.is_null())
325       callback.Run(GURL(request->url()), false);
326     return;
327   }
328 
329   net::URLFetcher* fetcher = net::URLFetcher::Create(
330       0 /* ID used for testing */,
331       GetClientReportUrl(kClientReportPhishingUrl),
332       net::URLFetcher::POST, this);
333 
334   // Remember which callback and URL correspond to the current fetcher object.
335   ClientReportInfo* info = new ClientReportInfo;
336   info->callback = callback;
337   info->phishing_url = GURL(request->url());
338   client_phishing_reports_[fetcher] = info;
339 
340   fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE);
341   fetcher->SetRequestContext(request_context_getter_.get());
342   fetcher->SetUploadData("application/octet-stream", request_data);
343   fetcher->Start();
344 
345   // Record that we made a request
346   phishing_report_times_.push(base::Time::Now());
347 }
348 
StartClientReportMalwareRequest(ClientMalwareRequest * verdict,const ClientReportMalwareRequestCallback & callback)349 void ClientSideDetectionService::StartClientReportMalwareRequest(
350     ClientMalwareRequest* verdict,
351     const ClientReportMalwareRequestCallback& callback) {
352   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
353   scoped_ptr<ClientMalwareRequest> request(verdict);
354 
355   if (!enabled_) {
356     if (!callback.is_null())
357       callback.Run(GURL(request->url()), GURL(request->url()), false);
358     return;
359   }
360 
361   std::string request_data;
362   if (!request->SerializeToString(&request_data)) {
363     UpdateEnumUMAHistogram(REPORT_FAILED_SERIALIZATION);
364     DVLOG(1) << "Unable to serialize the CSD request. Proto file changed?";
365     if (!callback.is_null())
366       callback.Run(GURL(request->url()), GURL(request->url()), false);
367     return;
368   }
369 
370   net::URLFetcher* fetcher = net::URLFetcher::Create(
371       0 /* ID used for testing */,
372       GetClientReportUrl(kClientReportMalwareUrl),
373       net::URLFetcher::POST, this);
374 
375   // Remember which callback and URL correspond to the current fetcher object.
376   ClientMalwareReportInfo* info = new ClientMalwareReportInfo;
377   info->callback = callback;
378   info->original_url = GURL(request->url());
379   client_malware_reports_[fetcher] = info;
380 
381   fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE);
382   fetcher->SetRequestContext(request_context_getter_.get());
383   fetcher->SetUploadData("application/octet-stream", request_data);
384   fetcher->Start();
385 
386   UMA_HISTOGRAM_ENUMERATION("SBClientMalware.SentReports",
387                             REPORT_SENT, REPORT_RESULT_MAX);
388 
389   UMA_HISTOGRAM_COUNTS("SBClientMalware.IPBlacklistRequestPayloadSize",
390                        request_data.size());
391 
392   // Record that we made a malware request
393   malware_report_times_.push(base::Time::Now());
394 }
395 
HandleModelResponse(const net::URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const net::ResponseCookies & cookies,const std::string & data)396 void ClientSideDetectionService::HandleModelResponse(
397     const net::URLFetcher* source,
398     const GURL& url,
399     const net::URLRequestStatus& status,
400     int response_code,
401     const net::ResponseCookies& cookies,
402     const std::string& data) {
403   base::TimeDelta max_age;
404   if (status.is_success() && net::HTTP_OK == response_code &&
405       source->GetResponseHeaders() &&
406       source->GetResponseHeaders()->GetMaxAgeValue(&max_age)) {
407     model_max_age_.reset(new base::TimeDelta(max_age));
408   }
409   scoped_ptr<ClientSideModel> model(new ClientSideModel());
410   ClientModelStatus model_status;
411   if (!status.is_success() || net::HTTP_OK != response_code) {
412     model_status = MODEL_FETCH_FAILED;
413   } else if (data.empty()) {
414     model_status = MODEL_EMPTY;
415   } else if (data.size() > kMaxModelSizeBytes) {
416     model_status = MODEL_TOO_LARGE;
417   } else if (!model->ParseFromString(data)) {
418     model_status = MODEL_PARSE_ERROR;
419   } else if (!model->IsInitialized() || !model->has_version()) {
420     model_status = MODEL_MISSING_FIELDS;
421   } else if (!ModelHasValidHashIds(*model)) {
422     model_status = MODEL_BAD_HASH_IDS;
423   } else if (model->version() < 0 ||
424              (model_.get() && model->version() < model_->version())) {
425     model_status = MODEL_INVALID_VERSION_NUMBER;
426   } else if (model_.get() && model->version() == model_->version()) {
427     model_status = MODEL_NOT_CHANGED;
428   } else {
429     // The model is valid => replace the existing model with the new one.
430     model_str_.assign(data);
431     model_.swap(model);
432     model_status = MODEL_SUCCESS;
433   }
434   EndFetchModel(model_status);
435 }
436 
HandlePhishingVerdict(const net::URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const net::ResponseCookies & cookies,const std::string & data)437 void ClientSideDetectionService::HandlePhishingVerdict(
438     const net::URLFetcher* source,
439     const GURL& url,
440     const net::URLRequestStatus& status,
441     int response_code,
442     const net::ResponseCookies& cookies,
443     const std::string& data) {
444   ClientPhishingResponse response;
445   scoped_ptr<ClientReportInfo> info(client_phishing_reports_[source]);
446   bool is_phishing = false;
447   if (status.is_success() && net::HTTP_OK == response_code &&
448       response.ParseFromString(data)) {
449     // Cache response, possibly flushing an old one.
450     cache_[info->phishing_url] =
451         make_linked_ptr(new CacheState(response.phishy(), base::Time::Now()));
452     is_phishing = response.phishy();
453   } else {
454     DLOG(ERROR) << "Unable to get the server verdict for URL: "
455                 << info->phishing_url << " status: " << status.status() << " "
456                 << "response_code:" << response_code;
457   }
458   if (!info->callback.is_null())
459     info->callback.Run(info->phishing_url, is_phishing);
460   client_phishing_reports_.erase(source);
461   delete source;
462 }
463 
HandleMalwareVerdict(const net::URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const net::ResponseCookies & cookies,const std::string & data)464 void ClientSideDetectionService::HandleMalwareVerdict(
465     const net::URLFetcher* source,
466     const GURL& url,
467     const net::URLRequestStatus& status,
468     int response_code,
469     const net::ResponseCookies& cookies,
470     const std::string& data) {
471   if (status.is_success()) {
472     UMA_HISTOGRAM_SPARSE_SLOWLY(
473         "SBClientMalware.IPBlacklistRequestResponseCode", response_code);
474   }
475   // status error is negative, so we put - in front of it.
476   UMA_HISTOGRAM_SPARSE_SLOWLY(
477       "SBClientMalware.IPBlacklistRequestNetError", -status.error());
478 
479   ClientMalwareResponse response;
480   scoped_ptr<ClientMalwareReportInfo> info(client_malware_reports_[source]);
481   bool should_blacklist = false;
482   if (status.is_success() && net::HTTP_OK == response_code &&
483       response.ParseFromString(data)) {
484     should_blacklist = response.blacklist();
485   } else {
486     DLOG(ERROR) << "Unable to get the server verdict for URL: "
487                 << info->original_url << " status: " << status.status() << " "
488                 << "response_code:" << response_code;
489   }
490 
491   if (!info->callback.is_null()) {
492     if (response.has_bad_url())
493       info->callback.Run(info->original_url, GURL(response.bad_url()),
494                          should_blacklist);
495     else
496       info->callback.Run(info->original_url, info->original_url, false);
497   }
498 
499   client_malware_reports_.erase(source);
500   delete source;
501 }
502 
IsInCache(const GURL & url)503 bool ClientSideDetectionService::IsInCache(const GURL& url) {
504   UpdateCache();
505 
506   return cache_.find(url) != cache_.end();
507 }
508 
GetValidCachedResult(const GURL & url,bool * is_phishing)509 bool ClientSideDetectionService::GetValidCachedResult(const GURL& url,
510                                                       bool* is_phishing) {
511   UpdateCache();
512 
513   PhishingCache::iterator it = cache_.find(url);
514   if (it == cache_.end()) {
515     return false;
516   }
517 
518   // We still need to check if the result is valid.
519   const CacheState& cache_state = *it->second;
520   if (cache_state.is_phishing ?
521       cache_state.timestamp > base::Time::Now() -
522           base::TimeDelta::FromMinutes(kPositiveCacheIntervalMinutes) :
523       cache_state.timestamp > base::Time::Now() -
524           base::TimeDelta::FromDays(kNegativeCacheIntervalDays)) {
525     *is_phishing = cache_state.is_phishing;
526     return true;
527   }
528   return false;
529 }
530 
UpdateCache()531 void ClientSideDetectionService::UpdateCache() {
532   // Since we limit the number of requests but allow pass-through for cache
533   // refreshes, we don't want to remove elements from the cache if they
534   // could be used for this purpose even if we will not use the entry to
535   // satisfy the request from the cache.
536   base::TimeDelta positive_cache_interval =
537       std::max(base::TimeDelta::FromMinutes(kPositiveCacheIntervalMinutes),
538                base::TimeDelta::FromDays(kReportsIntervalDays));
539   base::TimeDelta negative_cache_interval =
540       std::max(base::TimeDelta::FromDays(kNegativeCacheIntervalDays),
541                base::TimeDelta::FromDays(kReportsIntervalDays));
542 
543   // Remove elements from the cache that will no longer be used.
544   for (PhishingCache::iterator it = cache_.begin(); it != cache_.end();) {
545     const CacheState& cache_state = *it->second;
546     if (cache_state.is_phishing ?
547         cache_state.timestamp > base::Time::Now() - positive_cache_interval :
548         cache_state.timestamp > base::Time::Now() - negative_cache_interval) {
549       ++it;
550     } else {
551       cache_.erase(it++);
552     }
553   }
554 }
555 
OverMalwareReportLimit()556 bool ClientSideDetectionService::OverMalwareReportLimit() {
557   return GetMalwareNumReports() > kMaxReportsPerInterval;
558 }
559 
OverPhishingReportLimit()560 bool ClientSideDetectionService::OverPhishingReportLimit() {
561   return GetPhishingNumReports() > kMaxReportsPerInterval;
562 }
563 
GetMalwareNumReports()564 int ClientSideDetectionService::GetMalwareNumReports() {
565   return GetNumReports(&malware_report_times_);
566 }
567 
GetPhishingNumReports()568 int ClientSideDetectionService::GetPhishingNumReports() {
569   return GetNumReports(&phishing_report_times_);
570 }
571 
GetNumReports(std::queue<base::Time> * report_times)572 int ClientSideDetectionService::GetNumReports(
573     std::queue<base::Time>* report_times) {
574   base::Time cutoff =
575       base::Time::Now() - base::TimeDelta::FromDays(kReportsIntervalDays);
576 
577   // Erase items older than cutoff because we will never care about them again.
578   while (!report_times->empty() &&
579          report_times->front() < cutoff) {
580     report_times->pop();
581   }
582 
583   // Return the number of elements that are above the cutoff.
584   return report_times->size();
585 }
586 
587 // static
SetBadSubnets(const ClientSideModel & model,BadSubnetMap * bad_subnets)588 void ClientSideDetectionService::SetBadSubnets(const ClientSideModel& model,
589                                                BadSubnetMap* bad_subnets) {
590   bad_subnets->clear();
591   for (int i = 0; i < model.bad_subnet_size(); ++i) {
592     int size = model.bad_subnet(i).size();
593     if (size < 0 || size > static_cast<int>(net::kIPv6AddressSize) * 8) {
594       DLOG(ERROR) << "Invalid bad subnet size: " << size;
595       continue;
596     }
597     if (model.bad_subnet(i).prefix().size() != crypto::kSHA256Length) {
598       DLOG(ERROR) << "Invalid bad subnet prefix length: "
599                   << model.bad_subnet(i).prefix().size();
600       continue;
601     }
602     // We precompute the mask for the given subnet size to speed up lookups.
603     // Basically we need to create a 16B long string which has the highest
604     // |size| bits sets to one.
605     std::string mask(net::kIPv6AddressSize, '\x00');
606     mask.replace(0, size / 8, size / 8, '\xFF');
607     if (size % 8) {
608       mask[size / 8] = 0xFF << (8 - (size % 8));
609     }
610     (*bad_subnets)[mask].insert(model.bad_subnet(i).prefix());
611   }
612 }
613 
614 // static
ModelHasValidHashIds(const ClientSideModel & model)615 bool ClientSideDetectionService::ModelHasValidHashIds(
616     const ClientSideModel& model) {
617   const int max_index = model.hashes_size() - 1;
618   for (int i = 0; i < model.rule_size(); ++i) {
619     for (int j = 0; j < model.rule(i).feature_size(); ++j) {
620       if (model.rule(i).feature(j) < 0 ||
621           model.rule(i).feature(j) > max_index) {
622         return false;
623       }
624     }
625   }
626   for (int i = 0; i < model.page_term_size(); ++i) {
627     if (model.page_term(i) < 0 || model.page_term(i) > max_index) {
628       return false;
629     }
630   }
631   return true;
632 }
633 
634 // static
GetClientReportUrl(const std::string & report_url)635 GURL ClientSideDetectionService::GetClientReportUrl(
636     const std::string& report_url) {
637   GURL url(report_url);
638   std::string api_key = google_apis::GetAPIKey();
639   if (!api_key.empty())
640     url = url.Resolve("?key=" + net::EscapeQueryParamValue(api_key, true));
641 
642   return url;
643 }
644 }  // namespace safe_browsing
645