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 |