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/environment_data_collection.h"
29 #include "chrome/browser/safe_browsing/incident_reporting/incident_report_uploader_impl.h"
30 #include "chrome/browser/safe_browsing/incident_reporting/preference_validation_delegate.h"
31 #include "chrome/browser/safe_browsing/incident_reporting/tracked_preference_incident_handlers.h"
32 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
33 #include "chrome/common/pref_names.h"
34 #include "chrome/common/safe_browsing/csd.pb.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "content/public/browser/notification_service.h"
37 #include "net/url_request/url_request_context_getter.h"
39 namespace safe_browsing {
43 // The type of an incident. Used for user metrics and for pruning of
44 // previously-reported incidents.
46 // Start with 1 rather than zero; otherwise there won't be enough buckets for
48 TRACKED_PREFERENCE = 1,
50 // Values for new incident types go here.
54 // The action taken for an incident; used for user metrics (see
55 // LogIncidentDataType).
56 enum IncidentDisposition {
61 // The state persisted for a specific instance of an incident to enable pruning
62 // of previously-reported incidents.
63 struct PersistentIncidentState {
64 // The type of the incident.
67 // The key for a specific instance of an incident.
70 // A hash digest representing a specific instance of an incident.
74 // The amount of time the service will wait to collate incidents.
75 const int64 kDefaultUploadDelayMs = 1000 * 60; // one minute
77 // The amount of time between running delayed analysis callbacks.
78 const int64 kDefaultCallbackIntervalMs = 1000 * 20;
80 // Returns the number of incidents contained in |incident|. The result is
81 // expected to be 1. Used in DCHECKs.
82 size_t CountIncidents(const ClientIncidentReport_IncidentData& incident) {
84 if (incident.has_tracked_preference())
86 if (incident.has_binary_integrity())
88 // Add detection for new incident types here.
92 // Returns the type of incident contained in |incident_data|.
93 IncidentType GetIncidentType(
94 const ClientIncidentReport_IncidentData& incident_data) {
95 if (incident_data.has_tracked_preference())
96 return TRACKED_PREFERENCE;
97 if (incident_data.has_binary_integrity())
98 return BINARY_INTEGRITY;
100 // Add detection for new incident types here.
101 COMPILE_ASSERT(BINARY_INTEGRITY + 1 == NUM_INCIDENT_TYPES,
102 add_support_for_new_types);
104 return NUM_INCIDENT_TYPES;
107 // Logs the type of incident in |incident_data| to a user metrics histogram.
108 void LogIncidentDataType(
109 IncidentDisposition disposition,
110 const ClientIncidentReport_IncidentData& incident_data) {
111 IncidentType type = GetIncidentType(incident_data);
112 if (disposition == ACCEPTED) {
113 UMA_HISTOGRAM_ENUMERATION("SBIRS.Incident", type, NUM_INCIDENT_TYPES);
115 DCHECK_EQ(disposition, DROPPED);
116 UMA_HISTOGRAM_ENUMERATION("SBIRS.DroppedIncident", type,
121 // Computes the persistent state for an incident.
122 PersistentIncidentState ComputeIncidentState(
123 const ClientIncidentReport_IncidentData& incident) {
124 PersistentIncidentState state = {GetIncidentType(incident)};
125 switch (state.type) {
126 case TRACKED_PREFERENCE:
127 state.key = GetTrackedPreferenceIncidentKey(incident);
128 state.digest = GetTrackedPreferenceIncidentDigest(incident);
130 case BINARY_INTEGRITY:
131 state.key = GetBinaryIntegrityIncidentKey(incident);
132 state.digest = GetBinaryIntegrityIncidentDigest(incident);
134 // Add handling for new incident types here.
136 COMPILE_ASSERT(BINARY_INTEGRITY + 1 == NUM_INCIDENT_TYPES,
137 add_support_for_new_types);
144 // Returns true if the incident described by |state| has already been reported
145 // based on the bookkeeping in the |incidents_sent| preference dictionary.
146 bool IncidentHasBeenReported(const base::DictionaryValue* incidents_sent,
147 const PersistentIncidentState& state) {
148 const base::DictionaryValue* type_dict = NULL;
149 std::string digest_string;
150 return (incidents_sent &&
151 incidents_sent->GetDictionaryWithoutPathExpansion(
152 base::IntToString(state.type), &type_dict) &&
153 type_dict->GetStringWithoutPathExpansion(state.key, &digest_string) &&
154 digest_string == base::UintToString(state.digest));
157 // Marks the incidents described by |states| as having been reported
158 // in |incidents_set|.
159 void MarkIncidentsAsReported(const std::vector<PersistentIncidentState>& states,
160 base::DictionaryValue* incidents_sent) {
161 for (size_t i = 0; i < states.size(); ++i) {
162 const PersistentIncidentState& data = states[i];
163 base::DictionaryValue* type_dict = NULL;
164 const std::string type_string(base::IntToString(data.type));
165 if (!incidents_sent->GetDictionaryWithoutPathExpansion(type_string,
167 type_dict = new base::DictionaryValue();
168 incidents_sent->SetWithoutPathExpansion(type_string, type_dict);
170 type_dict->SetStringWithoutPathExpansion(data.key,
171 base::UintToString(data.digest));
175 // Runs |callback| on the thread to which |thread_runner| belongs. The callback
176 // is run immediately if this function is called on |thread_runner|'s thread.
177 void AddIncidentOnOriginThread(
178 const AddIncidentCallback& callback,
179 scoped_refptr<base::SingleThreadTaskRunner> thread_runner,
180 scoped_ptr<ClientIncidentReport_IncidentData> incident) {
181 if (thread_runner->BelongsToCurrentThread())
182 callback.Run(incident.Pass());
184 thread_runner->PostTask(FROM_HERE,
185 base::Bind(callback, base::Passed(&incident)));
190 struct IncidentReportingService::ProfileContext {
194 // The incidents collected for this profile pending creation and/or upload.
195 ScopedVector<ClientIncidentReport_IncidentData> incidents;
197 // False until PROFILE_ADDED notification is received.
201 DISALLOW_COPY_AND_ASSIGN(ProfileContext);
204 class IncidentReportingService::UploadContext {
206 typedef std::map<Profile*, std::vector<PersistentIncidentState> >
207 PersistentIncidentStateCollection;
209 explicit UploadContext(scoped_ptr<ClientIncidentReport> report);
212 // The report being uploaded.
213 scoped_ptr<ClientIncidentReport> report;
215 // The uploader in use. This is NULL until the CSD killswitch is checked.
216 scoped_ptr<IncidentReportUploader> uploader;
218 // A mapping of profiles to the data to be persisted upon successful upload.
219 PersistentIncidentStateCollection profiles_to_state;
222 DISALLOW_COPY_AND_ASSIGN(UploadContext);
225 IncidentReportingService::ProfileContext::ProfileContext() : added() {
228 IncidentReportingService::ProfileContext::~ProfileContext() {
231 IncidentReportingService::UploadContext::UploadContext(
232 scoped_ptr<ClientIncidentReport> report)
233 : report(report.Pass()) {
236 IncidentReportingService::UploadContext::~UploadContext() {
239 IncidentReportingService::IncidentReportingService(
240 SafeBrowsingService* safe_browsing_service,
241 const scoped_refptr<net::URLRequestContextGetter>& request_context_getter)
242 : database_manager_(safe_browsing_service ?
243 safe_browsing_service->database_manager() : NULL),
244 url_request_context_getter_(request_context_getter),
245 collect_environment_data_fn_(&CollectEnvironmentData),
246 environment_collection_task_runner_(
247 content::BrowserThread::GetBlockingPool()
248 ->GetTaskRunnerWithShutdownBehavior(
249 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
250 environment_collection_pending_(),
251 collation_timeout_pending_(),
252 collation_timer_(FROM_HERE,
253 base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs),
255 &IncidentReportingService::OnCollationTimeout),
256 delayed_analysis_callbacks_(
257 base::TimeDelta::FromMilliseconds(kDefaultCallbackIntervalMs),
258 content::BrowserThread::GetBlockingPool()
259 ->GetTaskRunnerWithShutdownBehavior(
260 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
261 receiver_weak_ptr_factory_(this),
262 weak_ptr_factory_(this) {
263 notification_registrar_.Add(this,
264 chrome::NOTIFICATION_PROFILE_ADDED,
265 content::NotificationService::AllSources());
266 notification_registrar_.Add(this,
267 chrome::NOTIFICATION_PROFILE_DESTROYED,
268 content::NotificationService::AllSources());
271 IncidentReportingService::~IncidentReportingService() {
272 CancelIncidentCollection();
274 // Cancel all internal asynchronous tasks.
275 weak_ptr_factory_.InvalidateWeakPtrs();
277 CancelEnvironmentCollection();
278 CancelDownloadCollection();
279 CancelAllReportUploads();
281 STLDeleteValues(&profiles_);
284 AddIncidentCallback IncidentReportingService::GetAddIncidentCallback(
286 // Force the context to be created so that incidents added before
287 // OnProfileAdded is called are held until the profile's preferences can be
289 ignore_result(GetOrCreateProfileContext(profile));
291 return base::Bind(&IncidentReportingService::AddIncident,
292 receiver_weak_ptr_factory_.GetWeakPtr(),
296 scoped_ptr<TrackedPreferenceValidationDelegate>
297 IncidentReportingService::CreatePreferenceValidationDelegate(Profile* profile) {
298 DCHECK(thread_checker_.CalledOnValidThread());
300 if (profile->IsOffTheRecord())
301 return scoped_ptr<TrackedPreferenceValidationDelegate>();
302 return scoped_ptr<TrackedPreferenceValidationDelegate>(
303 new PreferenceValidationDelegate(GetAddIncidentCallback(profile)));
306 void IncidentReportingService::RegisterDelayedAnalysisCallback(
307 const DelayedAnalysisCallback& callback) {
308 DCHECK(thread_checker_.CalledOnValidThread());
310 // |callback| will be run on the blocking pool, so it will likely run the
311 // AddIncidentCallback there as well. Bounce the run of that callback back to
312 // the current thread via AddIncidentOnOriginThread.
313 delayed_analysis_callbacks_.RegisterCallback(
315 base::Bind(&AddIncidentOnOriginThread,
316 GetAddIncidentCallback(NULL),
317 base::ThreadTaskRunnerHandle::Get())));
319 // Start running the callbacks if any profiles are participating in safe
320 // browsing. If none are now, running will commence if/when a participaing
322 if (FindEligibleProfile())
323 delayed_analysis_callbacks_.Start();
326 IncidentReportingService::IncidentReportingService(
327 SafeBrowsingService* safe_browsing_service,
328 const scoped_refptr<net::URLRequestContextGetter>& request_context_getter,
329 base::TimeDelta delayed_task_interval,
330 const scoped_refptr<base::TaskRunner>& delayed_task_runner)
331 : database_manager_(safe_browsing_service ?
332 safe_browsing_service->database_manager() : NULL),
333 url_request_context_getter_(request_context_getter),
334 collect_environment_data_fn_(&CollectEnvironmentData),
335 environment_collection_task_runner_(
336 content::BrowserThread::GetBlockingPool()
337 ->GetTaskRunnerWithShutdownBehavior(
338 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
339 environment_collection_pending_(),
340 collation_timeout_pending_(),
341 collation_timer_(FROM_HERE,
342 base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs),
344 &IncidentReportingService::OnCollationTimeout),
345 delayed_analysis_callbacks_(delayed_task_interval, delayed_task_runner),
346 receiver_weak_ptr_factory_(this),
347 weak_ptr_factory_(this) {
348 notification_registrar_.Add(this,
349 chrome::NOTIFICATION_PROFILE_ADDED,
350 content::NotificationService::AllSources());
351 notification_registrar_.Add(this,
352 chrome::NOTIFICATION_PROFILE_DESTROYED,
353 content::NotificationService::AllSources());
356 void IncidentReportingService::SetCollectEnvironmentHook(
357 CollectEnvironmentDataFn collect_environment_data_hook,
358 const scoped_refptr<base::TaskRunner>& task_runner) {
359 if (collect_environment_data_hook) {
360 collect_environment_data_fn_ = collect_environment_data_hook;
361 environment_collection_task_runner_ = task_runner;
363 collect_environment_data_fn_ = &CollectEnvironmentData;
364 environment_collection_task_runner_ =
365 content::BrowserThread::GetBlockingPool()
366 ->GetTaskRunnerWithShutdownBehavior(
367 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
371 void IncidentReportingService::OnProfileAdded(Profile* profile) {
372 DCHECK(thread_checker_.CalledOnValidThread());
374 // Track the addition of all profiles even when no report is being assembled
375 // so that the service can determine whether or not it can evaluate a
376 // profile's preferences at the time of incident addition.
377 ProfileContext* context = GetOrCreateProfileContext(profile);
378 context->added = true;
380 const bool safe_browsing_enabled =
381 profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled);
383 // Start processing delayed analysis callbacks if this new profile
384 // participates in safe browsing. Start is idempotent, so this is safe even if
385 // they're already running.
386 if (safe_browsing_enabled)
387 delayed_analysis_callbacks_.Start();
389 // Start a new report if this profile participates in safe browsing and there
390 // are process-wide incidents.
391 if (safe_browsing_enabled && GetProfileContext(NULL))
392 BeginReportProcessing();
394 // TODO(grt): register for pref change notifications to start delayed analysis
395 // and/or report processing if sb is currently disabled but subsequently
398 // Nothing else to do if a report is not being assembled.
402 // Drop all incidents associated with this profile that were received prior to
403 // its addition if the profile is not participating in safe browsing.
404 if (!context->incidents.empty() && !safe_browsing_enabled) {
405 for (size_t i = 0; i < context->incidents.size(); ++i)
406 LogIncidentDataType(DROPPED, *context->incidents[i]);
407 context->incidents.clear();
410 // Take another stab at finding the most recent download if a report is being
411 // assembled and one hasn't been found yet (the LastDownloadFinder operates
412 // only on profiles that have been added to the ProfileManager).
413 BeginDownloadCollection();
416 scoped_ptr<LastDownloadFinder> IncidentReportingService::CreateDownloadFinder(
417 const LastDownloadFinder::LastDownloadCallback& callback) {
418 return LastDownloadFinder::Create(callback).Pass();
421 scoped_ptr<IncidentReportUploader> IncidentReportingService::StartReportUpload(
422 const IncidentReportUploader::OnResultCallback& callback,
423 const scoped_refptr<net::URLRequestContextGetter>& request_context_getter,
424 const ClientIncidentReport& report) {
425 return IncidentReportUploaderImpl::UploadReport(
426 callback, request_context_getter, report).Pass();
429 IncidentReportingService::ProfileContext*
430 IncidentReportingService::GetOrCreateProfileContext(Profile* profile) {
431 ProfileContextCollection::iterator it =
432 profiles_.insert(ProfileContextCollection::value_type(profile, NULL))
435 it->second = new ProfileContext();
439 IncidentReportingService::ProfileContext*
440 IncidentReportingService::GetProfileContext(Profile* profile) {
441 ProfileContextCollection::iterator it = profiles_.find(profile);
442 return it == profiles_.end() ? NULL : it->second;
445 void IncidentReportingService::OnProfileDestroyed(Profile* profile) {
446 DCHECK(thread_checker_.CalledOnValidThread());
448 ProfileContextCollection::iterator it = profiles_.find(profile);
449 if (it == profiles_.end())
452 // TODO(grt): Persist incidents for upload on future profile load.
454 // Forget about this profile. Incidents not yet sent for upload are lost.
455 // No new incidents will be accepted for it.
459 // Remove the association with this profile from all pending uploads.
460 for (size_t i = 0; i < uploads_.size(); ++i)
461 uploads_[i]->profiles_to_state.erase(profile);
464 Profile* IncidentReportingService::FindEligibleProfile() const {
465 Profile* candidate = NULL;
466 for (ProfileContextCollection::const_iterator scan = profiles_.begin();
467 scan != profiles_.end();
469 // Skip over profiles that have yet to be added to the profile manager.
470 // This will also skip over the NULL-profile context used to hold
471 // process-wide incidents.
472 if (!scan->second->added)
474 PrefService* prefs = scan->first->GetPrefs();
475 if (prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) {
477 candidate = scan->first;
478 if (prefs->GetBoolean(prefs::kSafeBrowsingExtendedReportingEnabled)) {
479 candidate = scan->first;
487 void IncidentReportingService::AddIncident(
489 scoped_ptr<ClientIncidentReport_IncidentData> incident_data) {
490 DCHECK(thread_checker_.CalledOnValidThread());
491 DCHECK_EQ(1U, CountIncidents(*incident_data));
493 ProfileContext* context = GetProfileContext(profile);
494 // It is forbidden to call this function with a destroyed profile.
496 // If this is a process-wide incident, the context must not indicate that the
497 // profile (which is NULL) has been added to the profile manager.
498 DCHECK(profile || !context->added);
500 // Drop the incident immediately if the profile has already been added to the
501 // manager and is not participating in safe browsing. Preference evaluation is
502 // deferred until OnProfileAdded() otherwise.
503 if (context->added &&
504 !profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
505 LogIncidentDataType(DROPPED, *incident_data);
509 // Provide time to the new incident if the caller didn't do so.
510 if (!incident_data->has_incident_time_msec())
511 incident_data->set_incident_time_msec(base::Time::Now().ToJavaTime());
513 // Take ownership of the incident.
514 context->incidents.push_back(incident_data.release());
516 // Remember when the first incident for this report arrived.
517 if (first_incident_time_.is_null())
518 first_incident_time_ = base::Time::Now();
519 // Log the time between the previous incident and this one.
520 if (!last_incident_time_.is_null()) {
521 UMA_HISTOGRAM_TIMES("SBIRS.InterIncidentTime",
522 base::TimeTicks::Now() - last_incident_time_);
524 last_incident_time_ = base::TimeTicks::Now();
526 // Persist the incident data.
528 // Start assembling a new report if this is the first incident ever or the
529 // first since the last upload.
530 BeginReportProcessing();
533 void IncidentReportingService::BeginReportProcessing() {
534 DCHECK(thread_checker_.CalledOnValidThread());
536 // Creates a new report if needed.
538 report_.reset(new ClientIncidentReport());
540 // Ensure that collection tasks are running (calls are idempotent).
541 BeginIncidentCollation();
542 BeginEnvironmentCollection();
543 BeginDownloadCollection();
546 void IncidentReportingService::BeginIncidentCollation() {
547 // Restart the delay timer to send the report upon expiration.
548 collation_timeout_pending_ = true;
549 collation_timer_.Reset();
552 void IncidentReportingService::BeginEnvironmentCollection() {
553 DCHECK(thread_checker_.CalledOnValidThread());
555 // Nothing to do if environment collection is pending or has already
557 if (environment_collection_pending_ || report_->has_environment())
560 environment_collection_begin_ = base::TimeTicks::Now();
561 ClientIncidentReport_EnvironmentData* environment_data =
562 new ClientIncidentReport_EnvironmentData();
563 environment_collection_pending_ =
564 environment_collection_task_runner_->PostTaskAndReply(
566 base::Bind(collect_environment_data_fn_, environment_data),
567 base::Bind(&IncidentReportingService::OnEnvironmentDataCollected,
568 weak_ptr_factory_.GetWeakPtr(),
569 base::Passed(make_scoped_ptr(environment_data))));
571 // Posting the task will fail if the runner has been shut down. This should
572 // never happen since the blocking pool is shut down after this service.
573 DCHECK(environment_collection_pending_);
576 bool IncidentReportingService::WaitingForEnvironmentCollection() {
577 return environment_collection_pending_;
580 void IncidentReportingService::CancelEnvironmentCollection() {
581 environment_collection_begin_ = base::TimeTicks();
582 environment_collection_pending_ = false;
584 report_->clear_environment();
587 void IncidentReportingService::OnEnvironmentDataCollected(
588 scoped_ptr<ClientIncidentReport_EnvironmentData> environment_data) {
589 DCHECK(thread_checker_.CalledOnValidThread());
590 DCHECK(environment_collection_pending_);
591 DCHECK(report_ && !report_->has_environment());
592 environment_collection_pending_ = false;
594 // CurrentProcessInfo::CreationTime() is missing on some platforms.
595 #if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
596 base::TimeDelta uptime =
597 first_incident_time_ - base::CurrentProcessInfo::CreationTime();
598 environment_data->mutable_process()->set_uptime_msec(uptime.InMilliseconds());
601 report_->set_allocated_environment(environment_data.release());
603 UMA_HISTOGRAM_TIMES("SBIRS.EnvCollectionTime",
604 base::TimeTicks::Now() - environment_collection_begin_);
605 environment_collection_begin_ = base::TimeTicks();
607 UploadIfCollectionComplete();
610 bool IncidentReportingService::WaitingToCollateIncidents() {
611 return collation_timeout_pending_;
614 void IncidentReportingService::CancelIncidentCollection() {
615 collation_timeout_pending_ = false;
616 last_incident_time_ = base::TimeTicks();
620 void IncidentReportingService::OnCollationTimeout() {
621 DCHECK(thread_checker_.CalledOnValidThread());
623 // Exit early if collection was cancelled.
624 if (!collation_timeout_pending_)
627 // Wait another round if profile-bound incidents have come in from a profile
628 // that has yet to complete creation.
629 for (ProfileContextCollection::iterator scan = profiles_.begin();
630 scan != profiles_.end();
632 if (scan->first && !scan->second->added &&
633 !scan->second->incidents.empty()) {
634 collation_timer_.Reset();
639 collation_timeout_pending_ = false;
641 UploadIfCollectionComplete();
644 void IncidentReportingService::BeginDownloadCollection() {
645 DCHECK(thread_checker_.CalledOnValidThread());
647 // Nothing to do if a search for the most recent download is already pending
648 // or if one has already been found.
649 if (last_download_finder_ || report_->has_download())
652 last_download_begin_ = base::TimeTicks::Now();
653 last_download_finder_ = CreateDownloadFinder(
654 base::Bind(&IncidentReportingService::OnLastDownloadFound,
655 weak_ptr_factory_.GetWeakPtr()));
656 // No instance is returned if there are no eligible loaded profiles. Another
657 // search will be attempted in OnProfileAdded() if another profile appears on
659 if (!last_download_finder_)
660 last_download_begin_ = base::TimeTicks();
663 bool IncidentReportingService::WaitingForMostRecentDownload() {
664 DCHECK(report_); // Only call this when a report is being assembled.
665 // The easy case: not waiting if a download has already been found.
666 if (report_->has_download())
668 // The next easy case: waiting if the finder is operating.
669 if (last_download_finder_)
671 // The harder case: waiting if a profile has not yet been added.
672 for (ProfileContextCollection::const_iterator scan = profiles_.begin();
673 scan != profiles_.end();
675 if (!scan->second->added)
678 // There is no most recent download and there's nothing more to wait for.
682 void IncidentReportingService::CancelDownloadCollection() {
683 last_download_finder_.reset();
684 last_download_begin_ = base::TimeTicks();
686 report_->clear_download();
689 void IncidentReportingService::OnLastDownloadFound(
690 scoped_ptr<ClientIncidentReport_DownloadDetails> last_download) {
691 DCHECK(thread_checker_.CalledOnValidThread());
694 UMA_HISTOGRAM_TIMES("SBIRS.FindDownloadedBinaryTime",
695 base::TimeTicks::Now() - last_download_begin_);
696 last_download_begin_ = base::TimeTicks();
698 // Harvest the finder.
699 last_download_finder_.reset();
702 report_->set_allocated_download(last_download.release());
704 UploadIfCollectionComplete();
707 void IncidentReportingService::UploadIfCollectionComplete() {
709 // Bail out if there are still outstanding collection tasks. Completion of any
710 // of these will start another upload attempt.
711 if (WaitingForEnvironmentCollection() ||
712 WaitingToCollateIncidents() ||
713 WaitingForMostRecentDownload()) {
717 // Take ownership of the report and clear things for future reports.
718 scoped_ptr<ClientIncidentReport> report(report_.Pass());
719 first_incident_time_ = base::Time();
720 last_incident_time_ = base::TimeTicks();
722 // Drop the report if no executable download was found.
723 if (!report->has_download()) {
724 UMA_HISTOGRAM_ENUMERATION("SBIRS.UploadResult",
725 IncidentReportUploader::UPLOAD_NO_DOWNLOAD,
726 IncidentReportUploader::NUM_UPLOAD_RESULTS);
730 ClientIncidentReport_EnvironmentData_Process* process =
731 report->mutable_environment()->mutable_process();
733 // Not all platforms have a metrics reporting preference.
734 if (g_browser_process->local_state()->FindPreference(
735 prefs::kMetricsReportingEnabled)) {
736 process->set_metrics_consent(g_browser_process->local_state()->GetBoolean(
737 prefs::kMetricsReportingEnabled));
740 // Find the profile that benefits from the strongest protections.
741 Profile* eligible_profile = FindEligibleProfile();
742 process->set_extended_consent(
743 eligible_profile ? eligible_profile->GetPrefs()->GetBoolean(
744 prefs::kSafeBrowsingExtendedReportingEnabled) :
747 // Associate process-wide incidents with the profile that benefits from the
748 // strongest safe browsing protections.
749 ProfileContext* null_context = GetProfileContext(NULL);
750 if (null_context && !null_context->incidents.empty() && eligible_profile) {
751 ProfileContext* eligible_context = GetProfileContext(eligible_profile);
752 // Move the incidents to the target context.
753 eligible_context->incidents.insert(eligible_context->incidents.end(),
754 null_context->incidents.begin(),
755 null_context->incidents.end());
756 null_context->incidents.weak_clear();
759 // Collect incidents across all profiles participating in safe browsing. Drop
760 // incidents if the profile stopped participating before collection completed.
761 // Prune previously submitted incidents.
762 // Associate the profiles and their incident data with the upload.
763 size_t prune_count = 0;
764 UploadContext::PersistentIncidentStateCollection profiles_to_state;
765 for (ProfileContextCollection::iterator scan = profiles_.begin();
766 scan != profiles_.end();
768 // Bypass process-wide incidents that have not yet been associated with a
772 PrefService* prefs = scan->first->GetPrefs();
773 ProfileContext* context = scan->second;
774 if (context->incidents.empty())
776 if (!prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) {
777 for (size_t i = 0; i < context->incidents.size(); ++i) {
778 LogIncidentDataType(DROPPED, *context->incidents[i]);
780 context->incidents.clear();
783 std::vector<PersistentIncidentState> states;
784 const base::DictionaryValue* incidents_sent =
785 prefs->GetDictionary(prefs::kSafeBrowsingIncidentsSent);
786 // Prep persistent data and prune any incidents already sent.
787 for (size_t i = 0; i < context->incidents.size(); ++i) {
788 ClientIncidentReport_IncidentData* incident = context->incidents[i];
789 const PersistentIncidentState state = ComputeIncidentState(*incident);
790 if (IncidentHasBeenReported(incidents_sent, state)) {
792 delete context->incidents[i];
793 context->incidents[i] = NULL;
795 states.push_back(state);
798 if (prefs->GetBoolean(prefs::kSafeBrowsingIncidentReportSent)) {
799 // Prune all incidents as if they had been reported, migrating to the new
800 // technique. TODO(grt): remove this branch after it has shipped.
801 for (size_t i = 0; i < context->incidents.size(); ++i) {
802 if (context->incidents[i])
805 context->incidents.clear();
806 prefs->ClearPref(prefs::kSafeBrowsingIncidentReportSent);
807 DictionaryPrefUpdate pref_update(prefs,
808 prefs::kSafeBrowsingIncidentsSent);
809 MarkIncidentsAsReported(states, pref_update.Get());
811 for (size_t i = 0; i < context->incidents.size(); ++i) {
812 ClientIncidentReport_IncidentData* incident = context->incidents[i];
814 LogIncidentDataType(ACCEPTED, *incident);
815 // Ownership of the incident is passed to the report.
816 report->mutable_incident()->AddAllocated(incident);
819 context->incidents.weak_clear();
820 std::vector<PersistentIncidentState>& profile_states =
821 profiles_to_state[scan->first];
822 profile_states.swap(states);
826 const int count = report->incident_size();
827 // Abandon the request if all incidents were dropped with none pruned.
828 if (!count && !prune_count)
831 UMA_HISTOGRAM_COUNTS_100("SBIRS.IncidentCount", count + prune_count);
834 double prune_pct = static_cast<double>(prune_count);
835 prune_pct = prune_pct * 100.0 / (count + prune_count);
836 prune_pct = round(prune_pct);
837 UMA_HISTOGRAM_PERCENTAGE("SBIRS.PruneRatio", static_cast<int>(prune_pct));
839 // Abandon the report if all incidents were pruned.
843 scoped_ptr<UploadContext> context(new UploadContext(report.Pass()));
844 context->profiles_to_state.swap(profiles_to_state);
845 if (!database_manager_) {
846 // No database manager during testing. Take ownership of the context and
847 // continue processing.
848 UploadContext* temp_context = context.get();
849 uploads_.push_back(context.release());
850 IncidentReportingService::OnKillSwitchResult(temp_context, false);
852 if (content::BrowserThread::PostTaskAndReplyWithResult(
853 content::BrowserThread::IO,
855 base::Bind(&SafeBrowsingDatabaseManager::IsCsdWhitelistKillSwitchOn,
857 base::Bind(&IncidentReportingService::OnKillSwitchResult,
858 weak_ptr_factory_.GetWeakPtr(),
860 uploads_.push_back(context.release());
861 } // else should not happen. Let the context be deleted automatically.
865 void IncidentReportingService::CancelAllReportUploads() {
866 for (size_t i = 0; i < uploads_.size(); ++i) {
867 UMA_HISTOGRAM_ENUMERATION("SBIRS.UploadResult",
868 IncidentReportUploader::UPLOAD_CANCELLED,
869 IncidentReportUploader::NUM_UPLOAD_RESULTS);
874 void IncidentReportingService::OnKillSwitchResult(UploadContext* context,
875 bool is_killswitch_on) {
876 DCHECK(thread_checker_.CalledOnValidThread());
877 if (!is_killswitch_on) {
878 // Initiate the upload.
881 base::Bind(&IncidentReportingService::OnReportUploadResult,
882 weak_ptr_factory_.GetWeakPtr(),
884 url_request_context_getter_,
885 *context->report).Pass();
886 if (!context->uploader) {
887 OnReportUploadResult(context,
888 IncidentReportUploader::UPLOAD_INVALID_REQUEST,
889 scoped_ptr<ClientIncidentResponse>());
892 OnReportUploadResult(context,
893 IncidentReportUploader::UPLOAD_SUPPRESSED,
894 scoped_ptr<ClientIncidentResponse>());
898 void IncidentReportingService::HandleResponse(const UploadContext& context) {
899 for (UploadContext::PersistentIncidentStateCollection::const_iterator scan =
900 context.profiles_to_state.begin();
901 scan != context.profiles_to_state.end();
903 DictionaryPrefUpdate pref_update(scan->first->GetPrefs(),
904 prefs::kSafeBrowsingIncidentsSent);
905 MarkIncidentsAsReported(scan->second, pref_update.Get());
909 void IncidentReportingService::OnReportUploadResult(
910 UploadContext* context,
911 IncidentReportUploader::Result result,
912 scoped_ptr<ClientIncidentResponse> response) {
913 DCHECK(thread_checker_.CalledOnValidThread());
915 UMA_HISTOGRAM_ENUMERATION(
916 "SBIRS.UploadResult", result, IncidentReportUploader::NUM_UPLOAD_RESULTS);
918 // The upload is no longer outstanding, so take ownership of the context (from
919 // the collection of outstanding uploads) in this scope.
920 ScopedVector<UploadContext>::iterator it(
921 std::find(uploads_.begin(), uploads_.end(), context));
922 DCHECK(it != uploads_.end());
923 scoped_ptr<UploadContext> upload(context); // == *it
924 *it = uploads_.back();
925 uploads_.weak_erase(uploads_.end() - 1);
927 if (result == IncidentReportUploader::UPLOAD_SUCCESS)
928 HandleResponse(*upload);
932 void IncidentReportingService::Observe(
934 const content::NotificationSource& source,
935 const content::NotificationDetails& details) {
937 case chrome::NOTIFICATION_PROFILE_ADDED: {
938 Profile* profile = content::Source<Profile>(source).ptr();
939 if (!profile->IsOffTheRecord())
940 OnProfileAdded(profile);
943 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
944 Profile* profile = content::Source<Profile>(source).ptr();
945 if (!profile->IsOffTheRecord())
946 OnProfileDestroyed(profile);
954 } // namespace safe_browsing