ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / chrome / browser / safe_browsing / incident_reporting / last_download_finder.cc
blob7d180986a28ea6562296c70472d92c974aceb437
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"
7 #include <algorithm>
8 #include <functional>
9 #include <utility>
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 "components/history/core/browser/download_constants.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 {
29 namespace {
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.
57 return true;
60 // Returns true if a download represented by a DownloadRow has been opened.
61 bool HasBeenOpened(const history::DownloadRow& row) {
62 return row.opened;
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))
76 return false;
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)
89 continue;
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.
98 template <class D>
99 bool IsMostInteresting(const D& candidate,
100 const ClientIncidentReport_DownloadDetails* details,
101 const history::DownloadRow& best_row) {
102 return details ?
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
138 // opened in Chrome.
139 if (download.opened)
140 details->set_open_time_msec(download.end_time.ToJavaTime());
143 } // namespace
145 LastDownloadFinder::~LastDownloadFinder() {
148 // static
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(),
155 callback)));
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),
171 callback_(callback),
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.
184 std::for_each(
185 profiles.begin(),
186 profiles.end(),
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
192 // safe browsing.
193 if (profile->IsOffTheRecord() ||
194 !profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
195 return;
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))
202 return;
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(),
209 profile));
212 void LastDownloadFinder::OnMetadataQuery(
213 Profile* profile,
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())
218 return;
220 if (details) {
221 if (IsMostInteresting(*details, details_.get(), most_recent_row_)) {
222 details_ = details.Pass();
223 most_recent_row_.end_time = base::Time();
226 RemoveProfileAndReportIfDone(iter);
227 } else {
228 // Search history since no metadata was found.
229 iter->second = WAITING_FOR_HISTORY;
230 HistoryService* history_service = HistoryServiceFactory::GetForProfile(
231 profile, ServiceAccessType::IMPLICIT_ACCESS);
232 // No history service is returned for profiles that do not save history.
233 if (!history_service) {
234 RemoveProfileAndReportIfDone(iter);
235 return;
237 if (history_service->BackendLoaded()) {
238 history_service->QueryDownloads(
239 base::Bind(&LastDownloadFinder::OnDownloadQuery,
240 weak_ptr_factory_.GetWeakPtr(),
241 profile));
242 } else {
243 // else wait until history is loaded.
244 history_service_observer_.Add(history_service);
249 void LastDownloadFinder::AbandonSearchInProfile(Profile* profile) {
250 // |profile| may not be present in the set of profiles.
251 auto iter = profile_states_.find(profile);
252 if (iter != profile_states_.end())
253 RemoveProfileAndReportIfDone(iter);
256 void LastDownloadFinder::OnDownloadQuery(
257 Profile* profile,
258 scoped_ptr<std::vector<history::DownloadRow> > downloads) {
259 // Early-exit if the history search for this profile was abandoned.
260 auto iter = profile_states_.find(profile);
261 if (iter == profile_states_.end())
262 return;
264 // Find the most recent from this profile and use it if it's better than
265 // anything else found so far.
266 const history::DownloadRow* profile_best = FindMostInteresting(*downloads);
267 if (profile_best &&
268 IsMostInteresting(*profile_best, details_.get(), most_recent_row_)) {
269 details_.reset();
270 most_recent_row_ = *profile_best;
273 RemoveProfileAndReportIfDone(iter);
276 void LastDownloadFinder::RemoveProfileAndReportIfDone(
277 std::map<Profile*, ProfileWaitState>::iterator iter) {
278 DCHECK(iter != profile_states_.end());
279 profile_states_.erase(iter);
281 // Finish processing if all results are in.
282 if (profile_states_.empty())
283 ReportResults();
284 // Do not touch this LastDownloadFinder after reporting results.
287 void LastDownloadFinder::ReportResults() {
288 DCHECK(profile_states_.empty());
289 if (details_) {
290 callback_.Run(make_scoped_ptr(new ClientIncidentReport_DownloadDetails(
291 *details_)).Pass());
292 // Do not touch this LastDownloadFinder after running the callback, since it
293 // may have been deleted.
294 } else if (!most_recent_row_.end_time.is_null()) {
295 scoped_ptr<ClientIncidentReport_DownloadDetails> details(
296 new ClientIncidentReport_DownloadDetails());
297 PopulateDetailsFromRow(most_recent_row_, details.get());
298 callback_.Run(details.Pass());
299 // Do not touch this LastDownloadFinder after running the callback, since it
300 // may have been deleted.
301 } else {
302 callback_.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>());
303 // Do not touch this LastDownloadFinder after running the callback, since it
304 // may have been deleted.
308 void LastDownloadFinder::Observe(int type,
309 const content::NotificationSource& source,
310 const content::NotificationDetails& details) {
311 switch (type) {
312 case chrome::NOTIFICATION_PROFILE_ADDED:
313 SearchInProfile(content::Source<Profile>(source).ptr());
314 break;
315 case chrome::NOTIFICATION_PROFILE_DESTROYED:
316 AbandonSearchInProfile(content::Source<Profile>(source).ptr());
317 break;
318 default:
319 break;
323 void LastDownloadFinder::OnHistoryServiceLoaded(
324 HistoryService* history_service) {
325 for (const auto& pair : profile_states_) {
326 HistoryService* hs = HistoryServiceFactory::GetForProfileIfExists(
327 pair.first, ServiceAccessType::EXPLICIT_ACCESS);
328 if (hs == history_service) {
329 // Start the query in the history service if the finder was waiting for
330 // the service to load.
331 if (pair.second == WAITING_FOR_HISTORY) {
332 history_service->QueryDownloads(
333 base::Bind(&LastDownloadFinder::OnDownloadQuery,
334 weak_ptr_factory_.GetWeakPtr(),
335 pair.first));
337 return;
342 void LastDownloadFinder::HistoryServiceBeingDeleted(
343 HistoryService* history_service) {
344 history_service_observer_.Remove(history_service);
347 } // namespace safe_browsing