Add domain request detection to incident reporting service.
[chromium-blink-merge.git] / chrome / browser / safe_browsing / client_side_detection_service.cc
blob09f44b96351a0713f5c357f503b983995b2fca6f
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.
5 #include "chrome/browser/safe_browsing/client_side_detection_service.h"
7 #include <algorithm>
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"
43 using content::BrowserThread;
45 namespace safe_browsing {
47 namespace {
49 // malware report type for UMA histogram counting.
50 enum MalwareReportTypes {
51 REPORT_SENT,
52 REPORT_HIT_LIMIT,
53 REPORT_FAILED_SERIALIZATION,
55 // Always at the end
56 REPORT_RESULT_MAX
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);
65 } // namespace
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;
74 const int ClientSideDetectionService::kReportsIntervalDays = 1;
75 const int ClientSideDetectionService::kNegativeCacheIntervalDays = 1;
76 const int ClientSideDetectionService::kPositiveCacheIntervalMinutes = 30;
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";
85 struct ClientSideDetectionService::ClientReportInfo {
86 ClientReportPhishingRequestCallback callback;
87 GURL phishing_url;
90 struct ClientSideDetectionService::ClientMalwareReportInfo {
91 ClientReportMalwareRequestCallback callback;
92 // This is the original landing url, may not be the malware url.
93 GURL original_url;
96 ClientSideDetectionService::CacheState::CacheState(bool phish, base::Time time)
97 : is_phishing(phish),
98 timestamp(time) {}
100 ClientSideDetectionService::ClientSideDetectionService(
101 net::URLRequestContextGetter* request_context_getter)
102 : enabled_(false),
103 request_context_getter_(request_context_getter),
104 weak_factory_(this) {
105 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
106 content::NotificationService::AllBrowserContextsAndSources());
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();
119 // static
120 ClientSideDetectionService* ClientSideDetectionService::Create(
121 net::URLRequestContextGetter* request_context_getter) {
122 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
123 return new ClientSideDetectionService(request_context_getter);
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);
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);
160 STLDeleteContainerPairPointers(client_malware_reports_.begin(),
161 client_malware_reports_.end());
162 client_malware_reports_.clear();
163 cache_.clear();
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));
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));
187 bool ClientSideDetectionService::IsPrivateIPAddress(
188 const std::string& ip_address) const {
189 net::IPAddressNumber ip_number;
190 if (!net::ParseIPLiteralToNumber(ip_address, &ip_number)) {
191 DVLOG(2) << "Unable to parse IP address: '" << ip_address << "'";
192 // Err on the side of safety and assume this might be private.
193 return true;
196 return net::IsIPAddressReserved(ip_number);
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();
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;
232 SendModelToProcess(
233 content::Source<content::RenderProcessHost>(source).ptr());
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 DVLOG(2) << "Sending phishing model to RenderProcessHost @" << process;
245 model = model_str_;
246 } else {
247 DVLOG(2) << "Disabling client-side phishing detection for "
248 << "RenderProcessHost @" << process;
250 process->Send(new SafeBrowsingMsg_SetPhishingModel(model));
253 void ClientSideDetectionService::SendModelToRenderers() {
254 for (content::RenderProcessHost::iterator i(
255 content::RenderProcessHost::AllHostsIterator());
256 !i.IsAtEnd(); i.Advance()) {
257 SendModelToProcess(i.GetCurrentValue());
261 void ClientSideDetectionService::ScheduleFetchModel(int64 delay_ms) {
262 if (base::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));
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();
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();
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();
302 model_max_age_.reset();
304 // Schedule the next model reload.
305 ScheduleFetchModel(delay_ms);
308 void ClientSideDetectionService::StartClientReportPhishingRequest(
309 ClientPhishingRequest* verdict,
310 const ClientReportPhishingRequestCallback& callback) {
311 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
312 scoped_ptr<ClientPhishingRequest> request(verdict);
314 if (!enabled_) {
315 if (!callback.is_null())
316 callback.Run(GURL(request->url()), false);
317 return;
320 std::string request_data;
321 if (!request->SerializeToString(&request_data)) {
322 UMA_HISTOGRAM_COUNTS("SBClientPhishing.RequestNotSerialized", 1);
323 DVLOG(1) << "Unable to serialize the CSD request. Proto file changed?";
324 if (!callback.is_null())
325 callback.Run(GURL(request->url()), false);
326 return;
329 net::URLFetcher* fetcher = net::URLFetcher::Create(
330 0 /* ID used for testing */,
331 GetClientReportUrl(kClientReportPhishingUrl),
332 net::URLFetcher::POST, this);
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;
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();
345 // Record that we made a request
346 phishing_report_times_.push(base::Time::Now());
349 void ClientSideDetectionService::StartClientReportMalwareRequest(
350 ClientMalwareRequest* verdict,
351 const ClientReportMalwareRequestCallback& callback) {
352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
353 scoped_ptr<ClientMalwareRequest> request(verdict);
355 if (!enabled_) {
356 if (!callback.is_null())
357 callback.Run(GURL(request->url()), GURL(request->url()), false);
358 return;
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;
370 net::URLFetcher* fetcher = net::URLFetcher::Create(
371 0 /* ID used for testing */,
372 GetClientReportUrl(kClientReportMalwareUrl),
373 net::URLFetcher::POST, this);
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;
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();
386 UMA_HISTOGRAM_ENUMERATION("SBClientMalware.SentReports",
387 REPORT_SENT, REPORT_RESULT_MAX);
389 UMA_HISTOGRAM_COUNTS("SBClientMalware.IPBlacklistRequestPayloadSize",
390 request_data.size());
392 // Record that we made a malware request
393 malware_report_times_.push(base::Time::Now());
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));
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;
434 EndFetchModel(model_status);
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;
458 if (!info->callback.is_null())
459 info->callback.Run(info->phishing_url, is_phishing);
460 client_phishing_reports_.erase(source);
461 delete source;
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);
475 // status error is negative, so we put - in front of it.
476 UMA_HISTOGRAM_SPARSE_SLOWLY(
477 "SBClientMalware.IPBlacklistRequestNetError", -status.error());
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;
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);
499 client_malware_reports_.erase(source);
500 delete source;
503 bool ClientSideDetectionService::IsInCache(const GURL& url) {
504 UpdateCache();
506 return cache_.find(url) != cache_.end();
509 bool ClientSideDetectionService::GetValidCachedResult(const GURL& url,
510 bool* is_phishing) {
511 UpdateCache();
513 PhishingCache::iterator it = cache_.find(url);
514 if (it == cache_.end()) {
515 return false;
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;
528 return false;
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));
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++);
556 bool ClientSideDetectionService::OverMalwareReportLimit() {
557 return GetMalwareNumReports() > kMaxReportsPerInterval;
560 bool ClientSideDetectionService::OverPhishingReportLimit() {
561 return GetPhishingNumReports() > kMaxReportsPerInterval;
564 int ClientSideDetectionService::GetMalwareNumReports() {
565 return GetNumReports(&malware_report_times_);
568 int ClientSideDetectionService::GetPhishingNumReports() {
569 return GetNumReports(&phishing_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);
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();
583 // Return the number of elements that are above the cutoff.
584 return report_times->size();
587 // static
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;
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;
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));
610 (*bad_subnets)[mask].insert(model.bad_subnet(i).prefix());
614 // static
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;
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;
631 return true;
634 // static
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));
642 return url;
644 } // namespace safe_browsing