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"
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/profiler/scoped_profile.h"
18 #include "base/stl_util.h"
19 #include "base/strings/string_util.h"
20 #include "base/time/time.h"
21 #include "chrome/browser/browser_process.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chrome/common/pref_names.h"
25 #include "chrome/common/safe_browsing/client_model.pb.h"
26 #include "chrome/common/safe_browsing/csd.pb.h"
27 #include "chrome/common/safe_browsing/safebrowsing_messages.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/browser/notification_service.h"
30 #include "content/public/browser/notification_types.h"
31 #include "content/public/browser/render_process_host.h"
32 #include "crypto/sha2.h"
33 #include "google_apis/google_api_keys.h"
34 #include "net/base/escape.h"
35 #include "net/base/load_flags.h"
36 #include "net/base/net_util.h"
37 #include "net/http/http_response_headers.h"
38 #include "net/http/http_status_code.h"
39 #include "net/url_request/url_fetcher.h"
40 #include "net/url_request/url_request_context_getter.h"
41 #include "net/url_request/url_request_status.h"
44 using content::BrowserThread
;
46 namespace safe_browsing
{
50 // malware report type for UMA histogram counting.
51 enum MalwareReportTypes
{
54 REPORT_FAILED_SERIALIZATION
,
60 void UpdateEnumUMAHistogram(MalwareReportTypes report_type
) {
61 DCHECK(report_type
>= 0 && report_type
< REPORT_RESULT_MAX
);
62 UMA_HISTOGRAM_ENUMERATION("SBClientMalware.SentReports",
63 report_type
, REPORT_RESULT_MAX
);
68 const size_t ClientSideDetectionService::kMaxModelSizeBytes
= 90 * 1024;
69 const int ClientSideDetectionService::kMaxReportsPerInterval
= 3;
70 // TODO(noelutz): once we know this mechanism works as intended we should fetch
71 // the model much more frequently. E.g., every 5 minutes or so.
72 const int ClientSideDetectionService::kClientModelFetchIntervalMs
= 3600 * 1000;
73 const int ClientSideDetectionService::kInitialClientModelFetchDelayMs
= 10000;
75 const int ClientSideDetectionService::kReportsIntervalDays
= 1;
76 const int ClientSideDetectionService::kNegativeCacheIntervalDays
= 1;
77 const int ClientSideDetectionService::kPositiveCacheIntervalMinutes
= 30;
79 const char ClientSideDetectionService::kClientReportPhishingUrl
[] =
80 "https://sb-ssl.google.com/safebrowsing/clientreport/phishing";
81 const char ClientSideDetectionService::kClientReportMalwareUrl
[] =
82 "https://sb-ssl.google.com/safebrowsing/clientreport/malware-check";
83 const char ClientSideDetectionService::kClientModelUrl
[] =
84 "https://ssl.gstatic.com/safebrowsing/csd/client_model_v5.pb";
86 struct ClientSideDetectionService::ClientReportInfo
{
87 ClientReportPhishingRequestCallback callback
;
91 struct ClientSideDetectionService::ClientMalwareReportInfo
{
92 ClientReportMalwareRequestCallback callback
;
93 // This is the original landing url, may not be the malware url.
97 ClientSideDetectionService::CacheState::CacheState(bool phish
, base::Time time
)
101 ClientSideDetectionService::ClientSideDetectionService(
102 net::URLRequestContextGetter
* request_context_getter
)
104 request_context_getter_(request_context_getter
),
105 weak_factory_(this) {
106 registrar_
.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED
,
107 content::NotificationService::AllBrowserContextsAndSources());
110 ClientSideDetectionService::~ClientSideDetectionService() {
111 weak_factory_
.InvalidateWeakPtrs();
112 STLDeleteContainerPairPointers(client_phishing_reports_
.begin(),
113 client_phishing_reports_
.end());
114 client_phishing_reports_
.clear();
115 STLDeleteContainerPairPointers(client_malware_reports_
.begin(),
116 client_malware_reports_
.end());
117 client_malware_reports_
.clear();
121 ClientSideDetectionService
* ClientSideDetectionService::Create(
122 net::URLRequestContextGetter
* request_context_getter
) {
123 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
124 return new ClientSideDetectionService(request_context_getter
);
127 void ClientSideDetectionService::SetEnabledAndRefreshState(bool enabled
) {
128 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
129 SendModelToRenderers(); // always refresh the renderer state
130 if (enabled
== enabled_
)
134 // Refresh the model when the service is enabled. This can happen when the
135 // preference is toggled, or early during startup if the preference is
136 // already enabled. In a lot of cases the model will be in the cache so it
137 // won't actually be fetched from the network.
138 // We delay the first model fetch to avoid slowing down browser startup.
139 ScheduleFetchModel(kInitialClientModelFetchDelayMs
);
141 // Cancel pending requests.
142 model_fetcher_
.reset();
143 // Invoke pending callbacks with a false verdict.
144 for (std::map
<const net::URLFetcher
*, ClientReportInfo
*>::iterator it
=
145 client_phishing_reports_
.begin();
146 it
!= client_phishing_reports_
.end(); ++it
) {
147 ClientReportInfo
* info
= it
->second
;
148 if (!info
->callback
.is_null())
149 info
->callback
.Run(info
->phishing_url
, false);
151 STLDeleteContainerPairPointers(client_phishing_reports_
.begin(),
152 client_phishing_reports_
.end());
153 client_phishing_reports_
.clear();
154 for (std::map
<const net::URLFetcher
*, ClientMalwareReportInfo
*>::iterator it
155 = client_malware_reports_
.begin();
156 it
!= client_malware_reports_
.end(); ++it
) {
157 ClientMalwareReportInfo
* info
= it
->second
;
158 if (!info
->callback
.is_null())
159 info
->callback
.Run(info
->original_url
, info
->original_url
, false);
161 STLDeleteContainerPairPointers(client_malware_reports_
.begin(),
162 client_malware_reports_
.end());
163 client_malware_reports_
.clear();
168 void ClientSideDetectionService::SendClientReportPhishingRequest(
169 ClientPhishingRequest
* verdict
,
170 const ClientReportPhishingRequestCallback
& callback
) {
171 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
172 base::MessageLoop::current()->PostTask(
174 base::Bind(&ClientSideDetectionService::StartClientReportPhishingRequest
,
175 weak_factory_
.GetWeakPtr(), verdict
, callback
));
178 void ClientSideDetectionService::SendClientReportMalwareRequest(
179 ClientMalwareRequest
* verdict
,
180 const ClientReportMalwareRequestCallback
& callback
) {
181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
182 base::MessageLoop::current()->PostTask(
184 base::Bind(&ClientSideDetectionService::StartClientReportMalwareRequest
,
185 weak_factory_
.GetWeakPtr(), verdict
, callback
));
188 bool ClientSideDetectionService::IsPrivateIPAddress(
189 const std::string
& ip_address
) const {
190 net::IPAddressNumber ip_number
;
191 if (!net::ParseIPLiteralToNumber(ip_address
, &ip_number
)) {
192 VLOG(2) << "Unable to parse IP address: '" << ip_address
<< "'";
193 // Err on the side of safety and assume this might be private.
197 return net::IsIPAddressReserved(ip_number
);
200 void ClientSideDetectionService::OnURLFetchComplete(
201 const net::URLFetcher
* source
) {
202 // TODO(vadimt): Remove ScopedProfile below once crbug.com/422577 is fixed.
203 tracked_objects::ScopedProfile
tracking_profile(
204 FROM_HERE_WITH_EXPLICIT_FUNCTION(
205 "422577 ClientSideDetectionService::OnURLFetchComplete"));
208 source
->GetResponseAsString(&data
);
209 if (source
== model_fetcher_
.get()) {
211 source
, source
->GetURL(), source
->GetStatus(),
212 source
->GetResponseCode(), source
->GetCookies(), data
);
213 } else if (client_phishing_reports_
.find(source
) !=
214 client_phishing_reports_
.end()) {
215 HandlePhishingVerdict(
216 source
, source
->GetURL(), source
->GetStatus(),
217 source
->GetResponseCode(), source
->GetCookies(), data
);
218 } else if (client_malware_reports_
.find(source
) !=
219 client_malware_reports_
.end()) {
220 HandleMalwareVerdict(
221 source
, source
->GetURL(), source
->GetStatus(),
222 source
->GetResponseCode(), source
->GetCookies(), data
);
228 void ClientSideDetectionService::Observe(
230 const content::NotificationSource
& source
,
231 const content::NotificationDetails
& details
) {
232 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
233 DCHECK(type
== content::NOTIFICATION_RENDERER_PROCESS_CREATED
);
235 // Model might not be ready or maybe there was an error.
239 content::Source
<content::RenderProcessHost
>(source
).ptr());
242 void ClientSideDetectionService::SendModelToProcess(
243 content::RenderProcessHost
* process
) {
244 // The ClientSideDetectionService is enabled if _any_ active profile has
245 // SafeBrowsing turned on. Here we check the profile for each renderer
246 // process and only send the model to those that have SafeBrowsing enabled.
247 Profile
* profile
= Profile::FromBrowserContext(process
->GetBrowserContext());
249 if (profile
->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled
)) {
250 VLOG(2) << "Sending phishing model to RenderProcessHost @" << process
;
253 VLOG(2) << "Disabling client-side phishing detection for "
254 << "RenderProcessHost @" << process
;
256 process
->Send(new SafeBrowsingMsg_SetPhishingModel(model
));
259 void ClientSideDetectionService::SendModelToRenderers() {
260 for (content::RenderProcessHost::iterator
i(
261 content::RenderProcessHost::AllHostsIterator());
262 !i
.IsAtEnd(); i
.Advance()) {
263 SendModelToProcess(i
.GetCurrentValue());
267 void ClientSideDetectionService::ScheduleFetchModel(int64 delay_ms
) {
268 if (CommandLine::ForCurrentProcess()->HasSwitch(
269 switches::kSbDisableAutoUpdate
))
271 base::MessageLoop::current()->PostDelayedTask(
273 base::Bind(&ClientSideDetectionService::StartFetchModel
,
274 weak_factory_
.GetWeakPtr()),
275 base::TimeDelta::FromMilliseconds(delay_ms
));
278 void ClientSideDetectionService::StartFetchModel() {
280 // Start fetching the model either from the cache or possibly from the
281 // network if the model isn't in the cache.
282 model_fetcher_
.reset(net::URLFetcher::Create(
283 0 /* ID used for testing */, GURL(kClientModelUrl
),
284 net::URLFetcher::GET
, this));
285 model_fetcher_
->SetRequestContext(request_context_getter_
.get());
286 model_fetcher_
->Start();
290 void ClientSideDetectionService::EndFetchModel(ClientModelStatus status
) {
291 UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.ClientModelStatus",
294 if (status
== MODEL_SUCCESS
) {
295 SetBadSubnets(*model_
, &bad_subnets_
);
296 SendModelToRenderers();
298 int delay_ms
= kClientModelFetchIntervalMs
;
299 // If the most recently fetched model had a valid max-age and the model was
300 // valid we're scheduling the next model update for after the max-age expired.
301 if (model_max_age_
.get() &&
302 (status
== MODEL_SUCCESS
|| status
== MODEL_NOT_CHANGED
)) {
303 // We're adding 60s of additional delay to make sure we're past
305 *model_max_age_
+= base::TimeDelta::FromMinutes(1);
306 delay_ms
= model_max_age_
->InMilliseconds();
308 model_max_age_
.reset();
310 // Schedule the next model reload.
311 ScheduleFetchModel(delay_ms
);
314 void ClientSideDetectionService::StartClientReportPhishingRequest(
315 ClientPhishingRequest
* verdict
,
316 const ClientReportPhishingRequestCallback
& callback
) {
317 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
318 scoped_ptr
<ClientPhishingRequest
> request(verdict
);
321 if (!callback
.is_null())
322 callback
.Run(GURL(request
->url()), false);
326 std::string request_data
;
327 if (!request
->SerializeToString(&request_data
)) {
328 UMA_HISTOGRAM_COUNTS("SBClientPhishing.RequestNotSerialized", 1);
329 VLOG(1) << "Unable to serialize the CSD request. Proto file changed?";
330 if (!callback
.is_null())
331 callback
.Run(GURL(request
->url()), false);
335 net::URLFetcher
* fetcher
= net::URLFetcher::Create(
336 0 /* ID used for testing */,
337 GetClientReportUrl(kClientReportPhishingUrl
),
338 net::URLFetcher::POST
, this);
340 // Remember which callback and URL correspond to the current fetcher object.
341 ClientReportInfo
* info
= new ClientReportInfo
;
342 info
->callback
= callback
;
343 info
->phishing_url
= GURL(request
->url());
344 client_phishing_reports_
[fetcher
] = info
;
346 fetcher
->SetLoadFlags(net::LOAD_DISABLE_CACHE
);
347 fetcher
->SetRequestContext(request_context_getter_
.get());
348 fetcher
->SetUploadData("application/octet-stream", request_data
);
351 // Record that we made a request
352 phishing_report_times_
.push(base::Time::Now());
355 void ClientSideDetectionService::StartClientReportMalwareRequest(
356 ClientMalwareRequest
* verdict
,
357 const ClientReportMalwareRequestCallback
& callback
) {
358 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
359 scoped_ptr
<ClientMalwareRequest
> request(verdict
);
362 if (!callback
.is_null())
363 callback
.Run(GURL(request
->url()), GURL(request
->url()), false);
367 std::string request_data
;
368 if (!request
->SerializeToString(&request_data
)) {
369 UpdateEnumUMAHistogram(REPORT_FAILED_SERIALIZATION
);
370 DVLOG(1) << "Unable to serialize the CSD request. Proto file changed?";
371 if (!callback
.is_null())
372 callback
.Run(GURL(request
->url()), GURL(request
->url()), false);
376 net::URLFetcher
* fetcher
= net::URLFetcher::Create(
377 0 /* ID used for testing */,
378 GetClientReportUrl(kClientReportMalwareUrl
),
379 net::URLFetcher::POST
, this);
381 // Remember which callback and URL correspond to the current fetcher object.
382 ClientMalwareReportInfo
* info
= new ClientMalwareReportInfo
;
383 info
->callback
= callback
;
384 info
->original_url
= GURL(request
->url());
385 client_malware_reports_
[fetcher
] = info
;
387 fetcher
->SetLoadFlags(net::LOAD_DISABLE_CACHE
);
388 fetcher
->SetRequestContext(request_context_getter_
.get());
389 fetcher
->SetUploadData("application/octet-stream", request_data
);
392 UMA_HISTOGRAM_ENUMERATION("SBClientMalware.SentReports",
393 REPORT_SENT
, REPORT_RESULT_MAX
);
395 UMA_HISTOGRAM_COUNTS("SBClientMalware.IPBlacklistRequestPayloadSize",
396 request_data
.size());
398 // Record that we made a malware request
399 malware_report_times_
.push(base::Time::Now());
402 void ClientSideDetectionService::HandleModelResponse(
403 const net::URLFetcher
* source
,
405 const net::URLRequestStatus
& status
,
407 const net::ResponseCookies
& cookies
,
408 const std::string
& data
) {
409 base::TimeDelta max_age
;
410 if (status
.is_success() && net::HTTP_OK
== response_code
&&
411 source
->GetResponseHeaders() &&
412 source
->GetResponseHeaders()->GetMaxAgeValue(&max_age
)) {
413 model_max_age_
.reset(new base::TimeDelta(max_age
));
415 scoped_ptr
<ClientSideModel
> model(new ClientSideModel());
416 ClientModelStatus model_status
;
417 if (!status
.is_success() || net::HTTP_OK
!= response_code
) {
418 model_status
= MODEL_FETCH_FAILED
;
419 } else if (data
.empty()) {
420 model_status
= MODEL_EMPTY
;
421 } else if (data
.size() > kMaxModelSizeBytes
) {
422 model_status
= MODEL_TOO_LARGE
;
423 } else if (!model
->ParseFromString(data
)) {
424 model_status
= MODEL_PARSE_ERROR
;
425 } else if (!model
->IsInitialized() || !model
->has_version()) {
426 model_status
= MODEL_MISSING_FIELDS
;
427 } else if (!ModelHasValidHashIds(*model
)) {
428 model_status
= MODEL_BAD_HASH_IDS
;
429 } else if (model
->version() < 0 ||
430 (model_
.get() && model
->version() < model_
->version())) {
431 model_status
= MODEL_INVALID_VERSION_NUMBER
;
432 } else if (model_
.get() && model
->version() == model_
->version()) {
433 model_status
= MODEL_NOT_CHANGED
;
435 // The model is valid => replace the existing model with the new one.
436 model_str_
.assign(data
);
438 model_status
= MODEL_SUCCESS
;
440 EndFetchModel(model_status
);
443 void ClientSideDetectionService::HandlePhishingVerdict(
444 const net::URLFetcher
* source
,
446 const net::URLRequestStatus
& status
,
448 const net::ResponseCookies
& cookies
,
449 const std::string
& data
) {
450 ClientPhishingResponse response
;
451 scoped_ptr
<ClientReportInfo
> info(client_phishing_reports_
[source
]);
452 bool is_phishing
= false;
453 if (status
.is_success() && net::HTTP_OK
== response_code
&&
454 response
.ParseFromString(data
)) {
455 // Cache response, possibly flushing an old one.
456 cache_
[info
->phishing_url
] =
457 make_linked_ptr(new CacheState(response
.phishy(), base::Time::Now()));
458 is_phishing
= response
.phishy();
460 DLOG(ERROR
) << "Unable to get the server verdict for URL: "
461 << info
->phishing_url
<< " status: " << status
.status() << " "
462 << "response_code:" << response_code
;
464 if (!info
->callback
.is_null())
465 info
->callback
.Run(info
->phishing_url
, is_phishing
);
466 client_phishing_reports_
.erase(source
);
470 void ClientSideDetectionService::HandleMalwareVerdict(
471 const net::URLFetcher
* source
,
473 const net::URLRequestStatus
& status
,
475 const net::ResponseCookies
& cookies
,
476 const std::string
& data
) {
477 if (status
.is_success()) {
478 UMA_HISTOGRAM_SPARSE_SLOWLY(
479 "SBClientMalware.IPBlacklistRequestResponseCode", response_code
);
481 // status error is negative, so we put - in front of it.
482 UMA_HISTOGRAM_SPARSE_SLOWLY(
483 "SBClientMalware.IPBlacklistRequestNetError", -status
.error());
485 ClientMalwareResponse response
;
486 scoped_ptr
<ClientMalwareReportInfo
> info(client_malware_reports_
[source
]);
487 bool should_blacklist
= false;
488 if (status
.is_success() && net::HTTP_OK
== response_code
&&
489 response
.ParseFromString(data
)) {
490 should_blacklist
= response
.blacklist();
492 DLOG(ERROR
) << "Unable to get the server verdict for URL: "
493 << info
->original_url
<< " status: " << status
.status() << " "
494 << "response_code:" << response_code
;
497 if (!info
->callback
.is_null()) {
498 if (response
.has_bad_url())
499 info
->callback
.Run(info
->original_url
, GURL(response
.bad_url()),
502 info
->callback
.Run(info
->original_url
, info
->original_url
, false);
505 client_malware_reports_
.erase(source
);
509 bool ClientSideDetectionService::IsInCache(const GURL
& url
) {
512 return cache_
.find(url
) != cache_
.end();
515 bool ClientSideDetectionService::GetValidCachedResult(const GURL
& url
,
519 PhishingCache::iterator it
= cache_
.find(url
);
520 if (it
== cache_
.end()) {
524 // We still need to check if the result is valid.
525 const CacheState
& cache_state
= *it
->second
;
526 if (cache_state
.is_phishing
?
527 cache_state
.timestamp
> base::Time::Now() -
528 base::TimeDelta::FromMinutes(kPositiveCacheIntervalMinutes
) :
529 cache_state
.timestamp
> base::Time::Now() -
530 base::TimeDelta::FromDays(kNegativeCacheIntervalDays
)) {
531 *is_phishing
= cache_state
.is_phishing
;
537 void ClientSideDetectionService::UpdateCache() {
538 // Since we limit the number of requests but allow pass-through for cache
539 // refreshes, we don't want to remove elements from the cache if they
540 // could be used for this purpose even if we will not use the entry to
541 // satisfy the request from the cache.
542 base::TimeDelta positive_cache_interval
=
543 std::max(base::TimeDelta::FromMinutes(kPositiveCacheIntervalMinutes
),
544 base::TimeDelta::FromDays(kReportsIntervalDays
));
545 base::TimeDelta negative_cache_interval
=
546 std::max(base::TimeDelta::FromDays(kNegativeCacheIntervalDays
),
547 base::TimeDelta::FromDays(kReportsIntervalDays
));
549 // Remove elements from the cache that will no longer be used.
550 for (PhishingCache::iterator it
= cache_
.begin(); it
!= cache_
.end();) {
551 const CacheState
& cache_state
= *it
->second
;
552 if (cache_state
.is_phishing
?
553 cache_state
.timestamp
> base::Time::Now() - positive_cache_interval
:
554 cache_state
.timestamp
> base::Time::Now() - negative_cache_interval
) {
562 bool ClientSideDetectionService::OverMalwareReportLimit() {
563 return GetMalwareNumReports() > kMaxReportsPerInterval
;
566 bool ClientSideDetectionService::OverPhishingReportLimit() {
567 return GetPhishingNumReports() > kMaxReportsPerInterval
;
570 int ClientSideDetectionService::GetMalwareNumReports() {
571 return GetNumReports(&malware_report_times_
);
574 int ClientSideDetectionService::GetPhishingNumReports() {
575 return GetNumReports(&phishing_report_times_
);
578 int ClientSideDetectionService::GetNumReports(
579 std::queue
<base::Time
>* report_times
) {
581 base::Time::Now() - base::TimeDelta::FromDays(kReportsIntervalDays
);
583 // Erase items older than cutoff because we will never care about them again.
584 while (!report_times
->empty() &&
585 report_times
->front() < cutoff
) {
589 // Return the number of elements that are above the cutoff.
590 return report_times
->size();
594 void ClientSideDetectionService::SetBadSubnets(const ClientSideModel
& model
,
595 BadSubnetMap
* bad_subnets
) {
596 bad_subnets
->clear();
597 for (int i
= 0; i
< model
.bad_subnet_size(); ++i
) {
598 int size
= model
.bad_subnet(i
).size();
599 if (size
< 0 || size
> static_cast<int>(net::kIPv6AddressSize
) * 8) {
600 DLOG(ERROR
) << "Invalid bad subnet size: " << size
;
603 if (model
.bad_subnet(i
).prefix().size() != crypto::kSHA256Length
) {
604 DLOG(ERROR
) << "Invalid bad subnet prefix length: "
605 << model
.bad_subnet(i
).prefix().size();
608 // We precompute the mask for the given subnet size to speed up lookups.
609 // Basically we need to create a 16B long string which has the highest
610 // |size| bits sets to one.
611 std::string
mask(net::kIPv6AddressSize
, '\x00');
612 mask
.replace(0, size
/ 8, size
/ 8, '\xFF');
614 mask
[size
/ 8] = 0xFF << (8 - (size
% 8));
616 (*bad_subnets
)[mask
].insert(model
.bad_subnet(i
).prefix());
621 bool ClientSideDetectionService::ModelHasValidHashIds(
622 const ClientSideModel
& model
) {
623 const int max_index
= model
.hashes_size() - 1;
624 for (int i
= 0; i
< model
.rule_size(); ++i
) {
625 for (int j
= 0; j
< model
.rule(i
).feature_size(); ++j
) {
626 if (model
.rule(i
).feature(j
) < 0 ||
627 model
.rule(i
).feature(j
) > max_index
) {
632 for (int i
= 0; i
< model
.page_term_size(); ++i
) {
633 if (model
.page_term(i
) < 0 || model
.page_term(i
) > max_index
) {
641 GURL
ClientSideDetectionService::GetClientReportUrl(
642 const std::string
& report_url
) {
643 GURL
url(report_url
);
644 std::string api_key
= google_apis::GetAPIKey();
645 if (!api_key
.empty())
646 url
= url
.Resolve("?key=" + net::EscapeQueryParamValue(api_key
, true));
650 } // namespace safe_browsing