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/last_download_finder.h"
11 #include "base/bind.h"
12 #include "base/macros.h"
13 #include "base/prefs/pref_service.h"
14 #include "chrome/browser/browser_process.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/history/history_service_factory.h"
17 #include "chrome/browser/profiles/profile_manager.h"
18 #include "chrome/common/pref_names.h"
19 #include "chrome/common/safe_browsing/csd.pb.h"
20 #include "chrome/common/safe_browsing/download_protection_util.h"
21 #include "components/history/core/browser/download_constants.h"
22 #include "components/history/core/browser/history_service.h"
23 #include "content/public/browser/notification_details.h"
24 #include "content/public/browser/notification_service.h"
25 #include "content/public/browser/notification_source.h"
27 namespace safe_browsing
{
31 // The following functions are overloaded for the two object types that
32 // represent the metadata for a download: history::DownloadRow and
33 // ClientIncidentReport_DownloadDetails. These are used by the template
34 // functions that follow.
36 // Returns the end time of a download represented by a DownloadRow.
37 int64
GetEndTime(const history::DownloadRow
& row
) {
38 return row
.end_time
.ToJavaTime();
41 // Returns the end time of a download represented by a DownloadDetails.
42 int64
GetEndTime(const ClientIncidentReport_DownloadDetails
& details
) {
43 return details
.download_time_msec();
46 bool IsBinaryDownloadForCurrentOS(
47 ClientDownloadRequest::DownloadType download_type
) {
48 static_assert(ClientDownloadRequest::DownloadType_MAX
==
49 ClientDownloadRequest::ARCHIVE
,
50 "Update logic below");
52 // Platform-specific types are relevant only for their own platforms.
53 #if defined(OS_MACOSX)
54 if (download_type
== ClientDownloadRequest::MAC_EXECUTABLE
)
56 #elif defined(OS_ANDROID)
57 if (download_type
== ClientDownloadRequest::ANDROID_APK
)
61 // Extensions are supported where enabled.
62 #if defined(ENABLE_EXTENSIONS)
63 if (download_type
== ClientDownloadRequest::CHROME_EXTENSION
)
67 if (download_type
== ClientDownloadRequest::ZIPPED_EXECUTABLE
||
68 download_type
== ClientDownloadRequest::ZIPPED_ARCHIVE
||
69 download_type
== ClientDownloadRequest::ARCHIVE
) {
73 // The default return value of download_protection_util::GetDownloadType is
74 // ClientDownloadRequest::WIN_EXECUTABLE.
75 return download_type
== ClientDownloadRequest::WIN_EXECUTABLE
;
78 // Returns true if a download represented by a DownloadRow is binary file for
80 bool IsBinaryDownload(const history::DownloadRow
& row
) {
81 // TODO(grt): Peek into archives to see if they contain binaries;
82 // http://crbug.com/386915.
83 return (download_protection_util::IsSupportedBinaryFile(row
.target_path
) &&
84 !download_protection_util::IsArchiveFile(row
.target_path
) &&
85 IsBinaryDownloadForCurrentOS(
86 download_protection_util::GetDownloadType(row
.target_path
)));
89 // Returns true if a download represented by a DownloadDetails is binary file
90 // for the current OS.
91 bool IsBinaryDownload(const ClientIncidentReport_DownloadDetails
& details
) {
92 // DownloadDetails are only generated for binary downloads.
93 return IsBinaryDownloadForCurrentOS(details
.download().download_type());
96 // Returns true if a download represented by a DownloadRow has been opened.
97 bool HasBeenOpened(const history::DownloadRow
& row
) {
101 // Returns true if a download represented by a DownloadDetails has been opened.
102 bool HasBeenOpened(const ClientIncidentReport_DownloadDetails
& details
) {
103 return details
.has_open_time_msec() && details
.open_time_msec();
106 // Returns true if |first| is more recent than |second|, preferring opened over
107 // non-opened for downloads that completed at the same time (extraordinarily
108 // unlikely). Only files that look like some kind of executable are considered.
109 template <class A
, class B
>
110 bool IsMoreInterestingThan(const A
& first
, const B
& second
) {
111 if (GetEndTime(first
) < GetEndTime(second
) || !IsBinaryDownload(first
))
113 return (GetEndTime(first
) != GetEndTime(second
) ||
114 (HasBeenOpened(first
) && !HasBeenOpened(second
)));
117 // Returns a pointer to the most interesting completed download in |downloads|.
118 const history::DownloadRow
* FindMostInteresting(
119 const std::vector
<history::DownloadRow
>& downloads
) {
120 const history::DownloadRow
* most_recent_row
= NULL
;
121 for (size_t i
= 0; i
< downloads
.size(); ++i
) {
122 const history::DownloadRow
& row
= downloads
[i
];
123 // Ignore incomplete downloads.
124 if (row
.state
!= history::DownloadState::COMPLETE
)
126 if (!most_recent_row
|| IsMoreInterestingThan(row
, *most_recent_row
))
127 most_recent_row
= &row
;
129 return most_recent_row
;
132 // Returns true if |candidate| is more interesting than whichever of |details|
133 // or |best_row| is present.
135 bool IsMostInteresting(const D
& candidate
,
136 const ClientIncidentReport_DownloadDetails
* details
,
137 const history::DownloadRow
& best_row
) {
139 IsMoreInterestingThan(candidate
, *details
) :
140 IsMoreInterestingThan(candidate
, best_row
);
143 // Populates the |details| protobuf with information pertaining to |download|.
144 void PopulateDetailsFromRow(const history::DownloadRow
& download
,
145 ClientIncidentReport_DownloadDetails
* details
) {
146 ClientDownloadRequest
* download_request
= details
->mutable_download();
147 download_request
->set_url(download
.url_chain
.back().spec());
148 // digests is a required field, so force it to exist.
149 // TODO(grt): Include digests in reports; http://crbug.com/389123.
150 ignore_result(download_request
->mutable_digests());
151 download_request
->set_length(download
.received_bytes
);
152 for (size_t i
= 0; i
< download
.url_chain
.size(); ++i
) {
153 const GURL
& url
= download
.url_chain
[i
];
154 ClientDownloadRequest_Resource
* resource
=
155 download_request
->add_resources();
156 resource
->set_url(url
.spec());
157 if (i
!= download
.url_chain
.size() - 1) { // An intermediate redirect.
158 resource
->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT
);
159 } else { // The final download URL.
160 resource
->set_type(ClientDownloadRequest::DOWNLOAD_URL
);
161 if (!download
.referrer_url
.is_empty())
162 resource
->set_referrer(download
.referrer_url
.spec());
165 download_request
->set_file_basename(
166 download
.target_path
.BaseName().AsUTF8Unsafe());
167 download_request
->set_download_type(
168 download_protection_util::GetDownloadType(download
.target_path
));
169 download_request
->set_locale(
170 g_browser_process
->local_state()->GetString(prefs::kApplicationLocale
));
172 details
->set_download_time_msec(download
.end_time
.ToJavaTime());
173 // Opened time is unknown for now, so use the download time if the file was
176 details
->set_open_time_msec(download
.end_time
.ToJavaTime());
181 LastDownloadFinder::~LastDownloadFinder() {
185 scoped_ptr
<LastDownloadFinder
> LastDownloadFinder::Create(
186 const DownloadDetailsGetter
& download_details_getter
,
187 const LastDownloadCallback
& callback
) {
188 scoped_ptr
<LastDownloadFinder
> finder(make_scoped_ptr(new LastDownloadFinder(
189 download_details_getter
,
190 g_browser_process
->profile_manager()->GetLoadedProfiles(),
192 // Return NULL if there is no work to do.
193 if (finder
->profile_states_
.empty())
194 return scoped_ptr
<LastDownloadFinder
>();
195 return finder
.Pass();
198 LastDownloadFinder::LastDownloadFinder()
199 : history_service_observer_(this), weak_ptr_factory_(this) {
202 LastDownloadFinder::LastDownloadFinder(
203 const DownloadDetailsGetter
& download_details_getter
,
204 const std::vector
<Profile
*>& profiles
,
205 const LastDownloadCallback
& callback
)
206 : download_details_getter_(download_details_getter
),
208 history_service_observer_(this),
209 weak_ptr_factory_(this) {
210 // Observe profile lifecycle events so that the finder can begin or abandon
211 // the search in profiles while it is running.
212 notification_registrar_
.Add(this,
213 chrome::NOTIFICATION_PROFILE_ADDED
,
214 content::NotificationService::AllSources());
215 notification_registrar_
.Add(this,
216 chrome::NOTIFICATION_PROFILE_DESTROYED
,
217 content::NotificationService::AllSources());
219 // Begin the seach for all given profiles.
223 std::bind1st(std::mem_fun(&LastDownloadFinder::SearchInProfile
), this));
226 void LastDownloadFinder::SearchInProfile(Profile
* profile
) {
227 // Do not look in OTR profiles or in profiles that do not participate in
229 if (profile
->IsOffTheRecord() ||
230 !profile
->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled
)) {
234 // Exit early if already processing this profile. This could happen if, for
235 // example, NOTIFICATION_PROFILE_ADDED arrives after construction while
236 // waiting for OnHistoryServiceLoaded.
237 if (profile_states_
.count(profile
))
240 // Initiate a metadata search.
241 profile_states_
[profile
] = WAITING_FOR_METADATA
;
242 download_details_getter_
.Run(profile
,
243 base::Bind(&LastDownloadFinder::OnMetadataQuery
,
244 weak_ptr_factory_
.GetWeakPtr(),
248 void LastDownloadFinder::OnMetadataQuery(
250 scoped_ptr
<ClientIncidentReport_DownloadDetails
> details
) {
251 auto iter
= profile_states_
.find(profile
);
252 // Early-exit if the search for this profile was abandoned.
253 if (iter
== profile_states_
.end())
257 if (IsMostInteresting(*details
, details_
.get(), most_recent_row_
)) {
258 details_
= details
.Pass();
259 most_recent_row_
.end_time
= base::Time();
262 RemoveProfileAndReportIfDone(iter
);
264 // Search history since no metadata was found.
265 iter
->second
= WAITING_FOR_HISTORY
;
266 history::HistoryService
* history_service
=
267 HistoryServiceFactory::GetForProfile(
268 profile
, ServiceAccessType::IMPLICIT_ACCESS
);
269 // No history service is returned for profiles that do not save history.
270 if (!history_service
) {
271 RemoveProfileAndReportIfDone(iter
);
274 if (history_service
->BackendLoaded()) {
275 history_service
->QueryDownloads(
276 base::Bind(&LastDownloadFinder::OnDownloadQuery
,
277 weak_ptr_factory_
.GetWeakPtr(),
280 // else wait until history is loaded.
281 history_service_observer_
.Add(history_service
);
286 void LastDownloadFinder::AbandonSearchInProfile(Profile
* profile
) {
287 // |profile| may not be present in the set of profiles.
288 auto iter
= profile_states_
.find(profile
);
289 if (iter
!= profile_states_
.end())
290 RemoveProfileAndReportIfDone(iter
);
293 void LastDownloadFinder::OnDownloadQuery(
295 scoped_ptr
<std::vector
<history::DownloadRow
> > downloads
) {
296 // Early-exit if the history search for this profile was abandoned.
297 auto iter
= profile_states_
.find(profile
);
298 if (iter
== profile_states_
.end())
301 // Find the most recent from this profile and use it if it's better than
302 // anything else found so far.
303 const history::DownloadRow
* profile_best
= FindMostInteresting(*downloads
);
305 IsMostInteresting(*profile_best
, details_
.get(), most_recent_row_
)) {
307 most_recent_row_
= *profile_best
;
310 RemoveProfileAndReportIfDone(iter
);
313 void LastDownloadFinder::RemoveProfileAndReportIfDone(
314 std::map
<Profile
*, ProfileWaitState
>::iterator iter
) {
315 DCHECK(iter
!= profile_states_
.end());
316 profile_states_
.erase(iter
);
318 // Finish processing if all results are in.
319 if (profile_states_
.empty())
321 // Do not touch this LastDownloadFinder after reporting results.
324 void LastDownloadFinder::ReportResults() {
325 DCHECK(profile_states_
.empty());
327 callback_
.Run(make_scoped_ptr(new ClientIncidentReport_DownloadDetails(
329 // Do not touch this LastDownloadFinder after running the callback, since it
330 // may have been deleted.
331 } else if (!most_recent_row_
.end_time
.is_null()) {
332 scoped_ptr
<ClientIncidentReport_DownloadDetails
> details(
333 new ClientIncidentReport_DownloadDetails());
334 PopulateDetailsFromRow(most_recent_row_
, details
.get());
335 callback_
.Run(details
.Pass());
336 // Do not touch this LastDownloadFinder after running the callback, since it
337 // may have been deleted.
339 callback_
.Run(scoped_ptr
<ClientIncidentReport_DownloadDetails
>());
340 // Do not touch this LastDownloadFinder after running the callback, since it
341 // may have been deleted.
345 void LastDownloadFinder::Observe(int type
,
346 const content::NotificationSource
& source
,
347 const content::NotificationDetails
& details
) {
349 case chrome::NOTIFICATION_PROFILE_ADDED
:
350 SearchInProfile(content::Source
<Profile
>(source
).ptr());
352 case chrome::NOTIFICATION_PROFILE_DESTROYED
:
353 AbandonSearchInProfile(content::Source
<Profile
>(source
).ptr());
360 void LastDownloadFinder::OnHistoryServiceLoaded(
361 history::HistoryService
* history_service
) {
362 for (const auto& pair
: profile_states_
) {
363 history::HistoryService
* hs
= HistoryServiceFactory::GetForProfileIfExists(
364 pair
.first
, ServiceAccessType::EXPLICIT_ACCESS
);
365 if (hs
== history_service
) {
366 // Start the query in the history service if the finder was waiting for
367 // the service to load.
368 if (pair
.second
== WAITING_FOR_HISTORY
) {
369 history_service
->QueryDownloads(
370 base::Bind(&LastDownloadFinder::OnDownloadQuery
,
371 weak_ptr_factory_
.GetWeakPtr(),
379 void LastDownloadFinder::HistoryServiceBeingDeleted(
380 history::HistoryService
* history_service
) {
381 history_service_observer_
.Remove(history_service
);
384 } // namespace safe_browsing