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
3 // found in the LICENSE file.
5 #include "chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.h"
12 #include "base/metrics/histogram.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/prefs/scoped_user_pref_update.h"
15 #include "base/process/process_info.h"
16 #include "base/single_thread_task_runner.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "base/threading/sequenced_worker_pool.h"
21 #include "base/values.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/chrome_notification_types.h"
24 #include "chrome/browser/prefs/tracked/tracked_preference_validation_delegate.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/safe_browsing/database_manager.h"
27 #include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident_handlers.h"
28 #include "chrome/browser/safe_browsing/incident_reporting/blacklist_load_incident_handlers.h"
29 #include "chrome/browser/safe_browsing/incident_reporting/environment_data_collection.h"
30 #include "chrome/browser/safe_browsing/incident_reporting/incident_report_uploader_impl.h"
31 #include "chrome/browser/safe_browsing/incident_reporting/preference_validation_delegate.h"
32 #include "chrome/browser/safe_browsing/incident_reporting/tracked_preference_incident_handlers.h"
33 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
34 #include "chrome/common/pref_names.h"
35 #include "chrome/common/safe_browsing/csd.pb.h"
36 #include "content/public/browser/browser_thread.h"
37 #include "content/public/browser/notification_service.h"
38 #include "net/url_request/url_request_context_getter.h"
40 namespace safe_browsing {
44 // The type of an incident. Used for user metrics and for pruning of
45 // previously-reported incidents.
47 // Start with 1 rather than zero; otherwise there won't be enough buckets for
49 TRACKED_PREFERENCE = 1,
52 // Values for new incident types go here.
53 NUM_INCIDENT_TYPES = 4
56 // The action taken for an incident; used for user metrics (see
57 // LogIncidentDataType).
58 enum IncidentDisposition {
63 // The state persisted for a specific instance of an incident to enable pruning
64 // of previously-reported incidents.
65 struct PersistentIncidentState {
66 // The type of the incident.
69 // The key for a specific instance of an incident.
72 // A hash digest representing a specific instance of an incident.
76 // The amount of time the service will wait to collate incidents.
77 const int64 kDefaultUploadDelayMs = 1000 * 60; // one minute
79 // The amount of time between running delayed analysis callbacks.
80 const int64 kDefaultCallbackIntervalMs = 1000 * 20;
82 // Returns the number of incidents contained in |incident|. The result is
83 // expected to be 1. Used in DCHECKs.
84 size_t CountIncidents(const ClientIncidentReport_IncidentData& incident) {
86 if (incident.has_tracked_preference())
88 if (incident.has_binary_integrity())
90 if (incident.has_blacklist_load())
92 // Add detection for new incident types here.
96 // Returns the type of incident contained in |incident_data|.
97 IncidentType GetIncidentType(
98 const ClientIncidentReport_IncidentData& incident_data) {
99 if (incident_data.has_tracked_preference())
100 return TRACKED_PREFERENCE;
101 if (incident_data.has_binary_integrity())
102 return BINARY_INTEGRITY;
103 if (incident_data.has_blacklist_load())
104 return BLACKLIST_LOAD;
106 // Add detection for new incident types here.
107 COMPILE_ASSERT(BLACKLIST_LOAD + 1 == NUM_INCIDENT_TYPES,
108 add_support_for_new_types);
110 return NUM_INCIDENT_TYPES;
113 // Logs the type of incident in |incident_data| to a user metrics histogram.
114 void LogIncidentDataType(
115 IncidentDisposition disposition,
116 const ClientIncidentReport_IncidentData& incident_data) {
117 IncidentType type = GetIncidentType(incident_data);
118 if (disposition == ACCEPTED) {
119 UMA_HISTOGRAM_ENUMERATION("SBIRS.Incident", type, NUM_INCIDENT_TYPES);
121 DCHECK_EQ(disposition, DROPPED);
122 UMA_HISTOGRAM_ENUMERATION("SBIRS.DroppedIncident", type,
127 // Computes the persistent state for an incident.
128 PersistentIncidentState ComputeIncidentState(
129 const ClientIncidentReport_IncidentData& incident) {
130 PersistentIncidentState state = {GetIncidentType(incident)};
131 switch (state.type) {
132 case TRACKED_PREFERENCE:
133 state.key = GetTrackedPreferenceIncidentKey(incident);
134 state.digest = GetTrackedPreferenceIncidentDigest(incident);
136 case BINARY_INTEGRITY:
137 state.key = GetBinaryIntegrityIncidentKey(incident);
138 state.digest = GetBinaryIntegrityIncidentDigest(incident);
141 state.key = GetBlacklistLoadIncidentKey(incident);
142 state.digest = GetBlacklistLoadIncidentDigest(incident);
144 // Add handling for new incident types here.
146 COMPILE_ASSERT(BLACKLIST_LOAD + 1 == NUM_INCIDENT_TYPES,
147 add_support_for_new_types);
154 // Returns true if the incident described by |state| has already been reported
155 // based on the bookkeeping in the |incidents_sent| preference dictionary.
156 bool IncidentHasBeenReported(const base::DictionaryValue* incidents_sent,
157 const PersistentIncidentState& state) {
158 const base::DictionaryValue* type_dict = NULL;
159 std::string digest_string;
160 return (incidents_sent &&
161 incidents_sent->GetDictionaryWithoutPathExpansion(
162 base::IntToString(state.type), &type_dict) &&
163 type_dict->GetStringWithoutPathExpansion(state.key, &digest_string) &&
164 digest_string == base::UintToString(state.digest));
167 // Marks the incidents described by |states| as having been reported
168 // in |incidents_set|.
169 void MarkIncidentsAsReported(const std::vector<PersistentIncidentState>& states,
170 base::DictionaryValue* incidents_sent) {
171 for (size_t i = 0; i < states.size(); ++i) {
172 const PersistentIncidentState& data = states[i];
173 base::DictionaryValue* type_dict = NULL;
174 const std::string type_string(base::IntToString(data.type));
175 if (!incidents_sent->GetDictionaryWithoutPathExpansion(type_string,
177 type_dict = new base::DictionaryValue();
178 incidents_sent->SetWithoutPathExpansion(type_string, type_dict);
180 type_dict->SetStringWithoutPathExpansion(data.key,
181 base::UintToString(data.digest));
185 // Runs |callback| on the thread to which |thread_runner| belongs. The callback
186 // is run immediately if this function is called on |thread_runner|'s thread.
187 void AddIncidentOnOriginThread(
188 const AddIncidentCallback& callback,
189 scoped_refptr<base::SingleThreadTaskRunner> thread_runner,
190 scoped_ptr<ClientIncidentReport_IncidentData> incident) {
191 if (thread_runner->BelongsToCurrentThread())
192 callback.Run(incident.Pass());
194 thread_runner->PostTask(FROM_HERE,
195 base::Bind(callback, base::Passed(&incident)));
200 struct IncidentReportingService::ProfileContext {
204 // The incidents collected for this profile pending creation and/or upload.
205 ScopedVector<ClientIncidentReport_IncidentData> incidents;
207 // False until PROFILE_ADDED notification is received.
211 DISALLOW_COPY_AND_ASSIGN(ProfileContext);
214 class IncidentReportingService::UploadContext {
216 typedef std::map<Profile*, std::vector<PersistentIncidentState> >
217 PersistentIncidentStateCollection;
219 explicit UploadContext(scoped_ptr<ClientIncidentReport> report);
222 // The report being uploaded.
223 scoped_ptr<ClientIncidentReport> report;
225 // The uploader in use. This is NULL until the CSD killswitch is checked.
226 scoped_ptr<IncidentReportUploader> uploader;
228 // A mapping of profiles to the data to be persisted upon successful upload.
229 PersistentIncidentStateCollection profiles_to_state;
232 DISALLOW_COPY_AND_ASSIGN(UploadContext);
235 IncidentReportingService::ProfileContext::ProfileContext() : added() {
238 IncidentReportingService::ProfileContext::~ProfileContext() {
241 IncidentReportingService::UploadContext::UploadContext(
242 scoped_ptr<ClientIncidentReport> report)
243 : report(report.Pass()) {
246 IncidentReportingService::UploadContext::~UploadContext() {
249 IncidentReportingService::IncidentReportingService(
250 SafeBrowsingService* safe_browsing_service,
251 const scoped_refptr<net::URLRequestContextGetter>& request_context_getter)
252 : database_manager_(safe_browsing_service ?
253 safe_browsing_service->database_manager() : NULL),
254 url_request_context_getter_(request_context_getter),
255 collect_environment_data_fn_(&CollectEnvironmentData),
256 environment_collection_task_runner_(
257 content::BrowserThread::GetBlockingPool()
258 ->GetTaskRunnerWithShutdownBehavior(
259 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
260 environment_collection_pending_(),
261 collation_timeout_pending_(),
262 collation_timer_(FROM_HERE,
263 base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs),
265 &IncidentReportingService::OnCollationTimeout),
266 delayed_analysis_callbacks_(
267 base::TimeDelta::FromMilliseconds(kDefaultCallbackIntervalMs),
268 content::BrowserThread::GetBlockingPool()
269 ->GetTaskRunnerWithShutdownBehavior(
270 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
271 receiver_weak_ptr_factory_(this),
272 weak_ptr_factory_(this) {
273 notification_registrar_.Add(this,
274 chrome::NOTIFICATION_PROFILE_ADDED,
275 content::NotificationService::AllSources());
276 notification_registrar_.Add(this,
277 chrome::NOTIFICATION_PROFILE_DESTROYED,
278 content::NotificationService::AllSources());
281 IncidentReportingService::~IncidentReportingService() {
282 CancelIncidentCollection();
284 // Cancel all internal asynchronous tasks.
285 weak_ptr_factory_.InvalidateWeakPtrs();
287 CancelEnvironmentCollection();
288 CancelDownloadCollection();
289 CancelAllReportUploads();
291 STLDeleteValues(&profiles_);
294 AddIncidentCallback IncidentReportingService::GetAddIncidentCallback(
296 // Force the context to be created so that incidents added before
297 // OnProfileAdded is called are held until the profile's preferences can be
299 ignore_result(GetOrCreateProfileContext(profile));
301 return base::Bind(&IncidentReportingService::AddIncident,
302 receiver_weak_ptr_factory_.GetWeakPtr(),
306 scoped_ptr<TrackedPreferenceValidationDelegate>
307 IncidentReportingService::CreatePreferenceValidationDelegate(Profile* profile) {
308 DCHECK(thread_checker_.CalledOnValidThread());
310 if (profile->IsOffTheRecord())
311 return scoped_ptr<TrackedPreferenceValidationDelegate>();
312 return scoped_ptr<TrackedPreferenceValidationDelegate>(
313 new PreferenceValidationDelegate(GetAddIncidentCallback(profile)));
316 void IncidentReportingService::RegisterDelayedAnalysisCallback(
317 const DelayedAnalysisCallback& callback) {
318 DCHECK(thread_checker_.CalledOnValidThread());
320 // |callback| will be run on the blocking pool, so it will likely run the
321 // AddIncidentCallback there as well. Bounce the run of that callback back to
322 // the current thread via AddIncidentOnOriginThread.
323 delayed_analysis_callbacks_.RegisterCallback(
325 base::Bind(&AddIncidentOnOriginThread,
326 GetAddIncidentCallback(NULL),
327 base::ThreadTaskRunnerHandle::Get())));
329 // Start running the callbacks if any profiles are participating in safe
330 // browsing. If none are now, running will commence if/when a participaing
332 if (FindEligibleProfile())
333 delayed_analysis_callbacks_.Start();
336 IncidentReportingService::IncidentReportingService(
337 SafeBrowsingService* safe_browsing_service,
338 const scoped_refptr<net::URLRequestContextGetter>& request_context_getter,
339 base::TimeDelta delayed_task_interval,
340 const scoped_refptr<base::TaskRunner>& delayed_task_runner)
341 : database_manager_(safe_browsing_service ?
342 safe_browsing_service->database_manager() : NULL),
343 url_request_context_getter_(request_context_getter),
344 collect_environment_data_fn_(&CollectEnvironmentData),
345 environment_collection_task_runner_(
346 content::BrowserThread::GetBlockingPool()
347 ->GetTaskRunnerWithShutdownBehavior(
348 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
349 environment_collection_pending_(),
350 collation_timeout_pending_(),
351 collation_timer_(FROM_HERE,
352 base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs),
354 &IncidentReportingService::OnCollationTimeout),
355 delayed_analysis_callbacks_(delayed_task_interval, delayed_task_runner),
356 receiver_weak_ptr_factory_(this),
357 weak_ptr_factory_(this) {
358 notification_registrar_.Add(this,
359 chrome::NOTIFICATION_PROFILE_ADDED,
360 content::NotificationService::AllSources());
361 notification_registrar_.Add(this,
362 chrome::NOTIFICATION_PROFILE_DESTROYED,
363 content::NotificationService::AllSources());
366 void IncidentReportingService::SetCollectEnvironmentHook(
367 CollectEnvironmentDataFn collect_environment_data_hook,
368 const scoped_refptr<base::TaskRunner>& task_runner) {
369 if (collect_environment_data_hook) {
370 collect_environment_data_fn_ = collect_environment_data_hook;
371 environment_collection_task_runner_ = task_runner;
373 collect_environment_data_fn_ = &CollectEnvironmentData;
374 environment_collection_task_runner_ =
375 content::BrowserThread::GetBlockingPool()
376 ->GetTaskRunnerWithShutdownBehavior(
377 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
381 void IncidentReportingService::OnProfileAdded(Profile* profile) {
382 DCHECK(thread_checker_.CalledOnValidThread());
384 // Track the addition of all profiles even when no report is being assembled
385 // so that the service can determine whether or not it can evaluate a
386 // profile's preferences at the time of incident addition.
387 ProfileContext* context = GetOrCreateProfileContext(profile);
388 context->added = true;
390 const bool safe_browsing_enabled =
391 profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled);
393 // Start processing delayed analysis callbacks if this new profile
394 // participates in safe browsing. Start is idempotent, so this is safe even if
395 // they're already running.
396 if (safe_browsing_enabled)
397 delayed_analysis_callbacks_.Start();
399 // Start a new report if this profile participates in safe browsing and there
400 // are process-wide incidents.
401 if (safe_browsing_enabled && GetProfileContext(NULL) &&
402 GetProfileContext(NULL)->incidents.size()) {
403 BeginReportProcessing();
406 // TODO(grt): register for pref change notifications to start delayed analysis
407 // and/or report processing if sb is currently disabled but subsequently
410 // Nothing else to do if a report is not being assembled.
414 // Drop all incidents associated with this profile that were received prior to
415 // its addition if the profile is not participating in safe browsing.
416 if (!context->incidents.empty() && !safe_browsing_enabled) {
417 for (size_t i = 0; i < context->incidents.size(); ++i)
418 LogIncidentDataType(DROPPED, *context->incidents[i]);
419 context->incidents.clear();
422 // Take another stab at finding the most recent download if a report is being
423 // assembled and one hasn't been found yet (the LastDownloadFinder operates
424 // only on profiles that have been added to the ProfileManager).
425 BeginDownloadCollection();
428 scoped_ptr<LastDownloadFinder> IncidentReportingService::CreateDownloadFinder(
429 const LastDownloadFinder::LastDownloadCallback& callback) {
430 return LastDownloadFinder::Create(callback).Pass();
433 scoped_ptr<IncidentReportUploader> IncidentReportingService::StartReportUpload(
434 const IncidentReportUploader::OnResultCallback& callback,
435 const scoped_refptr<net::URLRequestContextGetter>& request_context_getter,
436 const ClientIncidentReport& report) {
437 return IncidentReportUploaderImpl::UploadReport(
438 callback, request_context_getter, report).Pass();
441 bool IncidentReportingService::IsProcessingReport() const {
442 return report_ != NULL;
445 IncidentReportingService::ProfileContext*
446 IncidentReportingService::GetOrCreateProfileContext(Profile* profile) {
447 ProfileContextCollection::iterator it =
448 profiles_.insert(ProfileContextCollection::value_type(profile, NULL))
451 it->second = new ProfileContext();
455 IncidentReportingService::ProfileContext*
456 IncidentReportingService::GetProfileContext(Profile* profile) {
457 ProfileContextCollection::iterator it = profiles_.find(profile);
458 return it == profiles_.end() ? NULL : it->second;
461 void IncidentReportingService::OnProfileDestroyed(Profile* profile) {
462 DCHECK(thread_checker_.CalledOnValidThread());
464 ProfileContextCollection::iterator it = profiles_.find(profile);
465 if (it == profiles_.end())
468 // TODO(grt): Persist incidents for upload on future profile load.
470 // Forget about this profile. Incidents not yet sent for upload are lost.
471 // No new incidents will be accepted for it.
475 // Remove the association with this profile from all pending uploads.
476 for (size_t i = 0; i < uploads_.size(); ++i)
477 uploads_[i]->profiles_to_state.erase(profile);
480 Profile* IncidentReportingService::FindEligibleProfile() const {
481 Profile* candidate = NULL;
482 for (ProfileContextCollection::const_iterator scan = profiles_.begin();
483 scan != profiles_.end();
485 // Skip over profiles that have yet to be added to the profile manager.
486 // This will also skip over the NULL-profile context used to hold
487 // process-wide incidents.
488 if (!scan->second->added)
490 PrefService* prefs = scan->first->GetPrefs();
491 if (prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) {
493 candidate = scan->first;
494 if (prefs->GetBoolean(prefs::kSafeBrowsingExtendedReportingEnabled)) {
495 candidate = scan->first;
503 void IncidentReportingService::AddIncident(
505 scoped_ptr<ClientIncidentReport_IncidentData> incident_data) {
506 DCHECK(thread_checker_.CalledOnValidThread());
507 DCHECK_EQ(1U, CountIncidents(*incident_data));
509 ProfileContext* context = GetProfileContext(profile);
510 // It is forbidden to call this function with a destroyed profile.
512 // If this is a process-wide incident, the context must not indicate that the
513 // profile (which is NULL) has been added to the profile manager.
514 DCHECK(profile || !context->added);
516 // Drop the incident immediately if the profile has already been added to the
517 // manager and is not participating in safe browsing. Preference evaluation is
518 // deferred until OnProfileAdded() otherwise.
519 if (context->added &&
520 !profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
521 LogIncidentDataType(DROPPED, *incident_data);
525 // Provide time to the new incident if the caller didn't do so.
526 if (!incident_data->has_incident_time_msec())
527 incident_data->set_incident_time_msec(base::Time::Now().ToJavaTime());
529 // Take ownership of the incident.
530 context->incidents.push_back(incident_data.release());
532 // Remember when the first incident for this report arrived.
533 if (first_incident_time_.is_null())
534 first_incident_time_ = base::Time::Now();
535 // Log the time between the previous incident and this one.
536 if (!last_incident_time_.is_null()) {
537 UMA_HISTOGRAM_TIMES("SBIRS.InterIncidentTime",
538 base::TimeTicks::Now() - last_incident_time_);
540 last_incident_time_ = base::TimeTicks::Now();
542 // Persist the incident data.
544 // Start assembling a new report if this is the first incident ever or the
545 // first since the last upload.
546 BeginReportProcessing();
549 void IncidentReportingService::BeginReportProcessing() {
550 DCHECK(thread_checker_.CalledOnValidThread());
552 // Creates a new report if needed.
554 report_.reset(new ClientIncidentReport());
556 // Ensure that collection tasks are running (calls are idempotent).
557 BeginIncidentCollation();
558 BeginEnvironmentCollection();
559 BeginDownloadCollection();
562 void IncidentReportingService::BeginIncidentCollation() {
563 // Restart the delay timer to send the report upon expiration.
564 collation_timeout_pending_ = true;
565 collation_timer_.Reset();
568 void IncidentReportingService::BeginEnvironmentCollection() {
569 DCHECK(thread_checker_.CalledOnValidThread());
571 // Nothing to do if environment collection is pending or has already
573 if (environment_collection_pending_ || report_->has_environment())
576 environment_collection_begin_ = base::TimeTicks::Now();
577 ClientIncidentReport_EnvironmentData* environment_data =
578 new ClientIncidentReport_EnvironmentData();
579 environment_collection_pending_ =
580 environment_collection_task_runner_->PostTaskAndReply(
582 base::Bind(collect_environment_data_fn_, environment_data),
583 base::Bind(&IncidentReportingService::OnEnvironmentDataCollected,
584 weak_ptr_factory_.GetWeakPtr(),
585 base::Passed(make_scoped_ptr(environment_data))));
587 // Posting the task will fail if the runner has been shut down. This should
588 // never happen since the blocking pool is shut down after this service.
589 DCHECK(environment_collection_pending_);
592 bool IncidentReportingService::WaitingForEnvironmentCollection() {
593 return environment_collection_pending_;
596 void IncidentReportingService::CancelEnvironmentCollection() {
597 environment_collection_begin_ = base::TimeTicks();
598 environment_collection_pending_ = false;
600 report_->clear_environment();
603 void IncidentReportingService::OnEnvironmentDataCollected(
604 scoped_ptr<ClientIncidentReport_EnvironmentData> environment_data) {
605 DCHECK(thread_checker_.CalledOnValidThread());
606 DCHECK(environment_collection_pending_);
607 DCHECK(report_ && !report_->has_environment());
608 environment_collection_pending_ = false;
610 // CurrentProcessInfo::CreationTime() is missing on some platforms.
611 #if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
612 base::TimeDelta uptime =
613 first_incident_time_ - base::CurrentProcessInfo::CreationTime();
614 environment_data->mutable_process()->set_uptime_msec(uptime.InMilliseconds());
617 report_->set_allocated_environment(environment_data.release());
619 UMA_HISTOGRAM_TIMES("SBIRS.EnvCollectionTime",
620 base::TimeTicks::Now() - environment_collection_begin_);
621 environment_collection_begin_ = base::TimeTicks();
623 UploadIfCollectionComplete();
626 bool IncidentReportingService::WaitingToCollateIncidents() {
627 return collation_timeout_pending_;
630 void IncidentReportingService::CancelIncidentCollection() {
631 collation_timeout_pending_ = false;
632 last_incident_time_ = base::TimeTicks();
636 void IncidentReportingService::OnCollationTimeout() {
637 DCHECK(thread_checker_.CalledOnValidThread());
639 // Exit early if collection was cancelled.
640 if (!collation_timeout_pending_)
643 // Wait another round if profile-bound incidents have come in from a profile
644 // that has yet to complete creation.
645 for (ProfileContextCollection::iterator scan = profiles_.begin();
646 scan != profiles_.end();
648 if (scan->first && !scan->second->added &&
649 !scan->second->incidents.empty()) {
650 collation_timer_.Reset();
655 collation_timeout_pending_ = false;
657 UploadIfCollectionComplete();
660 void IncidentReportingService::BeginDownloadCollection() {
661 DCHECK(thread_checker_.CalledOnValidThread());
663 // Nothing to do if a search for the most recent download is already pending
664 // or if one has already been found.
665 if (last_download_finder_ || report_->has_download())
668 last_download_begin_ = base::TimeTicks::Now();
669 last_download_finder_ = CreateDownloadFinder(
670 base::Bind(&IncidentReportingService::OnLastDownloadFound,
671 weak_ptr_factory_.GetWeakPtr()));
672 // No instance is returned if there are no eligible loaded profiles. Another
673 // search will be attempted in OnProfileAdded() if another profile appears on
675 if (!last_download_finder_)
676 last_download_begin_ = base::TimeTicks();
679 bool IncidentReportingService::WaitingForMostRecentDownload() {
680 DCHECK(report_); // Only call this when a report is being assembled.
681 // The easy case: not waiting if a download has already been found.
682 if (report_->has_download())
684 // The next easy case: waiting if the finder is operating.
685 if (last_download_finder_)
687 // The harder case: waiting if a non-NULL profile has not yet been added.
688 for (ProfileContextCollection::const_iterator scan = profiles_.begin();
689 scan != profiles_.end();
691 if (scan->first && !scan->second->added)
694 // There is no most recent download and there's nothing more to wait for.
698 void IncidentReportingService::CancelDownloadCollection() {
699 last_download_finder_.reset();
700 last_download_begin_ = base::TimeTicks();
702 report_->clear_download();
705 void IncidentReportingService::OnLastDownloadFound(
706 scoped_ptr<ClientIncidentReport_DownloadDetails> last_download) {
707 DCHECK(thread_checker_.CalledOnValidThread());
710 UMA_HISTOGRAM_TIMES("SBIRS.FindDownloadedBinaryTime",
711 base::TimeTicks::Now() - last_download_begin_);
712 last_download_begin_ = base::TimeTicks();
714 // Harvest the finder.
715 last_download_finder_.reset();
718 report_->set_allocated_download(last_download.release());
720 UploadIfCollectionComplete();
723 void IncidentReportingService::UploadIfCollectionComplete() {
725 // Bail out if there are still outstanding collection tasks. Completion of any
726 // of these will start another upload attempt.
727 if (WaitingForEnvironmentCollection() ||
728 WaitingToCollateIncidents() ||
729 WaitingForMostRecentDownload()) {
733 // Take ownership of the report and clear things for future reports.
734 scoped_ptr<ClientIncidentReport> report(report_.Pass());
735 first_incident_time_ = base::Time();
736 last_incident_time_ = base::TimeTicks();
738 // Drop the report if no executable download was found.
739 if (!report->has_download()) {
740 UMA_HISTOGRAM_ENUMERATION("SBIRS.UploadResult",
741 IncidentReportUploader::UPLOAD_NO_DOWNLOAD,
742 IncidentReportUploader::NUM_UPLOAD_RESULTS);
746 ClientIncidentReport_EnvironmentData_Process* process =
747 report->mutable_environment()->mutable_process();
749 // Not all platforms have a metrics reporting preference.
750 if (g_browser_process->local_state()->FindPreference(
751 prefs::kMetricsReportingEnabled)) {
752 process->set_metrics_consent(g_browser_process->local_state()->GetBoolean(
753 prefs::kMetricsReportingEnabled));
756 // Find the profile that benefits from the strongest protections.
757 Profile* eligible_profile = FindEligibleProfile();
758 process->set_extended_consent(
759 eligible_profile ? eligible_profile->GetPrefs()->GetBoolean(
760 prefs::kSafeBrowsingExtendedReportingEnabled) :
763 // Associate process-wide incidents with the profile that benefits from the
764 // strongest safe browsing protections.
765 ProfileContext* null_context = GetProfileContext(NULL);
766 if (null_context && !null_context->incidents.empty() && eligible_profile) {
767 ProfileContext* eligible_context = GetProfileContext(eligible_profile);
768 // Move the incidents to the target context.
769 eligible_context->incidents.insert(eligible_context->incidents.end(),
770 null_context->incidents.begin(),
771 null_context->incidents.end());
772 null_context->incidents.weak_clear();
775 // Collect incidents across all profiles participating in safe browsing. Drop
776 // incidents if the profile stopped participating before collection completed.
777 // Prune previously submitted incidents.
778 // Associate the profiles and their incident data with the upload.
779 size_t prune_count = 0;
780 UploadContext::PersistentIncidentStateCollection profiles_to_state;
781 for (ProfileContextCollection::iterator scan = profiles_.begin();
782 scan != profiles_.end();
784 // Bypass process-wide incidents that have not yet been associated with a
788 PrefService* prefs = scan->first->GetPrefs();
789 ProfileContext* context = scan->second;
790 if (context->incidents.empty())
792 if (!prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) {
793 for (size_t i = 0; i < context->incidents.size(); ++i) {
794 LogIncidentDataType(DROPPED, *context->incidents[i]);
796 context->incidents.clear();
799 std::vector<PersistentIncidentState> states;
800 const base::DictionaryValue* incidents_sent =
801 prefs->GetDictionary(prefs::kSafeBrowsingIncidentsSent);
802 // Prep persistent data and prune any incidents already sent.
803 for (size_t i = 0; i < context->incidents.size(); ++i) {
804 ClientIncidentReport_IncidentData* incident = context->incidents[i];
805 const PersistentIncidentState state = ComputeIncidentState(*incident);
806 if (IncidentHasBeenReported(incidents_sent, state)) {
808 delete context->incidents[i];
809 context->incidents[i] = NULL;
811 states.push_back(state);
814 if (prefs->GetBoolean(prefs::kSafeBrowsingIncidentReportSent)) {
815 // Prune all incidents as if they had been reported, migrating to the new
816 // technique. TODO(grt): remove this branch after it has shipped.
817 for (size_t i = 0; i < context->incidents.size(); ++i) {
818 if (context->incidents[i])
821 context->incidents.clear();
822 prefs->ClearPref(prefs::kSafeBrowsingIncidentReportSent);
823 DictionaryPrefUpdate pref_update(prefs,
824 prefs::kSafeBrowsingIncidentsSent);
825 MarkIncidentsAsReported(states, pref_update.Get());
827 for (size_t i = 0; i < context->incidents.size(); ++i) {
828 ClientIncidentReport_IncidentData* incident = context->incidents[i];
830 LogIncidentDataType(ACCEPTED, *incident);
831 // Ownership of the incident is passed to the report.
832 report->mutable_incident()->AddAllocated(incident);
835 context->incidents.weak_clear();
836 std::vector<PersistentIncidentState>& profile_states =
837 profiles_to_state[scan->first];
838 profile_states.swap(states);
842 const int count = report->incident_size();
843 // Abandon the request if all incidents were dropped with none pruned.
844 if (!count && !prune_count)
847 UMA_HISTOGRAM_COUNTS_100("SBIRS.IncidentCount", count + prune_count);
850 double prune_pct = static_cast<double>(prune_count);
851 prune_pct = prune_pct * 100.0 / (count + prune_count);
852 prune_pct = round(prune_pct);
853 UMA_HISTOGRAM_PERCENTAGE("SBIRS.PruneRatio", static_cast<int>(prune_pct));
855 // Abandon the report if all incidents were pruned.
859 scoped_ptr<UploadContext> context(new UploadContext(report.Pass()));
860 context->profiles_to_state.swap(profiles_to_state);
861 if (!database_manager_.get()) {
862 // No database manager during testing. Take ownership of the context and
863 // continue processing.
864 UploadContext* temp_context = context.get();
865 uploads_.push_back(context.release());
866 IncidentReportingService::OnKillSwitchResult(temp_context, false);
868 if (content::BrowserThread::PostTaskAndReplyWithResult(
869 content::BrowserThread::IO,
871 base::Bind(&SafeBrowsingDatabaseManager::IsCsdWhitelistKillSwitchOn,
873 base::Bind(&IncidentReportingService::OnKillSwitchResult,
874 weak_ptr_factory_.GetWeakPtr(),
876 uploads_.push_back(context.release());
877 } // else should not happen. Let the context be deleted automatically.
881 void IncidentReportingService::CancelAllReportUploads() {
882 for (size_t i = 0; i < uploads_.size(); ++i) {
883 UMA_HISTOGRAM_ENUMERATION("SBIRS.UploadResult",
884 IncidentReportUploader::UPLOAD_CANCELLED,
885 IncidentReportUploader::NUM_UPLOAD_RESULTS);
890 void IncidentReportingService::OnKillSwitchResult(UploadContext* context,
891 bool is_killswitch_on) {
892 DCHECK(thread_checker_.CalledOnValidThread());
893 if (!is_killswitch_on) {
894 // Initiate the upload.
897 base::Bind(&IncidentReportingService::OnReportUploadResult,
898 weak_ptr_factory_.GetWeakPtr(),
900 url_request_context_getter_,
901 *context->report).Pass();
902 if (!context->uploader) {
903 OnReportUploadResult(context,
904 IncidentReportUploader::UPLOAD_INVALID_REQUEST,
905 scoped_ptr<ClientIncidentResponse>());
908 OnReportUploadResult(context,
909 IncidentReportUploader::UPLOAD_SUPPRESSED,
910 scoped_ptr<ClientIncidentResponse>());
914 void IncidentReportingService::HandleResponse(const UploadContext& context) {
915 for (UploadContext::PersistentIncidentStateCollection::const_iterator scan =
916 context.profiles_to_state.begin();
917 scan != context.profiles_to_state.end();
919 DictionaryPrefUpdate pref_update(scan->first->GetPrefs(),
920 prefs::kSafeBrowsingIncidentsSent);
921 MarkIncidentsAsReported(scan->second, pref_update.Get());
925 void IncidentReportingService::OnReportUploadResult(
926 UploadContext* context,
927 IncidentReportUploader::Result result,
928 scoped_ptr<ClientIncidentResponse> response) {
929 DCHECK(thread_checker_.CalledOnValidThread());
931 UMA_HISTOGRAM_ENUMERATION(
932 "SBIRS.UploadResult", result, IncidentReportUploader::NUM_UPLOAD_RESULTS);
934 // The upload is no longer outstanding, so take ownership of the context (from
935 // the collection of outstanding uploads) in this scope.
936 ScopedVector<UploadContext>::iterator it(
937 std::find(uploads_.begin(), uploads_.end(), context));
938 DCHECK(it != uploads_.end());
939 scoped_ptr<UploadContext> upload(context); // == *it
940 *it = uploads_.back();
941 uploads_.weak_erase(uploads_.end() - 1);
943 if (result == IncidentReportUploader::UPLOAD_SUCCESS)
944 HandleResponse(*upload);
948 void IncidentReportingService::Observe(
950 const content::NotificationSource& source,
951 const content::NotificationDetails& details) {
953 case chrome::NOTIFICATION_PROFILE_ADDED: {
954 Profile* profile = content::Source<Profile>(source).ptr();
955 if (!profile->IsOffTheRecord())
956 OnProfileAdded(profile);
959 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
960 Profile* profile = content::Source<Profile>(source).ptr();
961 if (!profile->IsOffTheRecord())
962 OnProfileDestroyed(profile);
970 } // namespace safe_browsing