Upstream version 9.38.198.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/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"
38
39 namespace safe_browsing {
40
41 namespace {
42
43 // The type of an incident. Used for user metrics and for pruning of
44 // previously-reported incidents.
45 enum IncidentType {
46   // Start with 1 rather than zero; otherwise there won't be enough buckets for
47   // the histogram.
48   TRACKED_PREFERENCE = 1,
49   BINARY_INTEGRITY = 2,
50   // Values for new incident types go here.
51   NUM_INCIDENT_TYPES
52 };
53
54 // The action taken for an incident; used for user metrics (see
55 // LogIncidentDataType).
56 enum IncidentDisposition {
57   DROPPED,
58   ACCEPTED,
59 };
60
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.
65   IncidentType type;
66
67   // The key for a specific instance of an incident.
68   std::string key;
69
70   // A hash digest representing a specific instance of an incident.
71   uint32_t digest;
72 };
73
74 // The amount of time the service will wait to collate incidents.
75 const int64 kDefaultUploadDelayMs = 1000 * 60;  // one minute
76
77 // The amount of time between running delayed analysis callbacks.
78 const int64 kDefaultCallbackIntervalMs = 1000 * 20;
79
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) {
83   size_t result = 0;
84   if (incident.has_tracked_preference())
85     ++result;
86   if (incident.has_binary_integrity())
87     ++result;
88   // Add detection for new incident types here.
89   return result;
90 }
91
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;
99
100   // Add detection for new incident types here.
101   COMPILE_ASSERT(BINARY_INTEGRITY + 1 == NUM_INCIDENT_TYPES,
102                  add_support_for_new_types);
103   NOTREACHED();
104   return NUM_INCIDENT_TYPES;
105 }
106
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);
114   } else {
115     DCHECK_EQ(disposition, DROPPED);
116     UMA_HISTOGRAM_ENUMERATION("SBIRS.DroppedIncident", type,
117                               NUM_INCIDENT_TYPES);
118   }
119 }
120
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);
129       break;
130     case BINARY_INTEGRITY:
131       state.key = GetBinaryIntegrityIncidentKey(incident);
132       state.digest = GetBinaryIntegrityIncidentDigest(incident);
133       break;
134     // Add handling for new incident types here.
135     default:
136       COMPILE_ASSERT(BINARY_INTEGRITY + 1 == NUM_INCIDENT_TYPES,
137                      add_support_for_new_types);
138       NOTREACHED();
139       break;
140   }
141   return state;
142 }
143
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));
155 }
156
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,
166                                                            &type_dict)) {
167       type_dict = new base::DictionaryValue();
168       incidents_sent->SetWithoutPathExpansion(type_string, type_dict);
169     }
170     type_dict->SetStringWithoutPathExpansion(data.key,
171                                              base::UintToString(data.digest));
172   }
173 }
174
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());
183   else
184     thread_runner->PostTask(FROM_HERE,
185                             base::Bind(callback, base::Passed(&incident)));
186 }
187
188 }  // namespace
189
190 struct IncidentReportingService::ProfileContext {
191   ProfileContext();
192   ~ProfileContext();
193
194   // The incidents collected for this profile pending creation and/or upload.
195   ScopedVector<ClientIncidentReport_IncidentData> incidents;
196
197   // False until PROFILE_ADDED notification is received.
198   bool added;
199
200  private:
201   DISALLOW_COPY_AND_ASSIGN(ProfileContext);
202 };
203
204 class IncidentReportingService::UploadContext {
205  public:
206   typedef std::map<Profile*, std::vector<PersistentIncidentState> >
207       PersistentIncidentStateCollection;
208
209   explicit UploadContext(scoped_ptr<ClientIncidentReport> report);
210   ~UploadContext();
211
212   // The report being uploaded.
213   scoped_ptr<ClientIncidentReport> report;
214
215   // The uploader in use. This is NULL until the CSD killswitch is checked.
216   scoped_ptr<IncidentReportUploader> uploader;
217
218   // A mapping of profiles to the data to be persisted upon successful upload.
219   PersistentIncidentStateCollection profiles_to_state;
220
221  private:
222   DISALLOW_COPY_AND_ASSIGN(UploadContext);
223 };
224
225 IncidentReportingService::ProfileContext::ProfileContext() : added() {
226 }
227
228 IncidentReportingService::ProfileContext::~ProfileContext() {
229 }
230
231 IncidentReportingService::UploadContext::UploadContext(
232     scoped_ptr<ClientIncidentReport> report)
233     : report(report.Pass()) {
234 }
235
236 IncidentReportingService::UploadContext::~UploadContext() {
237 }
238
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),
254                        this,
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());
269 }
270
271 IncidentReportingService::~IncidentReportingService() {
272   CancelIncidentCollection();
273
274   // Cancel all internal asynchronous tasks.
275   weak_ptr_factory_.InvalidateWeakPtrs();
276
277   CancelEnvironmentCollection();
278   CancelDownloadCollection();
279   CancelAllReportUploads();
280
281   STLDeleteValues(&profiles_);
282 }
283
284 AddIncidentCallback IncidentReportingService::GetAddIncidentCallback(
285     Profile* profile) {
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
288   // queried.
289   ignore_result(GetOrCreateProfileContext(profile));
290
291   return base::Bind(&IncidentReportingService::AddIncident,
292                     receiver_weak_ptr_factory_.GetWeakPtr(),
293                     profile);
294 }
295
296 scoped_ptr<TrackedPreferenceValidationDelegate>
297 IncidentReportingService::CreatePreferenceValidationDelegate(Profile* profile) {
298   DCHECK(thread_checker_.CalledOnValidThread());
299
300   if (profile->IsOffTheRecord())
301     return scoped_ptr<TrackedPreferenceValidationDelegate>();
302   return scoped_ptr<TrackedPreferenceValidationDelegate>(
303       new PreferenceValidationDelegate(GetAddIncidentCallback(profile)));
304 }
305
306 void IncidentReportingService::RegisterDelayedAnalysisCallback(
307     const DelayedAnalysisCallback& callback) {
308   DCHECK(thread_checker_.CalledOnValidThread());
309
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(
314       base::Bind(callback,
315                  base::Bind(&AddIncidentOnOriginThread,
316                             GetAddIncidentCallback(NULL),
317                             base::ThreadTaskRunnerHandle::Get())));
318
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
321   // profile is added.
322   if (FindEligibleProfile())
323     delayed_analysis_callbacks_.Start();
324 }
325
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),
343                        this,
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());
354 }
355
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;
362   } else {
363     collect_environment_data_fn_ = &CollectEnvironmentData;
364     environment_collection_task_runner_ =
365         content::BrowserThread::GetBlockingPool()
366             ->GetTaskRunnerWithShutdownBehavior(
367                 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
368   }
369 }
370
371 void IncidentReportingService::OnProfileAdded(Profile* profile) {
372   DCHECK(thread_checker_.CalledOnValidThread());
373
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;
379
380   const bool safe_browsing_enabled =
381       profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled);
382
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();
388
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();
393
394   // TODO(grt): register for pref change notifications to start delayed analysis
395   // and/or report processing if sb is currently disabled but subsequently
396   // enabled.
397
398   // Nothing else to do if a report is not being assembled.
399   if (!report_)
400     return;
401
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();
408   }
409
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();
414 }
415
416 scoped_ptr<LastDownloadFinder> IncidentReportingService::CreateDownloadFinder(
417     const LastDownloadFinder::LastDownloadCallback& callback) {
418   return LastDownloadFinder::Create(callback).Pass();
419 }
420
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();
427 }
428
429 IncidentReportingService::ProfileContext*
430 IncidentReportingService::GetOrCreateProfileContext(Profile* profile) {
431   ProfileContextCollection::iterator it =
432       profiles_.insert(ProfileContextCollection::value_type(profile, NULL))
433           .first;
434   if (!it->second)
435     it->second = new ProfileContext();
436   return it->second;
437 }
438
439 IncidentReportingService::ProfileContext*
440 IncidentReportingService::GetProfileContext(Profile* profile) {
441   ProfileContextCollection::iterator it = profiles_.find(profile);
442   return it == profiles_.end() ? NULL : it->second;
443 }
444
445 void IncidentReportingService::OnProfileDestroyed(Profile* profile) {
446   DCHECK(thread_checker_.CalledOnValidThread());
447
448   ProfileContextCollection::iterator it = profiles_.find(profile);
449   if (it == profiles_.end())
450     return;
451
452   // TODO(grt): Persist incidents for upload on future profile load.
453
454   // Forget about this profile. Incidents not yet sent for upload are lost.
455   // No new incidents will be accepted for it.
456   delete it->second;
457   profiles_.erase(it);
458
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);
462 }
463
464 Profile* IncidentReportingService::FindEligibleProfile() const {
465   Profile* candidate = NULL;
466   for (ProfileContextCollection::const_iterator scan = profiles_.begin();
467        scan != profiles_.end();
468        ++scan) {
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)
473       continue;
474     PrefService* prefs = scan->first->GetPrefs();
475     if (prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) {
476       if (!candidate)
477         candidate = scan->first;
478       if (prefs->GetBoolean(prefs::kSafeBrowsingExtendedReportingEnabled)) {
479         candidate = scan->first;
480         break;
481       }
482     }
483   }
484   return candidate;
485 }
486
487 void IncidentReportingService::AddIncident(
488     Profile* profile,
489     scoped_ptr<ClientIncidentReport_IncidentData> incident_data) {
490   DCHECK(thread_checker_.CalledOnValidThread());
491   DCHECK_EQ(1U, CountIncidents(*incident_data));
492
493   ProfileContext* context = GetProfileContext(profile);
494   // It is forbidden to call this function with a destroyed profile.
495   DCHECK(context);
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);
499
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);
506     return;
507   }
508
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());
512
513   // Take ownership of the incident.
514   context->incidents.push_back(incident_data.release());
515
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_);
523   }
524   last_incident_time_ = base::TimeTicks::Now();
525
526   // Persist the incident data.
527
528   // Start assembling a new report if this is the first incident ever or the
529   // first since the last upload.
530   BeginReportProcessing();
531 }
532
533 void IncidentReportingService::BeginReportProcessing() {
534   DCHECK(thread_checker_.CalledOnValidThread());
535
536   // Creates a new report if needed.
537   if (!report_)
538     report_.reset(new ClientIncidentReport());
539
540   // Ensure that collection tasks are running (calls are idempotent).
541   BeginIncidentCollation();
542   BeginEnvironmentCollection();
543   BeginDownloadCollection();
544 }
545
546 void IncidentReportingService::BeginIncidentCollation() {
547   // Restart the delay timer to send the report upon expiration.
548   collation_timeout_pending_ = true;
549   collation_timer_.Reset();
550 }
551
552 void IncidentReportingService::BeginEnvironmentCollection() {
553   DCHECK(thread_checker_.CalledOnValidThread());
554   DCHECK(report_);
555   // Nothing to do if environment collection is pending or has already
556   // completed.
557   if (environment_collection_pending_ || report_->has_environment())
558     return;
559
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(
565           FROM_HERE,
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))));
570
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_);
574 }
575
576 bool IncidentReportingService::WaitingForEnvironmentCollection() {
577   return environment_collection_pending_;
578 }
579
580 void IncidentReportingService::CancelEnvironmentCollection() {
581   environment_collection_begin_ = base::TimeTicks();
582   environment_collection_pending_ = false;
583   if (report_)
584     report_->clear_environment();
585 }
586
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;
593
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());
599 #endif
600
601   report_->set_allocated_environment(environment_data.release());
602
603   UMA_HISTOGRAM_TIMES("SBIRS.EnvCollectionTime",
604                       base::TimeTicks::Now() - environment_collection_begin_);
605   environment_collection_begin_ = base::TimeTicks();
606
607   UploadIfCollectionComplete();
608 }
609
610 bool IncidentReportingService::WaitingToCollateIncidents() {
611   return collation_timeout_pending_;
612 }
613
614 void IncidentReportingService::CancelIncidentCollection() {
615   collation_timeout_pending_ = false;
616   last_incident_time_ = base::TimeTicks();
617   report_.reset();
618 }
619
620 void IncidentReportingService::OnCollationTimeout() {
621   DCHECK(thread_checker_.CalledOnValidThread());
622
623   // Exit early if collection was cancelled.
624   if (!collation_timeout_pending_)
625     return;
626
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();
631        ++scan) {
632     if (scan->first && !scan->second->added &&
633         !scan->second->incidents.empty()) {
634       collation_timer_.Reset();
635       return;
636     }
637   }
638
639   collation_timeout_pending_ = false;
640
641   UploadIfCollectionComplete();
642 }
643
644 void IncidentReportingService::BeginDownloadCollection() {
645   DCHECK(thread_checker_.CalledOnValidThread());
646   DCHECK(report_);
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())
650     return;
651
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
658   // the scene.
659   if (!last_download_finder_)
660     last_download_begin_ = base::TimeTicks();
661 }
662
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())
667     return false;
668   // The next easy case: waiting if the finder is operating.
669   if (last_download_finder_)
670     return true;
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();
674        ++scan) {
675     if (!scan->second->added)
676       return true;
677   }
678   // There is no most recent download and there's nothing more to wait for.
679   return false;
680 }
681
682 void IncidentReportingService::CancelDownloadCollection() {
683   last_download_finder_.reset();
684   last_download_begin_ = base::TimeTicks();
685   if (report_)
686     report_->clear_download();
687 }
688
689 void IncidentReportingService::OnLastDownloadFound(
690     scoped_ptr<ClientIncidentReport_DownloadDetails> last_download) {
691   DCHECK(thread_checker_.CalledOnValidThread());
692   DCHECK(report_);
693
694   UMA_HISTOGRAM_TIMES("SBIRS.FindDownloadedBinaryTime",
695                       base::TimeTicks::Now() - last_download_begin_);
696   last_download_begin_ = base::TimeTicks();
697
698   // Harvest the finder.
699   last_download_finder_.reset();
700
701   if (last_download)
702     report_->set_allocated_download(last_download.release());
703
704   UploadIfCollectionComplete();
705 }
706
707 void IncidentReportingService::UploadIfCollectionComplete() {
708   DCHECK(report_);
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()) {
714     return;
715   }
716
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();
721
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);
727     return;
728   }
729
730   ClientIncidentReport_EnvironmentData_Process* process =
731       report->mutable_environment()->mutable_process();
732
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));
738   }
739
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) :
745                        false);
746
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();
757   }
758
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();
767        ++scan) {
768     // Bypass process-wide incidents that have not yet been associated with a
769     // profile.
770     if (!scan->first)
771       continue;
772     PrefService* prefs = scan->first->GetPrefs();
773     ProfileContext* context = scan->second;
774     if (context->incidents.empty())
775       continue;
776     if (!prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) {
777       for (size_t i = 0; i < context->incidents.size(); ++i) {
778         LogIncidentDataType(DROPPED, *context->incidents[i]);
779       }
780       context->incidents.clear();
781       continue;
782     }
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)) {
791         ++prune_count;
792         delete context->incidents[i];
793         context->incidents[i] = NULL;
794       } else {
795         states.push_back(state);
796       }
797     }
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])
803           ++prune_count;
804       }
805       context->incidents.clear();
806       prefs->ClearPref(prefs::kSafeBrowsingIncidentReportSent);
807       DictionaryPrefUpdate pref_update(prefs,
808                                        prefs::kSafeBrowsingIncidentsSent);
809       MarkIncidentsAsReported(states, pref_update.Get());
810     } else {
811       for (size_t i = 0; i < context->incidents.size(); ++i) {
812         ClientIncidentReport_IncidentData* incident = context->incidents[i];
813         if (incident) {
814           LogIncidentDataType(ACCEPTED, *incident);
815           // Ownership of the incident is passed to the report.
816           report->mutable_incident()->AddAllocated(incident);
817         }
818       }
819       context->incidents.weak_clear();
820       std::vector<PersistentIncidentState>& profile_states =
821           profiles_to_state[scan->first];
822       profile_states.swap(states);
823     }
824   }
825
826   const int count = report->incident_size();
827   // Abandon the request if all incidents were dropped with none pruned.
828   if (!count && !prune_count)
829     return;
830
831   UMA_HISTOGRAM_COUNTS_100("SBIRS.IncidentCount", count + prune_count);
832
833   {
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));
838   }
839   // Abandon the report if all incidents were pruned.
840   if (!count)
841     return;
842
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);
851   } else {
852     if (content::BrowserThread::PostTaskAndReplyWithResult(
853             content::BrowserThread::IO,
854             FROM_HERE,
855             base::Bind(&SafeBrowsingDatabaseManager::IsCsdWhitelistKillSwitchOn,
856                        database_manager_),
857             base::Bind(&IncidentReportingService::OnKillSwitchResult,
858                        weak_ptr_factory_.GetWeakPtr(),
859                        context.get()))) {
860       uploads_.push_back(context.release());
861     }  // else should not happen. Let the context be deleted automatically.
862   }
863 }
864
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);
870   }
871   uploads_.clear();
872 }
873
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.
879     context->uploader =
880         StartReportUpload(
881             base::Bind(&IncidentReportingService::OnReportUploadResult,
882                        weak_ptr_factory_.GetWeakPtr(),
883                        context),
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>());
890     }
891   } else {
892     OnReportUploadResult(context,
893                          IncidentReportUploader::UPLOAD_SUPPRESSED,
894                          scoped_ptr<ClientIncidentResponse>());
895   }
896 }
897
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();
902        ++scan) {
903     DictionaryPrefUpdate pref_update(scan->first->GetPrefs(),
904                                      prefs::kSafeBrowsingIncidentsSent);
905     MarkIncidentsAsReported(scan->second, pref_update.Get());
906   }
907 }
908
909 void IncidentReportingService::OnReportUploadResult(
910     UploadContext* context,
911     IncidentReportUploader::Result result,
912     scoped_ptr<ClientIncidentResponse> response) {
913   DCHECK(thread_checker_.CalledOnValidThread());
914
915   UMA_HISTOGRAM_ENUMERATION(
916       "SBIRS.UploadResult", result, IncidentReportUploader::NUM_UPLOAD_RESULTS);
917
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);
926
927   if (result == IncidentReportUploader::UPLOAD_SUCCESS)
928     HandleResponse(*upload);
929   // else retry?
930 }
931
932 void IncidentReportingService::Observe(
933     int type,
934     const content::NotificationSource& source,
935     const content::NotificationDetails& details) {
936   switch (type) {
937     case chrome::NOTIFICATION_PROFILE_ADDED: {
938       Profile* profile = content::Source<Profile>(source).ptr();
939       if (!profile->IsOffTheRecord())
940         OnProfileAdded(profile);
941       break;
942     }
943     case chrome::NOTIFICATION_PROFILE_DESTROYED: {
944       Profile* profile = content::Source<Profile>(source).ptr();
945       if (!profile->IsOffTheRecord())
946         OnProfileDestroyed(profile);
947       break;
948     }
949     default:
950       break;
951   }
952 }
953
954 }  // namespace safe_browsing