Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / safe_browsing / incident_reporting / incident_reporting_service.cc
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.
4
5 #include "chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.h"
6
7 #include <math.h>
8
9 #include <algorithm>
10 #include <vector>
11
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"
39
40 namespace safe_browsing {
41
42 namespace {
43
44 // The type of an incident. Used for user metrics and for pruning of
45 // previously-reported incidents.
46 enum IncidentType {
47   // Start with 1 rather than zero; otherwise there won't be enough buckets for
48   // the histogram.
49   TRACKED_PREFERENCE = 1,
50   BINARY_INTEGRITY = 2,
51   BLACKLIST_LOAD = 3,
52   // Values for new incident types go here.
53   NUM_INCIDENT_TYPES = 4
54 };
55
56 // The action taken for an incident; used for user metrics (see
57 // LogIncidentDataType).
58 enum IncidentDisposition {
59   DROPPED,
60   ACCEPTED,
61 };
62
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.
67   IncidentType type;
68
69   // The key for a specific instance of an incident.
70   std::string key;
71
72   // A hash digest representing a specific instance of an incident.
73   uint32_t digest;
74 };
75
76 // The amount of time the service will wait to collate incidents.
77 const int64 kDefaultUploadDelayMs = 1000 * 60;  // one minute
78
79 // The amount of time between running delayed analysis callbacks.
80 const int64 kDefaultCallbackIntervalMs = 1000 * 20;
81
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) {
85   size_t result = 0;
86   if (incident.has_tracked_preference())
87     ++result;
88   if (incident.has_binary_integrity())
89     ++result;
90   if (incident.has_blacklist_load())
91     ++result;
92   // Add detection for new incident types here.
93   return result;
94 }
95
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;
105
106   // Add detection for new incident types here.
107   COMPILE_ASSERT(BLACKLIST_LOAD + 1 == NUM_INCIDENT_TYPES,
108                  add_support_for_new_types);
109   NOTREACHED();
110   return NUM_INCIDENT_TYPES;
111 }
112
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);
120   } else {
121     DCHECK_EQ(disposition, DROPPED);
122     UMA_HISTOGRAM_ENUMERATION("SBIRS.DroppedIncident", type,
123                               NUM_INCIDENT_TYPES);
124   }
125 }
126
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);
135       break;
136     case BINARY_INTEGRITY:
137       state.key = GetBinaryIntegrityIncidentKey(incident);
138       state.digest = GetBinaryIntegrityIncidentDigest(incident);
139       break;
140     case BLACKLIST_LOAD:
141       state.key = GetBlacklistLoadIncidentKey(incident);
142       state.digest = GetBlacklistLoadIncidentDigest(incident);
143       break;
144     // Add handling for new incident types here.
145     default:
146       COMPILE_ASSERT(BLACKLIST_LOAD + 1 == NUM_INCIDENT_TYPES,
147                      add_support_for_new_types);
148       NOTREACHED();
149       break;
150   }
151   return state;
152 }
153
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));
165 }
166
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,
176                                                            &type_dict)) {
177       type_dict = new base::DictionaryValue();
178       incidents_sent->SetWithoutPathExpansion(type_string, type_dict);
179     }
180     type_dict->SetStringWithoutPathExpansion(data.key,
181                                              base::UintToString(data.digest));
182   }
183 }
184
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());
193   else
194     thread_runner->PostTask(FROM_HERE,
195                             base::Bind(callback, base::Passed(&incident)));
196 }
197
198 }  // namespace
199
200 struct IncidentReportingService::ProfileContext {
201   ProfileContext();
202   ~ProfileContext();
203
204   // The incidents collected for this profile pending creation and/or upload.
205   ScopedVector<ClientIncidentReport_IncidentData> incidents;
206
207   // False until PROFILE_ADDED notification is received.
208   bool added;
209
210  private:
211   DISALLOW_COPY_AND_ASSIGN(ProfileContext);
212 };
213
214 class IncidentReportingService::UploadContext {
215  public:
216   typedef std::map<Profile*, std::vector<PersistentIncidentState> >
217       PersistentIncidentStateCollection;
218
219   explicit UploadContext(scoped_ptr<ClientIncidentReport> report);
220   ~UploadContext();
221
222   // The report being uploaded.
223   scoped_ptr<ClientIncidentReport> report;
224
225   // The uploader in use. This is NULL until the CSD killswitch is checked.
226   scoped_ptr<IncidentReportUploader> uploader;
227
228   // A mapping of profiles to the data to be persisted upon successful upload.
229   PersistentIncidentStateCollection profiles_to_state;
230
231  private:
232   DISALLOW_COPY_AND_ASSIGN(UploadContext);
233 };
234
235 IncidentReportingService::ProfileContext::ProfileContext() : added() {
236 }
237
238 IncidentReportingService::ProfileContext::~ProfileContext() {
239 }
240
241 IncidentReportingService::UploadContext::UploadContext(
242     scoped_ptr<ClientIncidentReport> report)
243     : report(report.Pass()) {
244 }
245
246 IncidentReportingService::UploadContext::~UploadContext() {
247 }
248
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),
264                        this,
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());
279 }
280
281 IncidentReportingService::~IncidentReportingService() {
282   CancelIncidentCollection();
283
284   // Cancel all internal asynchronous tasks.
285   weak_ptr_factory_.InvalidateWeakPtrs();
286
287   CancelEnvironmentCollection();
288   CancelDownloadCollection();
289   CancelAllReportUploads();
290
291   STLDeleteValues(&profiles_);
292 }
293
294 AddIncidentCallback IncidentReportingService::GetAddIncidentCallback(
295     Profile* profile) {
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
298   // queried.
299   ignore_result(GetOrCreateProfileContext(profile));
300
301   return base::Bind(&IncidentReportingService::AddIncident,
302                     receiver_weak_ptr_factory_.GetWeakPtr(),
303                     profile);
304 }
305
306 scoped_ptr<TrackedPreferenceValidationDelegate>
307 IncidentReportingService::CreatePreferenceValidationDelegate(Profile* profile) {
308   DCHECK(thread_checker_.CalledOnValidThread());
309
310   if (profile->IsOffTheRecord())
311     return scoped_ptr<TrackedPreferenceValidationDelegate>();
312   return scoped_ptr<TrackedPreferenceValidationDelegate>(
313       new PreferenceValidationDelegate(GetAddIncidentCallback(profile)));
314 }
315
316 void IncidentReportingService::RegisterDelayedAnalysisCallback(
317     const DelayedAnalysisCallback& callback) {
318   DCHECK(thread_checker_.CalledOnValidThread());
319
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(
324       base::Bind(callback,
325                  base::Bind(&AddIncidentOnOriginThread,
326                             GetAddIncidentCallback(NULL),
327                             base::ThreadTaskRunnerHandle::Get())));
328
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
331   // profile is added.
332   if (FindEligibleProfile())
333     delayed_analysis_callbacks_.Start();
334 }
335
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),
353                        this,
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());
364 }
365
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;
372   } else {
373     collect_environment_data_fn_ = &CollectEnvironmentData;
374     environment_collection_task_runner_ =
375         content::BrowserThread::GetBlockingPool()
376             ->GetTaskRunnerWithShutdownBehavior(
377                 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
378   }
379 }
380
381 void IncidentReportingService::OnProfileAdded(Profile* profile) {
382   DCHECK(thread_checker_.CalledOnValidThread());
383
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;
389
390   const bool safe_browsing_enabled =
391       profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled);
392
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();
398
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();
404   }
405
406   // TODO(grt): register for pref change notifications to start delayed analysis
407   // and/or report processing if sb is currently disabled but subsequently
408   // enabled.
409
410   // Nothing else to do if a report is not being assembled.
411   if (!report_)
412     return;
413
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();
420   }
421
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();
426 }
427
428 scoped_ptr<LastDownloadFinder> IncidentReportingService::CreateDownloadFinder(
429     const LastDownloadFinder::LastDownloadCallback& callback) {
430   return LastDownloadFinder::Create(callback).Pass();
431 }
432
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();
439 }
440
441 bool IncidentReportingService::IsProcessingReport() const {
442   return report_ != NULL;
443 }
444
445 IncidentReportingService::ProfileContext*
446 IncidentReportingService::GetOrCreateProfileContext(Profile* profile) {
447   ProfileContextCollection::iterator it =
448       profiles_.insert(ProfileContextCollection::value_type(profile, NULL))
449           .first;
450   if (!it->second)
451     it->second = new ProfileContext();
452   return it->second;
453 }
454
455 IncidentReportingService::ProfileContext*
456 IncidentReportingService::GetProfileContext(Profile* profile) {
457   ProfileContextCollection::iterator it = profiles_.find(profile);
458   return it == profiles_.end() ? NULL : it->second;
459 }
460
461 void IncidentReportingService::OnProfileDestroyed(Profile* profile) {
462   DCHECK(thread_checker_.CalledOnValidThread());
463
464   ProfileContextCollection::iterator it = profiles_.find(profile);
465   if (it == profiles_.end())
466     return;
467
468   // TODO(grt): Persist incidents for upload on future profile load.
469
470   // Forget about this profile. Incidents not yet sent for upload are lost.
471   // No new incidents will be accepted for it.
472   delete it->second;
473   profiles_.erase(it);
474
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);
478 }
479
480 Profile* IncidentReportingService::FindEligibleProfile() const {
481   Profile* candidate = NULL;
482   for (ProfileContextCollection::const_iterator scan = profiles_.begin();
483        scan != profiles_.end();
484        ++scan) {
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)
489       continue;
490     PrefService* prefs = scan->first->GetPrefs();
491     if (prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) {
492       if (!candidate)
493         candidate = scan->first;
494       if (prefs->GetBoolean(prefs::kSafeBrowsingExtendedReportingEnabled)) {
495         candidate = scan->first;
496         break;
497       }
498     }
499   }
500   return candidate;
501 }
502
503 void IncidentReportingService::AddIncident(
504     Profile* profile,
505     scoped_ptr<ClientIncidentReport_IncidentData> incident_data) {
506   DCHECK(thread_checker_.CalledOnValidThread());
507   DCHECK_EQ(1U, CountIncidents(*incident_data));
508
509   ProfileContext* context = GetProfileContext(profile);
510   // It is forbidden to call this function with a destroyed profile.
511   DCHECK(context);
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);
515
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);
522     return;
523   }
524
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());
528
529   // Take ownership of the incident.
530   context->incidents.push_back(incident_data.release());
531
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_);
539   }
540   last_incident_time_ = base::TimeTicks::Now();
541
542   // Persist the incident data.
543
544   // Start assembling a new report if this is the first incident ever or the
545   // first since the last upload.
546   BeginReportProcessing();
547 }
548
549 void IncidentReportingService::BeginReportProcessing() {
550   DCHECK(thread_checker_.CalledOnValidThread());
551
552   // Creates a new report if needed.
553   if (!report_)
554     report_.reset(new ClientIncidentReport());
555
556   // Ensure that collection tasks are running (calls are idempotent).
557   BeginIncidentCollation();
558   BeginEnvironmentCollection();
559   BeginDownloadCollection();
560 }
561
562 void IncidentReportingService::BeginIncidentCollation() {
563   // Restart the delay timer to send the report upon expiration.
564   collation_timeout_pending_ = true;
565   collation_timer_.Reset();
566 }
567
568 void IncidentReportingService::BeginEnvironmentCollection() {
569   DCHECK(thread_checker_.CalledOnValidThread());
570   DCHECK(report_);
571   // Nothing to do if environment collection is pending or has already
572   // completed.
573   if (environment_collection_pending_ || report_->has_environment())
574     return;
575
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(
581           FROM_HERE,
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))));
586
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_);
590 }
591
592 bool IncidentReportingService::WaitingForEnvironmentCollection() {
593   return environment_collection_pending_;
594 }
595
596 void IncidentReportingService::CancelEnvironmentCollection() {
597   environment_collection_begin_ = base::TimeTicks();
598   environment_collection_pending_ = false;
599   if (report_)
600     report_->clear_environment();
601 }
602
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;
609
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());
615 #endif
616
617   report_->set_allocated_environment(environment_data.release());
618
619   UMA_HISTOGRAM_TIMES("SBIRS.EnvCollectionTime",
620                       base::TimeTicks::Now() - environment_collection_begin_);
621   environment_collection_begin_ = base::TimeTicks();
622
623   UploadIfCollectionComplete();
624 }
625
626 bool IncidentReportingService::WaitingToCollateIncidents() {
627   return collation_timeout_pending_;
628 }
629
630 void IncidentReportingService::CancelIncidentCollection() {
631   collation_timeout_pending_ = false;
632   last_incident_time_ = base::TimeTicks();
633   report_.reset();
634 }
635
636 void IncidentReportingService::OnCollationTimeout() {
637   DCHECK(thread_checker_.CalledOnValidThread());
638
639   // Exit early if collection was cancelled.
640   if (!collation_timeout_pending_)
641     return;
642
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();
647        ++scan) {
648     if (scan->first && !scan->second->added &&
649         !scan->second->incidents.empty()) {
650       collation_timer_.Reset();
651       return;
652     }
653   }
654
655   collation_timeout_pending_ = false;
656
657   UploadIfCollectionComplete();
658 }
659
660 void IncidentReportingService::BeginDownloadCollection() {
661   DCHECK(thread_checker_.CalledOnValidThread());
662   DCHECK(report_);
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())
666     return;
667
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
674   // the scene.
675   if (!last_download_finder_)
676     last_download_begin_ = base::TimeTicks();
677 }
678
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())
683     return false;
684   // The next easy case: waiting if the finder is operating.
685   if (last_download_finder_)
686     return true;
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();
690        ++scan) {
691     if (scan->first && !scan->second->added)
692       return true;
693   }
694   // There is no most recent download and there's nothing more to wait for.
695   return false;
696 }
697
698 void IncidentReportingService::CancelDownloadCollection() {
699   last_download_finder_.reset();
700   last_download_begin_ = base::TimeTicks();
701   if (report_)
702     report_->clear_download();
703 }
704
705 void IncidentReportingService::OnLastDownloadFound(
706     scoped_ptr<ClientIncidentReport_DownloadDetails> last_download) {
707   DCHECK(thread_checker_.CalledOnValidThread());
708   DCHECK(report_);
709
710   UMA_HISTOGRAM_TIMES("SBIRS.FindDownloadedBinaryTime",
711                       base::TimeTicks::Now() - last_download_begin_);
712   last_download_begin_ = base::TimeTicks();
713
714   // Harvest the finder.
715   last_download_finder_.reset();
716
717   if (last_download)
718     report_->set_allocated_download(last_download.release());
719
720   UploadIfCollectionComplete();
721 }
722
723 void IncidentReportingService::UploadIfCollectionComplete() {
724   DCHECK(report_);
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()) {
730     return;
731   }
732
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();
737
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);
743     return;
744   }
745
746   ClientIncidentReport_EnvironmentData_Process* process =
747       report->mutable_environment()->mutable_process();
748
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));
754   }
755
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) :
761                        false);
762
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();
773   }
774
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();
783        ++scan) {
784     // Bypass process-wide incidents that have not yet been associated with a
785     // profile.
786     if (!scan->first)
787       continue;
788     PrefService* prefs = scan->first->GetPrefs();
789     ProfileContext* context = scan->second;
790     if (context->incidents.empty())
791       continue;
792     if (!prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) {
793       for (size_t i = 0; i < context->incidents.size(); ++i) {
794         LogIncidentDataType(DROPPED, *context->incidents[i]);
795       }
796       context->incidents.clear();
797       continue;
798     }
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)) {
807         ++prune_count;
808         delete context->incidents[i];
809         context->incidents[i] = NULL;
810       } else {
811         states.push_back(state);
812       }
813     }
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])
819           ++prune_count;
820       }
821       context->incidents.clear();
822       prefs->ClearPref(prefs::kSafeBrowsingIncidentReportSent);
823       DictionaryPrefUpdate pref_update(prefs,
824                                        prefs::kSafeBrowsingIncidentsSent);
825       MarkIncidentsAsReported(states, pref_update.Get());
826     } else {
827       for (size_t i = 0; i < context->incidents.size(); ++i) {
828         ClientIncidentReport_IncidentData* incident = context->incidents[i];
829         if (incident) {
830           LogIncidentDataType(ACCEPTED, *incident);
831           // Ownership of the incident is passed to the report.
832           report->mutable_incident()->AddAllocated(incident);
833         }
834       }
835       context->incidents.weak_clear();
836       std::vector<PersistentIncidentState>& profile_states =
837           profiles_to_state[scan->first];
838       profile_states.swap(states);
839     }
840   }
841
842   const int count = report->incident_size();
843   // Abandon the request if all incidents were dropped with none pruned.
844   if (!count && !prune_count)
845     return;
846
847   UMA_HISTOGRAM_COUNTS_100("SBIRS.IncidentCount", count + prune_count);
848
849   {
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));
854   }
855   // Abandon the report if all incidents were pruned.
856   if (!count)
857     return;
858
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);
867   } else {
868     if (content::BrowserThread::PostTaskAndReplyWithResult(
869             content::BrowserThread::IO,
870             FROM_HERE,
871             base::Bind(&SafeBrowsingDatabaseManager::IsCsdWhitelistKillSwitchOn,
872                        database_manager_),
873             base::Bind(&IncidentReportingService::OnKillSwitchResult,
874                        weak_ptr_factory_.GetWeakPtr(),
875                        context.get()))) {
876       uploads_.push_back(context.release());
877     }  // else should not happen. Let the context be deleted automatically.
878   }
879 }
880
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);
886   }
887   uploads_.clear();
888 }
889
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.
895     context->uploader =
896         StartReportUpload(
897             base::Bind(&IncidentReportingService::OnReportUploadResult,
898                        weak_ptr_factory_.GetWeakPtr(),
899                        context),
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>());
906     }
907   } else {
908     OnReportUploadResult(context,
909                          IncidentReportUploader::UPLOAD_SUPPRESSED,
910                          scoped_ptr<ClientIncidentResponse>());
911   }
912 }
913
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();
918        ++scan) {
919     DictionaryPrefUpdate pref_update(scan->first->GetPrefs(),
920                                      prefs::kSafeBrowsingIncidentsSent);
921     MarkIncidentsAsReported(scan->second, pref_update.Get());
922   }
923 }
924
925 void IncidentReportingService::OnReportUploadResult(
926     UploadContext* context,
927     IncidentReportUploader::Result result,
928     scoped_ptr<ClientIncidentResponse> response) {
929   DCHECK(thread_checker_.CalledOnValidThread());
930
931   UMA_HISTOGRAM_ENUMERATION(
932       "SBIRS.UploadResult", result, IncidentReportUploader::NUM_UPLOAD_RESULTS);
933
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);
942
943   if (result == IncidentReportUploader::UPLOAD_SUCCESS)
944     HandleResponse(*upload);
945   // else retry?
946 }
947
948 void IncidentReportingService::Observe(
949     int type,
950     const content::NotificationSource& source,
951     const content::NotificationDetails& details) {
952   switch (type) {
953     case chrome::NOTIFICATION_PROFILE_ADDED: {
954       Profile* profile = content::Source<Profile>(source).ptr();
955       if (!profile->IsOffTheRecord())
956         OnProfileAdded(profile);
957       break;
958     }
959     case chrome::NOTIFICATION_PROFILE_DESTROYED: {
960       Profile* profile = content::Source<Profile>(source).ptr();
961       if (!profile->IsOffTheRecord())
962         OnProfileDestroyed(profile);
963       break;
964     }
965     default:
966       break;
967   }
968 }
969
970 }  // namespace safe_browsing