summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgrt@chromium.org <grt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-08 21:13:57 +0000
committergrt@chromium.org <grt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-08 21:13:57 +0000
commit5f24715c505ab7e22bf12cd88179eb8c0fdf7313 (patch)
tree41404a183bd8237d9e7d1536fdd6bba8f3890529
parent6f625a200cf121fdff091509bdb6efce9ba51d26 (diff)
downloadchromium_src-5f24715c505ab7e22bf12cd88179eb8c0fdf7313.zip
chromium_src-5f24715c505ab7e22bf12cd88179eb8c0fdf7313.tar.gz
chromium_src-5f24715c505ab7e22bf12cd88179eb8c0fdf7313.tar.bz2
Merge 281089 "Include the latest binary download info in safe br..."
> Include the latest binary download info in safe browsing incident reports. > > BUG=383042 > > Review URL: https://codereview.chromium.org/330653006 TBR=grt@chromium.org Review URL: https://codereview.chromium.org/379563006 git-svn-id: svn://svn.chromium.org/chrome/branches/2062/src@281833 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/safe_browsing/download_protection_service.cc16
-rw-r--r--chrome/browser/safe_browsing/incident_report_uploader.h1
-rw-r--r--chrome/browser/safe_browsing/incident_reporting_service.cc148
-rw-r--r--chrome/browser/safe_browsing/incident_reporting_service.h59
-rw-r--r--chrome/browser/safe_browsing/incident_reporting_service_unittest.cc229
-rw-r--r--chrome/browser/safe_browsing/last_download_finder.cc249
-rw-r--r--chrome/browser/safe_browsing/last_download_finder.h117
-rw-r--r--chrome/browser/safe_browsing/last_download_finder_unittest.cc331
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/chrome_common.gypi1
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--chrome/common/BUILD.gn1
-rw-r--r--chrome/common/safe_browsing/download_protection_util.cc17
-rw-r--r--chrome/common/safe_browsing/download_protection_util.h6
-rw-r--r--tools/metrics/histograms/histograms.xml43
15 files changed, 1113 insertions, 108 deletions
diff --git a/chrome/browser/safe_browsing/download_protection_service.cc b/chrome/browser/safe_browsing/download_protection_service.cc
index 256aba6..785ceca 100644
--- a/chrome/browser/safe_browsing/download_protection_service.cc
+++ b/chrome/browser/safe_browsing/download_protection_service.cc
@@ -56,20 +56,6 @@ const char DownloadProtectionService::kDownloadRequestUrl[] =
"https://sb-ssl.google.com/safebrowsing/clientreport/download";
namespace {
-ClientDownloadRequest::DownloadType GetDownloadType(
- const base::FilePath& file) {
- DCHECK(download_protection_util::IsBinaryFile(file));
- if (file.MatchesExtension(FILE_PATH_LITERAL(".apk")))
- return ClientDownloadRequest::ANDROID_APK;
- else if (file.MatchesExtension(FILE_PATH_LITERAL(".crx")))
- return ClientDownloadRequest::CHROME_EXTENSION;
- // For zip files, we use the ZIPPED_EXECUTABLE type since we will only send
- // the pingback if we find an executable inside the zip archive.
- else if (file.MatchesExtension(FILE_PATH_LITERAL(".zip")))
- return ClientDownloadRequest::ZIPPED_EXECUTABLE;
- return ClientDownloadRequest::WIN_EXECUTABLE;
-}
-
// List of extensions for which we track some UMA stats.
enum MaliciousExtensionType {
EXTENSION_EXE,
@@ -477,7 +463,7 @@ class DownloadProtectionService::CheckClientDownloadRequest
*reason = REASON_NOT_BINARY_FILE;
return false;
}
- *type = GetDownloadType(target_path);
+ *type = download_protection_util::GetDownloadType(target_path);
return true;
}
diff --git a/chrome/browser/safe_browsing/incident_report_uploader.h b/chrome/browser/safe_browsing/incident_report_uploader.h
index 4449c95..d564848 100644
--- a/chrome/browser/safe_browsing/incident_report_uploader.h
+++ b/chrome/browser/safe_browsing/incident_report_uploader.h
@@ -24,6 +24,7 @@ class IncidentReportUploader {
UPLOAD_CANCELLED = 3, // The upload was cancelled.
UPLOAD_REQUEST_FAILED = 4, // Upload failed.
UPLOAD_INVALID_RESPONSE = 5, // The response was not recognized.
+ UPLOAD_NO_DOWNLOAD = 6, // No last download was found.
NUM_UPLOAD_RESULTS
};
diff --git a/chrome/browser/safe_browsing/incident_reporting_service.cc b/chrome/browser/safe_browsing/incident_reporting_service.cc
index fb251f6..46cb9f3 100644
--- a/chrome/browser/safe_browsing/incident_reporting_service.cc
+++ b/chrome/browser/safe_browsing/incident_reporting_service.cc
@@ -73,8 +73,8 @@ struct IncidentReportingService::ProfileContext {
// The incidents collected for this profile pending creation and/or upload.
ScopedVector<ClientIncidentReport_IncidentData> incidents;
- // False until PROFILE_CREATED notification is received.
- bool created;
+ // False until PROFILE_ADDED notification is received.
+ bool added;
private:
DISALLOW_COPY_AND_ASSIGN(ProfileContext);
@@ -98,7 +98,7 @@ class IncidentReportingService::UploadContext {
DISALLOW_COPY_AND_ASSIGN(UploadContext);
};
-IncidentReportingService::ProfileContext::ProfileContext() : created() {
+IncidentReportingService::ProfileContext::ProfileContext() : added() {
}
IncidentReportingService::ProfileContext::~ProfileContext() {
@@ -132,7 +132,7 @@ IncidentReportingService::IncidentReportingService(
receiver_weak_ptr_factory_(this),
weak_ptr_factory_(this) {
notification_registrar_.Add(this,
- chrome::NOTIFICATION_PROFILE_CREATED,
+ chrome::NOTIFICATION_PROFILE_ADDED,
content::NotificationService::AllSources());
notification_registrar_.Add(this,
chrome::NOTIFICATION_PROFILE_DESTROYED,
@@ -146,6 +146,7 @@ IncidentReportingService::~IncidentReportingService() {
weak_ptr_factory_.InvalidateWeakPtrs();
CancelEnvironmentCollection();
+ CancelDownloadCollection();
CancelAllReportUploads();
STLDeleteValues(&profiles_);
@@ -154,7 +155,7 @@ IncidentReportingService::~IncidentReportingService() {
AddIncidentCallback IncidentReportingService::GetAddIncidentCallback(
Profile* profile) {
// Force the context to be created so that incidents added before
- // OnProfileCreated is called are held until the profile's preferences can be
+ // OnProfileAdded is called are held until the profile's preferences can be
// queried.
ignore_result(GetOrCreateProfileContext(profile));
@@ -188,13 +189,21 @@ void IncidentReportingService::SetCollectEnvironmentHook(
}
}
-void IncidentReportingService::OnProfileCreated(Profile* profile) {
+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->created = true;
+ context->added = true;
- // Drop all incidents if this profile is not participating in safe browsing.
+ // Nothing else to do if a report is not being assembled.
+ if (!report_)
+ return;
+
+ // Drop all incidents received prior to creation if the profile is not
+ // participating in safe browsing.
if (!context->incidents.empty() &&
!profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
for (size_t i = 0; i < context->incidents.size(); ++i) {
@@ -202,19 +211,24 @@ void IncidentReportingService::OnProfileCreated(Profile* profile) {
}
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) {
-#if 0
return IncidentReportUploaderImpl::UploadReport(
callback, request_context_getter, report).Pass();
-#else
- // TODO(grt): Remove this temporary suppression of all uploads.
- return scoped_ptr<IncidentReportUploader>();
-#endif
}
IncidentReportingService::ProfileContext*
@@ -272,21 +286,23 @@ void IncidentReportingService::AddIncident(
DCHECK(context);
// Drop the incident immediately if profile creation has completed and the
- // profile is not participating in safe browsing.
- if (context->created &&
+ // profile is not participating in safe browsing. Preference evaluation is
+ // deferred until OnProfileAdded() if profile creation has not yet
+ // completed.
+ if (context->added &&
!profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
LogIncidentDataType(DROPPED, *incident_data);
return;
}
- // Create a new report if this is the first incident ever or first since last
- // upload.
+ // Start assembling a new report if this is the first incident ever or the
+ // first since the last upload.
if (!report_) {
report_.reset(new ClientIncidentReport());
first_incident_time_ = base::Time::Now();
}
- // Provide time to the new incident if the caller didn't provide it.
+ // 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());
@@ -306,11 +322,14 @@ void IncidentReportingService::AddIncident(
upload_timer_.Reset();
BeginEnvironmentCollection();
+ BeginDownloadCollection();
}
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;
@@ -330,6 +349,10 @@ void IncidentReportingService::BeginEnvironmentCollection() {
DCHECK(environment_collection_pending_);
}
+bool IncidentReportingService::WaitingForEnvironmentCollection() {
+ return environment_collection_pending_;
+}
+
void IncidentReportingService::CancelEnvironmentCollection() {
environment_collection_begin_ = base::TimeTicks();
environment_collection_pending_ = false;
@@ -361,6 +384,10 @@ void IncidentReportingService::OnEnvironmentDataCollected(
UploadIfCollectionComplete();
}
+bool IncidentReportingService::WaitingToCollateIncidents() {
+ return collection_timeout_pending_;
+}
+
void IncidentReportingService::CancelIncidentCollection() {
collection_timeout_pending_ = false;
last_incident_time_ = base::TimeTicks();
@@ -379,7 +406,7 @@ void IncidentReportingService::OnCollectionTimeout() {
for (ProfileContextCollection::iterator scan = profiles_.begin();
scan != profiles_.end();
++scan) {
- if (!scan->second->created && !scan->second->incidents.empty()) {
+ if (!scan->second->added && !scan->second->incidents.empty()) {
upload_timer_.Reset();
return;
}
@@ -390,22 +417,91 @@ void IncidentReportingService::OnCollectionTimeout() {
UploadIfCollectionComplete();
}
-void IncidentReportingService::CollectDownloadDetails(
- ClientIncidentReport_DownloadDetails* download_details) {
+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());
- // TODO(grt): collect download info; http://crbug.com/383042.
+ 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.
- if (environment_collection_pending_ || collection_timeout_pending_)
+ // 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());
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();
@@ -566,10 +662,10 @@ void IncidentReportingService::Observe(
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
- case chrome::NOTIFICATION_PROFILE_CREATED: {
+ case chrome::NOTIFICATION_PROFILE_ADDED: {
Profile* profile = content::Source<Profile>(source).ptr();
if (!profile->IsOffTheRecord())
- OnProfileCreated(profile);
+ OnProfileAdded(profile);
break;
}
case chrome::NOTIFICATION_PROFILE_DESTROYED: {
diff --git a/chrome/browser/safe_browsing/incident_reporting_service.h b/chrome/browser/safe_browsing/incident_reporting_service.h
index 39e9fa2..bd02472 100644
--- a/chrome/browser/safe_browsing/incident_reporting_service.h
+++ b/chrome/browser/safe_browsing/incident_reporting_service.h
@@ -20,6 +20,7 @@
#include "base/timer/timer.h"
#include "chrome/browser/safe_browsing/add_incident_callback.h"
#include "chrome/browser/safe_browsing/incident_report_uploader.h"
+#include "chrome/browser/safe_browsing/last_download_finder.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
@@ -54,10 +55,10 @@ class ClientIncidentReport_IncidentData;
// Incidents reported from a profile that is loading are held until the profile
// is fully created. Incidents originating from profiles that do not participate
// in safe browsing are dropped. Following the addition of an incident that is
-// not dropped, the service collects environmental data and waits a bit.
-// Additional incidents that arrive during this time are collated with the
-// initial incident. Finally, already-reported incidents are pruned and any
-// remaining are uploaded in an incident report.
+// not dropped, the service collects environmental data, finds the most recent
+// binary download, and waits a bit. Additional incidents that arrive during
+// this time are collated with the initial incident. Finally, already-reported
+// incidents are pruned and any remaining are uploaded in an incident report.
class IncidentReportingService : public content::NotificationObserver {
public:
IncidentReportingService(SafeBrowsingService* safe_browsing_service,
@@ -94,11 +95,18 @@ class IncidentReportingService : public content::NotificationObserver {
CollectEnvironmentDataFn collect_environment_data_hook,
const scoped_refptr<base::TaskRunner>& task_runner);
- // Handles the creation of a new profile. Creates a new context for |profile|
- // if one does not exist, and drops any received incidents for the profile if
- // the profile is not participating in safe browsing. Overridden by unit tests
+ // Handles the addition of a new profile to the ProfileManager. Creates a new
+ // context for |profile| if one does not exist, drops any received incidents
+ // for the profile if the profile is not participating in safe browsing, and
+ // initiates a new search for the most recent download if a report is being
+ // assembled and the most recent has not been found. Overridden by unit tests
// to inject incidents prior to creation.
- virtual void OnProfileCreated(Profile* profile);
+ virtual void OnProfileAdded(Profile* profile);
+
+ // Initiates a search for the most recent binary download. Overriden by unit
+ // tests to provide a fake finder.
+ virtual scoped_ptr<LastDownloadFinder> CreateDownloadFinder(
+ const LastDownloadFinder::LastDownloadCallback& callback);
// Initiates an upload. Overridden by unit tests to provide a fake uploader.
virtual scoped_ptr<IncidentReportUploader> StartReportUpload(
@@ -131,6 +139,9 @@ class IncidentReportingService : public content::NotificationObserver {
// Starts a task to collect environment data in the blocking pool.
void BeginEnvironmentCollection();
+ // Returns true if the environment collection task is outstanding.
+ bool WaitingForEnvironmentCollection();
+
// Cancels any pending environment collection task and drops any data that has
// already been collected.
void CancelEnvironmentCollection();
@@ -141,6 +152,10 @@ class IncidentReportingService : public content::NotificationObserver {
void OnEnvironmentDataCollected(
scoped_ptr<ClientIncidentReport_EnvironmentData> environment_data);
+ // Returns true if the service is waiting for additional incidents before
+ // uploading a report.
+ bool WaitingToCollateIncidents();
+
// Cancels the collection timeout.
void CancelIncidentCollection();
@@ -149,10 +164,23 @@ class IncidentReportingService : public content::NotificationObserver {
// environment data to arrive or by sending an incident report.
void OnCollectionTimeout();
- // Populates |download_details| with information about the most recent
- // download.
- void CollectDownloadDetails(
- ClientIncidentReport_DownloadDetails* download_details);
+ // Starts the asynchronous process of finding the most recent executable
+ // download if one is not currently being search for and/or has not already
+ // been found.
+ void BeginDownloadCollection();
+
+ // True if the service is waiting to discover the most recent download either
+ // because a task to do so is outstanding, or because one or more profiles
+ // have yet to be added to the ProfileManager.
+ bool WaitingForMostRecentDownload();
+
+ // Cancels the search for the most recent executable download.
+ void CancelDownloadCollection();
+
+ // A callback invoked on the UI thread by the last download finder when the
+ // search for the most recent binary download is complete.
+ void OnLastDownloadFound(
+ scoped_ptr<ClientIncidentReport_DownloadDetails> last_download);
// Uploads an incident report if all data collection is complete. Incidents
// originating from profiles that do not participate in safe browsing are
@@ -227,12 +255,19 @@ class IncidentReportingService : public content::NotificationObserver {
// The time at which environmental data collection was initiated.
base::TimeTicks environment_collection_begin_;
+ // The time at which download collection was initiated.
+ base::TimeTicks last_download_begin_;
+
// Context data for all on-the-record profiles.
ProfileContextCollection profiles_;
// The collection of uploads in progress.
ScopedVector<UploadContext> uploads_;
+ // An object that asynchronously searches for the most recent binary download.
+ // Non-NULL while such a search is outstanding.
+ scoped_ptr<LastDownloadFinder> last_download_finder_;
+
// A factory for handing out weak pointers for AddIncident callbacks.
base::WeakPtrFactory<IncidentReportingService> receiver_weak_ptr_factory_;
diff --git a/chrome/browser/safe_browsing/incident_reporting_service_unittest.cc b/chrome/browser/safe_browsing/incident_reporting_service_unittest.cc
index 1496486..1a369f8 100644
--- a/chrome/browser/safe_browsing/incident_reporting_service_unittest.cc
+++ b/chrome/browser/safe_browsing/incident_reporting_service_unittest.cc
@@ -16,6 +16,7 @@
#include "base/threading/thread_local.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/browser/safe_browsing/incident_report_uploader.h"
+#include "chrome/browser/safe_browsing/last_download_finder.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/safe_browsing/csd.pb.h"
#include "chrome/test/base/testing_browser_process.h"
@@ -35,24 +36,30 @@ class IncidentReportingServiceTest : public testing::Test {
class TestIncidentReportingService
: public safe_browsing::IncidentReportingService {
public:
- typedef base::Callback<void(Profile*)> PreProfileCreateCallback;
+ typedef base::Callback<void(Profile*)> PreProfileAddCallback;
typedef base::Callback<
void(safe_browsing::ClientIncidentReport_EnvironmentData*)>
CollectEnvironmentCallback;
+ typedef base::Callback<scoped_ptr<safe_browsing::LastDownloadFinder>(
+ const safe_browsing::LastDownloadFinder::LastDownloadCallback&
+ callback)> CreateDownloadFinderCallback;
+
typedef base::Callback<scoped_ptr<safe_browsing::IncidentReportUploader>(
const safe_browsing::IncidentReportUploader::OnResultCallback&,
const safe_browsing::ClientIncidentReport& report)> StartUploadCallback;
TestIncidentReportingService(
const scoped_refptr<base::TaskRunner>& task_runner,
- const PreProfileCreateCallback& pre_profile_create_callback,
+ const PreProfileAddCallback& pre_profile_add_callback,
const CollectEnvironmentCallback& collect_environment_callback,
+ const CreateDownloadFinderCallback& create_download_finder_callback,
const StartUploadCallback& start_upload_callback)
: IncidentReportingService(NULL, NULL),
- pre_profile_create_callback_(pre_profile_create_callback),
+ pre_profile_add_callback_(pre_profile_add_callback),
collect_environment_callback_(collect_environment_callback),
+ create_download_finder_callback_(create_download_finder_callback),
start_upload_callback_(start_upload_callback) {
SetCollectEnvironmentHook(&CollectEnvironmentData, task_runner);
test_instance_.Get().Set(this);
@@ -61,9 +68,15 @@ class IncidentReportingServiceTest : public testing::Test {
virtual ~TestIncidentReportingService() { test_instance_.Get().Set(NULL); }
protected:
- virtual void OnProfileCreated(Profile* profile) OVERRIDE {
- pre_profile_create_callback_.Run(profile);
- safe_browsing::IncidentReportingService::OnProfileCreated(profile);
+ virtual void OnProfileAdded(Profile* profile) OVERRIDE {
+ pre_profile_add_callback_.Run(profile);
+ safe_browsing::IncidentReportingService::OnProfileAdded(profile);
+ }
+
+ virtual scoped_ptr<safe_browsing::LastDownloadFinder> CreateDownloadFinder(
+ const safe_browsing::LastDownloadFinder::LastDownloadCallback& callback)
+ OVERRIDE {
+ return create_download_finder_callback_.Run(callback);
}
virtual scoped_ptr<safe_browsing::IncidentReportUploader> StartReportUpload(
@@ -87,8 +100,9 @@ class IncidentReportingServiceTest : public testing::Test {
static base::LazyInstance<base::ThreadLocalPointer<
TestIncidentReportingService> >::Leaky test_instance_;
- PreProfileCreateCallback pre_profile_create_callback_;
+ PreProfileAddCallback pre_profile_add_callback_;
CollectEnvironmentCallback collect_environment_callback_;
+ CreateDownloadFinderCallback create_download_finder_callback_;
StartUploadCallback start_upload_callback_;
};
@@ -100,14 +114,26 @@ class IncidentReportingServiceTest : public testing::Test {
};
// A type for specifying the action to be taken by the test fixture during
- // profile initialization (before NOTIFICATION_PROFILE_CREATED is sent).
- enum OnProfileCreationAction {
- ON_PROFILE_CREATION_NO_ACTION,
- ON_PROFILE_CREATION_ADD_INCIDENT, // Add an incident to the service.
+ // profile initialization (before NOTIFICATION_PROFILE_ADDED is sent).
+ enum OnProfileAdditionAction {
+ ON_PROFILE_ADDITION_NO_ACTION,
+ ON_PROFILE_ADDITION_ADD_INCIDENT, // Add an incident to the service.
+ };
+
+ // A type for specifying the action to be taken by the test fixture when the
+ // service creates a LastDownloadFinder.
+ enum OnCreateDownloadFinderAction {
+ // Post a task that reports a download.
+ ON_CREATE_DOWNLOAD_FINDER_DOWNLOAD_FOUND,
+ // Post a task that reports no downloads found.
+ ON_CREATE_DOWNLOAD_FINDER_NO_DOWNLOADS,
+ // Immediately return due to a lack of eligible profiles.
+ ON_CREATE_DOWNLOAD_FINDER_NO_PROFILES,
};
static const int64 kIncidentTimeMsec;
static const char kFakeOsName[];
+ static const char kFakeDownloadToken[];
IncidentReportingServiceTest()
: task_runner_(new base::TestSimpleTaskRunner),
@@ -115,14 +141,20 @@ class IncidentReportingServiceTest : public testing::Test {
profile_manager_(TestingBrowserProcess::GetGlobal()),
instance_(new TestIncidentReportingService(
task_runner_,
- base::Bind(&IncidentReportingServiceTest::PreProfileCreate,
+ base::Bind(&IncidentReportingServiceTest::PreProfileAdd,
base::Unretained(this)),
base::Bind(&IncidentReportingServiceTest::CollectEnvironmentData,
base::Unretained(this)),
+ base::Bind(&IncidentReportingServiceTest::CreateDownloadFinder,
+ base::Unretained(this)),
base::Bind(&IncidentReportingServiceTest::StartUpload,
base::Unretained(this)))),
+ on_create_download_finder_action_(
+ ON_CREATE_DOWNLOAD_FINDER_DOWNLOAD_FOUND),
upload_result_(safe_browsing::IncidentReportUploader::UPLOAD_SUCCESS),
environment_collected_(),
+ download_finder_created_(),
+ download_finder_destroyed_(),
uploader_destroyed_() {}
virtual void SetUp() OVERRIDE {
@@ -130,12 +162,18 @@ class IncidentReportingServiceTest : public testing::Test {
ASSERT_TRUE(profile_manager_.SetUp());
}
+ // Sets the action to be taken by the test fixture when the service creates a
+ // LastDownloadFinder.
+ void SetCreateDownloadFinderAction(OnCreateDownloadFinderAction action) {
+ on_create_download_finder_action_ = action;
+ }
+
// Creates and returns a profile (owned by the profile manager) with or
// without safe browsing enabled. An incident will be created within
- // PreProfileCreate if requested.
+ // PreProfileAdd if requested.
TestingProfile* CreateProfile(const std::string& profile_name,
SafeBrowsingDisposition safe_browsing_opt_in,
- OnProfileCreationAction on_creation_action) {
+ OnProfileAdditionAction on_addition_action) {
// Create prefs for the profile with safe browsing enabled or not.
scoped_ptr<TestingPrefServiceSyncable> prefs(
new TestingPrefServiceSyncable);
@@ -144,7 +182,7 @@ class IncidentReportingServiceTest : public testing::Test {
safe_browsing_opt_in == SAFE_BROWSING_OPT_IN);
// Remember whether or not to create an incident.
- profile_properties_[profile_name].on_creation_action = on_creation_action;
+ profile_properties_[profile_name].on_addition_action = on_addition_action;
// Boom (or fizzle).
return profile_manager_.CreateTestingProfile(
@@ -195,13 +233,17 @@ class IncidentReportingServiceTest : public testing::Test {
ASSERT_TRUE(uploaded_report_->environment().os().has_os_name());
ASSERT_EQ(std::string(kFakeOsName),
uploaded_report_->environment().os().os_name());
+ ASSERT_EQ(std::string(kFakeDownloadToken),
+ uploaded_report_->download().token());
uploaded_report_.reset();
}
- void ExpectNoUpload() { ASSERT_FALSE(uploaded_report_); }
+ void AssertNoUpload() { ASSERT_FALSE(uploaded_report_); }
bool HasCollectedEnvironmentData() const { return environment_collected_; }
+ bool HasCreatedDownloadFinder() const { return download_finder_created_; }
+ bool DownloadFinderDestroyed() const { return download_finder_destroyed_; }
bool UploaderDestroyed() const { return uploader_destroyed_; }
scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
@@ -209,9 +251,12 @@ class IncidentReportingServiceTest : public testing::Test {
TestingProfileManager profile_manager_;
scoped_ptr<safe_browsing::IncidentReportingService> instance_;
base::Closure on_start_upload_callback_;
+ OnCreateDownloadFinderAction on_create_download_finder_action_;
safe_browsing::IncidentReportUploader::Result upload_result_;
bool environment_collected_;
+ bool download_finder_created_;
scoped_ptr<safe_browsing::ClientIncidentReport> uploaded_report_;
+ bool download_finder_destroyed_;
bool uploader_destroyed_;
private:
@@ -248,20 +293,46 @@ class IncidentReportingServiceTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(FakeUploader);
};
+ class FakeDownloadFinder : public safe_browsing::LastDownloadFinder {
+ public:
+ static scoped_ptr<safe_browsing::LastDownloadFinder> Create(
+ const base::Closure& on_deleted,
+ scoped_ptr<safe_browsing::ClientIncidentReport_DownloadDetails>
+ download,
+ const safe_browsing::LastDownloadFinder::LastDownloadCallback&
+ callback) {
+ // Post a task to run the callback.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, base::Passed(&download)));
+ return scoped_ptr<safe_browsing::LastDownloadFinder>(
+ new FakeDownloadFinder(on_deleted));
+ }
+
+ virtual ~FakeDownloadFinder() { on_deleted_.Run(); }
+
+ private:
+ explicit FakeDownloadFinder(const base::Closure& on_deleted)
+ : on_deleted_(on_deleted) {}
+
+ base::Closure on_deleted_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeDownloadFinder);
+ };
+
// Properties for a profile that impact the behavior of the test.
struct ProfileProperties {
- ProfileProperties() : on_creation_action(ON_PROFILE_CREATION_NO_ACTION) {}
+ ProfileProperties() : on_addition_action(ON_PROFILE_ADDITION_NO_ACTION) {}
// The action taken by the test fixture during profile initialization
- // (before NOTIFICATION_PROFILE_CREATED is sent).
- OnProfileCreationAction on_creation_action;
+ // (before NOTIFICATION_PROFILE_ADDED is sent).
+ OnProfileAdditionAction on_addition_action;
};
// Returns the name of a profile as provided to CreateProfile.
static std::string GetProfileName(Profile* profile) {
// Cannot reliably use profile->GetProfileName() since the test needs the
// name before the profile manager sets it (which happens after profile
- // creation).
+ // addition).
return profile->GetPath().BaseName().AsUTF8Unsafe();
}
@@ -274,20 +345,20 @@ class IncidentReportingServiceTest : public testing::Test {
GetProfileName(profile)));
}
- // A callback run by the test fixture when a profile is created. An incident
+ // A callback run by the test fixture when a profile is added. An incident
// is added.
- void PreProfileCreate(Profile* profile) {
+ void PreProfileAdd(Profile* profile) {
// The instance must have already been created.
ASSERT_TRUE(instance_);
// Add a test incident to the service if requested.
- switch (profile_properties_[GetProfileName(profile)].on_creation_action) {
- case ON_PROFILE_CREATION_ADD_INCIDENT:
+ switch (profile_properties_[GetProfileName(profile)].on_addition_action) {
+ case ON_PROFILE_ADDITION_ADD_INCIDENT:
AddTestIncident(profile);
break;
default:
ASSERT_EQ(
- ON_PROFILE_CREATION_NO_ACTION,
- profile_properties_[GetProfileName(profile)].on_creation_action);
+ ON_PROFILE_ADDITION_NO_ACTION,
+ profile_properties_[GetProfileName(profile)].on_addition_action);
break;
}
}
@@ -303,6 +374,29 @@ class IncidentReportingServiceTest : public testing::Test {
environment_collected_ = true;
}
+ // A fake CreateDownloadFinder implementation invoked by the service during
+ // operation.
+ scoped_ptr<safe_browsing::LastDownloadFinder> CreateDownloadFinder(
+ const safe_browsing::LastDownloadFinder::LastDownloadCallback& callback) {
+ download_finder_created_ = true;
+ scoped_ptr<safe_browsing::ClientIncidentReport_DownloadDetails> download;
+ if (on_create_download_finder_action_ ==
+ ON_CREATE_DOWNLOAD_FINDER_NO_PROFILES) {
+ return scoped_ptr<safe_browsing::LastDownloadFinder>();
+ }
+ if (on_create_download_finder_action_ ==
+ ON_CREATE_DOWNLOAD_FINDER_DOWNLOAD_FOUND) {
+ download.reset(new safe_browsing::ClientIncidentReport_DownloadDetails);
+ download->set_token(kFakeDownloadToken);
+ }
+ return scoped_ptr<safe_browsing::LastDownloadFinder>(
+ FakeDownloadFinder::Create(
+ base::Bind(&IncidentReportingServiceTest::OnDownloadFinderDestroyed,
+ base::Unretained(this)),
+ download.Pass(),
+ callback));
+ }
+
// A fake StartUpload implementation invoked by the service during operation.
scoped_ptr<safe_browsing::IncidentReportUploader> StartUpload(
const safe_browsing::IncidentReportUploader::OnResultCallback& callback,
@@ -314,13 +408,16 @@ class IncidentReportingServiceTest : public testing::Test {
on_start_upload_callback_.Run();
on_start_upload_callback_ = base::Closure();
}
- return scoped_ptr<safe_browsing::IncidentReportUploader>(new FakeUploader(
- base::Bind(&IncidentReportingServiceTest::OnUploaderDestroyed,
- base::Unretained(this)),
- callback,
- upload_result_));
+ return scoped_ptr<safe_browsing::IncidentReportUploader>(
+ new FakeUploader(
+ base::Bind(
+ &IncidentReportingServiceTest::OnUploaderDestroyed,
+ base::Unretained(this)),
+ callback,
+ upload_result_)).Pass();
}
+ void OnDownloadFinderDestroyed() { download_finder_destroyed_ = true; }
void OnUploaderDestroyed() { uploader_destroyed_ = true; }
// A mapping of profile name to its corresponding properties.
@@ -335,12 +432,13 @@ base::LazyInstance<base::ThreadLocalPointer<
const int64 IncidentReportingServiceTest::kIncidentTimeMsec = 47LL;
const char IncidentReportingServiceTest::kFakeOsName[] = "fakedows";
+const char IncidentReportingServiceTest::kFakeDownloadToken[] = "fakedlt";
// Tests that an incident added during profile initialization when safe browsing
// is on is uploaded.
TEST_F(IncidentReportingServiceTest, AddIncident) {
CreateProfile(
- "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_CREATION_ADD_INCIDENT);
+ "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_ADDITION_ADD_INCIDENT);
// Let all tasks run.
task_runner_->RunUntilIdle();
@@ -348,11 +446,15 @@ TEST_F(IncidentReportingServiceTest, AddIncident) {
// Verify that environment collection took place.
EXPECT_TRUE(HasCollectedEnvironmentData());
- // Verify that report upload took place and contained the incident and
- // environment data.
+ // Verify that the most recent download was looked for.
+ EXPECT_TRUE(HasCreatedDownloadFinder());
+
+ // Verify that report upload took place and contained the incident,
+ // environment data, and download details.
ExpectTestIncidentUploaded();
- // Verify that the uploader was destroyed.
+ // Verify that the download finder and the uploader were destroyed.
+ ASSERT_TRUE(DownloadFinderDestroyed());
ASSERT_TRUE(UploaderDestroyed());
}
@@ -361,20 +463,61 @@ TEST_F(IncidentReportingServiceTest, AddIncident) {
TEST_F(IncidentReportingServiceTest, NoSafeBrowsing) {
// Create the profile, thereby causing the test to begin.
CreateProfile(
- "profile1", SAFE_BROWSING_OPT_OUT, ON_PROFILE_CREATION_ADD_INCIDENT);
+ "profile1", SAFE_BROWSING_OPT_OUT, ON_PROFILE_ADDITION_ADD_INCIDENT);
// Let all tasks run.
task_runner_->RunUntilIdle();
// Verify that no report upload took place.
- ExpectNoUpload();
+ AssertNoUpload();
+}
+
+// Tests that no incident report is uploaded if there is no recent download.
+TEST_F(IncidentReportingServiceTest, NoDownloadNoUpload) {
+ // Tell the fixture to return no downloads found.
+ SetCreateDownloadFinderAction(ON_CREATE_DOWNLOAD_FINDER_NO_DOWNLOADS);
+
+ // Create the profile, thereby causing the test to begin.
+ CreateProfile(
+ "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_ADDITION_ADD_INCIDENT);
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // Verify that the download finder was run but that no report upload took
+ // place.
+ EXPECT_TRUE(HasCreatedDownloadFinder());
+ AssertNoUpload();
+ EXPECT_TRUE(DownloadFinderDestroyed());
+}
+
+// Tests that no incident report is uploaded if there is no recent download.
+TEST_F(IncidentReportingServiceTest, NoProfilesNoUpload) {
+ // Tell the fixture to pretend there are no profiles eligible for finding
+ // downloads.
+ SetCreateDownloadFinderAction(ON_CREATE_DOWNLOAD_FINDER_NO_PROFILES);
+
+ // Create the profile, thereby causing the test to begin.
+ CreateProfile(
+ "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_ADDITION_ADD_INCIDENT);
+
+ // Let all tasks run.
+ task_runner_->RunUntilIdle();
+
+ // Verify that the download finder was run but that no report upload took
+ // place.
+ EXPECT_TRUE(HasCreatedDownloadFinder());
+ AssertNoUpload();
+ // Although CreateDownloadFinder was called, no instance was returned so there
+ // is nothing to have been destroyed.
+ EXPECT_FALSE(DownloadFinderDestroyed());
}
// Tests that an incident added after upload is not uploaded again.
TEST_F(IncidentReportingServiceTest, OnlyOneUpload) {
// Create the profile, thereby causing the test to begin.
Profile* profile = CreateProfile(
- "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_CREATION_ADD_INCIDENT);
+ "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_ADDITION_ADD_INCIDENT);
// Let all tasks run.
task_runner_->RunUntilIdle();
@@ -390,7 +533,7 @@ TEST_F(IncidentReportingServiceTest, OnlyOneUpload) {
task_runner_->RunUntilIdle();
// Verify that no additional report upload took place.
- ExpectNoUpload();
+ AssertNoUpload();
}
// Tests that the same incident added for two different profiles in sequence
@@ -398,7 +541,7 @@ TEST_F(IncidentReportingServiceTest, OnlyOneUpload) {
TEST_F(IncidentReportingServiceTest, TwoProfilesTwoUploads) {
// Create the profile, thereby causing the test to begin.
CreateProfile(
- "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_CREATION_ADD_INCIDENT);
+ "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_ADDITION_ADD_INCIDENT);
// Let all tasks run.
task_runner_->RunUntilIdle();
@@ -407,9 +550,9 @@ TEST_F(IncidentReportingServiceTest, TwoProfilesTwoUploads) {
// environment data.
ExpectTestIncidentUploaded();
- // Create a second profile with its own incident on creation.
+ // Create a second profile with its own incident on addition.
CreateProfile(
- "profile2", SAFE_BROWSING_OPT_IN, ON_PROFILE_CREATION_ADD_INCIDENT);
+ "profile2", SAFE_BROWSING_OPT_IN, ON_PROFILE_ADDITION_ADD_INCIDENT);
// Let all tasks run.
task_runner_->RunUntilIdle();
@@ -423,7 +566,7 @@ TEST_F(IncidentReportingServiceTest, TwoProfilesTwoUploads) {
TEST_F(IncidentReportingServiceTest, ProfileDestroyedDuringUpload) {
// Create a profile for which an incident will be added.
Profile* profile = CreateProfile(
- "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_CREATION_ADD_INCIDENT);
+ "profile1", SAFE_BROWSING_OPT_IN, ON_PROFILE_ADDITION_ADD_INCIDENT);
// Hook up a callback to run when the upload is started that will post a task
// to delete the profile. This task will run before the upload finishes.
diff --git a/chrome/browser/safe_browsing/last_download_finder.cc b/chrome/browser/safe_browsing/last_download_finder.cc
new file mode 100644
index 0000000..05e7c42
--- /dev/null
+++ b/chrome/browser/safe_browsing/last_download_finder.cc
@@ -0,0 +1,249 @@
+// 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/last_download_finder.h"
+
+#include <algorithm>
+#include <functional>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/prefs/pref_service.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/history/history_service.h"
+#include "chrome/browser/history/history_service_factory.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
+#include "chrome/common/safe_browsing/download_protection_util.h"
+#include "content/public/browser/download_item.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+
+namespace safe_browsing {
+
+namespace {
+
+// Returns true if |first| is more recent than |second|, preferring opened over
+// non-opened for downloads that completed at the same time (extraordinarily
+// unlikely). Only files that look like some kind of executable are considered.
+bool IsMoreInterestingThan(const history::DownloadRow& first,
+ const history::DownloadRow& second) {
+ if (first.end_time < second.end_time)
+ return false;
+ // TODO(grt): Peek into archives to see if they contain binaries;
+ // http://crbug.com/386915.
+ if (!download_protection_util::IsBinaryFile(first.target_path) ||
+ download_protection_util::IsArchiveFile(first.target_path)) {
+ return false;
+ }
+ return (first.end_time != second.end_time ||
+ (first.opened && !second.opened));
+}
+
+// Returns a pointer to the most interesting completed download in |downloads|.
+const history::DownloadRow* FindMostInteresting(
+ const std::vector<history::DownloadRow>& downloads) {
+ const history::DownloadRow* most_recent_row = NULL;
+ for (size_t i = 0; i < downloads.size(); ++i) {
+ const history::DownloadRow& row = downloads[i];
+ // Ignore incomplete downloads.
+ if (row.state != content::DownloadItem::COMPLETE)
+ continue;
+ if (!most_recent_row || IsMoreInterestingThan(row, *most_recent_row))
+ most_recent_row = &row;
+ }
+ return most_recent_row;
+}
+
+// Populates the |details| protobuf with information pertaining to |download|.
+void PopulateDetails(const history::DownloadRow& download,
+ ClientIncidentReport_DownloadDetails* details) {
+ ClientDownloadRequest* download_request = details->mutable_download();
+ download_request->set_url(download.url_chain.back().spec());
+ // digests is a required field, so force it to exist.
+ // TODO(grt): Include digests in reports; http://crbug.com/389123.
+ ignore_result(download_request->mutable_digests());
+ download_request->set_length(download.received_bytes);
+ for (size_t i = 0; i < download.url_chain.size(); ++i) {
+ const GURL& url = download.url_chain[i];
+ ClientDownloadRequest_Resource* resource =
+ download_request->add_resources();
+ resource->set_url(url.spec());
+ if (i != download.url_chain.size() - 1) { // An intermediate redirect.
+ resource->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT);
+ } else { // The final download URL.
+ resource->set_type(ClientDownloadRequest::DOWNLOAD_URL);
+ if (!download.referrer_url.is_empty())
+ resource->set_referrer(download.referrer_url.spec());
+ }
+ }
+ download_request->set_file_basename(
+ download.target_path.BaseName().AsUTF8Unsafe());
+ download_request->set_download_type(
+ download_protection_util::GetDownloadType(download.target_path));
+ download_request->set_locale(
+ g_browser_process->local_state()->GetString(prefs::kApplicationLocale));
+
+ details->set_download_time_msec(download.end_time.ToJavaTime());
+ // Opened time is unknown for now, so use the download time if the file was
+ // opened in Chrome.
+ if (download.opened)
+ details->set_open_time_msec(download.end_time.ToJavaTime());
+}
+
+} // namespace
+
+LastDownloadFinder::~LastDownloadFinder() {
+}
+
+// static
+scoped_ptr<LastDownloadFinder> LastDownloadFinder::Create(
+ const LastDownloadCallback& callback) {
+ scoped_ptr<LastDownloadFinder> finder(make_scoped_ptr(new LastDownloadFinder(
+ g_browser_process->profile_manager()->GetLoadedProfiles(), callback)));
+ // Return NULL if there is no work to do.
+ if (finder->profiles_.empty())
+ return scoped_ptr<LastDownloadFinder>();
+ return finder.Pass();
+}
+
+LastDownloadFinder::LastDownloadFinder() : weak_ptr_factory_(this) {
+}
+
+LastDownloadFinder::LastDownloadFinder(const std::vector<Profile*>& profiles,
+ const LastDownloadCallback& callback)
+ : callback_(callback), weak_ptr_factory_(this) {
+ // Observe profile lifecycle events so that the finder can begin or abandon
+ // the search in profiles while it is running.
+ notification_registrar_.Add(this,
+ chrome::NOTIFICATION_PROFILE_ADDED,
+ content::NotificationService::AllSources());
+ notification_registrar_.Add(this,
+ chrome::NOTIFICATION_HISTORY_LOADED,
+ content::NotificationService::AllSources());
+ notification_registrar_.Add(this,
+ chrome::NOTIFICATION_PROFILE_DESTROYED,
+ content::NotificationService::AllSources());
+
+ // Begin the seach for all given profiles.
+ std::for_each(
+ profiles.begin(),
+ profiles.end(),
+ std::bind1st(std::mem_fun(&LastDownloadFinder::SearchInProfile), this));
+}
+
+void LastDownloadFinder::SearchInProfile(Profile* profile) {
+ // Do not look in OTR profiles or in profiles that do not participate in
+ // safe browsing.
+ if (profile->IsOffTheRecord() ||
+ !profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
+ return;
+ }
+
+ HistoryService* history_service =
+ HistoryServiceFactory::GetForProfile(profile, Profile::IMPLICIT_ACCESS);
+ // No history service is returned for profiles that do not save history.
+ if (!history_service)
+ return;
+
+ profiles_.push_back(profile);
+ if (history_service->BackendLoaded()) {
+ history_service->QueryDownloads(
+ base::Bind(&LastDownloadFinder::OnDownloadQuery,
+ weak_ptr_factory_.GetWeakPtr(),
+ profile));
+ } // else wait until history is loaded.
+}
+
+void LastDownloadFinder::OnProfileHistoryLoaded(
+ Profile* profile,
+ HistoryService* history_service) {
+ if (std::find(profiles_.begin(), profiles_.end(), profile) !=
+ profiles_.end()) {
+ history_service->QueryDownloads(
+ base::Bind(&LastDownloadFinder::OnDownloadQuery,
+ weak_ptr_factory_.GetWeakPtr(),
+ profile));
+ }
+}
+
+void LastDownloadFinder::AbandonSearchInProfile(Profile* profile) {
+ // |profile| may not be present in the set of profiles.
+ std::vector<Profile*>::iterator it =
+ std::find(profiles_.begin(), profiles_.end(), profile);
+ if (it != profiles_.end())
+ RemoveProfileAndReportIfDone(it);
+}
+
+void LastDownloadFinder::OnDownloadQuery(
+ Profile* profile,
+ scoped_ptr<std::vector<history::DownloadRow> > downloads) {
+ // Early-exit if the history search for this profile was abandoned.
+ std::vector<Profile*>::iterator it =
+ std::find(profiles_.begin(), profiles_.end(), profile);
+ if (it == profiles_.end())
+ return;
+
+ // Find the most recent from this profile and use it if it's better than
+ // anything else found so far.
+ const history::DownloadRow* profile_best = FindMostInteresting(*downloads);
+ if (profile_best && IsMoreInterestingThan(*profile_best, most_recent_row_))
+ most_recent_row_ = *profile_best;
+
+ RemoveProfileAndReportIfDone(it);
+}
+
+void LastDownloadFinder::RemoveProfileAndReportIfDone(
+ std::vector<Profile*>::iterator it) {
+ DCHECK(it != profiles_.end());
+
+ *it = profiles_.back();
+ profiles_.resize(profiles_.size() - 1);
+
+ // Finish processing if all results are in.
+ if (profiles_.empty())
+ ReportResults();
+ // Do not touch this instance after reporting results.
+}
+
+void LastDownloadFinder::ReportResults() {
+ DCHECK(profiles_.empty());
+ if (most_recent_row_.end_time.is_null()) {
+ callback_.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>());
+ // Do not touch this instance after running the callback, since it may have
+ // been deleted.
+ } else {
+ scoped_ptr<ClientIncidentReport_DownloadDetails> details(
+ new ClientIncidentReport_DownloadDetails());
+ PopulateDetails(most_recent_row_, details.get());
+ callback_.Run(details.Pass());
+ // Do not touch this instance after running the callback, since it may have
+ // been deleted.
+ }
+}
+
+void LastDownloadFinder::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case chrome::NOTIFICATION_PROFILE_ADDED:
+ SearchInProfile(content::Source<Profile>(source).ptr());
+ break;
+ case chrome::NOTIFICATION_HISTORY_LOADED:
+ OnProfileHistoryLoaded(content::Source<Profile>(source).ptr(),
+ content::Details<HistoryService>(details).ptr());
+ break;
+ case chrome::NOTIFICATION_PROFILE_DESTROYED:
+ AbandonSearchInProfile(content::Source<Profile>(source).ptr());
+ break;
+ default:
+ break;
+ }
+}
+
+} // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/last_download_finder.h b/chrome/browser/safe_browsing/last_download_finder.h
new file mode 100644
index 0000000..4fef70c
--- /dev/null
+++ b/chrome/browser/safe_browsing/last_download_finder.h
@@ -0,0 +1,117 @@
+// 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.
+
+#ifndef CHROME_BROWSER_SAFE_BROWSING_LAST_DOWNLOAD_FINDER_H_
+#define CHROME_BROWSER_SAFE_BROWSING_LAST_DOWNLOAD_FINDER_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/history/download_row.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+class HistoryService;
+class Profile;
+
+namespace content {
+class NotificationDetails;
+class NotificationSource;
+}
+
+namespace history {
+struct DownloadRow;
+}
+
+namespace safe_browsing {
+
+class ClientIncidentReport_DownloadDetails;
+
+// Finds the most recent executable downloaded by any on-the-record profile with
+// history that participates in safe browsing.
+class LastDownloadFinder : public content::NotificationObserver {
+ public:
+ // The type of a callback run by the finder upon completion. The argument is a
+ // protobuf containing details of the download that was found, or an empty
+ // pointer if none was found.
+ typedef base::Callback<void(scoped_ptr<ClientIncidentReport_DownloadDetails>)>
+ LastDownloadCallback;
+
+ virtual ~LastDownloadFinder();
+
+ // Initiates an asynchronous search for the most recent download. |callback|
+ // will be run when the search is complete. The returned instance can be
+ // deleted to terminate the search, in which case |callback| is not invoked.
+ // Returns NULL without running |callback| if there are no eligible profiles
+ // to search.
+ static scoped_ptr<LastDownloadFinder> Create(
+ const LastDownloadCallback& callback);
+
+ protected:
+ // Protected constructor so that unit tests can create a fake finder.
+ LastDownloadFinder();
+
+ private:
+ LastDownloadFinder(const std::vector<Profile*>& profiles,
+ const LastDownloadCallback& callback);
+
+ // Adds |profile| to the set of profiles to be searched if it is an
+ // on-the-record profile with history that participates in safe browsing. The
+ // search is initiated if the profile has already loaded history.
+ void SearchInProfile(Profile* profile);
+
+ // Initiates a search in |profile| if it is in the set of profiles to be
+ // searched.
+ void OnProfileHistoryLoaded(Profile* profile,
+ HistoryService* history_service);
+
+ // Abandons the search for downloads in |profile|, reporting results if there
+ // are no more pending queries.
+ void AbandonSearchInProfile(Profile* profile);
+
+ // HistoryService::DownloadQueryCallback. Retrieves the most recent completed
+ // executable download from |downloads| and reports results if there are no
+ // more pending queries.
+ void OnDownloadQuery(
+ Profile* profile,
+ scoped_ptr<std::vector<history::DownloadRow> > downloads);
+
+ // Removes the profile pointed to by |it| from profiles_ and reports results
+ // if there are no more pending queries.
+ void RemoveProfileAndReportIfDone(std::vector<Profile*>::iterator it);
+
+ // Invokes the caller-supplied callback with the download found.
+ void ReportResults();
+
+ // content::NotificationObserver methods.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ // Caller-supplied callback to be invoked when the most recent download is
+ // found.
+ LastDownloadCallback callback_;
+
+ // The profiles for which a download query is pending.
+ std::vector<Profile*> profiles_;
+
+ // Registrar for observing profile lifecycle notifications.
+ content::NotificationRegistrar notification_registrar_;
+
+ // The most recent download, updated progressively as query results arrive.
+ history::DownloadRow most_recent_row_;
+
+ // A factory for asynchronous operations on profiles' HistoryService.
+ base::WeakPtrFactory<LastDownloadFinder> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(LastDownloadFinder);
+};
+
+} // namespace safe_browsing
+
+#endif // CHROME_BROWSER_SAFE_BROWSING_LAST_DOWNLOAD_FINDER_H_
diff --git a/chrome/browser/safe_browsing/last_download_finder_unittest.cc b/chrome/browser/safe_browsing/last_download_finder_unittest.cc
new file mode 100644
index 0000000..eedacb6
--- /dev/null
+++ b/chrome/browser/safe_browsing/last_download_finder_unittest.cc
@@ -0,0 +1,331 @@
+// 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/last_download_finder.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/file_util.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/history/chrome_history_client.h"
+#include "chrome/browser/history/chrome_history_client_factory.h"
+#include "chrome/browser/history/download_row.h"
+#include "chrome/browser/history/history_service.h"
+#include "chrome/browser/history/history_service_factory.h"
+#include "chrome/browser/history/web_history_service_factory.h"
+#include "chrome/browser/prefs/browser_prefs.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/safe_browsing/csd.pb.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_pref_service_syncable.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// A BrowserContextKeyedServiceFactory::TestingFactoryFunction that creates a
+// HistoryService for a TestingProfile.
+KeyedService* BuildHistoryService(content::BrowserContext* context) {
+ TestingProfile* profile = static_cast<TestingProfile*>(context);
+
+ // Delete the file before creating the service.
+ base::FilePath history_path(
+ profile->GetPath().Append(chrome::kHistoryFilename));
+ if (!base::DeleteFile(history_path, false) ||
+ base::PathExists(history_path)) {
+ ADD_FAILURE() << "failed to delete history db file "
+ << history_path.value();
+ return NULL;
+ }
+
+ HistoryService* history_service = new HistoryService(
+ ChromeHistoryClientFactory::GetForProfile(profile), profile);
+ if (history_service->Init(profile->GetPath()))
+ return history_service;
+
+ ADD_FAILURE() << "failed to initialize history service";
+ delete history_service;
+ return NULL;
+}
+
+} // namespace
+
+class LastDownloadFinderTest : public testing::Test {
+ public:
+ void NeverCalled(scoped_ptr<
+ safe_browsing::ClientIncidentReport_DownloadDetails> download) {
+ FAIL();
+ }
+
+ // Creates a new profile that participates in safe browsing and adds a
+ // download to its history.
+ void CreateProfileWithDownload() {
+ TestingProfile* profile = CreateProfile(SAFE_BROWSING_OPT_IN);
+ HistoryService* history_service =
+ HistoryServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
+ history_service->CreateDownload(
+ CreateTestDownloadRow(),
+ base::Bind(&LastDownloadFinderTest::OnDownloadCreated,
+ base::Unretained(this)));
+ }
+
+ // safe_browsing::LastDownloadFinder::LastDownloadCallback implementation that
+ // passes the found download to |result| and then runs a closure.
+ void OnLastDownload(
+ scoped_ptr<safe_browsing::ClientIncidentReport_DownloadDetails>* result,
+ const base::Closure& quit_closure,
+ scoped_ptr<safe_browsing::ClientIncidentReport_DownloadDetails>
+ download) {
+ *result = download.Pass();
+ quit_closure.Run();
+ }
+
+ protected:
+ // A type for specifying whether or not a profile created by CreateProfile
+ // participates in safe browsing.
+ enum SafeBrowsingDisposition {
+ SAFE_BROWSING_OPT_OUT,
+ SAFE_BROWSING_OPT_IN,
+ };
+
+ LastDownloadFinderTest() : profile_number_() {}
+
+ virtual void SetUp() OVERRIDE {
+ testing::Test::SetUp();
+ profile_manager_.reset(
+ new TestingProfileManager(TestingBrowserProcess::GetGlobal()));
+ ASSERT_TRUE(profile_manager_->SetUp());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ // Shut down the history service on all profiles.
+ std::vector<Profile*> profiles(
+ profile_manager_->profile_manager()->GetLoadedProfiles());
+ for (size_t i = 0; i < profiles.size(); ++i) {
+ profiles[0]->AsTestingProfile()->DestroyHistoryService();
+ }
+ profile_manager_.reset();
+ TestingBrowserProcess::DeleteInstance();
+ testing::Test::TearDown();
+ }
+
+ TestingProfile* CreateProfile(SafeBrowsingDisposition safe_browsing_opt_in) {
+ std::string profile_name("profile");
+ profile_name.append(base::IntToString(++profile_number_));
+
+ // Set up keyed service factories.
+ TestingProfile::TestingFactories factories;
+ // Build up a custom history service.
+ factories.push_back(std::make_pair(HistoryServiceFactory::GetInstance(),
+ &BuildHistoryService));
+ // Suppress WebHistoryService since it makes network requests.
+ factories.push_back(std::make_pair(
+ WebHistoryServiceFactory::GetInstance(),
+ static_cast<BrowserContextKeyedServiceFactory::TestingFactoryFunction>(
+ NULL)));
+
+ // Create prefs for the profile with safe browsing enabled or not.
+ scoped_ptr<TestingPrefServiceSyncable> prefs(
+ new TestingPrefServiceSyncable);
+ chrome::RegisterUserProfilePrefs(prefs->registry());
+ prefs->SetBoolean(prefs::kSafeBrowsingEnabled,
+ safe_browsing_opt_in == SAFE_BROWSING_OPT_IN);
+
+ TestingProfile* profile = profile_manager_->CreateTestingProfile(
+ profile_name,
+ prefs.PassAs<PrefServiceSyncable>(),
+ base::UTF8ToUTF16(profile_name), // user_name
+ 0, // avatar_id
+ std::string(), // supervised_user_id
+ factories);
+
+ return profile;
+ }
+
+ void AddDownload(Profile* profile, const history::DownloadRow& download) {
+ base::RunLoop run_loop;
+
+ HistoryService* history_service =
+ HistoryServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
+ history_service->CreateDownload(
+ download,
+ base::Bind(&LastDownloadFinderTest::ContinueOnDownloadCreated,
+ base::Unretained(this),
+ run_loop.QuitClosure()));
+ run_loop.Run();
+ }
+
+ // Wait for the history backend thread to process any outstanding tasks.
+ // This is needed because HistoryService::QueryDownloads uses PostTaskAndReply
+ // to do work on the backend thread and then invoke the caller's callback on
+ // the originating thread. The PostTaskAndReplyRelay holds a reference to the
+ // backend until its RunReplyAndSelfDestruct is called on the originating
+ // thread. This reference MUST be released (on the originating thread,
+ // remember) _before_ calling DestroyHistoryService in TearDown(). See the
+ // giant comment in HistoryService::Cleanup explaining where the backend's
+ // dtor must be run.
+ void FlushHistoryBackend(Profile* profile) {
+ base::RunLoop run_loop;
+ HistoryServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS)
+ ->FlushForTest(run_loop.QuitClosure());
+ run_loop.Run();
+ // Then make sure anything bounced back to the main thread has been handled.
+ base::RunLoop().RunUntilIdle();
+ }
+
+ // Runs the last download finder on all loaded profiles, returning the found
+ // download or an empty pointer if none was found.
+ scoped_ptr<safe_browsing::ClientIncidentReport_DownloadDetails>
+ RunLastDownloadFinder() {
+ base::RunLoop run_loop;
+
+ scoped_ptr<safe_browsing::ClientIncidentReport_DownloadDetails>
+ last_download;
+
+ scoped_ptr<safe_browsing::LastDownloadFinder> finder(
+ safe_browsing::LastDownloadFinder::Create(
+ base::Bind(&LastDownloadFinderTest::OnLastDownload,
+ base::Unretained(this),
+ &last_download,
+ run_loop.QuitClosure())));
+
+ if (finder)
+ run_loop.Run();
+
+ return last_download.Pass();
+ }
+
+ history::DownloadRow CreateTestDownloadRow() {
+ base::Time now(base::Time::Now());
+ return history::DownloadRow(
+ base::FilePath(FILE_PATH_LITERAL("spam.exe")),
+ base::FilePath(FILE_PATH_LITERAL("spam.exe")),
+ std::vector<GURL>(1, GURL("http://www.google.com")), // url_chain
+ GURL(), // referrer
+ "application/octet-stream", // mime_type
+ "application/octet-stream", // original_mime_type
+ now - base::TimeDelta::FromMinutes(10), // start
+ now - base::TimeDelta::FromMinutes(9), // end
+ std::string(), // etag
+ std::string(), // last_modified
+ 47LL, // received
+ 47LL, // total
+ content::DownloadItem::COMPLETE, // download_state
+ content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, // danger_type
+ content::DOWNLOAD_INTERRUPT_REASON_NONE, // interrupt_reason,
+ 1, // id
+ false, // download_opened
+ std::string(), // ext_id
+ std::string()); // ext_name
+ }
+
+ void ExpectNoDownloadFound(scoped_ptr<
+ safe_browsing::ClientIncidentReport_DownloadDetails> download) {
+ EXPECT_FALSE(download);
+ }
+
+ void ExpectFoundTestDownload(scoped_ptr<
+ safe_browsing::ClientIncidentReport_DownloadDetails> download) {
+ ASSERT_TRUE(download);
+ }
+
+ content::TestBrowserThreadBundle browser_thread_bundle_;
+ scoped_ptr<TestingProfileManager> profile_manager_;
+
+ private:
+ // A HistoryService::DownloadCreateCallback that asserts that the download was
+ // created and runs |closure|.
+ void ContinueOnDownloadCreated(const base::Closure& closure, bool created) {
+ ASSERT_TRUE(created);
+ closure.Run();
+ }
+
+ // A HistoryService::DownloadCreateCallback that asserts that the download was
+ // created.
+ void OnDownloadCreated(bool created) { ASSERT_TRUE(created); }
+
+ int profile_number_;
+};
+
+// Tests that nothing happens if there are no profiles at all.
+TEST_F(LastDownloadFinderTest, NoProfiles) {
+ ExpectNoDownloadFound(RunLastDownloadFinder());
+}
+
+// Tests that nothing happens other than the callback being invoked if there are
+// no profiles participating in safe browsing.
+TEST_F(LastDownloadFinderTest, NoParticipatingProfiles) {
+ // Create a profile with a history service that is opted-out
+ TestingProfile* profile = CreateProfile(SAFE_BROWSING_OPT_OUT);
+
+ // Add a download.
+ AddDownload(profile, CreateTestDownloadRow());
+
+ ExpectNoDownloadFound(RunLastDownloadFinder());
+}
+
+// Tests that a download is found from a single profile.
+TEST_F(LastDownloadFinderTest, SimpleEndToEnd) {
+ // Create a profile with a history service that is opted-in.
+ TestingProfile* profile = CreateProfile(SAFE_BROWSING_OPT_IN);
+
+ // Add a download.
+ AddDownload(profile, CreateTestDownloadRow());
+
+ ExpectFoundTestDownload(RunLastDownloadFinder());
+}
+
+// Tests that there is no crash if the finder is deleted before results arrive.
+TEST_F(LastDownloadFinderTest, DeleteBeforeResults) {
+ // Create a profile with a history service that is opted-in.
+ TestingProfile* profile = CreateProfile(SAFE_BROWSING_OPT_IN);
+
+ // Add a download.
+ AddDownload(profile, CreateTestDownloadRow());
+
+ // Start a finder and kill it before the search completes.
+ safe_browsing::LastDownloadFinder::Create(
+ base::Bind(&LastDownloadFinderTest::NeverCalled, base::Unretained(this)))
+ .reset();
+
+ // Flush tasks on the history backend thread.
+ FlushHistoryBackend(profile);
+}
+
+// Tests that a download in profile added after the search is begun is found.
+TEST_F(LastDownloadFinderTest, AddProfileAfterStarting) {
+ // Create a profile with a history service that is opted-in.
+ CreateProfile(SAFE_BROWSING_OPT_IN);
+
+ scoped_ptr<safe_browsing::ClientIncidentReport_DownloadDetails> last_download;
+ base::RunLoop run_loop;
+
+ // Post a task that will create a second profile once the main loop is run.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&LastDownloadFinderTest::CreateProfileWithDownload,
+ base::Unretained(this)));
+
+ // Create a finder that we expect will find a download in the second profile.
+ scoped_ptr<safe_browsing::LastDownloadFinder> finder(
+ safe_browsing::LastDownloadFinder::Create(
+ base::Bind(&LastDownloadFinderTest::OnLastDownload,
+ base::Unretained(this),
+ &last_download,
+ run_loop.QuitClosure())));
+
+ run_loop.Run();
+
+ ExpectFoundTestDownload(last_download.Pass());
+}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index f871ffb..dbf5571 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1875,6 +1875,8 @@
'browser/safe_browsing/incident_report_uploader_impl.h',
'browser/safe_browsing/incident_reporting_service.cc',
'browser/safe_browsing/incident_reporting_service.h',
+ 'browser/safe_browsing/last_download_finder.cc',
+ 'browser/safe_browsing/last_download_finder.h',
'browser/safe_browsing/malware_details.cc',
'browser/safe_browsing/malware_details.h',
'browser/safe_browsing/malware_details_cache.cc',
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi
index 4125a86..fb1aee2 100644
--- a/chrome/chrome_common.gypi
+++ b/chrome/chrome_common.gypi
@@ -287,6 +287,7 @@
'common_version',
'installer_util',
'metrics_proto',
+ 'safe_browsing_proto',
'<(DEPTH)/base/base.gyp:base',
'<(DEPTH)/base/base.gyp:base_i18n',
'<(DEPTH)/base/base.gyp:base_prefs',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 15dd4aa7..6752069 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -1251,6 +1251,7 @@
'browser/safe_browsing/download_protection_service_unittest.cc',
'browser/safe_browsing/incident_report_uploader_impl_unittest.cc',
'browser/safe_browsing/incident_reporting_service_unittest.cc',
+ 'browser/safe_browsing/last_download_finder_unittest.cc',
'browser/safe_browsing/local_two_phase_testserver.cc',
'browser/safe_browsing/malware_details_unittest.cc',
'browser/safe_browsing/pe_image_reader_win_unittest.cc',
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index e4560b0..6cb0b5e 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -43,6 +43,7 @@ static_library("common") {
"//chrome/common:constants",
"//chrome/common/metrics/proto",
"//chrome/common/net",
+ "//chrome/common/safe_browsing:proto",
#"//components/cloud_devices:common", TODO(GYP)
#"//components/json_schema", TODO(GYP)
"//components/metrics",
diff --git a/chrome/common/safe_browsing/download_protection_util.cc b/chrome/common/safe_browsing/download_protection_util.cc
index 88d5cab..ee09cd1 100644
--- a/chrome/common/safe_browsing/download_protection_util.cc
+++ b/chrome/common/safe_browsing/download_protection_util.cc
@@ -2,7 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "chrome/common/safe_browsing/download_protection_util.h"
+
#include "base/files/file_path.h"
+#include "base/logging.h"
namespace safe_browsing {
namespace download_protection_util {
@@ -34,5 +37,19 @@ bool IsBinaryFile(const base::FilePath& file) {
IsArchiveFile(file));
}
+ClientDownloadRequest::DownloadType GetDownloadType(
+ const base::FilePath& file) {
+ DCHECK(IsBinaryFile(file));
+ if (file.MatchesExtension(FILE_PATH_LITERAL(".apk")))
+ return ClientDownloadRequest::ANDROID_APK;
+ else if (file.MatchesExtension(FILE_PATH_LITERAL(".crx")))
+ return ClientDownloadRequest::CHROME_EXTENSION;
+ // For zip files, we use the ZIPPED_EXECUTABLE type since we will only send
+ // the pingback if we find an executable inside the zip archive.
+ else if (file.MatchesExtension(FILE_PATH_LITERAL(".zip")))
+ return ClientDownloadRequest::ZIPPED_EXECUTABLE;
+ return ClientDownloadRequest::WIN_EXECUTABLE;
+}
+
} // namespace download_protection_util
} // namespace safe_browsing
diff --git a/chrome/common/safe_browsing/download_protection_util.h b/chrome/common/safe_browsing/download_protection_util.h
index 8a0ff7c..a9eaf53 100644
--- a/chrome/common/safe_browsing/download_protection_util.h
+++ b/chrome/common/safe_browsing/download_protection_util.h
@@ -5,6 +5,8 @@
#ifndef CHROME_COMMON_SAFE_BROWSING_DOWNLOAD_PROTECTION_UTIL_H_
#define CHROME_COMMON_SAFE_BROWSING_DOWNLOAD_PROTECTION_UTIL_H_
+#include "chrome/common/safe_browsing/csd.pb.h"
+
namespace base {
class FilePath;
}
@@ -18,6 +20,10 @@ bool IsBinaryFile(const base::FilePath& file);
// Returns true if the given file is a supported archive file type.
bool IsArchiveFile(const base::FilePath& file);
+// Returns the DownloadType of the file at |path|. This function is only valid
+// for paths that satisfy IsBinaryFile() above.
+ClientDownloadRequest::DownloadType GetDownloadType(const base::FilePath& file);
+
} // namespace download_protection_util
} // namespace safe_browsing
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index ea2df26..f554f77 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -25081,6 +25081,14 @@ Therefore, the affected-histogram name has to have at least one dot in it.
</summary>
</histogram>
+<histogram name="SBIRS.FindDownloadedBinaryTime" units="milliseconds">
+ <owner>grt@google.com</owner>
+ <summary>
+ The elapsed time to find the most recent binary download from all loaded
+ profiles when creating a safe browsing incident report.
+ </summary>
+</histogram>
+
<histogram name="SBIRS.Incident" enum="IncidentType">
<owner>grt@google.com</owner>
<summary>
@@ -25122,11 +25130,11 @@ Therefore, the affected-histogram name has to have at least one dot in it.
<summary>The elapsed time to upload a safe browsing incident report.</summary>
</histogram>
-<histogram name="SBIRS.UploadResult" enum="UploadResult">
+<histogram name="SBIRS.UploadResult" enum="ReportProcessingResult">
<owner>grt@google.com</owner>
<summary>
- The result of a report upload by the safe browsing incident reporting
- service.
+ The result of an attempted report upload by the safe browsing incident
+ reporting service.
</summary>
</histogram>
@@ -43316,6 +43324,26 @@ Therefore, the affected-histogram name has to have at least one dot in it.
<int value="58" label="IDC_SPELLCHECK_SUGGESTION"/>
</enum>
+<enum name="ReportProcessingResult" type="int">
+ <int value="0" label="Success">A report was created and uploaded</int>
+ <int value="1" label="Suppressed">
+ A report was not uploaded because the CSD Whitelist killswitch was present
+ </int>
+ <int value="2" label="InvalidRequest">
+ A report was not uploaded because it could not be serialized
+ </int>
+ <int value="3" label="Cancelled">
+ A report upload was cancelled due to service shutdown
+ </int>
+ <int value="4" label="RequestFailed">A report upload failed</int>
+ <int value="5" label="InvalidResponse">
+ The response from a report upload was invalid
+ </int>
+ <int value="6" label="NoDownload">
+ A report was not uploaded because no binary download was found to report
+ </int>
+</enum>
+
<enum name="ResolutionCategory" type="int">
<int value="0" label="RESOLVE_SUCCESS"/>
<int value="1" label="RESOLVE_FAIL"/>
@@ -45489,15 +45517,6 @@ Therefore, the affected-histogram name has to have at least one dot in it.
<int value="3" label="AUTO_UPDATES_ONLY"/>
</enum>
-<enum name="UploadResult" type="int">
- <int value="0" label="Success"/>
- <int value="1" label="Suppressed">CSD Whitelist killswitch present</int>
- <int value="2" label="InvalidRequest"/>
- <int value="3" label="Cancelled"/>
- <int value="4" label="RequestFailed"/>
- <int value="5" label="InvalidResponse"/>
-</enum>
-
<enum name="UrlResolutionResult" type="int">
<int value="0" label="Absolute URL"/>
<int value="1" label="Resolutions Differ"/>