| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/safe_browsing/browser_feature_extractor.h" | 5 #include "chrome/browser/safe_browsing/browser_feature_extractor.h" |
| 6 | 6 |
| 7 #include <map> | 7 #include <map> |
| 8 #include <utility> | 8 #include <utility> |
| 9 | 9 |
| 10 #include "base/bind.h" | 10 #include "base/bind.h" |
| 11 #include "base/bind_helpers.h" | 11 #include "base/bind_helpers.h" |
| 12 #include "base/format_macros.h" | 12 #include "base/format_macros.h" |
| 13 #include "base/stl_util.h" | 13 #include "base/stl_util.h" |
| 14 #include "base/strings/stringprintf.h" | 14 #include "base/strings/stringprintf.h" |
| 15 #include "base/time/time.h" | 15 #include "base/time/time.h" |
| 16 #include "chrome/browser/common/cancelable_request.h" |
| 16 #include "chrome/browser/history/history_service.h" | 17 #include "chrome/browser/history/history_service.h" |
| 17 #include "chrome/browser/history/history_service_factory.h" | 18 #include "chrome/browser/history/history_service_factory.h" |
| 18 #include "chrome/browser/history/history_types.h" | 19 #include "chrome/browser/history/history_types.h" |
| 19 #include "chrome/browser/profiles/profile.h" | 20 #include "chrome/browser/profiles/profile.h" |
| 20 #include "chrome/browser/safe_browsing/browser_features.h" | 21 #include "chrome/browser/safe_browsing/browser_features.h" |
| 21 #include "chrome/browser/safe_browsing/client_side_detection_host.h" | 22 #include "chrome/browser/safe_browsing/client_side_detection_host.h" |
| 22 #include "chrome/browser/safe_browsing/database_manager.h" | 23 #include "chrome/browser/safe_browsing/database_manager.h" |
| 23 #include "chrome/common/safe_browsing/csd.pb.h" | 24 #include "chrome/common/safe_browsing/csd.pb.h" |
| 24 #include "content/public/browser/browser_thread.h" | 25 #include "content/public/browser/browser_thread.h" |
| 25 #include "content/public/browser/navigation_controller.h" | 26 #include "content/public/browser/navigation_controller.h" |
| (...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 166 WebContents* tab, | 167 WebContents* tab, |
| 167 ClientSideDetectionHost* host) | 168 ClientSideDetectionHost* host) |
| 168 : tab_(tab), | 169 : tab_(tab), |
| 169 host_(host), | 170 host_(host), |
| 170 weak_factory_(this) { | 171 weak_factory_(this) { |
| 171 DCHECK(tab); | 172 DCHECK(tab); |
| 172 } | 173 } |
| 173 | 174 |
| 174 BrowserFeatureExtractor::~BrowserFeatureExtractor() { | 175 BrowserFeatureExtractor::~BrowserFeatureExtractor() { |
| 175 weak_factory_.InvalidateWeakPtrs(); | 176 weak_factory_.InvalidateWeakPtrs(); |
| 177 // Delete all the pending extractions (delete callback and request objects). |
| 178 STLDeleteContainerPairFirstPointers(pending_extractions_.begin(), |
| 179 pending_extractions_.end()); |
| 180 |
| 181 // Also cancel all the pending history service queries. |
| 182 HistoryService* history; |
| 183 bool success = GetHistoryService(&history); |
| 184 DCHECK(success || pending_queries_.size() == 0); |
| 185 // Cancel all the pending history lookups and cleanup the memory. |
| 186 for (PendingQueriesMap::iterator it = pending_queries_.begin(); |
| 187 it != pending_queries_.end(); ++it) { |
| 188 if (history) { |
| 189 history->CancelRequest(it->first); |
| 190 } |
| 191 ExtractionData& extraction = it->second; |
| 192 delete extraction.first; // delete request |
| 193 } |
| 194 pending_queries_.clear(); |
| 176 } | 195 } |
| 177 | 196 |
| 178 void BrowserFeatureExtractor::ExtractFeatures(const BrowseInfo* info, | 197 void BrowserFeatureExtractor::ExtractFeatures(const BrowseInfo* info, |
| 179 ClientPhishingRequest* request, | 198 ClientPhishingRequest* request, |
| 180 const DoneCallback& callback) { | 199 const DoneCallback& callback) { |
| 181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 200 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 182 DCHECK(request); | 201 DCHECK(request); |
| 183 DCHECK(info); | 202 DCHECK(info); |
| 184 DCHECK_EQ(0U, request->url().find("http:")); | 203 DCHECK_EQ(0U, request->url().find("http:")); |
| 185 DCHECK(!callback.is_null()); | 204 DCHECK(!callback.is_null()); |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 223 std::string(), controller, url_index, info->url_redirects, request); | 242 std::string(), controller, url_index, info->url_redirects, request); |
| 224 } | 243 } |
| 225 if (first_host_index != -1) { | 244 if (first_host_index != -1) { |
| 226 AddNavigationFeatures(features::kHostPrefix, | 245 AddNavigationFeatures(features::kHostPrefix, |
| 227 controller, | 246 controller, |
| 228 first_host_index, | 247 first_host_index, |
| 229 info->host_redirects, | 248 info->host_redirects, |
| 230 request); | 249 request); |
| 231 } | 250 } |
| 232 | 251 |
| 233 // The API doesn't take a scoped_ptr because the API gets mocked and we | |
| 234 // cannot mock an API that takes scoped_ptr as arguments. | |
| 235 scoped_ptr<ClientPhishingRequest> req(request); | |
| 236 | |
| 237 ExtractBrowseInfoFeatures(*info, request); | 252 ExtractBrowseInfoFeatures(*info, request); |
| 253 pending_extractions_[request] = callback; |
| 238 base::MessageLoop::current()->PostTask( | 254 base::MessageLoop::current()->PostTask( |
| 239 FROM_HERE, | 255 FROM_HERE, |
| 240 base::Bind(&BrowserFeatureExtractor::StartExtractFeatures, | 256 base::Bind(&BrowserFeatureExtractor::StartExtractFeatures, |
| 241 weak_factory_.GetWeakPtr(), | 257 weak_factory_.GetWeakPtr(), request, callback)); |
| 242 base::Passed(&req), | |
| 243 callback)); | |
| 244 } | 258 } |
| 245 | 259 |
| 246 void BrowserFeatureExtractor::ExtractMalwareFeatures( | 260 void BrowserFeatureExtractor::ExtractMalwareFeatures( |
| 247 BrowseInfo* info, | 261 BrowseInfo* info, |
| 248 ClientMalwareRequest* request, | 262 ClientMalwareRequest* request, |
| 249 const MalwareDoneCallback& callback) { | 263 const MalwareDoneCallback& callback) { |
| 250 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 264 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 251 DCHECK(!callback.is_null()); | 265 DCHECK(!callback.is_null()); |
| 252 | 266 |
| 253 // Grab the IPs because they might go away before we're done | 267 // Grab the IPs because they might go away before we're done |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 292 AddFeature(features::kSafeBrowsingThreatType, | 306 AddFeature(features::kSafeBrowsingThreatType, |
| 293 static_cast<double>(info.unsafe_resource->threat_type), | 307 static_cast<double>(info.unsafe_resource->threat_type), |
| 294 request); | 308 request); |
| 295 } | 309 } |
| 296 if (info.http_status_code != 0) { | 310 if (info.http_status_code != 0) { |
| 297 AddFeature(features::kHttpStatusCode, info.http_status_code, request); | 311 AddFeature(features::kHttpStatusCode, info.http_status_code, request); |
| 298 } | 312 } |
| 299 } | 313 } |
| 300 | 314 |
| 301 void BrowserFeatureExtractor::StartExtractFeatures( | 315 void BrowserFeatureExtractor::StartExtractFeatures( |
| 302 scoped_ptr<ClientPhishingRequest> request, | 316 ClientPhishingRequest* request, |
| 303 const DoneCallback& callback) { | 317 const DoneCallback& callback) { |
| 304 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 318 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 319 size_t removed = pending_extractions_.erase(request); |
| 320 DCHECK_EQ(1U, removed); |
| 305 HistoryService* history; | 321 HistoryService* history; |
| 306 if (!request || !request->IsInitialized() || !GetHistoryService(&history)) { | 322 if (!request || !request->IsInitialized() || !GetHistoryService(&history)) { |
| 307 callback.Run(false, request.Pass()); | 323 callback.Run(false, request); |
| 308 return; | 324 return; |
| 309 } | 325 } |
| 310 GURL request_url = GURL(request->url()); | 326 // HistoryService::QueryURL migrated from CancelableRequestComsumer to |
| 311 history->QueryURL(request_url, | 327 // CancelableRequestTracker and there is no Handle to associate to the |
| 328 // request. Instead manage the request object lifetime by using a scoped_ptr |
| 329 // and using base::Passed(). So if the asynchronous call is canceled, the |
| 330 // request is deleted, otherwise the callback becomes the owner. |
| 331 scoped_ptr<ClientPhishingRequest> owned_request(request); |
| 332 history->QueryURL(GURL(request->url()), |
| 312 true /* wants_visits */, | 333 true /* wants_visits */, |
| 313 base::Bind(&BrowserFeatureExtractor::QueryUrlHistoryDone, | 334 base::Bind(&BrowserFeatureExtractor::QueryUrlHistoryDone, |
| 314 base::Unretained(this), | 335 base::Unretained(this), |
| 315 base::Passed(&request), | 336 base::Passed(&owned_request), |
| 316 callback), | 337 callback), |
| 317 &cancelable_task_tracker_); | 338 &cancelable_task_tracker_); |
| 318 } | 339 } |
| 319 | 340 |
| 320 void BrowserFeatureExtractor::QueryUrlHistoryDone( | 341 void BrowserFeatureExtractor::QueryUrlHistoryDone( |
| 321 scoped_ptr<ClientPhishingRequest> request, | 342 scoped_ptr<ClientPhishingRequest> owned_request, |
| 322 const DoneCallback& callback, | 343 const DoneCallback& callback, |
| 323 bool success, | 344 bool success, |
| 324 const history::URLRow& row, | 345 const history::URLRow& row, |
| 325 const history::VisitVector& visits) { | 346 const history::VisitVector& visits) { |
| 326 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 347 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 327 DCHECK(request); | 348 DCHECK(owned_request); |
| 328 DCHECK(!callback.is_null()); | 349 DCHECK(!callback.is_null()); |
| 350 ClientPhishingRequest* request = owned_request.release(); |
| 329 if (!success) { | 351 if (!success) { |
| 330 // URL is not found in the history. In practice this should not | 352 // URL is not found in the history. In practice this should not |
| 331 // happen (unless there is a real error) because we just visited | 353 // happen (unless there is a real error) because we just visited |
| 332 // that URL. | 354 // that URL. |
| 333 callback.Run(false, request.Pass()); | 355 callback.Run(false, request); |
| 334 return; | 356 return; |
| 335 } | 357 } |
| 336 AddFeature(features::kUrlHistoryVisitCount, | 358 AddFeature(features::kUrlHistoryVisitCount, |
| 337 static_cast<double>(row.visit_count()), | 359 static_cast<double>(row.visit_count()), |
| 338 request.get()); | 360 request); |
| 339 | 361 |
| 340 base::Time threshold = base::Time::Now() - base::TimeDelta::FromDays(1); | 362 base::Time threshold = base::Time::Now() - base::TimeDelta::FromDays(1); |
| 341 int num_visits_24h_ago = 0; | 363 int num_visits_24h_ago = 0; |
| 342 int num_visits_typed = 0; | 364 int num_visits_typed = 0; |
| 343 int num_visits_link = 0; | 365 int num_visits_link = 0; |
| 344 for (history::VisitVector::const_iterator it = visits.begin(); | 366 for (history::VisitVector::const_iterator it = visits.begin(); |
| 345 it != visits.end(); | 367 it != visits.end(); |
| 346 ++it) { | 368 ++it) { |
| 347 if (!content::PageTransitionIsMainFrame(it->transition)) { | 369 if (!content::PageTransitionIsMainFrame(it->transition)) { |
| 348 continue; | 370 continue; |
| 349 } | 371 } |
| 350 if (it->visit_time < threshold) { | 372 if (it->visit_time < threshold) { |
| 351 ++num_visits_24h_ago; | 373 ++num_visits_24h_ago; |
| 352 } | 374 } |
| 353 content::PageTransition transition = content::PageTransitionStripQualifier( | 375 content::PageTransition transition = content::PageTransitionStripQualifier( |
| 354 it->transition); | 376 it->transition); |
| 355 if (transition == content::PAGE_TRANSITION_TYPED) { | 377 if (transition == content::PAGE_TRANSITION_TYPED) { |
| 356 ++num_visits_typed; | 378 ++num_visits_typed; |
| 357 } else if (transition == content::PAGE_TRANSITION_LINK) { | 379 } else if (transition == content::PAGE_TRANSITION_LINK) { |
| 358 ++num_visits_link; | 380 ++num_visits_link; |
| 359 } | 381 } |
| 360 } | 382 } |
| 361 AddFeature(features::kUrlHistoryVisitCountMoreThan24hAgo, | 383 AddFeature(features::kUrlHistoryVisitCountMoreThan24hAgo, |
| 362 static_cast<double>(num_visits_24h_ago), | 384 static_cast<double>(num_visits_24h_ago), |
| 363 request.get()); | 385 request); |
| 364 AddFeature(features::kUrlHistoryTypedCount, | 386 AddFeature(features::kUrlHistoryTypedCount, |
| 365 static_cast<double>(num_visits_typed), | 387 static_cast<double>(num_visits_typed), |
| 366 request.get()); | 388 request); |
| 367 AddFeature(features::kUrlHistoryLinkCount, | 389 AddFeature(features::kUrlHistoryLinkCount, |
| 368 static_cast<double>(num_visits_link), | 390 static_cast<double>(num_visits_link), |
| 369 request.get()); | 391 request); |
| 370 | 392 |
| 371 // Issue next history lookup for host visits. | 393 // Issue next history lookup for host visits. |
| 372 HistoryService* history; | 394 HistoryService* history; |
| 373 if (!GetHistoryService(&history)) { | 395 if (!GetHistoryService(&history)) { |
| 374 callback.Run(false, request.Pass()); | 396 callback.Run(false, request); |
| 375 return; | 397 return; |
| 376 } | 398 } |
| 377 GURL request_url = GURL(request->url()); | 399 CancelableRequestProvider::Handle next_handle = |
| 378 history->GetVisibleVisitCountToHost( | 400 history->GetVisibleVisitCountToHost( |
| 379 request_url, | 401 GURL(request->url()), |
| 380 base::Bind(&BrowserFeatureExtractor::QueryHttpHostVisitsDone, | 402 &request_consumer_, |
| 381 base::Unretained(this), | 403 base::Bind(&BrowserFeatureExtractor::QueryHttpHostVisitsDone, |
| 382 base::Passed(&request), | 404 base::Unretained(this))); |
| 383 callback), | 405 StorePendingQuery(next_handle, request, callback); |
| 384 &cancelable_task_tracker_); | |
| 385 } | 406 } |
| 386 | 407 |
| 387 void BrowserFeatureExtractor::QueryHttpHostVisitsDone( | 408 void BrowserFeatureExtractor::QueryHttpHostVisitsDone( |
| 388 scoped_ptr<ClientPhishingRequest> request, | 409 CancelableRequestProvider::Handle handle, |
| 389 const DoneCallback& callback, | |
| 390 bool success, | 410 bool success, |
| 391 int num_visits, | 411 int num_visits, |
| 392 base::Time first_visit) { | 412 base::Time first_visit) { |
| 393 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 413 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 414 ClientPhishingRequest* request; |
| 415 DoneCallback callback; |
| 416 if (!GetPendingQuery(handle, &request, &callback)) { |
| 417 DLOG(FATAL) << "No pending history query found"; |
| 418 return; |
| 419 } |
| 394 DCHECK(request); | 420 DCHECK(request); |
| 395 DCHECK(!callback.is_null()); | 421 DCHECK(!callback.is_null()); |
| 396 if (!success) { | 422 if (!success) { |
| 397 callback.Run(false, request.Pass()); | 423 callback.Run(false, request); |
| 398 return; | 424 return; |
| 399 } | 425 } |
| 400 SetHostVisitsFeatures(num_visits, first_visit, true, request.get()); | 426 SetHostVisitsFeatures(num_visits, first_visit, true, request); |
| 401 | 427 |
| 402 // Same lookup but for the HTTPS URL. | 428 // Same lookup but for the HTTPS URL. |
| 403 HistoryService* history; | 429 HistoryService* history; |
| 404 if (!GetHistoryService(&history)) { | 430 if (!GetHistoryService(&history)) { |
| 405 callback.Run(false, request.Pass()); | 431 callback.Run(false, request); |
| 406 return; | 432 return; |
| 407 } | 433 } |
| 408 std::string https_url = request->url(); | 434 std::string https_url = request->url(); |
| 409 history->GetVisibleVisitCountToHost( | 435 CancelableRequestProvider::Handle next_handle = |
| 410 GURL(https_url.replace(0, 5, "https:")), | 436 history->GetVisibleVisitCountToHost( |
| 411 base::Bind(&BrowserFeatureExtractor::QueryHttpsHostVisitsDone, | 437 GURL(https_url.replace(0, 5, "https:")), |
| 412 base::Unretained(this), | 438 &request_consumer_, |
| 413 base::Passed(&request), | 439 base::Bind(&BrowserFeatureExtractor::QueryHttpsHostVisitsDone, |
| 414 callback), | 440 base::Unretained(this))); |
| 415 &cancelable_task_tracker_); | 441 StorePendingQuery(next_handle, request, callback); |
| 416 } | 442 } |
| 417 | 443 |
| 418 void BrowserFeatureExtractor::QueryHttpsHostVisitsDone( | 444 void BrowserFeatureExtractor::QueryHttpsHostVisitsDone( |
| 419 scoped_ptr<ClientPhishingRequest> request, | 445 CancelableRequestProvider::Handle handle, |
| 420 const DoneCallback& callback, | |
| 421 bool success, | 446 bool success, |
| 422 int num_visits, | 447 int num_visits, |
| 423 base::Time first_visit) { | 448 base::Time first_visit) { |
| 424 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 449 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 450 ClientPhishingRequest* request; |
| 451 DoneCallback callback; |
| 452 if (!GetPendingQuery(handle, &request, &callback)) { |
| 453 DLOG(FATAL) << "No pending history query found"; |
| 454 return; |
| 455 } |
| 425 DCHECK(request); | 456 DCHECK(request); |
| 426 DCHECK(!callback.is_null()); | 457 DCHECK(!callback.is_null()); |
| 427 if (!success) { | 458 if (!success) { |
| 428 callback.Run(false, request.Pass()); | 459 callback.Run(false, request); |
| 429 return; | 460 return; |
| 430 } | 461 } |
| 431 SetHostVisitsFeatures(num_visits, first_visit, false, request.get()); | 462 SetHostVisitsFeatures(num_visits, first_visit, false, request); |
| 432 callback.Run(true, request.Pass()); | 463 callback.Run(true, request); // We're done with all the history lookups. |
| 433 } | 464 } |
| 434 | 465 |
| 435 void BrowserFeatureExtractor::SetHostVisitsFeatures( | 466 void BrowserFeatureExtractor::SetHostVisitsFeatures( |
| 436 int num_visits, | 467 int num_visits, |
| 437 base::Time first_visit, | 468 base::Time first_visit, |
| 438 bool is_http_query, | 469 bool is_http_query, |
| 439 ClientPhishingRequest* request) { | 470 ClientPhishingRequest* request) { |
| 440 DCHECK(request); | 471 DCHECK(request); |
| 441 AddFeature(is_http_query ? | 472 AddFeature(is_http_query ? |
| 442 features::kHttpHostVisitCount : features::kHttpsHostVisitCount, | 473 features::kHttpHostVisitCount : features::kHttpsHostVisitCount, |
| 443 static_cast<double>(num_visits), | 474 static_cast<double>(num_visits), |
| 444 request); | 475 request); |
| 445 if (num_visits > 0) { | 476 if (num_visits > 0) { |
| 446 AddFeature( | 477 AddFeature( |
| 447 is_http_query ? | 478 is_http_query ? |
| 448 features::kFirstHttpHostVisitMoreThan24hAgo : | 479 features::kFirstHttpHostVisitMoreThan24hAgo : |
| 449 features::kFirstHttpsHostVisitMoreThan24hAgo, | 480 features::kFirstHttpsHostVisitMoreThan24hAgo, |
| 450 (first_visit < (base::Time::Now() - base::TimeDelta::FromDays(1))) ? | 481 (first_visit < (base::Time::Now() - base::TimeDelta::FromDays(1))) ? |
| 451 1.0 : 0.0, | 482 1.0 : 0.0, |
| 452 request); | 483 request); |
| 453 } | 484 } |
| 454 } | 485 } |
| 455 | 486 |
| 487 void BrowserFeatureExtractor::StorePendingQuery( |
| 488 CancelableRequestProvider::Handle handle, |
| 489 ClientPhishingRequest* request, |
| 490 const DoneCallback& callback) { |
| 491 DCHECK_EQ(0U, pending_queries_.count(handle)); |
| 492 pending_queries_[handle] = std::make_pair(request, callback); |
| 493 } |
| 494 |
| 495 bool BrowserFeatureExtractor::GetPendingQuery( |
| 496 CancelableRequestProvider::Handle handle, |
| 497 ClientPhishingRequest** request, |
| 498 DoneCallback* callback) { |
| 499 PendingQueriesMap::iterator it = pending_queries_.find(handle); |
| 500 DCHECK(it != pending_queries_.end()); |
| 501 if (it != pending_queries_.end()) { |
| 502 *request = it->second.first; |
| 503 *callback = it->second.second; |
| 504 pending_queries_.erase(it); |
| 505 return true; |
| 506 } |
| 507 return false; |
| 508 } |
| 509 |
| 456 bool BrowserFeatureExtractor::GetHistoryService(HistoryService** history) { | 510 bool BrowserFeatureExtractor::GetHistoryService(HistoryService** history) { |
| 457 *history = NULL; | 511 *history = NULL; |
| 458 if (tab_ && tab_->GetBrowserContext()) { | 512 if (tab_ && tab_->GetBrowserContext()) { |
| 459 Profile* profile = Profile::FromBrowserContext(tab_->GetBrowserContext()); | 513 Profile* profile = Profile::FromBrowserContext(tab_->GetBrowserContext()); |
| 460 *history = HistoryServiceFactory::GetForProfile(profile, | 514 *history = HistoryServiceFactory::GetForProfile(profile, |
| 461 Profile::EXPLICIT_ACCESS); | 515 Profile::EXPLICIT_ACCESS); |
| 462 if (*history) { | 516 if (*history) { |
| 463 return true; | 517 return true; |
| 464 } | 518 } |
| 465 } | 519 } |
| (...skipping 14 matching lines...) Expand all Loading... |
| 480 // Limit the number of matched bad IPs in one request to control | 534 // Limit the number of matched bad IPs in one request to control |
| 481 // the request's size | 535 // the request's size |
| 482 if (matched_bad_ips >= kMaxMalwareIPPerRequest) { | 536 if (matched_bad_ips >= kMaxMalwareIPPerRequest) { |
| 483 break; | 537 break; |
| 484 } | 538 } |
| 485 } | 539 } |
| 486 callback.Run(true, request.Pass()); | 540 callback.Run(true, request.Pass()); |
| 487 } | 541 } |
| 488 | 542 |
| 489 } // namespace safe_browsing | 543 } // namespace safe_browsing |
| OLD | NEW |