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.
5 #include "chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.h"
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/incident_reporting/variations_seed_signature_incident_handlers.h"
34 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
35 #include "chrome/common/pref_names.h"
36 #include "chrome/common/safe_browsing/csd.pb.h"
37 #include "content/public/browser/browser_thread.h"
38 #include "content/public/browser/notification_service.h"
39 #include "net/url_request/url_request_context_getter.h"
41 namespace safe_browsing
{
45 // The type of an incident. Used for user metrics and for pruning of
46 // previously-reported incidents.
48 // Start with 1 rather than zero; otherwise there won't be enough buckets for
50 TRACKED_PREFERENCE
= 1,
53 VARIATIONS_SEED_SIGNATURE
= 4,
54 // Values for new incident types go here.
55 NUM_INCIDENT_TYPES
= 5
58 // The action taken for an incident; used for user metrics (see
59 // LogIncidentDataType).
60 enum IncidentDisposition
{
65 // The state persisted for a specific instance of an incident to enable pruning
66 // of previously-reported incidents.
67 struct PersistentIncidentState
{
68 // The type of the incident.
71 // The key for a specific instance of an incident.
74 // A hash digest representing a specific instance of an incident.
78 // The amount of time the service will wait to collate incidents.
79 const int64 kDefaultUploadDelayMs
= 1000 * 60; // one minute
81 // The amount of time between running delayed analysis callbacks.
82 const int64 kDefaultCallbackIntervalMs
= 1000 * 20;
84 // Returns the number of incidents contained in |incident|. The result is
85 // expected to be 1. Used in DCHECKs.
86 size_t CountIncidents(const ClientIncidentReport_IncidentData
& incident
) {
88 if (incident
.has_tracked_preference())
90 if (incident
.has_binary_integrity())
92 if (incident
.has_blacklist_load())
94 if (incident
.has_variations_seed_signature())
96 // Add detection for new incident types here.
100 // Returns the type of incident contained in |incident_data|.
101 IncidentType
GetIncidentType(
102 const ClientIncidentReport_IncidentData
& incident_data
) {
103 if (incident_data
.has_tracked_preference())
104 return TRACKED_PREFERENCE
;
105 if (incident_data
.has_binary_integrity())
106 return BINARY_INTEGRITY
;
107 if (incident_data
.has_blacklist_load())
108 return BLACKLIST_LOAD
;
109 if (incident_data
.has_variations_seed_signature())
110 return VARIATIONS_SEED_SIGNATURE
;
112 // Add detection for new incident types here.
113 COMPILE_ASSERT(VARIATIONS_SEED_SIGNATURE
+ 1 == NUM_INCIDENT_TYPES
,
114 add_support_for_new_types
);
116 return NUM_INCIDENT_TYPES
;
119 // Logs the type of incident in |incident_data| to a user metrics histogram.
120 void LogIncidentDataType(
121 IncidentDisposition disposition
,
122 const ClientIncidentReport_IncidentData
& incident_data
) {
123 IncidentType type
= GetIncidentType(incident_data
);
124 if (disposition
== ACCEPTED
) {
125 UMA_HISTOGRAM_ENUMERATION("SBIRS.Incident", type
, NUM_INCIDENT_TYPES
);
127 DCHECK_EQ(disposition
, DROPPED
);
128 UMA_HISTOGRAM_ENUMERATION("SBIRS.DroppedIncident", type
,
133 // Computes the persistent state for an incident.
134 PersistentIncidentState
ComputeIncidentState(
135 const ClientIncidentReport_IncidentData
& incident
) {
136 PersistentIncidentState state
= {GetIncidentType(incident
)};
137 switch (state
.type
) {
138 case TRACKED_PREFERENCE
:
139 state
.key
= GetTrackedPreferenceIncidentKey(incident
);
140 state
.digest
= GetTrackedPreferenceIncidentDigest(incident
);
142 case BINARY_INTEGRITY
:
143 state
.key
= GetBinaryIntegrityIncidentKey(incident
);
144 state
.digest
= GetBinaryIntegrityIncidentDigest(incident
);
147 state
.key
= GetBlacklistLoadIncidentKey(incident
);
148 state
.digest
= GetBlacklistLoadIncidentDigest(incident
);
150 case VARIATIONS_SEED_SIGNATURE
:
151 state
.key
= GetVariationsSeedSignatureIncidentKey(incident
);
152 state
.digest
= GetVariationsSeedSignatureIncidentDigest(incident
);
154 // Add handling for new incident types here.
156 COMPILE_ASSERT(VARIATIONS_SEED_SIGNATURE
+ 1 == NUM_INCIDENT_TYPES
,
157 add_support_for_new_types
);
164 // Returns true if the incident described by |state| has already been reported
165 // based on the bookkeeping in the |incidents_sent| preference dictionary.
166 bool IncidentHasBeenReported(const base::DictionaryValue
* incidents_sent
,
167 const PersistentIncidentState
& state
) {
168 const base::DictionaryValue
* type_dict
= NULL
;
169 std::string digest_string
;
170 return (incidents_sent
&&
171 incidents_sent
->GetDictionaryWithoutPathExpansion(
172 base::IntToString(state
.type
), &type_dict
) &&
173 type_dict
->GetStringWithoutPathExpansion(state
.key
, &digest_string
) &&
174 digest_string
== base::UintToString(state
.digest
));
177 // Marks the incidents described by |states| as having been reported
178 // in |incidents_set|.
179 void MarkIncidentsAsReported(const std::vector
<PersistentIncidentState
>& states
,
180 base::DictionaryValue
* incidents_sent
) {
181 for (size_t i
= 0; i
< states
.size(); ++i
) {
182 const PersistentIncidentState
& data
= states
[i
];
183 base::DictionaryValue
* type_dict
= NULL
;
184 const std::string
type_string(base::IntToString(data
.type
));
185 if (!incidents_sent
->GetDictionaryWithoutPathExpansion(type_string
,
187 type_dict
= new base::DictionaryValue();
188 incidents_sent
->SetWithoutPathExpansion(type_string
, type_dict
);
190 type_dict
->SetStringWithoutPathExpansion(data
.key
,
191 base::UintToString(data
.digest
));
195 // Runs |callback| on the thread to which |thread_runner| belongs. The callback
196 // is run immediately if this function is called on |thread_runner|'s thread.
197 void AddIncidentOnOriginThread(
198 const AddIncidentCallback
& callback
,
199 scoped_refptr
<base::SingleThreadTaskRunner
> thread_runner
,
200 scoped_ptr
<ClientIncidentReport_IncidentData
> incident
) {
201 if (thread_runner
->BelongsToCurrentThread())
202 callback
.Run(incident
.Pass());
204 thread_runner
->PostTask(FROM_HERE
,
205 base::Bind(callback
, base::Passed(&incident
)));
210 struct IncidentReportingService::ProfileContext
{
214 // The incidents collected for this profile pending creation and/or upload.
215 ScopedVector
<ClientIncidentReport_IncidentData
> incidents
;
217 // False until PROFILE_ADDED notification is received.
221 DISALLOW_COPY_AND_ASSIGN(ProfileContext
);
224 class IncidentReportingService::UploadContext
{
226 typedef std::map
<Profile
*, std::vector
<PersistentIncidentState
> >
227 PersistentIncidentStateCollection
;
229 explicit UploadContext(scoped_ptr
<ClientIncidentReport
> report
);
232 // The report being uploaded.
233 scoped_ptr
<ClientIncidentReport
> report
;
235 // The uploader in use. This is NULL until the CSD killswitch is checked.
236 scoped_ptr
<IncidentReportUploader
> uploader
;
238 // A mapping of profiles to the data to be persisted upon successful upload.
239 PersistentIncidentStateCollection profiles_to_state
;
242 DISALLOW_COPY_AND_ASSIGN(UploadContext
);
245 IncidentReportingService::ProfileContext::ProfileContext() : added() {
248 IncidentReportingService::ProfileContext::~ProfileContext() {
251 IncidentReportingService::UploadContext::UploadContext(
252 scoped_ptr
<ClientIncidentReport
> report
)
253 : report(report
.Pass()) {
256 IncidentReportingService::UploadContext::~UploadContext() {
259 IncidentReportingService::IncidentReportingService(
260 SafeBrowsingService
* safe_browsing_service
,
261 const scoped_refptr
<net::URLRequestContextGetter
>& request_context_getter
)
262 : database_manager_(safe_browsing_service
?
263 safe_browsing_service
->database_manager() : NULL
),
264 url_request_context_getter_(request_context_getter
),
265 collect_environment_data_fn_(&CollectEnvironmentData
),
266 environment_collection_task_runner_(
267 content::BrowserThread::GetBlockingPool()
268 ->GetTaskRunnerWithShutdownBehavior(
269 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
)),
270 environment_collection_pending_(),
271 collation_timeout_pending_(),
272 collation_timer_(FROM_HERE
,
273 base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs
),
275 &IncidentReportingService::OnCollationTimeout
),
276 delayed_analysis_callbacks_(
277 base::TimeDelta::FromMilliseconds(kDefaultCallbackIntervalMs
),
278 content::BrowserThread::GetBlockingPool()
279 ->GetTaskRunnerWithShutdownBehavior(
280 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
)),
281 receiver_weak_ptr_factory_(this),
282 weak_ptr_factory_(this) {
283 notification_registrar_
.Add(this,
284 chrome::NOTIFICATION_PROFILE_ADDED
,
285 content::NotificationService::AllSources());
286 notification_registrar_
.Add(this,
287 chrome::NOTIFICATION_PROFILE_DESTROYED
,
288 content::NotificationService::AllSources());
291 IncidentReportingService::~IncidentReportingService() {
292 CancelIncidentCollection();
294 // Cancel all internal asynchronous tasks.
295 weak_ptr_factory_
.InvalidateWeakPtrs();
297 CancelEnvironmentCollection();
298 CancelDownloadCollection();
299 CancelAllReportUploads();
301 STLDeleteValues(&profiles_
);
304 AddIncidentCallback
IncidentReportingService::GetAddIncidentCallback(
306 // Force the context to be created so that incidents added before
307 // OnProfileAdded is called are held until the profile's preferences can be
309 ignore_result(GetOrCreateProfileContext(profile
));
311 return base::Bind(&IncidentReportingService::AddIncident
,
312 receiver_weak_ptr_factory_
.GetWeakPtr(),
316 scoped_ptr
<TrackedPreferenceValidationDelegate
>
317 IncidentReportingService::CreatePreferenceValidationDelegate(Profile
* profile
) {
318 DCHECK(thread_checker_
.CalledOnValidThread());
320 if (profile
->IsOffTheRecord())
321 return scoped_ptr
<TrackedPreferenceValidationDelegate
>();
322 return scoped_ptr
<TrackedPreferenceValidationDelegate
>(
323 new PreferenceValidationDelegate(GetAddIncidentCallback(profile
)));
326 void IncidentReportingService::RegisterDelayedAnalysisCallback(
327 const DelayedAnalysisCallback
& callback
) {
328 DCHECK(thread_checker_
.CalledOnValidThread());
330 // |callback| will be run on the blocking pool, so it will likely run the
331 // AddIncidentCallback there as well. Bounce the run of that callback back to
332 // the current thread via AddIncidentOnOriginThread.
333 delayed_analysis_callbacks_
.RegisterCallback(
335 base::Bind(&AddIncidentOnOriginThread
,
336 GetAddIncidentCallback(NULL
),
337 base::ThreadTaskRunnerHandle::Get())));
339 // Start running the callbacks if any profiles are participating in safe
340 // browsing. If none are now, running will commence if/when a participaing
342 if (FindEligibleProfile())
343 delayed_analysis_callbacks_
.Start();
346 IncidentReportingService::IncidentReportingService(
347 SafeBrowsingService
* safe_browsing_service
,
348 const scoped_refptr
<net::URLRequestContextGetter
>& request_context_getter
,
349 base::TimeDelta delayed_task_interval
,
350 const scoped_refptr
<base::TaskRunner
>& delayed_task_runner
)
351 : database_manager_(safe_browsing_service
?
352 safe_browsing_service
->database_manager() : NULL
),
353 url_request_context_getter_(request_context_getter
),
354 collect_environment_data_fn_(&CollectEnvironmentData
),
355 environment_collection_task_runner_(
356 content::BrowserThread::GetBlockingPool()
357 ->GetTaskRunnerWithShutdownBehavior(
358 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
)),
359 environment_collection_pending_(),
360 collation_timeout_pending_(),
361 collation_timer_(FROM_HERE
,
362 base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs
),
364 &IncidentReportingService::OnCollationTimeout
),
365 delayed_analysis_callbacks_(delayed_task_interval
, delayed_task_runner
),
366 receiver_weak_ptr_factory_(this),
367 weak_ptr_factory_(this) {
368 notification_registrar_
.Add(this,
369 chrome::NOTIFICATION_PROFILE_ADDED
,
370 content::NotificationService::AllSources());
371 notification_registrar_
.Add(this,
372 chrome::NOTIFICATION_PROFILE_DESTROYED
,
373 content::NotificationService::AllSources());
376 void IncidentReportingService::SetCollectEnvironmentHook(
377 CollectEnvironmentDataFn collect_environment_data_hook
,
378 const scoped_refptr
<base::TaskRunner
>& task_runner
) {
379 if (collect_environment_data_hook
) {
380 collect_environment_data_fn_
= collect_environment_data_hook
;
381 environment_collection_task_runner_
= task_runner
;
383 collect_environment_data_fn_
= &CollectEnvironmentData
;
384 environment_collection_task_runner_
=
385 content::BrowserThread::GetBlockingPool()
386 ->GetTaskRunnerWithShutdownBehavior(
387 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
391 void IncidentReportingService::OnProfileAdded(Profile
* profile
) {
392 DCHECK(thread_checker_
.CalledOnValidThread());
394 // Track the addition of all profiles even when no report is being assembled
395 // so that the service can determine whether or not it can evaluate a
396 // profile's preferences at the time of incident addition.
397 ProfileContext
* context
= GetOrCreateProfileContext(profile
);
398 context
->added
= true;
400 const bool safe_browsing_enabled
=
401 profile
->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled
);
403 // Start processing delayed analysis callbacks if this new profile
404 // participates in safe browsing. Start is idempotent, so this is safe even if
405 // they're already running.
406 if (safe_browsing_enabled
)
407 delayed_analysis_callbacks_
.Start();
409 // Start a new report if this profile participates in safe browsing and there
410 // are process-wide incidents.
411 if (safe_browsing_enabled
&& GetProfileContext(NULL
) &&
412 GetProfileContext(NULL
)->incidents
.size()) {
413 BeginReportProcessing();
416 // TODO(grt): register for pref change notifications to start delayed analysis
417 // and/or report processing if sb is currently disabled but subsequently
420 // Nothing else to do if a report is not being assembled.
424 // Drop all incidents associated with this profile that were received prior to
425 // its addition if the profile is not participating in safe browsing.
426 if (!context
->incidents
.empty() && !safe_browsing_enabled
) {
427 for (size_t i
= 0; i
< context
->incidents
.size(); ++i
)
428 LogIncidentDataType(DROPPED
, *context
->incidents
[i
]);
429 context
->incidents
.clear();
432 // Take another stab at finding the most recent download if a report is being
433 // assembled and one hasn't been found yet (the LastDownloadFinder operates
434 // only on profiles that have been added to the ProfileManager).
435 BeginDownloadCollection();
438 scoped_ptr
<LastDownloadFinder
> IncidentReportingService::CreateDownloadFinder(
439 const LastDownloadFinder::LastDownloadCallback
& callback
) {
440 return LastDownloadFinder::Create(callback
).Pass();
443 scoped_ptr
<IncidentReportUploader
> IncidentReportingService::StartReportUpload(
444 const IncidentReportUploader::OnResultCallback
& callback
,
445 const scoped_refptr
<net::URLRequestContextGetter
>& request_context_getter
,
446 const ClientIncidentReport
& report
) {
447 return IncidentReportUploaderImpl::UploadReport(
448 callback
, request_context_getter
, report
).Pass();
451 bool IncidentReportingService::IsProcessingReport() const {
452 return report_
!= NULL
;
455 IncidentReportingService::ProfileContext
*
456 IncidentReportingService::GetOrCreateProfileContext(Profile
* profile
) {
457 ProfileContextCollection::iterator it
=
458 profiles_
.insert(ProfileContextCollection::value_type(profile
, NULL
))
461 it
->second
= new ProfileContext();
465 IncidentReportingService::ProfileContext
*
466 IncidentReportingService::GetProfileContext(Profile
* profile
) {
467 ProfileContextCollection::iterator it
= profiles_
.find(profile
);
468 return it
== profiles_
.end() ? NULL
: it
->second
;
471 void IncidentReportingService::OnProfileDestroyed(Profile
* profile
) {
472 DCHECK(thread_checker_
.CalledOnValidThread());
474 ProfileContextCollection::iterator it
= profiles_
.find(profile
);
475 if (it
== profiles_
.end())
478 // TODO(grt): Persist incidents for upload on future profile load.
480 // Forget about this profile. Incidents not yet sent for upload are lost.
481 // No new incidents will be accepted for it.
485 // Remove the association with this profile from all pending uploads.
486 for (size_t i
= 0; i
< uploads_
.size(); ++i
)
487 uploads_
[i
]->profiles_to_state
.erase(profile
);
490 Profile
* IncidentReportingService::FindEligibleProfile() const {
491 Profile
* candidate
= NULL
;
492 for (ProfileContextCollection::const_iterator scan
= profiles_
.begin();
493 scan
!= profiles_
.end();
495 // Skip over profiles that have yet to be added to the profile manager.
496 // This will also skip over the NULL-profile context used to hold
497 // process-wide incidents.
498 if (!scan
->second
->added
)
500 PrefService
* prefs
= scan
->first
->GetPrefs();
501 if (prefs
->GetBoolean(prefs::kSafeBrowsingEnabled
)) {
503 candidate
= scan
->first
;
504 if (prefs
->GetBoolean(prefs::kSafeBrowsingExtendedReportingEnabled
)) {
505 candidate
= scan
->first
;
513 void IncidentReportingService::AddIncident(
515 scoped_ptr
<ClientIncidentReport_IncidentData
> incident_data
) {
516 DCHECK(thread_checker_
.CalledOnValidThread());
517 DCHECK_EQ(1U, CountIncidents(*incident_data
));
519 ProfileContext
* context
= GetProfileContext(profile
);
520 // It is forbidden to call this function with a destroyed profile.
522 // If this is a process-wide incident, the context must not indicate that the
523 // profile (which is NULL) has been added to the profile manager.
524 DCHECK(profile
|| !context
->added
);
526 // Drop the incident immediately if the profile has already been added to the
527 // manager and is not participating in safe browsing. Preference evaluation is
528 // deferred until OnProfileAdded() otherwise.
529 if (context
->added
&&
530 !profile
->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled
)) {
531 LogIncidentDataType(DROPPED
, *incident_data
);
535 // Provide time to the new incident if the caller didn't do so.
536 if (!incident_data
->has_incident_time_msec())
537 incident_data
->set_incident_time_msec(base::Time::Now().ToJavaTime());
539 // Take ownership of the incident.
540 context
->incidents
.push_back(incident_data
.release());
542 // Remember when the first incident for this report arrived.
543 if (first_incident_time_
.is_null())
544 first_incident_time_
= base::Time::Now();
545 // Log the time between the previous incident and this one.
546 if (!last_incident_time_
.is_null()) {
547 UMA_HISTOGRAM_TIMES("SBIRS.InterIncidentTime",
548 base::TimeTicks::Now() - last_incident_time_
);
550 last_incident_time_
= base::TimeTicks::Now();
552 // Persist the incident data.
554 // Start assembling a new report if this is the first incident ever or the
555 // first since the last upload.
556 BeginReportProcessing();
559 void IncidentReportingService::BeginReportProcessing() {
560 DCHECK(thread_checker_
.CalledOnValidThread());
562 // Creates a new report if needed.
564 report_
.reset(new ClientIncidentReport());
566 // Ensure that collection tasks are running (calls are idempotent).
567 BeginIncidentCollation();
568 BeginEnvironmentCollection();
569 BeginDownloadCollection();
572 void IncidentReportingService::BeginIncidentCollation() {
573 // Restart the delay timer to send the report upon expiration.
574 collation_timeout_pending_
= true;
575 collation_timer_
.Reset();
578 void IncidentReportingService::BeginEnvironmentCollection() {
579 DCHECK(thread_checker_
.CalledOnValidThread());
581 // Nothing to do if environment collection is pending or has already
583 if (environment_collection_pending_
|| report_
->has_environment())
586 environment_collection_begin_
= base::TimeTicks::Now();
587 ClientIncidentReport_EnvironmentData
* environment_data
=
588 new ClientIncidentReport_EnvironmentData();
589 environment_collection_pending_
=
590 environment_collection_task_runner_
->PostTaskAndReply(
592 base::Bind(collect_environment_data_fn_
, environment_data
),
593 base::Bind(&IncidentReportingService::OnEnvironmentDataCollected
,
594 weak_ptr_factory_
.GetWeakPtr(),
595 base::Passed(make_scoped_ptr(environment_data
))));
597 // Posting the task will fail if the runner has been shut down. This should
598 // never happen since the blocking pool is shut down after this service.
599 DCHECK(environment_collection_pending_
);
602 bool IncidentReportingService::WaitingForEnvironmentCollection() {
603 return environment_collection_pending_
;
606 void IncidentReportingService::CancelEnvironmentCollection() {
607 environment_collection_begin_
= base::TimeTicks();
608 environment_collection_pending_
= false;
610 report_
->clear_environment();
613 void IncidentReportingService::OnEnvironmentDataCollected(
614 scoped_ptr
<ClientIncidentReport_EnvironmentData
> environment_data
) {
615 DCHECK(thread_checker_
.CalledOnValidThread());
616 DCHECK(environment_collection_pending_
);
617 DCHECK(report_
&& !report_
->has_environment());
618 environment_collection_pending_
= false;
620 // CurrentProcessInfo::CreationTime() is missing on some platforms.
621 #if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
622 base::TimeDelta uptime
=
623 first_incident_time_
- base::CurrentProcessInfo::CreationTime();
624 environment_data
->mutable_process()->set_uptime_msec(uptime
.InMilliseconds());
627 report_
->set_allocated_environment(environment_data
.release());
629 UMA_HISTOGRAM_TIMES("SBIRS.EnvCollectionTime",
630 base::TimeTicks::Now() - environment_collection_begin_
);
631 environment_collection_begin_
= base::TimeTicks();
633 UploadIfCollectionComplete();
636 bool IncidentReportingService::WaitingToCollateIncidents() {
637 return collation_timeout_pending_
;
640 void IncidentReportingService::CancelIncidentCollection() {
641 collation_timeout_pending_
= false;
642 last_incident_time_
= base::TimeTicks();
646 void IncidentReportingService::OnCollationTimeout() {
647 DCHECK(thread_checker_
.CalledOnValidThread());
649 // Exit early if collection was cancelled.
650 if (!collation_timeout_pending_
)
653 // Wait another round if profile-bound incidents have come in from a profile
654 // that has yet to complete creation.
655 for (ProfileContextCollection::iterator scan
= profiles_
.begin();
656 scan
!= profiles_
.end();
658 if (scan
->first
&& !scan
->second
->added
&&
659 !scan
->second
->incidents
.empty()) {
660 collation_timer_
.Reset();
665 collation_timeout_pending_
= false;
667 UploadIfCollectionComplete();
670 void IncidentReportingService::BeginDownloadCollection() {
671 DCHECK(thread_checker_
.CalledOnValidThread());
673 // Nothing to do if a search for the most recent download is already pending
674 // or if one has already been found.
675 if (last_download_finder_
|| report_
->has_download())
678 last_download_begin_
= base::TimeTicks::Now();
679 last_download_finder_
= CreateDownloadFinder(
680 base::Bind(&IncidentReportingService::OnLastDownloadFound
,
681 weak_ptr_factory_
.GetWeakPtr()));
682 // No instance is returned if there are no eligible loaded profiles. Another
683 // search will be attempted in OnProfileAdded() if another profile appears on
685 if (!last_download_finder_
)
686 last_download_begin_
= base::TimeTicks();
689 bool IncidentReportingService::WaitingForMostRecentDownload() {
690 DCHECK(report_
); // Only call this when a report is being assembled.
691 // The easy case: not waiting if a download has already been found.
692 if (report_
->has_download())
694 // The next easy case: waiting if the finder is operating.
695 if (last_download_finder_
)
697 // The harder case: waiting if a non-NULL profile has not yet been added.
698 for (ProfileContextCollection::const_iterator scan
= profiles_
.begin();
699 scan
!= profiles_
.end();
701 if (scan
->first
&& !scan
->second
->added
)
704 // There is no most recent download and there's nothing more to wait for.
708 void IncidentReportingService::CancelDownloadCollection() {
709 last_download_finder_
.reset();
710 last_download_begin_
= base::TimeTicks();
712 report_
->clear_download();
715 void IncidentReportingService::OnLastDownloadFound(
716 scoped_ptr
<ClientIncidentReport_DownloadDetails
> last_download
) {
717 DCHECK(thread_checker_
.CalledOnValidThread());
720 UMA_HISTOGRAM_TIMES("SBIRS.FindDownloadedBinaryTime",
721 base::TimeTicks::Now() - last_download_begin_
);
722 last_download_begin_
= base::TimeTicks();
724 // Harvest the finder.
725 last_download_finder_
.reset();
728 report_
->set_allocated_download(last_download
.release());
730 UploadIfCollectionComplete();
733 void IncidentReportingService::UploadIfCollectionComplete() {
735 // Bail out if there are still outstanding collection tasks. Completion of any
736 // of these will start another upload attempt.
737 if (WaitingForEnvironmentCollection() ||
738 WaitingToCollateIncidents() ||
739 WaitingForMostRecentDownload()) {
743 // Take ownership of the report and clear things for future reports.
744 scoped_ptr
<ClientIncidentReport
> report(report_
.Pass());
745 first_incident_time_
= base::Time();
746 last_incident_time_
= base::TimeTicks();
748 // Drop the report if no executable download was found.
749 if (!report
->has_download()) {
750 UMA_HISTOGRAM_ENUMERATION("SBIRS.UploadResult",
751 IncidentReportUploader::UPLOAD_NO_DOWNLOAD
,
752 IncidentReportUploader::NUM_UPLOAD_RESULTS
);
756 ClientIncidentReport_EnvironmentData_Process
* process
=
757 report
->mutable_environment()->mutable_process();
759 // Not all platforms have a metrics reporting preference.
760 if (g_browser_process
->local_state()->FindPreference(
761 prefs::kMetricsReportingEnabled
)) {
762 process
->set_metrics_consent(g_browser_process
->local_state()->GetBoolean(
763 prefs::kMetricsReportingEnabled
));
766 // Find the profile that benefits from the strongest protections.
767 Profile
* eligible_profile
= FindEligibleProfile();
768 process
->set_extended_consent(
769 eligible_profile
? eligible_profile
->GetPrefs()->GetBoolean(
770 prefs::kSafeBrowsingExtendedReportingEnabled
) :
773 // Associate process-wide incidents with the profile that benefits from the
774 // strongest safe browsing protections.
775 ProfileContext
* null_context
= GetProfileContext(NULL
);
776 if (null_context
&& !null_context
->incidents
.empty() && eligible_profile
) {
777 ProfileContext
* eligible_context
= GetProfileContext(eligible_profile
);
778 // Move the incidents to the target context.
779 eligible_context
->incidents
.insert(eligible_context
->incidents
.end(),
780 null_context
->incidents
.begin(),
781 null_context
->incidents
.end());
782 null_context
->incidents
.weak_clear();
785 // Collect incidents across all profiles participating in safe browsing. Drop
786 // incidents if the profile stopped participating before collection completed.
787 // Prune previously submitted incidents.
788 // Associate the profiles and their incident data with the upload.
789 size_t prune_count
= 0;
790 UploadContext::PersistentIncidentStateCollection profiles_to_state
;
791 for (ProfileContextCollection::iterator scan
= profiles_
.begin();
792 scan
!= profiles_
.end();
794 // Bypass process-wide incidents that have not yet been associated with a
798 PrefService
* prefs
= scan
->first
->GetPrefs();
799 ProfileContext
* context
= scan
->second
;
800 if (context
->incidents
.empty())
802 if (!prefs
->GetBoolean(prefs::kSafeBrowsingEnabled
)) {
803 for (size_t i
= 0; i
< context
->incidents
.size(); ++i
) {
804 LogIncidentDataType(DROPPED
, *context
->incidents
[i
]);
806 context
->incidents
.clear();
809 std::vector
<PersistentIncidentState
> states
;
810 const base::DictionaryValue
* incidents_sent
=
811 prefs
->GetDictionary(prefs::kSafeBrowsingIncidentsSent
);
812 // Prep persistent data and prune any incidents already sent.
813 for (size_t i
= 0; i
< context
->incidents
.size(); ++i
) {
814 ClientIncidentReport_IncidentData
* incident
= context
->incidents
[i
];
815 const PersistentIncidentState state
= ComputeIncidentState(*incident
);
816 if (IncidentHasBeenReported(incidents_sent
, state
)) {
818 delete context
->incidents
[i
];
819 context
->incidents
[i
] = NULL
;
821 states
.push_back(state
);
824 if (prefs
->GetBoolean(prefs::kSafeBrowsingIncidentReportSent
)) {
825 // Prune all incidents as if they had been reported, migrating to the new
826 // technique. TODO(grt): remove this branch after it has shipped.
827 for (size_t i
= 0; i
< context
->incidents
.size(); ++i
) {
828 if (context
->incidents
[i
])
831 context
->incidents
.clear();
832 prefs
->ClearPref(prefs::kSafeBrowsingIncidentReportSent
);
833 DictionaryPrefUpdate
pref_update(prefs
,
834 prefs::kSafeBrowsingIncidentsSent
);
835 MarkIncidentsAsReported(states
, pref_update
.Get());
837 for (size_t i
= 0; i
< context
->incidents
.size(); ++i
) {
838 ClientIncidentReport_IncidentData
* incident
= context
->incidents
[i
];
840 LogIncidentDataType(ACCEPTED
, *incident
);
841 // Ownership of the incident is passed to the report.
842 report
->mutable_incident()->AddAllocated(incident
);
845 context
->incidents
.weak_clear();
846 std::vector
<PersistentIncidentState
>& profile_states
=
847 profiles_to_state
[scan
->first
];
848 profile_states
.swap(states
);
852 const int count
= report
->incident_size();
853 // Abandon the request if all incidents were dropped with none pruned.
854 if (!count
&& !prune_count
)
857 UMA_HISTOGRAM_COUNTS_100("SBIRS.IncidentCount", count
+ prune_count
);
860 double prune_pct
= static_cast<double>(prune_count
);
861 prune_pct
= prune_pct
* 100.0 / (count
+ prune_count
);
862 prune_pct
= round(prune_pct
);
863 UMA_HISTOGRAM_PERCENTAGE("SBIRS.PruneRatio", static_cast<int>(prune_pct
));
865 // Abandon the report if all incidents were pruned.
869 scoped_ptr
<UploadContext
> context(new UploadContext(report
.Pass()));
870 context
->profiles_to_state
.swap(profiles_to_state
);
871 if (!database_manager_
.get()) {
872 // No database manager during testing. Take ownership of the context and
873 // continue processing.
874 UploadContext
* temp_context
= context
.get();
875 uploads_
.push_back(context
.release());
876 IncidentReportingService::OnKillSwitchResult(temp_context
, false);
878 if (content::BrowserThread::PostTaskAndReplyWithResult(
879 content::BrowserThread::IO
,
881 base::Bind(&SafeBrowsingDatabaseManager::IsCsdWhitelistKillSwitchOn
,
883 base::Bind(&IncidentReportingService::OnKillSwitchResult
,
884 weak_ptr_factory_
.GetWeakPtr(),
886 uploads_
.push_back(context
.release());
887 } // else should not happen. Let the context be deleted automatically.
891 void IncidentReportingService::CancelAllReportUploads() {
892 for (size_t i
= 0; i
< uploads_
.size(); ++i
) {
893 UMA_HISTOGRAM_ENUMERATION("SBIRS.UploadResult",
894 IncidentReportUploader::UPLOAD_CANCELLED
,
895 IncidentReportUploader::NUM_UPLOAD_RESULTS
);
900 void IncidentReportingService::OnKillSwitchResult(UploadContext
* context
,
901 bool is_killswitch_on
) {
902 DCHECK(thread_checker_
.CalledOnValidThread());
903 if (!is_killswitch_on
) {
904 // Initiate the upload.
907 base::Bind(&IncidentReportingService::OnReportUploadResult
,
908 weak_ptr_factory_
.GetWeakPtr(),
910 url_request_context_getter_
,
911 *context
->report
).Pass();
912 if (!context
->uploader
) {
913 OnReportUploadResult(context
,
914 IncidentReportUploader::UPLOAD_INVALID_REQUEST
,
915 scoped_ptr
<ClientIncidentResponse
>());
918 OnReportUploadResult(context
,
919 IncidentReportUploader::UPLOAD_SUPPRESSED
,
920 scoped_ptr
<ClientIncidentResponse
>());
924 void IncidentReportingService::HandleResponse(const UploadContext
& context
) {
925 for (UploadContext::PersistentIncidentStateCollection::const_iterator scan
=
926 context
.profiles_to_state
.begin();
927 scan
!= context
.profiles_to_state
.end();
929 DictionaryPrefUpdate
pref_update(scan
->first
->GetPrefs(),
930 prefs::kSafeBrowsingIncidentsSent
);
931 MarkIncidentsAsReported(scan
->second
, pref_update
.Get());
935 void IncidentReportingService::OnReportUploadResult(
936 UploadContext
* context
,
937 IncidentReportUploader::Result result
,
938 scoped_ptr
<ClientIncidentResponse
> response
) {
939 DCHECK(thread_checker_
.CalledOnValidThread());
941 UMA_HISTOGRAM_ENUMERATION(
942 "SBIRS.UploadResult", result
, IncidentReportUploader::NUM_UPLOAD_RESULTS
);
944 // The upload is no longer outstanding, so take ownership of the context (from
945 // the collection of outstanding uploads) in this scope.
946 ScopedVector
<UploadContext
>::iterator
it(
947 std::find(uploads_
.begin(), uploads_
.end(), context
));
948 DCHECK(it
!= uploads_
.end());
949 scoped_ptr
<UploadContext
> upload(context
); // == *it
950 *it
= uploads_
.back();
951 uploads_
.weak_erase(uploads_
.end() - 1);
953 if (result
== IncidentReportUploader::UPLOAD_SUCCESS
)
954 HandleResponse(*upload
);
958 void IncidentReportingService::Observe(
960 const content::NotificationSource
& source
,
961 const content::NotificationDetails
& details
) {
963 case chrome::NOTIFICATION_PROFILE_ADDED
: {
964 Profile
* profile
= content::Source
<Profile
>(source
).ptr();
965 if (!profile
->IsOffTheRecord())
966 OnProfileAdded(profile
);
969 case chrome::NOTIFICATION_PROFILE_DESTROYED
: {
970 Profile
* profile
= content::Source
<Profile
>(source
).ptr();
971 if (!profile
->IsOffTheRecord())
972 OnProfileDestroyed(profile
);
980 } // namespace safe_browsing