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
43using content::BrowserThread;
44
45namespace safe_browsing {
46
47namespace {
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
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
67const size_t ClientSideDetectionService::kMaxModelSizeBytes = 90 * 1024;
68const 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.
71const int ClientSideDetectionService::kClientModelFetchIntervalMs = 3600 * 1000;
72const int ClientSideDetectionService::kInitialClientModelFetchDelayMs = 10000;
73
74const int ClientSideDetectionService::kReportsIntervalDays = 1;
75const int ClientSideDetectionService::kNegativeCacheIntervalDays = 1;
76const int ClientSideDetectionService::kPositiveCacheIntervalMinutes = 30;
77
78const char ClientSideDetectionService::kClientReportPhishingUrl[] =
79    "https://sb-ssl.google.com/safebrowsing/clientreport/phishing";
80const char ClientSideDetectionService::kClientReportMalwareUrl[] =
81    "https://sb-ssl.google.com/safebrowsing/clientreport/malware-check";
82const char ClientSideDetectionService::kClientModelUrl[] =
83    "https://ssl.gstatic.com/safebrowsing/csd/client_model_v5.pb";
84
85struct ClientSideDetectionService::ClientReportInfo {
86  ClientReportPhishingRequestCallback callback;
87  GURL phishing_url;
88};
89
90struct ClientSideDetectionService::ClientMalwareReportInfo {
91  ClientReportMalwareRequestCallback callback;
92  // This is the original landing url, may not be the malware url.
93  GURL original_url;
94};
95
96ClientSideDetectionService::CacheState::CacheState(bool phish, base::Time time)
97    : is_phishing(phish),
98      timestamp(time) {}
99
100ClientSideDetectionService::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
109ClientSideDetectionService::~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
120ClientSideDetectionService* ClientSideDetectionService::Create(
121    net::URLRequestContextGetter* request_context_getter) {
122  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
123  return new ClientSideDetectionService(request_context_getter);
124}
125
126void 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
167void 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
177void 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
187bool 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
199void 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
222void 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
236void 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
253void ClientSideDetectionService::SendModelToRenderers() {
254  for (content::RenderProcessHost::iterator i(
255          content::RenderProcessHost::AllHostsIterator());
256       !i.IsAtEnd(); i.Advance()) {
257    SendModelToProcess(i.GetCurrentValue());
258  }
259}
260
261void 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
272void 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
284void 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
308void 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
349void 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
396void 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
437void 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
464void 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
503bool ClientSideDetectionService::IsInCache(const GURL& url) {
504  UpdateCache();
505
506  return cache_.find(url) != cache_.end();
507}
508
509bool 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
531void 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
556bool ClientSideDetectionService::OverMalwareReportLimit() {
557  return GetMalwareNumReports() > kMaxReportsPerInterval;
558}
559
560bool ClientSideDetectionService::OverPhishingReportLimit() {
561  return GetPhishingNumReports() > kMaxReportsPerInterval;
562}
563
564int ClientSideDetectionService::GetMalwareNumReports() {
565  return GetNumReports(&malware_report_times_);
566}
567
568int ClientSideDetectionService::GetPhishingNumReports() {
569  return GetNumReports(&phishing_report_times_);
570}
571
572int 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
588void 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
615bool 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
635GURL 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