Refactor WebsiteSettings to operate on a SecurityInfo
[chromium-blink-merge.git] / chrome / browser / safe_browsing / incident_reporting / last_download_finder.cc
blobf967ab11f6505cfb8ebc62366cb6f4f43976f5d0
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_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 {
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 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)
55 return true;
56 #elif defined(OS_ANDROID)
57 if (download_type == ClientDownloadRequest::ANDROID_APK)
58 return true;
59 #endif
61 // Extensions are supported where enabled.
62 #if defined(ENABLE_EXTENSIONS)
63 if (download_type == ClientDownloadRequest::CHROME_EXTENSION)
64 return true;
65 #endif
67 if (download_type == ClientDownloadRequest::ZIPPED_EXECUTABLE ||
68 download_type == ClientDownloadRequest::ZIPPED_ARCHIVE ||
69 download_type == ClientDownloadRequest::ARCHIVE) {
70 return true;
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
79 // the current OS.
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) {
98 return row.opened;
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))
112 return false;
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)
125 continue;
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.
134 template <class D>
135 bool IsMostInteresting(const D& candidate,
136 const ClientIncidentReport_DownloadDetails* details,
137 const history::DownloadRow& best_row) {
138 return details ?
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
174 // opened in Chrome.
175 if (download.opened)
176 details->set_open_time_msec(download.end_time.ToJavaTime());
179 } // namespace
181 LastDownloadFinder::~LastDownloadFinder() {
184 // static
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(),
191 callback)));
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),
207 callback_(callback),
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.
220 std::for_each(
221 profiles.begin(),
222 profiles.end(),
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
228 // safe browsing.
229 if (profile->IsOffTheRecord() ||
230 !profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
231 return;
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))
238 return;
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(),
245 profile));
248 void LastDownloadFinder::OnMetadataQuery(
249 Profile* profile,
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())
254 return;
256 if (details) {
257 if (IsMostInteresting(*details, details_.get(), most_recent_row_)) {
258 details_ = details.Pass();
259 most_recent_row_.end_time = base::Time();
262 RemoveProfileAndReportIfDone(iter);
263 } else {
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);
272 return;
274 if (history_service->BackendLoaded()) {
275 history_service->QueryDownloads(
276 base::Bind(&LastDownloadFinder::OnDownloadQuery,
277 weak_ptr_factory_.GetWeakPtr(),
278 profile));
279 } else {
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(
294 Profile* profile,
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())
299 return;
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);
304 if (profile_best &&
305 IsMostInteresting(*profile_best, details_.get(), most_recent_row_)) {
306 details_.reset();
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())
320 ReportResults();
321 // Do not touch this LastDownloadFinder after reporting results.
324 void LastDownloadFinder::ReportResults() {
325 DCHECK(profile_states_.empty());
326 if (details_) {
327 callback_.Run(make_scoped_ptr(new ClientIncidentReport_DownloadDetails(
328 *details_)).Pass());
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.
338 } else {
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) {
348 switch (type) {
349 case chrome::NOTIFICATION_PROFILE_ADDED:
350 SearchInProfile(content::Source<Profile>(source).ptr());
351 break;
352 case chrome::NOTIFICATION_PROFILE_DESTROYED:
353 AbandonSearchInProfile(content::Source<Profile>(source).ptr());
354 break;
355 default:
356 break;
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(),
372 pair.first));
374 return;
379 void LastDownloadFinder::HistoryServiceBeingDeleted(
380 history::HistoryService* history_service) {
381 history_service_observer_.Remove(history_service);
384 } // namespace safe_browsing