| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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/incident_reporting_service.h" | 5 #include "chrome/browser/safe_browsing/incident_reporting_service.h" |
| 6 | 6 |
| 7 #include <math.h> | 7 #include <math.h> |
| 8 | 8 |
| 9 #include <algorithm> |
| 10 #include <vector> |
| 11 |
| 9 #include "base/metrics/histogram.h" | 12 #include "base/metrics/histogram.h" |
| 10 #include "base/prefs/pref_service.h" | 13 #include "base/prefs/pref_service.h" |
| 11 #include "base/process/process_info.h" | 14 #include "base/process/process_info.h" |
| 12 #include "base/stl_util.h" | 15 #include "base/stl_util.h" |
| 13 #include "base/threading/sequenced_worker_pool.h" | 16 #include "base/threading/sequenced_worker_pool.h" |
| 14 #include "chrome/browser/browser_process.h" | 17 #include "chrome/browser/browser_process.h" |
| 15 #include "chrome/browser/chrome_notification_types.h" | 18 #include "chrome/browser/chrome_notification_types.h" |
| 16 #include "chrome/browser/prefs/tracked/tracked_preference_validation_delegate.h" | 19 #include "chrome/browser/prefs/tracked/tracked_preference_validation_delegate.h" |
| 17 #include "chrome/browser/profiles/profile.h" | 20 #include "chrome/browser/profiles/profile.h" |
| 18 #include "chrome/browser/safe_browsing/database_manager.h" | 21 #include "chrome/browser/safe_browsing/database_manager.h" |
| (...skipping 21 matching lines...) Expand all Loading... |
| 40 enum IncidentDisposition { | 43 enum IncidentDisposition { |
| 41 DROPPED, | 44 DROPPED, |
| 42 ACCEPTED, | 45 ACCEPTED, |
| 43 }; | 46 }; |
| 44 | 47 |
| 45 const int64 kDefaultUploadDelayMs = 1000 * 60; // one minute | 48 const int64 kDefaultUploadDelayMs = 1000 * 60; // one minute |
| 46 | 49 |
| 47 void LogIncidentDataType( | 50 void LogIncidentDataType( |
| 48 IncidentDisposition disposition, | 51 IncidentDisposition disposition, |
| 49 const ClientIncidentReport_IncidentData& incident_data) { | 52 const ClientIncidentReport_IncidentData& incident_data) { |
| 50 static const char kAcceptedMetric[] = "SBIRS.Incident"; | |
| 51 static const char kDroppedMetric[] = "SBIRS.DroppedIncident"; | |
| 52 | |
| 53 IncidentType type = TRACKED_PREFERENCE; | 53 IncidentType type = TRACKED_PREFERENCE; |
| 54 | 54 |
| 55 // Add a switch statement once other types are supported. | 55 // Add a switch statement once other types are supported. |
| 56 DCHECK(incident_data.has_tracked_preference()); | 56 DCHECK(incident_data.has_tracked_preference()); |
| 57 | 57 |
| 58 if (disposition == ACCEPTED) { | 58 if (disposition == ACCEPTED) { |
| 59 UMA_HISTOGRAM_ENUMERATION(kAcceptedMetric, type, NUM_INCIDENT_TYPES); | 59 UMA_HISTOGRAM_ENUMERATION("SBIRS.Incident", type, NUM_INCIDENT_TYPES); |
| 60 } else { | 60 } else { |
| 61 DCHECK_EQ(disposition, DROPPED); | 61 DCHECK_EQ(disposition, DROPPED); |
| 62 UMA_HISTOGRAM_ENUMERATION(kDroppedMetric, type, NUM_INCIDENT_TYPES); | 62 UMA_HISTOGRAM_ENUMERATION("SBIRS.DroppedIncident", type, |
| 63 NUM_INCIDENT_TYPES); |
| 63 } | 64 } |
| 64 } | 65 } |
| 65 | 66 |
| 66 } // namespace | 67 } // namespace |
| 67 | 68 |
| 68 struct IncidentReportingService::ProfileContext { | 69 struct IncidentReportingService::ProfileContext { |
| 69 ProfileContext(); | 70 ProfileContext(); |
| 70 ~ProfileContext(); | 71 ~ProfileContext(); |
| 71 | 72 |
| 72 // The incidents collected for this profile pending creation and/or upload. | 73 // The incidents collected for this profile pending creation and/or upload. |
| 73 ScopedVector<ClientIncidentReport_IncidentData> incidents; | 74 ScopedVector<ClientIncidentReport_IncidentData> incidents; |
| 74 | 75 |
| 75 // False until PROFILE_CREATED notification is received. | 76 // False until PROFILE_CREATED notification is received. |
| 76 bool created; | 77 bool created; |
| 77 | 78 |
| 78 private: | 79 private: |
| 79 DISALLOW_COPY_AND_ASSIGN(ProfileContext); | 80 DISALLOW_COPY_AND_ASSIGN(ProfileContext); |
| 80 }; | 81 }; |
| 81 | 82 |
| 82 class IncidentReportingService::UploadContext { | 83 class IncidentReportingService::UploadContext { |
| 83 public: | 84 public: |
| 84 explicit UploadContext(scoped_ptr<ClientIncidentReport> report); | 85 explicit UploadContext(scoped_ptr<ClientIncidentReport> report); |
| 85 ~UploadContext(); | 86 ~UploadContext(); |
| 86 | 87 |
| 88 // The report being uploaded. |
| 87 scoped_ptr<ClientIncidentReport> report; | 89 scoped_ptr<ClientIncidentReport> report; |
| 90 |
| 91 // The uploader in use. This is NULL until the CSD killswitch is checked. |
| 88 scoped_ptr<IncidentReportUploader> uploader; | 92 scoped_ptr<IncidentReportUploader> uploader; |
| 89 | 93 |
| 94 // The set of profiles from which incidents in |report| originated. |
| 95 std::vector<Profile*> profiles; |
| 96 |
| 90 private: | 97 private: |
| 91 DISALLOW_COPY_AND_ASSIGN(UploadContext); | 98 DISALLOW_COPY_AND_ASSIGN(UploadContext); |
| 92 }; | 99 }; |
| 93 | 100 |
| 94 IncidentReportingService::ProfileContext::ProfileContext() : created() { | 101 IncidentReportingService::ProfileContext::ProfileContext() : created() { |
| 95 } | 102 } |
| 96 | 103 |
| 97 IncidentReportingService::ProfileContext::~ProfileContext() { | 104 IncidentReportingService::ProfileContext::~ProfileContext() { |
| 98 } | 105 } |
| 99 | 106 |
| (...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 232 ProfileContextCollection::iterator it = profiles_.find(profile); | 239 ProfileContextCollection::iterator it = profiles_.find(profile); |
| 233 if (it == profiles_.end()) | 240 if (it == profiles_.end()) |
| 234 return; | 241 return; |
| 235 | 242 |
| 236 // TODO(grt): Persist incidents for upload on future profile load. | 243 // TODO(grt): Persist incidents for upload on future profile load. |
| 237 | 244 |
| 238 // Forget about this profile. Incidents not yet sent for upload are lost. | 245 // Forget about this profile. Incidents not yet sent for upload are lost. |
| 239 // No new incidents will be accepted for it. | 246 // No new incidents will be accepted for it. |
| 240 delete it->second; | 247 delete it->second; |
| 241 profiles_.erase(it); | 248 profiles_.erase(it); |
| 249 |
| 250 // Remove the association with this profile from any pending uploads. |
| 251 for (size_t i = 0; i < uploads_.size(); ++i) { |
| 252 UploadContext* upload = uploads_[i]; |
| 253 std::vector<Profile*>::iterator it = |
| 254 std::find(upload->profiles.begin(), upload->profiles.end(), profile); |
| 255 if (it != upload->profiles.end()) { |
| 256 *it = upload->profiles.back(); |
| 257 upload->profiles.resize(upload->profiles.size() - 1); |
| 258 break; |
| 259 } |
| 260 } |
| 242 } | 261 } |
| 243 | 262 |
| 244 void IncidentReportingService::AddIncident( | 263 void IncidentReportingService::AddIncident( |
| 245 Profile* profile, | 264 Profile* profile, |
| 246 scoped_ptr<ClientIncidentReport_IncidentData> incident_data) { | 265 scoped_ptr<ClientIncidentReport_IncidentData> incident_data) { |
| 247 DCHECK(thread_checker_.CalledOnValidThread()); | 266 DCHECK(thread_checker_.CalledOnValidThread()); |
| 248 // Incidents outside the context of a profile are not supported at the moment. | 267 // Incidents outside the context of a profile are not supported at the moment. |
| 249 DCHECK(profile); | 268 DCHECK(profile); |
| 250 | 269 |
| 251 ProfileContext* context = GetProfileContext(profile); | 270 ProfileContext* context = GetProfileContext(profile); |
| (...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 370 | 389 |
| 371 UploadIfCollectionComplete(); | 390 UploadIfCollectionComplete(); |
| 372 } | 391 } |
| 373 | 392 |
| 374 void IncidentReportingService::CollectDownloadDetails( | 393 void IncidentReportingService::CollectDownloadDetails( |
| 375 ClientIncidentReport_DownloadDetails* download_details) { | 394 ClientIncidentReport_DownloadDetails* download_details) { |
| 376 DCHECK(thread_checker_.CalledOnValidThread()); | 395 DCHECK(thread_checker_.CalledOnValidThread()); |
| 377 // TODO(grt): collect download info; http://crbug.com/383042. | 396 // TODO(grt): collect download info; http://crbug.com/383042. |
| 378 } | 397 } |
| 379 | 398 |
| 380 void IncidentReportingService::RecordReportedIncidents() { | |
| 381 // TODO(grt): store state about reported incidents. | |
| 382 } | |
| 383 | |
| 384 void IncidentReportingService::PruneReportedIncidents( | |
| 385 ClientIncidentReport* report) { | |
| 386 // TODO(grt): remove previously reported incidents; http://crbug.com/383043. | |
| 387 } | |
| 388 | |
| 389 void IncidentReportingService::UploadIfCollectionComplete() { | 399 void IncidentReportingService::UploadIfCollectionComplete() { |
| 390 DCHECK(report_); | 400 DCHECK(report_); |
| 391 // Bail out if there are still outstanding collection tasks. | 401 // Bail out if there are still outstanding collection tasks. |
| 392 if (environment_collection_pending_ || collection_timeout_pending_) | 402 if (environment_collection_pending_ || collection_timeout_pending_) |
| 393 return; | 403 return; |
| 394 | 404 |
| 395 // Take ownership of the report and clear things for future reports. | 405 // Take ownership of the report and clear things for future reports. |
| 396 scoped_ptr<ClientIncidentReport> report(report_.Pass()); | 406 scoped_ptr<ClientIncidentReport> report(report_.Pass()); |
| 397 last_incident_time_ = base::TimeTicks(); | 407 last_incident_time_ = base::TimeTicks(); |
| 398 | 408 |
| 399 ClientIncidentReport_EnvironmentData_Process* process = | 409 ClientIncidentReport_EnvironmentData_Process* process = |
| 400 report->mutable_environment()->mutable_process(); | 410 report->mutable_environment()->mutable_process(); |
| 401 | 411 |
| 402 // Not all platforms have a metrics reporting preference. | 412 // Not all platforms have a metrics reporting preference. |
| 403 if (g_browser_process->local_state()->FindPreference( | 413 if (g_browser_process->local_state()->FindPreference( |
| 404 prefs::kMetricsReportingEnabled)) { | 414 prefs::kMetricsReportingEnabled)) { |
| 405 process->set_metrics_consent(g_browser_process->local_state()->GetBoolean( | 415 process->set_metrics_consent(g_browser_process->local_state()->GetBoolean( |
| 406 prefs::kMetricsReportingEnabled)); | 416 prefs::kMetricsReportingEnabled)); |
| 407 } | 417 } |
| 418 |
| 408 // Check for extended consent in any profile while collecting incidents. | 419 // Check for extended consent in any profile while collecting incidents. |
| 409 process->set_extended_consent(false); | 420 process->set_extended_consent(false); |
| 410 // Collect incidents across all profiles participating in safe browsing. Drop | 421 // Collect incidents across all profiles participating in safe browsing. Drop |
| 411 // incidents if the profile stopped participating before collection completed. | 422 // incidents if the profile stopped participating before collection completed. |
| 423 // Prune incidents if the profile has already submitted any incidents. |
| 424 // Associate the participating profiles with the upload. |
| 425 size_t prune_count = 0; |
| 426 std::vector<Profile*> profiles; |
| 412 for (ProfileContextCollection::iterator scan = profiles_.begin(); | 427 for (ProfileContextCollection::iterator scan = profiles_.begin(); |
| 413 scan != profiles_.end(); | 428 scan != profiles_.end(); |
| 414 ++scan) { | 429 ++scan) { |
| 415 PrefService* prefs = scan->first->GetPrefs(); | 430 PrefService* prefs = scan->first->GetPrefs(); |
| 416 if (process && | 431 if (process && |
| 417 prefs->GetBoolean(prefs::kSafeBrowsingExtendedReportingEnabled)) { | 432 prefs->GetBoolean(prefs::kSafeBrowsingExtendedReportingEnabled)) { |
| 418 process->set_extended_consent(true); | 433 process->set_extended_consent(true); |
| 419 process = NULL; // Don't check any more once one is found. | 434 process = NULL; // Don't check any more once one is found. |
| 420 } | 435 } |
| 421 ProfileContext* context = scan->second; | 436 ProfileContext* context = scan->second; |
| 422 if (context->incidents.empty()) | 437 if (context->incidents.empty()) |
| 423 continue; | 438 continue; |
| 424 if (prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) { | 439 if (!prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) { |
| 440 for (size_t i = 0; i < context->incidents.size(); ++i) { |
| 441 LogIncidentDataType(DROPPED, *context->incidents[i]); |
| 442 } |
| 443 context->incidents.clear(); |
| 444 } else if (prefs->GetBoolean(prefs::kSafeBrowsingIncidentReportSent)) { |
| 445 // Prune all incidents. |
| 446 // TODO(grt): Only prune previously submitted incidents; |
| 447 // http://crbug.com/383043. |
| 448 prune_count += context->incidents.size(); |
| 449 context->incidents.clear(); |
| 450 } else { |
| 425 for (size_t i = 0; i < context->incidents.size(); ++i) { | 451 for (size_t i = 0; i < context->incidents.size(); ++i) { |
| 426 ClientIncidentReport_IncidentData* incident = context->incidents[i]; | 452 ClientIncidentReport_IncidentData* incident = context->incidents[i]; |
| 427 LogIncidentDataType(ACCEPTED, *incident); | 453 LogIncidentDataType(ACCEPTED, *incident); |
| 428 // Ownership of the incident is passed to the report. | 454 // Ownership of the incident is passed to the report. |
| 429 report->mutable_incident()->AddAllocated(incident); | 455 report->mutable_incident()->AddAllocated(incident); |
| 430 } | 456 } |
| 431 context->incidents.weak_clear(); | 457 context->incidents.weak_clear(); |
| 432 } else { | 458 profiles.push_back(scan->first); |
| 433 for (size_t i = 0; i < context->incidents.size(); ++i) { | |
| 434 LogIncidentDataType(DROPPED, *context->incidents[i]); | |
| 435 } | |
| 436 context->incidents.clear(); | |
| 437 } | 459 } |
| 438 } | 460 } |
| 439 | 461 |
| 440 const int original_count = report->incident_size(); | 462 const int count = report->incident_size(); |
| 441 // Abandon the request if all incidents were dropped. | 463 // Abandon the request if all incidents were dropped with none pruned. |
| 442 if (!original_count) | 464 if (!count && !prune_count) |
| 443 return; | 465 return; |
| 444 | 466 |
| 445 UMA_HISTOGRAM_COUNTS_100("SBIRS.IncidentCount", original_count); | 467 UMA_HISTOGRAM_COUNTS_100("SBIRS.IncidentCount", count + prune_count); |
| 446 | 468 |
| 447 PruneReportedIncidents(report.get()); | |
| 448 | |
| 449 int final_count = report->incident_size(); | |
| 450 { | 469 { |
| 451 double prune_pct = static_cast<double>(original_count - final_count); | 470 double prune_pct = static_cast<double>(prune_count); |
| 452 prune_pct = prune_pct * 100.0 / original_count; | 471 prune_pct = prune_pct * 100.0 / (count + prune_count); |
| 453 prune_pct = round(prune_pct); | 472 prune_pct = round(prune_pct); |
| 454 UMA_HISTOGRAM_PERCENTAGE("SBIRS.PruneRatio", static_cast<int>(prune_pct)); | 473 UMA_HISTOGRAM_PERCENTAGE("SBIRS.PruneRatio", static_cast<int>(prune_pct)); |
| 455 } | 474 } |
| 456 // Abandon the report if all incidents were pruned. | 475 // Abandon the report if all incidents were pruned. |
| 457 if (!final_count) | 476 if (!count) |
| 458 return; | 477 return; |
| 459 | 478 |
| 460 scoped_ptr<UploadContext> context(new UploadContext(report.Pass())); | 479 scoped_ptr<UploadContext> context(new UploadContext(report.Pass())); |
| 480 context->profiles.swap(profiles); |
| 461 if (!database_manager_) { | 481 if (!database_manager_) { |
| 462 // No database manager during testing. Take ownership of the context and | 482 // No database manager during testing. Take ownership of the context and |
| 463 // continue processing. | 483 // continue processing. |
| 464 UploadContext* temp_context = context.get(); | 484 UploadContext* temp_context = context.get(); |
| 465 uploads_.push_back(context.release()); | 485 uploads_.push_back(context.release()); |
| 466 IncidentReportingService::OnKillSwitchResult(temp_context, false); | 486 IncidentReportingService::OnKillSwitchResult(temp_context, false); |
| 467 } else { | 487 } else { |
| 468 if (content::BrowserThread::PostTaskAndReplyWithResult( | 488 if (content::BrowserThread::PostTaskAndReplyWithResult( |
| 469 content::BrowserThread::IO, | 489 content::BrowserThread::IO, |
| 470 FROM_HERE, | 490 FROM_HERE, |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 504 IncidentReportUploader::UPLOAD_INVALID_REQUEST, | 524 IncidentReportUploader::UPLOAD_INVALID_REQUEST, |
| 505 scoped_ptr<ClientIncidentResponse>()); | 525 scoped_ptr<ClientIncidentResponse>()); |
| 506 } | 526 } |
| 507 } else { | 527 } else { |
| 508 OnReportUploadResult(context, | 528 OnReportUploadResult(context, |
| 509 IncidentReportUploader::UPLOAD_SUPPRESSED, | 529 IncidentReportUploader::UPLOAD_SUPPRESSED, |
| 510 scoped_ptr<ClientIncidentResponse>()); | 530 scoped_ptr<ClientIncidentResponse>()); |
| 511 } | 531 } |
| 512 } | 532 } |
| 513 | 533 |
| 514 void IncidentReportingService::HandleResponse( | 534 void IncidentReportingService::HandleResponse(const UploadContext& context) { |
| 515 scoped_ptr<ClientIncidentReport> report, | 535 for (size_t i = 0; i < context.profiles.size(); ++i) { |
| 516 scoped_ptr<ClientIncidentResponse> response) { | 536 context.profiles[i]->GetPrefs()->SetBoolean( |
| 517 RecordReportedIncidents(); | 537 prefs::kSafeBrowsingIncidentReportSent, true); |
| 538 } |
| 518 } | 539 } |
| 519 | 540 |
| 520 void IncidentReportingService::OnReportUploadResult( | 541 void IncidentReportingService::OnReportUploadResult( |
| 521 UploadContext* context, | 542 UploadContext* context, |
| 522 IncidentReportUploader::Result result, | 543 IncidentReportUploader::Result result, |
| 523 scoped_ptr<ClientIncidentResponse> response) { | 544 scoped_ptr<ClientIncidentResponse> response) { |
| 524 DCHECK(thread_checker_.CalledOnValidThread()); | 545 DCHECK(thread_checker_.CalledOnValidThread()); |
| 525 | 546 |
| 526 UMA_HISTOGRAM_ENUMERATION( | 547 UMA_HISTOGRAM_ENUMERATION( |
| 527 "SBIRS.UploadResult", result, IncidentReportUploader::NUM_UPLOAD_RESULTS); | 548 "SBIRS.UploadResult", result, IncidentReportUploader::NUM_UPLOAD_RESULTS); |
| 528 | 549 |
| 529 // The upload is no longer outstanding, so take ownership of the context (from | 550 // The upload is no longer outstanding, so take ownership of the context (from |
| 530 // the collection of outstanding uploads) in this scope. | 551 // the collection of outstanding uploads) in this scope. |
| 531 ScopedVector<UploadContext>::iterator it( | 552 ScopedVector<UploadContext>::iterator it( |
| 532 std::find(uploads_.begin(), uploads_.end(), context)); | 553 std::find(uploads_.begin(), uploads_.end(), context)); |
| 533 DCHECK(it != uploads_.end()); | 554 DCHECK(it != uploads_.end()); |
| 534 scoped_ptr<UploadContext> upload(context); // == *it | 555 scoped_ptr<UploadContext> upload(context); // == *it |
| 535 *it = uploads_.back(); | 556 *it = uploads_.back(); |
| 536 uploads_.weak_erase(uploads_.end() - 1); | 557 uploads_.weak_erase(uploads_.end() - 1); |
| 537 | 558 |
| 538 if (result == IncidentReportUploader::UPLOAD_SUCCESS) | 559 if (result == IncidentReportUploader::UPLOAD_SUCCESS) |
| 539 HandleResponse(upload->report.Pass(), response.Pass()); | 560 HandleResponse(*upload); |
| 540 // else retry? | 561 // else retry? |
| 541 } | 562 } |
| 542 | 563 |
| 543 void IncidentReportingService::Observe( | 564 void IncidentReportingService::Observe( |
| 544 int type, | 565 int type, |
| 545 const content::NotificationSource& source, | 566 const content::NotificationSource& source, |
| 546 const content::NotificationDetails& details) { | 567 const content::NotificationDetails& details) { |
| 547 switch (type) { | 568 switch (type) { |
| 548 case chrome::NOTIFICATION_PROFILE_CREATED: { | 569 case chrome::NOTIFICATION_PROFILE_CREATED: { |
| 549 Profile* profile = content::Source<Profile>(source).ptr(); | 570 Profile* profile = content::Source<Profile>(source).ptr(); |
| 550 if (!profile->IsOffTheRecord()) | 571 if (!profile->IsOffTheRecord()) |
| 551 OnProfileCreated(profile); | 572 OnProfileCreated(profile); |
| 552 break; | 573 break; |
| 553 } | 574 } |
| 554 case chrome::NOTIFICATION_PROFILE_DESTROYED: { | 575 case chrome::NOTIFICATION_PROFILE_DESTROYED: { |
| 555 Profile* profile = content::Source<Profile>(source).ptr(); | 576 Profile* profile = content::Source<Profile>(source).ptr(); |
| 556 if (!profile->IsOffTheRecord()) | 577 if (!profile->IsOffTheRecord()) |
| 557 OnProfileDestroyed(profile); | 578 OnProfileDestroyed(profile); |
| 558 break; | 579 break; |
| 559 } | 580 } |
| 560 default: | 581 default: |
| 561 break; | 582 break; |
| 562 } | 583 } |
| 563 } | 584 } |
| 564 | 585 |
| 565 } // namespace safe_browsing | 586 } // namespace safe_browsing |
| OLD | NEW |