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
40namespace safe_browsing {
41
42namespace {
43
44// The type of an incident. Used for user metrics and for pruning of
45// previously-reported incidents.
46enum 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).
58enum 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.
65struct 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.
77const int64 kDefaultUploadDelayMs = 1000 * 60;  // one minute
78
79// The amount of time between running delayed analysis callbacks.
80const 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.
84size_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|.
97IncidentType 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.
114void 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.
128PersistentIncidentState 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.
156bool 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|.
169void 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.
187void 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
200struct 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
214class 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
235IncidentReportingService::ProfileContext::ProfileContext() : added() {
236}
237
238IncidentReportingService::ProfileContext::~ProfileContext() {
239}
240
241IncidentReportingService::UploadContext::UploadContext(
242    scoped_ptr<ClientIncidentReport> report)
243    : report(report.Pass()) {
244}
245
246IncidentReportingService::UploadContext::~UploadContext() {
247}
248
249IncidentReportingService::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
281IncidentReportingService::~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
294AddIncidentCallback 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
306scoped_ptr<TrackedPreferenceValidationDelegate>
307IncidentReportingService::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
316void 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
336IncidentReportingService::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
366void 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
381void 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
428scoped_ptr<LastDownloadFinder> IncidentReportingService::CreateDownloadFinder(
429    const LastDownloadFinder::LastDownloadCallback& callback) {
430  return LastDownloadFinder::Create(callback).Pass();
431}
432
433scoped_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
441bool IncidentReportingService::IsProcessingReport() const {
442  return report_ != NULL;
443}
444
445IncidentReportingService::ProfileContext*
446IncidentReportingService::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
455IncidentReportingService::ProfileContext*
456IncidentReportingService::GetProfileContext(Profile* profile) {
457  ProfileContextCollection::iterator it = profiles_.find(profile);
458  return it == profiles_.end() ? NULL : it->second;
459}
460
461void 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
480Profile* 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
503void 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
549void 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
562void IncidentReportingService::BeginIncidentCollation() {
563  // Restart the delay timer to send the report upon expiration.
564  collation_timeout_pending_ = true;
565  collation_timer_.Reset();
566}
567
568void 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
592bool IncidentReportingService::WaitingForEnvironmentCollection() {
593  return environment_collection_pending_;
594}
595
596void IncidentReportingService::CancelEnvironmentCollection() {
597  environment_collection_begin_ = base::TimeTicks();
598  environment_collection_pending_ = false;
599  if (report_)
600    report_->clear_environment();
601}
602
603void 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
626bool IncidentReportingService::WaitingToCollateIncidents() {
627  return collation_timeout_pending_;
628}
629
630void IncidentReportingService::CancelIncidentCollection() {
631  collation_timeout_pending_ = false;
632  last_incident_time_ = base::TimeTicks();
633  report_.reset();
634}
635
636void 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
660void 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
679bool 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
698void IncidentReportingService::CancelDownloadCollection() {
699  last_download_finder_.reset();
700  last_download_begin_ = base::TimeTicks();
701  if (report_)
702    report_->clear_download();
703}
704
705void 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
723void 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
881void 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
890void 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
914void 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
925void 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
948void 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
971