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 // Returns true if a download represented by a DownloadRow is binary file.
47 bool IsBinaryDownload(const history::DownloadRow
& row
) {
48 // TODO(grt): Peek into archives to see if they contain binaries;
49 // http://crbug.com/386915.
50 return (download_protection_util::IsBinaryFile(row
.target_path
) &&
51 !download_protection_util::IsArchiveFile(row
.target_path
));
54 // Returns true if a download represented by a DownloadDetails is binary file.
55 bool IsBinaryDownload(const ClientIncidentReport_DownloadDetails
& details
) {
56 // DownloadDetails are only generated for binary downloads.
60 // Returns true if a download represented by a DownloadRow has been opened.
61 bool HasBeenOpened(const history::DownloadRow
& row
) {
65 // Returns true if a download represented by a DownloadDetails has been opened.
66 bool HasBeenOpened(const ClientIncidentReport_DownloadDetails
& details
) {
67 return details
.has_open_time_msec() && details
.open_time_msec();
70 // Returns true if |first| is more recent than |second|, preferring opened over
71 // non-opened for downloads that completed at the same time (extraordinarily
72 // unlikely). Only files that look like some kind of executable are considered.
73 template <class A
, class B
>
74 bool IsMoreInterestingThan(const A
& first
, const B
& second
) {
75 if (GetEndTime(first
) < GetEndTime(second
) || !IsBinaryDownload(first
))
77 return (GetEndTime(first
) != GetEndTime(second
) ||
78 (HasBeenOpened(first
) && !HasBeenOpened(second
)));
81 // Returns a pointer to the most interesting completed download in |downloads|.
82 const history::DownloadRow
* FindMostInteresting(
83 const std::vector
<history::DownloadRow
>& downloads
) {
84 const history::DownloadRow
* most_recent_row
= NULL
;
85 for (size_t i
= 0; i
< downloads
.size(); ++i
) {
86 const history::DownloadRow
& row
= downloads
[i
];
87 // Ignore incomplete downloads.
88 if (row
.state
!= history::DownloadState::COMPLETE
)
90 if (!most_recent_row
|| IsMoreInterestingThan(row
, *most_recent_row
))
91 most_recent_row
= &row
;
93 return most_recent_row
;
96 // Returns true if |candidate| is more interesting than whichever of |details|
97 // or |best_row| is present.
99 bool IsMostInteresting(const D
& candidate
,
100 const ClientIncidentReport_DownloadDetails
* details
,
101 const history::DownloadRow
& best_row
) {
103 IsMoreInterestingThan(candidate
, *details
) :
104 IsMoreInterestingThan(candidate
, best_row
);
107 // Populates the |details| protobuf with information pertaining to |download|.
108 void PopulateDetailsFromRow(const history::DownloadRow
& download
,
109 ClientIncidentReport_DownloadDetails
* details
) {
110 ClientDownloadRequest
* download_request
= details
->mutable_download();
111 download_request
->set_url(download
.url_chain
.back().spec());
112 // digests is a required field, so force it to exist.
113 // TODO(grt): Include digests in reports; http://crbug.com/389123.
114 ignore_result(download_request
->mutable_digests());
115 download_request
->set_length(download
.received_bytes
);
116 for (size_t i
= 0; i
< download
.url_chain
.size(); ++i
) {
117 const GURL
& url
= download
.url_chain
[i
];
118 ClientDownloadRequest_Resource
* resource
=
119 download_request
->add_resources();
120 resource
->set_url(url
.spec());
121 if (i
!= download
.url_chain
.size() - 1) { // An intermediate redirect.
122 resource
->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT
);
123 } else { // The final download URL.
124 resource
->set_type(ClientDownloadRequest::DOWNLOAD_URL
);
125 if (!download
.referrer_url
.is_empty())
126 resource
->set_referrer(download
.referrer_url
.spec());
129 download_request
->set_file_basename(
130 download
.target_path
.BaseName().AsUTF8Unsafe());
131 download_request
->set_download_type(
132 download_protection_util::GetDownloadType(download
.target_path
));
133 download_request
->set_locale(
134 g_browser_process
->local_state()->GetString(prefs::kApplicationLocale
));
136 details
->set_download_time_msec(download
.end_time
.ToJavaTime());
137 // Opened time is unknown for now, so use the download time if the file was
140 details
->set_open_time_msec(download
.end_time
.ToJavaTime());
145 LastDownloadFinder::~LastDownloadFinder() {
149 scoped_ptr
<LastDownloadFinder
> LastDownloadFinder::Create(
150 const DownloadDetailsGetter
& download_details_getter
,
151 const LastDownloadCallback
& callback
) {
152 scoped_ptr
<LastDownloadFinder
> finder(make_scoped_ptr(new LastDownloadFinder(
153 download_details_getter
,
154 g_browser_process
->profile_manager()->GetLoadedProfiles(),
156 // Return NULL if there is no work to do.
157 if (finder
->profile_states_
.empty())
158 return scoped_ptr
<LastDownloadFinder
>();
159 return finder
.Pass();
162 LastDownloadFinder::LastDownloadFinder()
163 : history_service_observer_(this), weak_ptr_factory_(this) {
166 LastDownloadFinder::LastDownloadFinder(
167 const DownloadDetailsGetter
& download_details_getter
,
168 const std::vector
<Profile
*>& profiles
,
169 const LastDownloadCallback
& callback
)
170 : download_details_getter_(download_details_getter
),
172 history_service_observer_(this),
173 weak_ptr_factory_(this) {
174 // Observe profile lifecycle events so that the finder can begin or abandon
175 // the search in profiles while it is running.
176 notification_registrar_
.Add(this,
177 chrome::NOTIFICATION_PROFILE_ADDED
,
178 content::NotificationService::AllSources());
179 notification_registrar_
.Add(this,
180 chrome::NOTIFICATION_PROFILE_DESTROYED
,
181 content::NotificationService::AllSources());
183 // Begin the seach for all given profiles.
187 std::bind1st(std::mem_fun(&LastDownloadFinder::SearchInProfile
), this));
190 void LastDownloadFinder::SearchInProfile(Profile
* profile
) {
191 // Do not look in OTR profiles or in profiles that do not participate in
193 if (profile
->IsOffTheRecord() ||
194 !profile
->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled
)) {
198 // Exit early if already processing this profile. This could happen if, for
199 // example, NOTIFICATION_PROFILE_ADDED arrives after construction while
200 // waiting for OnHistoryServiceLoaded.
201 if (profile_states_
.count(profile
))
204 // Initiate a metadata search.
205 profile_states_
[profile
] = WAITING_FOR_METADATA
;
206 download_details_getter_
.Run(profile
,
207 base::Bind(&LastDownloadFinder::OnMetadataQuery
,
208 weak_ptr_factory_
.GetWeakPtr(),
212 void LastDownloadFinder::OnMetadataQuery(
214 scoped_ptr
<ClientIncidentReport_DownloadDetails
> details
) {
215 auto iter
= profile_states_
.find(profile
);
216 // Early-exit if the search for this profile was abandoned.
217 if (iter
== profile_states_
.end())
221 if (IsMostInteresting(*details
, details_
.get(), most_recent_row_
)) {
222 details_
= details
.Pass();
223 most_recent_row_
.end_time
= base::Time();
226 RemoveProfileAndReportIfDone(iter
);
228 // Search history since no metadata was found.
229 iter
->second
= WAITING_FOR_HISTORY
;
230 history::HistoryService
* history_service
=
231 HistoryServiceFactory::GetForProfile(
232 profile
, ServiceAccessType::IMPLICIT_ACCESS
);
233 // No history service is returned for profiles that do not save history.
234 if (!history_service
) {
235 RemoveProfileAndReportIfDone(iter
);
238 if (history_service
->BackendLoaded()) {
239 history_service
->QueryDownloads(
240 base::Bind(&LastDownloadFinder::OnDownloadQuery
,
241 weak_ptr_factory_
.GetWeakPtr(),
244 // else wait until history is loaded.
245 history_service_observer_
.Add(history_service
);
250 void LastDownloadFinder::AbandonSearchInProfile(Profile
* profile
) {
251 // |profile| may not be present in the set of profiles.
252 auto iter
= profile_states_
.find(profile
);
253 if (iter
!= profile_states_
.end())
254 RemoveProfileAndReportIfDone(iter
);
257 void LastDownloadFinder::OnDownloadQuery(
259 scoped_ptr
<std::vector
<history::DownloadRow
> > downloads
) {
260 // Early-exit if the history search for this profile was abandoned.
261 auto iter
= profile_states_
.find(profile
);
262 if (iter
== profile_states_
.end())
265 // Find the most recent from this profile and use it if it's better than
266 // anything else found so far.
267 const history::DownloadRow
* profile_best
= FindMostInteresting(*downloads
);
269 IsMostInteresting(*profile_best
, details_
.get(), most_recent_row_
)) {
271 most_recent_row_
= *profile_best
;
274 RemoveProfileAndReportIfDone(iter
);
277 void LastDownloadFinder::RemoveProfileAndReportIfDone(
278 std::map
<Profile
*, ProfileWaitState
>::iterator iter
) {
279 DCHECK(iter
!= profile_states_
.end());
280 profile_states_
.erase(iter
);
282 // Finish processing if all results are in.
283 if (profile_states_
.empty())
285 // Do not touch this LastDownloadFinder after reporting results.
288 void LastDownloadFinder::ReportResults() {
289 DCHECK(profile_states_
.empty());
291 callback_
.Run(make_scoped_ptr(new ClientIncidentReport_DownloadDetails(
293 // Do not touch this LastDownloadFinder after running the callback, since it
294 // may have been deleted.
295 } else if (!most_recent_row_
.end_time
.is_null()) {
296 scoped_ptr
<ClientIncidentReport_DownloadDetails
> details(
297 new ClientIncidentReport_DownloadDetails());
298 PopulateDetailsFromRow(most_recent_row_
, details
.get());
299 callback_
.Run(details
.Pass());
300 // Do not touch this LastDownloadFinder after running the callback, since it
301 // may have been deleted.
303 callback_
.Run(scoped_ptr
<ClientIncidentReport_DownloadDetails
>());
304 // Do not touch this LastDownloadFinder after running the callback, since it
305 // may have been deleted.
309 void LastDownloadFinder::Observe(int type
,
310 const content::NotificationSource
& source
,
311 const content::NotificationDetails
& details
) {
313 case chrome::NOTIFICATION_PROFILE_ADDED
:
314 SearchInProfile(content::Source
<Profile
>(source
).ptr());
316 case chrome::NOTIFICATION_PROFILE_DESTROYED
:
317 AbandonSearchInProfile(content::Source
<Profile
>(source
).ptr());
324 void LastDownloadFinder::OnHistoryServiceLoaded(
325 history::HistoryService
* history_service
) {
326 for (const auto& pair
: profile_states_
) {
327 history::HistoryService
* hs
= HistoryServiceFactory::GetForProfileIfExists(
328 pair
.first
, ServiceAccessType::EXPLICIT_ACCESS
);
329 if (hs
== history_service
) {
330 // Start the query in the history service if the finder was waiting for
331 // the service to load.
332 if (pair
.second
== WAITING_FOR_HISTORY
) {
333 history_service
->QueryDownloads(
334 base::Bind(&LastDownloadFinder::OnDownloadQuery
,
335 weak_ptr_factory_
.GetWeakPtr(),
343 void LastDownloadFinder::HistoryServiceBeingDeleted(
344 history::HistoryService
* history_service
) {
345 history_service_observer_
.Remove(history_service
);
348 } // namespace safe_browsing