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.h"
17 #include "chrome/browser/history/history_service_factory.h"
18 #include "chrome/browser/profiles/profile_manager.h"
19 #include "chrome/common/pref_names.h"
20 #include "chrome/common/safe_browsing/csd.pb.h"
21 #include "chrome/common/safe_browsing/download_protection_util.h"
22 #include "content/public/browser/download_item.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 // Returns true if |first| is more recent than |second|, preferring opened over
32 // non-opened for downloads that completed at the same time (extraordinarily
33 // unlikely). Only files that look like some kind of executable are considered.
34 bool IsMoreInterestingThan(const history::DownloadRow
& first
,
35 const history::DownloadRow
& second
) {
36 if (first
.end_time
< second
.end_time
)
38 // TODO(grt): Peek into archives to see if they contain binaries;
39 // http://crbug.com/386915.
40 if (!download_protection_util::IsBinaryFile(first
.target_path
) ||
41 download_protection_util::IsArchiveFile(first
.target_path
)) {
44 return (first
.end_time
!= second
.end_time
||
45 (first
.opened
&& !second
.opened
));
48 // Returns a pointer to the most interesting completed download in |downloads|.
49 const history::DownloadRow
* FindMostInteresting(
50 const std::vector
<history::DownloadRow
>& downloads
) {
51 const history::DownloadRow
* most_recent_row
= NULL
;
52 for (size_t i
= 0; i
< downloads
.size(); ++i
) {
53 const history::DownloadRow
& row
= downloads
[i
];
54 // Ignore incomplete downloads.
55 if (row
.state
!= content::DownloadItem::COMPLETE
)
57 if (!most_recent_row
|| IsMoreInterestingThan(row
, *most_recent_row
))
58 most_recent_row
= &row
;
60 return most_recent_row
;
63 // Populates the |details| protobuf with information pertaining to |download|.
64 void PopulateDetails(const history::DownloadRow
& download
,
65 ClientIncidentReport_DownloadDetails
* details
) {
66 ClientDownloadRequest
* download_request
= details
->mutable_download();
67 download_request
->set_url(download
.url_chain
.back().spec());
68 // digests is a required field, so force it to exist.
69 // TODO(grt): Include digests in reports; http://crbug.com/389123.
70 ignore_result(download_request
->mutable_digests());
71 download_request
->set_length(download
.received_bytes
);
72 for (size_t i
= 0; i
< download
.url_chain
.size(); ++i
) {
73 const GURL
& url
= download
.url_chain
[i
];
74 ClientDownloadRequest_Resource
* resource
=
75 download_request
->add_resources();
76 resource
->set_url(url
.spec());
77 if (i
!= download
.url_chain
.size() - 1) { // An intermediate redirect.
78 resource
->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT
);
79 } else { // The final download URL.
80 resource
->set_type(ClientDownloadRequest::DOWNLOAD_URL
);
81 if (!download
.referrer_url
.is_empty())
82 resource
->set_referrer(download
.referrer_url
.spec());
85 download_request
->set_file_basename(
86 download
.target_path
.BaseName().AsUTF8Unsafe());
87 download_request
->set_download_type(
88 download_protection_util::GetDownloadType(download
.target_path
));
89 download_request
->set_locale(
90 g_browser_process
->local_state()->GetString(prefs::kApplicationLocale
));
92 details
->set_download_time_msec(download
.end_time
.ToJavaTime());
93 // Opened time is unknown for now, so use the download time if the file was
96 details
->set_open_time_msec(download
.end_time
.ToJavaTime());
101 LastDownloadFinder::~LastDownloadFinder() {
105 scoped_ptr
<LastDownloadFinder
> LastDownloadFinder::Create(
106 const LastDownloadCallback
& callback
) {
107 scoped_ptr
<LastDownloadFinder
> finder(make_scoped_ptr(new LastDownloadFinder(
108 g_browser_process
->profile_manager()->GetLoadedProfiles(), callback
)));
109 // Return NULL if there is no work to do.
110 if (finder
->profiles_
.empty())
111 return scoped_ptr
<LastDownloadFinder
>();
112 return finder
.Pass();
115 LastDownloadFinder::LastDownloadFinder() : weak_ptr_factory_(this) {
118 LastDownloadFinder::LastDownloadFinder(const std::vector
<Profile
*>& profiles
,
119 const LastDownloadCallback
& callback
)
120 : callback_(callback
), weak_ptr_factory_(this) {
121 // Observe profile lifecycle events so that the finder can begin or abandon
122 // the search in profiles while it is running.
123 notification_registrar_
.Add(this,
124 chrome::NOTIFICATION_PROFILE_ADDED
,
125 content::NotificationService::AllSources());
126 notification_registrar_
.Add(this,
127 chrome::NOTIFICATION_HISTORY_LOADED
,
128 content::NotificationService::AllSources());
129 notification_registrar_
.Add(this,
130 chrome::NOTIFICATION_PROFILE_DESTROYED
,
131 content::NotificationService::AllSources());
133 // Begin the seach for all given profiles.
137 std::bind1st(std::mem_fun(&LastDownloadFinder::SearchInProfile
), this));
140 void LastDownloadFinder::SearchInProfile(Profile
* profile
) {
141 // Do not look in OTR profiles or in profiles that do not participate in
143 if (profile
->IsOffTheRecord() ||
144 !profile
->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled
)) {
148 // Exit early if already processing this profile. This could happen if, for
149 // example, NOTIFICATION_PROFILE_ADDED arrives after construction while
150 // waiting for NOTIFICATION_HISTORY_LOADED.
151 if (std::find(profiles_
.begin(), profiles_
.end(), profile
) !=
156 HistoryService
* history_service
=
157 HistoryServiceFactory::GetForProfile(profile
, Profile::IMPLICIT_ACCESS
);
158 // No history service is returned for profiles that do not save history.
159 if (!history_service
)
162 profiles_
.push_back(profile
);
163 if (history_service
->BackendLoaded()) {
164 history_service
->QueryDownloads(
165 base::Bind(&LastDownloadFinder::OnDownloadQuery
,
166 weak_ptr_factory_
.GetWeakPtr(),
168 } // else wait until history is loaded.
171 void LastDownloadFinder::OnProfileHistoryLoaded(
173 HistoryService
* history_service
) {
174 if (std::find(profiles_
.begin(), profiles_
.end(), profile
) !=
176 history_service
->QueryDownloads(
177 base::Bind(&LastDownloadFinder::OnDownloadQuery
,
178 weak_ptr_factory_
.GetWeakPtr(),
183 void LastDownloadFinder::AbandonSearchInProfile(Profile
* profile
) {
184 // |profile| may not be present in the set of profiles.
185 std::vector
<Profile
*>::iterator it
=
186 std::find(profiles_
.begin(), profiles_
.end(), profile
);
187 if (it
!= profiles_
.end())
188 RemoveProfileAndReportIfDone(it
);
191 void LastDownloadFinder::OnDownloadQuery(
193 scoped_ptr
<std::vector
<history::DownloadRow
> > downloads
) {
194 // Early-exit if the history search for this profile was abandoned.
195 std::vector
<Profile
*>::iterator it
=
196 std::find(profiles_
.begin(), profiles_
.end(), profile
);
197 if (it
== profiles_
.end())
200 // Find the most recent from this profile and use it if it's better than
201 // anything else found so far.
202 const history::DownloadRow
* profile_best
= FindMostInteresting(*downloads
);
203 if (profile_best
&& IsMoreInterestingThan(*profile_best
, most_recent_row_
))
204 most_recent_row_
= *profile_best
;
206 RemoveProfileAndReportIfDone(it
);
209 void LastDownloadFinder::RemoveProfileAndReportIfDone(
210 std::vector
<Profile
*>::iterator it
) {
211 DCHECK(it
!= profiles_
.end());
213 *it
= profiles_
.back();
214 profiles_
.resize(profiles_
.size() - 1);
216 // Finish processing if all results are in.
217 if (profiles_
.empty())
219 // Do not touch this instance after reporting results.
222 void LastDownloadFinder::ReportResults() {
223 DCHECK(profiles_
.empty());
224 if (most_recent_row_
.end_time
.is_null()) {
225 callback_
.Run(scoped_ptr
<ClientIncidentReport_DownloadDetails
>());
226 // Do not touch this instance after running the callback, since it may have
229 scoped_ptr
<ClientIncidentReport_DownloadDetails
> details(
230 new ClientIncidentReport_DownloadDetails());
231 PopulateDetails(most_recent_row_
, details
.get());
232 callback_
.Run(details
.Pass());
233 // Do not touch this instance after running the callback, since it may have
238 void LastDownloadFinder::Observe(int type
,
239 const content::NotificationSource
& source
,
240 const content::NotificationDetails
& details
) {
242 case chrome::NOTIFICATION_PROFILE_ADDED
:
243 SearchInProfile(content::Source
<Profile
>(source
).ptr());
245 case chrome::NOTIFICATION_HISTORY_LOADED
:
246 OnProfileHistoryLoaded(content::Source
<Profile
>(source
).ptr(),
247 content::Details
<HistoryService
>(details
).ptr());
249 case chrome::NOTIFICATION_PROFILE_DESTROYED
:
250 AbandonSearchInProfile(content::Source
<Profile
>(source
).ptr());
257 } // namespace safe_browsing