| Index: chrome/browser/safe_browsing/incident_reporting_service.cc
|
| diff --git a/chrome/browser/safe_browsing/incident_reporting_service.cc b/chrome/browser/safe_browsing/incident_reporting_service.cc
|
| deleted file mode 100644
|
| index fe13feccb6c7efb4dacc43910c9846b9c91419b7..0000000000000000000000000000000000000000
|
| --- a/chrome/browser/safe_browsing/incident_reporting_service.cc
|
| +++ /dev/null
|
| @@ -1,954 +0,0 @@
|
| -// Copyright 2014 The Chromium Authors. All rights reserved.
|
| -// Use of this source code is governed by a BSD-style license that can be
|
| -// found in the LICENSE file.
|
| -
|
| -#include "chrome/browser/safe_browsing/incident_reporting_service.h"
|
| -
|
| -#include <math.h>
|
| -
|
| -#include <algorithm>
|
| -#include <vector>
|
| -
|
| -#include "base/metrics/histogram.h"
|
| -#include "base/prefs/pref_service.h"
|
| -#include "base/prefs/scoped_user_pref_update.h"
|
| -#include "base/process/process_info.h"
|
| -#include "base/single_thread_task_runner.h"
|
| -#include "base/stl_util.h"
|
| -#include "base/strings/string_number_conversions.h"
|
| -#include "base/thread_task_runner_handle.h"
|
| -#include "base/threading/sequenced_worker_pool.h"
|
| -#include "base/values.h"
|
| -#include "chrome/browser/browser_process.h"
|
| -#include "chrome/browser/chrome_notification_types.h"
|
| -#include "chrome/browser/prefs/tracked/tracked_preference_validation_delegate.h"
|
| -#include "chrome/browser/profiles/profile.h"
|
| -#include "chrome/browser/safe_browsing/binary_integrity_incident_handlers.h"
|
| -#include "chrome/browser/safe_browsing/database_manager.h"
|
| -#include "chrome/browser/safe_browsing/environment_data_collection.h"
|
| -#include "chrome/browser/safe_browsing/incident_report_uploader_impl.h"
|
| -#include "chrome/browser/safe_browsing/preference_validation_delegate.h"
|
| -#include "chrome/browser/safe_browsing/safe_browsing_service.h"
|
| -#include "chrome/browser/safe_browsing/tracked_preference_incident_handlers.h"
|
| -#include "chrome/common/pref_names.h"
|
| -#include "chrome/common/safe_browsing/csd.pb.h"
|
| -#include "content/public/browser/browser_thread.h"
|
| -#include "content/public/browser/notification_service.h"
|
| -#include "net/url_request/url_request_context_getter.h"
|
| -
|
| -namespace safe_browsing {
|
| -
|
| -namespace {
|
| -
|
| -// The type of an incident. Used for user metrics and for pruning of
|
| -// previously-reported incidents.
|
| -enum IncidentType {
|
| - // Start with 1 rather than zero; otherwise there won't be enough buckets for
|
| - // the histogram.
|
| - TRACKED_PREFERENCE = 1,
|
| - BINARY_INTEGRITY = 2,
|
| - // Values for new incident types go here.
|
| - NUM_INCIDENT_TYPES
|
| -};
|
| -
|
| -// The action taken for an incident; used for user metrics (see
|
| -// LogIncidentDataType).
|
| -enum IncidentDisposition {
|
| - DROPPED,
|
| - ACCEPTED,
|
| -};
|
| -
|
| -// The state persisted for a specific instance of an incident to enable pruning
|
| -// of previously-reported incidents.
|
| -struct PersistentIncidentState {
|
| - // The type of the incident.
|
| - IncidentType type;
|
| -
|
| - // The key for a specific instance of an incident.
|
| - std::string key;
|
| -
|
| - // A hash digest representing a specific instance of an incident.
|
| - uint32_t digest;
|
| -};
|
| -
|
| -// The amount of time the service will wait to collate incidents.
|
| -const int64 kDefaultUploadDelayMs = 1000 * 60; // one minute
|
| -
|
| -// The amount of time between running delayed analysis callbacks.
|
| -const int64 kDefaultCallbackIntervalMs = 1000 * 20;
|
| -
|
| -// Returns the number of incidents contained in |incident|. The result is
|
| -// expected to be 1. Used in DCHECKs.
|
| -size_t CountIncidents(const ClientIncidentReport_IncidentData& incident) {
|
| - size_t result = 0;
|
| - if (incident.has_tracked_preference())
|
| - ++result;
|
| - if (incident.has_binary_integrity())
|
| - ++result;
|
| - // Add detection for new incident types here.
|
| - return result;
|
| -}
|
| -
|
| -// Returns the type of incident contained in |incident_data|.
|
| -IncidentType GetIncidentType(
|
| - const ClientIncidentReport_IncidentData& incident_data) {
|
| - if (incident_data.has_tracked_preference())
|
| - return TRACKED_PREFERENCE;
|
| - if (incident_data.has_binary_integrity())
|
| - return BINARY_INTEGRITY;
|
| -
|
| - // Add detection for new incident types here.
|
| - COMPILE_ASSERT(BINARY_INTEGRITY + 1 == NUM_INCIDENT_TYPES,
|
| - add_support_for_new_types);
|
| - NOTREACHED();
|
| - return NUM_INCIDENT_TYPES;
|
| -}
|
| -
|
| -// Logs the type of incident in |incident_data| to a user metrics histogram.
|
| -void LogIncidentDataType(
|
| - IncidentDisposition disposition,
|
| - const ClientIncidentReport_IncidentData& incident_data) {
|
| - IncidentType type = GetIncidentType(incident_data);
|
| - if (disposition == ACCEPTED) {
|
| - UMA_HISTOGRAM_ENUMERATION("SBIRS.Incident", type, NUM_INCIDENT_TYPES);
|
| - } else {
|
| - DCHECK_EQ(disposition, DROPPED);
|
| - UMA_HISTOGRAM_ENUMERATION("SBIRS.DroppedIncident", type,
|
| - NUM_INCIDENT_TYPES);
|
| - }
|
| -}
|
| -
|
| -// Computes the persistent state for an incident.
|
| -PersistentIncidentState ComputeIncidentState(
|
| - const ClientIncidentReport_IncidentData& incident) {
|
| - PersistentIncidentState state = {GetIncidentType(incident)};
|
| - switch (state.type) {
|
| - case TRACKED_PREFERENCE:
|
| - state.key = GetTrackedPreferenceIncidentKey(incident);
|
| - state.digest = GetTrackedPreferenceIncidentDigest(incident);
|
| - break;
|
| - case BINARY_INTEGRITY:
|
| - state.key = GetBinaryIntegrityIncidentKey(incident);
|
| - state.digest = GetBinaryIntegrityIncidentDigest(incident);
|
| - break;
|
| - // Add handling for new incident types here.
|
| - default:
|
| - COMPILE_ASSERT(BINARY_INTEGRITY + 1 == NUM_INCIDENT_TYPES,
|
| - add_support_for_new_types);
|
| - NOTREACHED();
|
| - break;
|
| - }
|
| - return state;
|
| -}
|
| -
|
| -// Returns true if the incident described by |state| has already been reported
|
| -// based on the bookkeeping in the |incidents_sent| preference dictionary.
|
| -bool IncidentHasBeenReported(const base::DictionaryValue* incidents_sent,
|
| - const PersistentIncidentState& state) {
|
| - const base::DictionaryValue* type_dict = NULL;
|
| - std::string digest_string;
|
| - return (incidents_sent &&
|
| - incidents_sent->GetDictionaryWithoutPathExpansion(
|
| - base::IntToString(state.type), &type_dict) &&
|
| - type_dict->GetStringWithoutPathExpansion(state.key, &digest_string) &&
|
| - digest_string == base::UintToString(state.digest));
|
| -}
|
| -
|
| -// Marks the incidents described by |states| as having been reported
|
| -// in |incidents_set|.
|
| -void MarkIncidentsAsReported(const std::vector<PersistentIncidentState>& states,
|
| - base::DictionaryValue* incidents_sent) {
|
| - for (size_t i = 0; i < states.size(); ++i) {
|
| - const PersistentIncidentState& data = states[i];
|
| - base::DictionaryValue* type_dict = NULL;
|
| - const std::string type_string(base::IntToString(data.type));
|
| - if (!incidents_sent->GetDictionaryWithoutPathExpansion(type_string,
|
| - &type_dict)) {
|
| - type_dict = new base::DictionaryValue();
|
| - incidents_sent->SetWithoutPathExpansion(type_string, type_dict);
|
| - }
|
| - type_dict->SetStringWithoutPathExpansion(data.key,
|
| - base::UintToString(data.digest));
|
| - }
|
| -}
|
| -
|
| -// Runs |callback| on the thread to which |thread_runner| belongs. The callback
|
| -// is run immediately if this function is called on |thread_runner|'s thread.
|
| -void AddIncidentOnOriginThread(
|
| - const AddIncidentCallback& callback,
|
| - scoped_refptr<base::SingleThreadTaskRunner> thread_runner,
|
| - scoped_ptr<ClientIncidentReport_IncidentData> incident) {
|
| - if (thread_runner->BelongsToCurrentThread())
|
| - callback.Run(incident.Pass());
|
| - else
|
| - thread_runner->PostTask(FROM_HERE,
|
| - base::Bind(callback, base::Passed(&incident)));
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -struct IncidentReportingService::ProfileContext {
|
| - ProfileContext();
|
| - ~ProfileContext();
|
| -
|
| - // The incidents collected for this profile pending creation and/or upload.
|
| - ScopedVector<ClientIncidentReport_IncidentData> incidents;
|
| -
|
| - // False until PROFILE_ADDED notification is received.
|
| - bool added;
|
| -
|
| - private:
|
| - DISALLOW_COPY_AND_ASSIGN(ProfileContext);
|
| -};
|
| -
|
| -class IncidentReportingService::UploadContext {
|
| - public:
|
| - typedef std::map<Profile*, std::vector<PersistentIncidentState> >
|
| - PersistentIncidentStateCollection;
|
| -
|
| - explicit UploadContext(scoped_ptr<ClientIncidentReport> report);
|
| - ~UploadContext();
|
| -
|
| - // The report being uploaded.
|
| - scoped_ptr<ClientIncidentReport> report;
|
| -
|
| - // The uploader in use. This is NULL until the CSD killswitch is checked.
|
| - scoped_ptr<IncidentReportUploader> uploader;
|
| -
|
| - // A mapping of profiles to the data to be persisted upon successful upload.
|
| - PersistentIncidentStateCollection profiles_to_state;
|
| -
|
| - private:
|
| - DISALLOW_COPY_AND_ASSIGN(UploadContext);
|
| -};
|
| -
|
| -IncidentReportingService::ProfileContext::ProfileContext() : added() {
|
| -}
|
| -
|
| -IncidentReportingService::ProfileContext::~ProfileContext() {
|
| -}
|
| -
|
| -IncidentReportingService::UploadContext::UploadContext(
|
| - scoped_ptr<ClientIncidentReport> report)
|
| - : report(report.Pass()) {
|
| -}
|
| -
|
| -IncidentReportingService::UploadContext::~UploadContext() {
|
| -}
|
| -
|
| -IncidentReportingService::IncidentReportingService(
|
| - SafeBrowsingService* safe_browsing_service,
|
| - const scoped_refptr<net::URLRequestContextGetter>& request_context_getter)
|
| - : database_manager_(safe_browsing_service ?
|
| - safe_browsing_service->database_manager() : NULL),
|
| - url_request_context_getter_(request_context_getter),
|
| - collect_environment_data_fn_(&CollectEnvironmentData),
|
| - environment_collection_task_runner_(
|
| - content::BrowserThread::GetBlockingPool()
|
| - ->GetTaskRunnerWithShutdownBehavior(
|
| - base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
|
| - environment_collection_pending_(),
|
| - collation_timeout_pending_(),
|
| - collation_timer_(FROM_HERE,
|
| - base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs),
|
| - this,
|
| - &IncidentReportingService::OnCollationTimeout),
|
| - delayed_analysis_callbacks_(
|
| - base::TimeDelta::FromMilliseconds(kDefaultCallbackIntervalMs),
|
| - content::BrowserThread::GetBlockingPool()
|
| - ->GetTaskRunnerWithShutdownBehavior(
|
| - base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
|
| - receiver_weak_ptr_factory_(this),
|
| - weak_ptr_factory_(this) {
|
| - notification_registrar_.Add(this,
|
| - chrome::NOTIFICATION_PROFILE_ADDED,
|
| - content::NotificationService::AllSources());
|
| - notification_registrar_.Add(this,
|
| - chrome::NOTIFICATION_PROFILE_DESTROYED,
|
| - content::NotificationService::AllSources());
|
| -}
|
| -
|
| -IncidentReportingService::~IncidentReportingService() {
|
| - CancelIncidentCollection();
|
| -
|
| - // Cancel all internal asynchronous tasks.
|
| - weak_ptr_factory_.InvalidateWeakPtrs();
|
| -
|
| - CancelEnvironmentCollection();
|
| - CancelDownloadCollection();
|
| - CancelAllReportUploads();
|
| -
|
| - STLDeleteValues(&profiles_);
|
| -}
|
| -
|
| -AddIncidentCallback IncidentReportingService::GetAddIncidentCallback(
|
| - Profile* profile) {
|
| - // Force the context to be created so that incidents added before
|
| - // OnProfileAdded is called are held until the profile's preferences can be
|
| - // queried.
|
| - ignore_result(GetOrCreateProfileContext(profile));
|
| -
|
| - return base::Bind(&IncidentReportingService::AddIncident,
|
| - receiver_weak_ptr_factory_.GetWeakPtr(),
|
| - profile);
|
| -}
|
| -
|
| -scoped_ptr<TrackedPreferenceValidationDelegate>
|
| -IncidentReportingService::CreatePreferenceValidationDelegate(Profile* profile) {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| -
|
| - if (profile->IsOffTheRecord())
|
| - return scoped_ptr<TrackedPreferenceValidationDelegate>();
|
| - return scoped_ptr<TrackedPreferenceValidationDelegate>(
|
| - new PreferenceValidationDelegate(GetAddIncidentCallback(profile)));
|
| -}
|
| -
|
| -void IncidentReportingService::RegisterDelayedAnalysisCallback(
|
| - const DelayedAnalysisCallback& callback) {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| -
|
| - // |callback| will be run on the blocking pool, so it will likely run the
|
| - // AddIncidentCallback there as well. Bounce the run of that callback back to
|
| - // the current thread via AddIncidentOnOriginThread.
|
| - delayed_analysis_callbacks_.RegisterCallback(
|
| - base::Bind(callback,
|
| - base::Bind(&AddIncidentOnOriginThread,
|
| - GetAddIncidentCallback(NULL),
|
| - base::ThreadTaskRunnerHandle::Get())));
|
| -
|
| - // Start running the callbacks if any profiles are participating in safe
|
| - // browsing. If none are now, running will commence if/when a participaing
|
| - // profile is added.
|
| - if (FindEligibleProfile())
|
| - delayed_analysis_callbacks_.Start();
|
| -}
|
| -
|
| -IncidentReportingService::IncidentReportingService(
|
| - SafeBrowsingService* safe_browsing_service,
|
| - const scoped_refptr<net::URLRequestContextGetter>& request_context_getter,
|
| - base::TimeDelta delayed_task_interval,
|
| - const scoped_refptr<base::TaskRunner>& delayed_task_runner)
|
| - : database_manager_(safe_browsing_service ?
|
| - safe_browsing_service->database_manager() : NULL),
|
| - url_request_context_getter_(request_context_getter),
|
| - collect_environment_data_fn_(&CollectEnvironmentData),
|
| - environment_collection_task_runner_(
|
| - content::BrowserThread::GetBlockingPool()
|
| - ->GetTaskRunnerWithShutdownBehavior(
|
| - base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
|
| - environment_collection_pending_(),
|
| - collation_timeout_pending_(),
|
| - collation_timer_(FROM_HERE,
|
| - base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs),
|
| - this,
|
| - &IncidentReportingService::OnCollationTimeout),
|
| - delayed_analysis_callbacks_(delayed_task_interval, delayed_task_runner),
|
| - receiver_weak_ptr_factory_(this),
|
| - weak_ptr_factory_(this) {
|
| - notification_registrar_.Add(this,
|
| - chrome::NOTIFICATION_PROFILE_ADDED,
|
| - content::NotificationService::AllSources());
|
| - notification_registrar_.Add(this,
|
| - chrome::NOTIFICATION_PROFILE_DESTROYED,
|
| - content::NotificationService::AllSources());
|
| -}
|
| -
|
| -void IncidentReportingService::SetCollectEnvironmentHook(
|
| - CollectEnvironmentDataFn collect_environment_data_hook,
|
| - const scoped_refptr<base::TaskRunner>& task_runner) {
|
| - if (collect_environment_data_hook) {
|
| - collect_environment_data_fn_ = collect_environment_data_hook;
|
| - environment_collection_task_runner_ = task_runner;
|
| - } else {
|
| - collect_environment_data_fn_ = &CollectEnvironmentData;
|
| - environment_collection_task_runner_ =
|
| - content::BrowserThread::GetBlockingPool()
|
| - ->GetTaskRunnerWithShutdownBehavior(
|
| - base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
|
| - }
|
| -}
|
| -
|
| -void IncidentReportingService::OnProfileAdded(Profile* profile) {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| -
|
| - // Track the addition of all profiles even when no report is being assembled
|
| - // so that the service can determine whether or not it can evaluate a
|
| - // profile's preferences at the time of incident addition.
|
| - ProfileContext* context = GetOrCreateProfileContext(profile);
|
| - context->added = true;
|
| -
|
| - const bool safe_browsing_enabled =
|
| - profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled);
|
| -
|
| - // Start processing delayed analysis callbacks if this new profile
|
| - // participates in safe browsing. Start is idempotent, so this is safe even if
|
| - // they're already running.
|
| - if (safe_browsing_enabled)
|
| - delayed_analysis_callbacks_.Start();
|
| -
|
| - // Start a new report if this profile participates in safe browsing and there
|
| - // are process-wide incidents.
|
| - if (safe_browsing_enabled && GetProfileContext(NULL))
|
| - BeginReportProcessing();
|
| -
|
| - // TODO(grt): register for pref change notifications to start delayed analysis
|
| - // and/or report processing if sb is currently disabled but subsequently
|
| - // enabled.
|
| -
|
| - // Nothing else to do if a report is not being assembled.
|
| - if (!report_)
|
| - return;
|
| -
|
| - // Drop all incidents associated with this profile that were received prior to
|
| - // its addition if the profile is not participating in safe browsing.
|
| - if (!context->incidents.empty() && !safe_browsing_enabled) {
|
| - for (size_t i = 0; i < context->incidents.size(); ++i)
|
| - LogIncidentDataType(DROPPED, *context->incidents[i]);
|
| - context->incidents.clear();
|
| - }
|
| -
|
| - // Take another stab at finding the most recent download if a report is being
|
| - // assembled and one hasn't been found yet (the LastDownloadFinder operates
|
| - // only on profiles that have been added to the ProfileManager).
|
| - BeginDownloadCollection();
|
| -}
|
| -
|
| -scoped_ptr<LastDownloadFinder> IncidentReportingService::CreateDownloadFinder(
|
| - const LastDownloadFinder::LastDownloadCallback& callback) {
|
| - return LastDownloadFinder::Create(callback).Pass();
|
| -}
|
| -
|
| -scoped_ptr<IncidentReportUploader> IncidentReportingService::StartReportUpload(
|
| - const IncidentReportUploader::OnResultCallback& callback,
|
| - const scoped_refptr<net::URLRequestContextGetter>& request_context_getter,
|
| - const ClientIncidentReport& report) {
|
| - return IncidentReportUploaderImpl::UploadReport(
|
| - callback, request_context_getter, report).Pass();
|
| -}
|
| -
|
| -IncidentReportingService::ProfileContext*
|
| -IncidentReportingService::GetOrCreateProfileContext(Profile* profile) {
|
| - ProfileContextCollection::iterator it =
|
| - profiles_.insert(ProfileContextCollection::value_type(profile, NULL))
|
| - .first;
|
| - if (!it->second)
|
| - it->second = new ProfileContext();
|
| - return it->second;
|
| -}
|
| -
|
| -IncidentReportingService::ProfileContext*
|
| -IncidentReportingService::GetProfileContext(Profile* profile) {
|
| - ProfileContextCollection::iterator it = profiles_.find(profile);
|
| - return it == profiles_.end() ? NULL : it->second;
|
| -}
|
| -
|
| -void IncidentReportingService::OnProfileDestroyed(Profile* profile) {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| -
|
| - ProfileContextCollection::iterator it = profiles_.find(profile);
|
| - if (it == profiles_.end())
|
| - return;
|
| -
|
| - // TODO(grt): Persist incidents for upload on future profile load.
|
| -
|
| - // Forget about this profile. Incidents not yet sent for upload are lost.
|
| - // No new incidents will be accepted for it.
|
| - delete it->second;
|
| - profiles_.erase(it);
|
| -
|
| - // Remove the association with this profile from all pending uploads.
|
| - for (size_t i = 0; i < uploads_.size(); ++i)
|
| - uploads_[i]->profiles_to_state.erase(profile);
|
| -}
|
| -
|
| -Profile* IncidentReportingService::FindEligibleProfile() const {
|
| - Profile* candidate = NULL;
|
| - for (ProfileContextCollection::const_iterator scan = profiles_.begin();
|
| - scan != profiles_.end();
|
| - ++scan) {
|
| - // Skip over profiles that have yet to be added to the profile manager.
|
| - // This will also skip over the NULL-profile context used to hold
|
| - // process-wide incidents.
|
| - if (!scan->second->added)
|
| - continue;
|
| - PrefService* prefs = scan->first->GetPrefs();
|
| - if (prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) {
|
| - if (!candidate)
|
| - candidate = scan->first;
|
| - if (prefs->GetBoolean(prefs::kSafeBrowsingExtendedReportingEnabled)) {
|
| - candidate = scan->first;
|
| - break;
|
| - }
|
| - }
|
| - }
|
| - return candidate;
|
| -}
|
| -
|
| -void IncidentReportingService::AddIncident(
|
| - Profile* profile,
|
| - scoped_ptr<ClientIncidentReport_IncidentData> incident_data) {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - DCHECK_EQ(1U, CountIncidents(*incident_data));
|
| -
|
| - ProfileContext* context = GetProfileContext(profile);
|
| - // It is forbidden to call this function with a destroyed profile.
|
| - DCHECK(context);
|
| - // If this is a process-wide incident, the context must not indicate that the
|
| - // profile (which is NULL) has been added to the profile manager.
|
| - DCHECK(profile || !context->added);
|
| -
|
| - // Drop the incident immediately if the profile has already been added to the
|
| - // manager and is not participating in safe browsing. Preference evaluation is
|
| - // deferred until OnProfileAdded() otherwise.
|
| - if (context->added &&
|
| - !profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
|
| - LogIncidentDataType(DROPPED, *incident_data);
|
| - return;
|
| - }
|
| -
|
| - // Provide time to the new incident if the caller didn't do so.
|
| - if (!incident_data->has_incident_time_msec())
|
| - incident_data->set_incident_time_msec(base::Time::Now().ToJavaTime());
|
| -
|
| - // Take ownership of the incident.
|
| - context->incidents.push_back(incident_data.release());
|
| -
|
| - // Remember when the first incident for this report arrived.
|
| - if (first_incident_time_.is_null())
|
| - first_incident_time_ = base::Time::Now();
|
| - // Log the time between the previous incident and this one.
|
| - if (!last_incident_time_.is_null()) {
|
| - UMA_HISTOGRAM_TIMES("SBIRS.InterIncidentTime",
|
| - base::TimeTicks::Now() - last_incident_time_);
|
| - }
|
| - last_incident_time_ = base::TimeTicks::Now();
|
| -
|
| - // Persist the incident data.
|
| -
|
| - // Start assembling a new report if this is the first incident ever or the
|
| - // first since the last upload.
|
| - BeginReportProcessing();
|
| -}
|
| -
|
| -void IncidentReportingService::BeginReportProcessing() {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| -
|
| - // Creates a new report if needed.
|
| - if (!report_)
|
| - report_.reset(new ClientIncidentReport());
|
| -
|
| - // Ensure that collection tasks are running (calls are idempotent).
|
| - BeginIncidentCollation();
|
| - BeginEnvironmentCollection();
|
| - BeginDownloadCollection();
|
| -}
|
| -
|
| -void IncidentReportingService::BeginIncidentCollation() {
|
| - // Restart the delay timer to send the report upon expiration.
|
| - collation_timeout_pending_ = true;
|
| - collation_timer_.Reset();
|
| -}
|
| -
|
| -void IncidentReportingService::BeginEnvironmentCollection() {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - DCHECK(report_);
|
| - // Nothing to do if environment collection is pending or has already
|
| - // completed.
|
| - if (environment_collection_pending_ || report_->has_environment())
|
| - return;
|
| -
|
| - environment_collection_begin_ = base::TimeTicks::Now();
|
| - ClientIncidentReport_EnvironmentData* environment_data =
|
| - new ClientIncidentReport_EnvironmentData();
|
| - environment_collection_pending_ =
|
| - environment_collection_task_runner_->PostTaskAndReply(
|
| - FROM_HERE,
|
| - base::Bind(collect_environment_data_fn_, environment_data),
|
| - base::Bind(&IncidentReportingService::OnEnvironmentDataCollected,
|
| - weak_ptr_factory_.GetWeakPtr(),
|
| - base::Passed(make_scoped_ptr(environment_data))));
|
| -
|
| - // Posting the task will fail if the runner has been shut down. This should
|
| - // never happen since the blocking pool is shut down after this service.
|
| - DCHECK(environment_collection_pending_);
|
| -}
|
| -
|
| -bool IncidentReportingService::WaitingForEnvironmentCollection() {
|
| - return environment_collection_pending_;
|
| -}
|
| -
|
| -void IncidentReportingService::CancelEnvironmentCollection() {
|
| - environment_collection_begin_ = base::TimeTicks();
|
| - environment_collection_pending_ = false;
|
| - if (report_)
|
| - report_->clear_environment();
|
| -}
|
| -
|
| -void IncidentReportingService::OnEnvironmentDataCollected(
|
| - scoped_ptr<ClientIncidentReport_EnvironmentData> environment_data) {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - DCHECK(environment_collection_pending_);
|
| - DCHECK(report_ && !report_->has_environment());
|
| - environment_collection_pending_ = false;
|
| -
|
| -// CurrentProcessInfo::CreationTime() is missing on some platforms.
|
| -#if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
|
| - base::TimeDelta uptime =
|
| - first_incident_time_ - base::CurrentProcessInfo::CreationTime();
|
| - environment_data->mutable_process()->set_uptime_msec(uptime.InMilliseconds());
|
| -#endif
|
| -
|
| - report_->set_allocated_environment(environment_data.release());
|
| -
|
| - UMA_HISTOGRAM_TIMES("SBIRS.EnvCollectionTime",
|
| - base::TimeTicks::Now() - environment_collection_begin_);
|
| - environment_collection_begin_ = base::TimeTicks();
|
| -
|
| - UploadIfCollectionComplete();
|
| -}
|
| -
|
| -bool IncidentReportingService::WaitingToCollateIncidents() {
|
| - return collation_timeout_pending_;
|
| -}
|
| -
|
| -void IncidentReportingService::CancelIncidentCollection() {
|
| - collation_timeout_pending_ = false;
|
| - last_incident_time_ = base::TimeTicks();
|
| - report_.reset();
|
| -}
|
| -
|
| -void IncidentReportingService::OnCollationTimeout() {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| -
|
| - // Exit early if collection was cancelled.
|
| - if (!collation_timeout_pending_)
|
| - return;
|
| -
|
| - // Wait another round if profile-bound incidents have come in from a profile
|
| - // that has yet to complete creation.
|
| - for (ProfileContextCollection::iterator scan = profiles_.begin();
|
| - scan != profiles_.end();
|
| - ++scan) {
|
| - if (scan->first && !scan->second->added &&
|
| - !scan->second->incidents.empty()) {
|
| - collation_timer_.Reset();
|
| - return;
|
| - }
|
| - }
|
| -
|
| - collation_timeout_pending_ = false;
|
| -
|
| - UploadIfCollectionComplete();
|
| -}
|
| -
|
| -void IncidentReportingService::BeginDownloadCollection() {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - DCHECK(report_);
|
| - // Nothing to do if a search for the most recent download is already pending
|
| - // or if one has already been found.
|
| - if (last_download_finder_ || report_->has_download())
|
| - return;
|
| -
|
| - last_download_begin_ = base::TimeTicks::Now();
|
| - last_download_finder_ = CreateDownloadFinder(
|
| - base::Bind(&IncidentReportingService::OnLastDownloadFound,
|
| - weak_ptr_factory_.GetWeakPtr()));
|
| - // No instance is returned if there are no eligible loaded profiles. Another
|
| - // search will be attempted in OnProfileAdded() if another profile appears on
|
| - // the scene.
|
| - if (!last_download_finder_)
|
| - last_download_begin_ = base::TimeTicks();
|
| -}
|
| -
|
| -bool IncidentReportingService::WaitingForMostRecentDownload() {
|
| - DCHECK(report_); // Only call this when a report is being assembled.
|
| - // The easy case: not waiting if a download has already been found.
|
| - if (report_->has_download())
|
| - return false;
|
| - // The next easy case: waiting if the finder is operating.
|
| - if (last_download_finder_)
|
| - return true;
|
| - // The harder case: waiting if a profile has not yet been added.
|
| - for (ProfileContextCollection::const_iterator scan = profiles_.begin();
|
| - scan != profiles_.end();
|
| - ++scan) {
|
| - if (!scan->second->added)
|
| - return true;
|
| - }
|
| - // There is no most recent download and there's nothing more to wait for.
|
| - return false;
|
| -}
|
| -
|
| -void IncidentReportingService::CancelDownloadCollection() {
|
| - last_download_finder_.reset();
|
| - last_download_begin_ = base::TimeTicks();
|
| - if (report_)
|
| - report_->clear_download();
|
| -}
|
| -
|
| -void IncidentReportingService::OnLastDownloadFound(
|
| - scoped_ptr<ClientIncidentReport_DownloadDetails> last_download) {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - DCHECK(report_);
|
| -
|
| - UMA_HISTOGRAM_TIMES("SBIRS.FindDownloadedBinaryTime",
|
| - base::TimeTicks::Now() - last_download_begin_);
|
| - last_download_begin_ = base::TimeTicks();
|
| -
|
| - // Harvest the finder.
|
| - last_download_finder_.reset();
|
| -
|
| - if (last_download)
|
| - report_->set_allocated_download(last_download.release());
|
| -
|
| - UploadIfCollectionComplete();
|
| -}
|
| -
|
| -void IncidentReportingService::UploadIfCollectionComplete() {
|
| - DCHECK(report_);
|
| - // Bail out if there are still outstanding collection tasks. Completion of any
|
| - // of these will start another upload attempt.
|
| - if (WaitingForEnvironmentCollection() ||
|
| - WaitingToCollateIncidents() ||
|
| - WaitingForMostRecentDownload()) {
|
| - return;
|
| - }
|
| -
|
| - // Take ownership of the report and clear things for future reports.
|
| - scoped_ptr<ClientIncidentReport> report(report_.Pass());
|
| - first_incident_time_ = base::Time();
|
| - last_incident_time_ = base::TimeTicks();
|
| -
|
| - // Drop the report if no executable download was found.
|
| - if (!report->has_download()) {
|
| - UMA_HISTOGRAM_ENUMERATION("SBIRS.UploadResult",
|
| - IncidentReportUploader::UPLOAD_NO_DOWNLOAD,
|
| - IncidentReportUploader::NUM_UPLOAD_RESULTS);
|
| - return;
|
| - }
|
| -
|
| - ClientIncidentReport_EnvironmentData_Process* process =
|
| - report->mutable_environment()->mutable_process();
|
| -
|
| - // Not all platforms have a metrics reporting preference.
|
| - if (g_browser_process->local_state()->FindPreference(
|
| - prefs::kMetricsReportingEnabled)) {
|
| - process->set_metrics_consent(g_browser_process->local_state()->GetBoolean(
|
| - prefs::kMetricsReportingEnabled));
|
| - }
|
| -
|
| - // Find the profile that benefits from the strongest protections.
|
| - Profile* eligible_profile = FindEligibleProfile();
|
| - process->set_extended_consent(
|
| - eligible_profile ? eligible_profile->GetPrefs()->GetBoolean(
|
| - prefs::kSafeBrowsingExtendedReportingEnabled) :
|
| - false);
|
| -
|
| - // Associate process-wide incidents with the profile that benefits from the
|
| - // strongest safe browsing protections.
|
| - ProfileContext* null_context = GetProfileContext(NULL);
|
| - if (null_context && !null_context->incidents.empty() && eligible_profile) {
|
| - ProfileContext* eligible_context = GetProfileContext(eligible_profile);
|
| - // Move the incidents to the target context.
|
| - eligible_context->incidents.insert(eligible_context->incidents.end(),
|
| - null_context->incidents.begin(),
|
| - null_context->incidents.end());
|
| - null_context->incidents.weak_clear();
|
| - }
|
| -
|
| - // Collect incidents across all profiles participating in safe browsing. Drop
|
| - // incidents if the profile stopped participating before collection completed.
|
| - // Prune previously submitted incidents.
|
| - // Associate the profiles and their incident data with the upload.
|
| - size_t prune_count = 0;
|
| - UploadContext::PersistentIncidentStateCollection profiles_to_state;
|
| - for (ProfileContextCollection::iterator scan = profiles_.begin();
|
| - scan != profiles_.end();
|
| - ++scan) {
|
| - // Bypass process-wide incidents that have not yet been associated with a
|
| - // profile.
|
| - if (!scan->first)
|
| - continue;
|
| - PrefService* prefs = scan->first->GetPrefs();
|
| - ProfileContext* context = scan->second;
|
| - if (context->incidents.empty())
|
| - continue;
|
| - if (!prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) {
|
| - for (size_t i = 0; i < context->incidents.size(); ++i) {
|
| - LogIncidentDataType(DROPPED, *context->incidents[i]);
|
| - }
|
| - context->incidents.clear();
|
| - continue;
|
| - }
|
| - std::vector<PersistentIncidentState> states;
|
| - const base::DictionaryValue* incidents_sent =
|
| - prefs->GetDictionary(prefs::kSafeBrowsingIncidentsSent);
|
| - // Prep persistent data and prune any incidents already sent.
|
| - for (size_t i = 0; i < context->incidents.size(); ++i) {
|
| - ClientIncidentReport_IncidentData* incident = context->incidents[i];
|
| - const PersistentIncidentState state = ComputeIncidentState(*incident);
|
| - if (IncidentHasBeenReported(incidents_sent, state)) {
|
| - ++prune_count;
|
| - delete context->incidents[i];
|
| - context->incidents[i] = NULL;
|
| - } else {
|
| - states.push_back(state);
|
| - }
|
| - }
|
| - if (prefs->GetBoolean(prefs::kSafeBrowsingIncidentReportSent)) {
|
| - // Prune all incidents as if they had been reported, migrating to the new
|
| - // technique. TODO(grt): remove this branch after it has shipped.
|
| - for (size_t i = 0; i < context->incidents.size(); ++i) {
|
| - if (context->incidents[i])
|
| - ++prune_count;
|
| - }
|
| - context->incidents.clear();
|
| - prefs->ClearPref(prefs::kSafeBrowsingIncidentReportSent);
|
| - DictionaryPrefUpdate pref_update(prefs,
|
| - prefs::kSafeBrowsingIncidentsSent);
|
| - MarkIncidentsAsReported(states, pref_update.Get());
|
| - } else {
|
| - for (size_t i = 0; i < context->incidents.size(); ++i) {
|
| - ClientIncidentReport_IncidentData* incident = context->incidents[i];
|
| - if (incident) {
|
| - LogIncidentDataType(ACCEPTED, *incident);
|
| - // Ownership of the incident is passed to the report.
|
| - report->mutable_incident()->AddAllocated(incident);
|
| - }
|
| - }
|
| - context->incidents.weak_clear();
|
| - std::vector<PersistentIncidentState>& profile_states =
|
| - profiles_to_state[scan->first];
|
| - profile_states.swap(states);
|
| - }
|
| - }
|
| -
|
| - const int count = report->incident_size();
|
| - // Abandon the request if all incidents were dropped with none pruned.
|
| - if (!count && !prune_count)
|
| - return;
|
| -
|
| - UMA_HISTOGRAM_COUNTS_100("SBIRS.IncidentCount", count + prune_count);
|
| -
|
| - {
|
| - double prune_pct = static_cast<double>(prune_count);
|
| - prune_pct = prune_pct * 100.0 / (count + prune_count);
|
| - prune_pct = round(prune_pct);
|
| - UMA_HISTOGRAM_PERCENTAGE("SBIRS.PruneRatio", static_cast<int>(prune_pct));
|
| - }
|
| - // Abandon the report if all incidents were pruned.
|
| - if (!count)
|
| - return;
|
| -
|
| - scoped_ptr<UploadContext> context(new UploadContext(report.Pass()));
|
| - context->profiles_to_state.swap(profiles_to_state);
|
| - if (!database_manager_) {
|
| - // No database manager during testing. Take ownership of the context and
|
| - // continue processing.
|
| - UploadContext* temp_context = context.get();
|
| - uploads_.push_back(context.release());
|
| - IncidentReportingService::OnKillSwitchResult(temp_context, false);
|
| - } else {
|
| - if (content::BrowserThread::PostTaskAndReplyWithResult(
|
| - content::BrowserThread::IO,
|
| - FROM_HERE,
|
| - base::Bind(&SafeBrowsingDatabaseManager::IsCsdWhitelistKillSwitchOn,
|
| - database_manager_),
|
| - base::Bind(&IncidentReportingService::OnKillSwitchResult,
|
| - weak_ptr_factory_.GetWeakPtr(),
|
| - context.get()))) {
|
| - uploads_.push_back(context.release());
|
| - } // else should not happen. Let the context be deleted automatically.
|
| - }
|
| -}
|
| -
|
| -void IncidentReportingService::CancelAllReportUploads() {
|
| - for (size_t i = 0; i < uploads_.size(); ++i) {
|
| - UMA_HISTOGRAM_ENUMERATION("SBIRS.UploadResult",
|
| - IncidentReportUploader::UPLOAD_CANCELLED,
|
| - IncidentReportUploader::NUM_UPLOAD_RESULTS);
|
| - }
|
| - uploads_.clear();
|
| -}
|
| -
|
| -void IncidentReportingService::OnKillSwitchResult(UploadContext* context,
|
| - bool is_killswitch_on) {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| - if (!is_killswitch_on) {
|
| - // Initiate the upload.
|
| - context->uploader =
|
| - StartReportUpload(
|
| - base::Bind(&IncidentReportingService::OnReportUploadResult,
|
| - weak_ptr_factory_.GetWeakPtr(),
|
| - context),
|
| - url_request_context_getter_,
|
| - *context->report).Pass();
|
| - if (!context->uploader) {
|
| - OnReportUploadResult(context,
|
| - IncidentReportUploader::UPLOAD_INVALID_REQUEST,
|
| - scoped_ptr<ClientIncidentResponse>());
|
| - }
|
| - } else {
|
| - OnReportUploadResult(context,
|
| - IncidentReportUploader::UPLOAD_SUPPRESSED,
|
| - scoped_ptr<ClientIncidentResponse>());
|
| - }
|
| -}
|
| -
|
| -void IncidentReportingService::HandleResponse(const UploadContext& context) {
|
| - for (UploadContext::PersistentIncidentStateCollection::const_iterator scan =
|
| - context.profiles_to_state.begin();
|
| - scan != context.profiles_to_state.end();
|
| - ++scan) {
|
| - DictionaryPrefUpdate pref_update(scan->first->GetPrefs(),
|
| - prefs::kSafeBrowsingIncidentsSent);
|
| - MarkIncidentsAsReported(scan->second, pref_update.Get());
|
| - }
|
| -}
|
| -
|
| -void IncidentReportingService::OnReportUploadResult(
|
| - UploadContext* context,
|
| - IncidentReportUploader::Result result,
|
| - scoped_ptr<ClientIncidentResponse> response) {
|
| - DCHECK(thread_checker_.CalledOnValidThread());
|
| -
|
| - UMA_HISTOGRAM_ENUMERATION(
|
| - "SBIRS.UploadResult", result, IncidentReportUploader::NUM_UPLOAD_RESULTS);
|
| -
|
| - // The upload is no longer outstanding, so take ownership of the context (from
|
| - // the collection of outstanding uploads) in this scope.
|
| - ScopedVector<UploadContext>::iterator it(
|
| - std::find(uploads_.begin(), uploads_.end(), context));
|
| - DCHECK(it != uploads_.end());
|
| - scoped_ptr<UploadContext> upload(context); // == *it
|
| - *it = uploads_.back();
|
| - uploads_.weak_erase(uploads_.end() - 1);
|
| -
|
| - if (result == IncidentReportUploader::UPLOAD_SUCCESS)
|
| - HandleResponse(*upload);
|
| - // else retry?
|
| -}
|
| -
|
| -void IncidentReportingService::Observe(
|
| - int type,
|
| - const content::NotificationSource& source,
|
| - const content::NotificationDetails& details) {
|
| - switch (type) {
|
| - case chrome::NOTIFICATION_PROFILE_ADDED: {
|
| - Profile* profile = content::Source<Profile>(source).ptr();
|
| - if (!profile->IsOffTheRecord())
|
| - OnProfileAdded(profile);
|
| - break;
|
| - }
|
| - case chrome::NOTIFICATION_PROFILE_DESTROYED: {
|
| - Profile* profile = content::Source<Profile>(source).ptr();
|
| - if (!profile->IsOffTheRecord())
|
| - OnProfileDestroyed(profile);
|
| - break;
|
| - }
|
| - default:
|
| - break;
|
| - }
|
| -}
|
| -
|
| -} // namespace safe_browsing
|
|
|