Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / safe_browsing / incident_reporting / last_download_finder.cc
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.
4
5 #include "chrome/browser/safe_browsing/incident_reporting/last_download_finder.h"
6
7 #include <algorithm>
8 #include <functional>
9 #include <utility>
10
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"
26
27 namespace safe_browsing {
28
29 namespace {
30
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.
35
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();
39 }
40
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();
44 }
45
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));
52 }
53
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;
58 }
59
60 // Returns true if a download represented by a DownloadRow has been opened.
61 bool HasBeenOpened(const history::DownloadRow& row) {
62   return row.opened;
63 }
64
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();
68 }
69
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)));
79 }
80
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 != content::DownloadItem::COMPLETE)
89       continue;
90     if (!most_recent_row || IsMoreInterestingThan(row, *most_recent_row))
91       most_recent_row = &row;
92   }
93   return most_recent_row;
94 }
95
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);
105 }
106
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());
127     }
128   }
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));
135
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());
141 }
142
143 }  // namespace
144
145 LastDownloadFinder::~LastDownloadFinder() {
146 }
147
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();
160 }
161
162 LastDownloadFinder::LastDownloadFinder() : weak_ptr_factory_(this) {
163 }
164
165 LastDownloadFinder::LastDownloadFinder(
166     const DownloadDetailsGetter& download_details_getter,
167     const std::vector<Profile*>& profiles,
168     const LastDownloadCallback& callback)
169     : download_details_getter_(download_details_getter),
170       callback_(callback),
171       weak_ptr_factory_(this) {
172   // Observe profile lifecycle events so that the finder can begin or abandon
173   // the search in profiles while it is running.
174   notification_registrar_.Add(this,
175                               chrome::NOTIFICATION_PROFILE_ADDED,
176                               content::NotificationService::AllSources());
177   notification_registrar_.Add(this,
178                               chrome::NOTIFICATION_HISTORY_LOADED,
179                               content::NotificationService::AllSources());
180   notification_registrar_.Add(this,
181                               chrome::NOTIFICATION_PROFILE_DESTROYED,
182                               content::NotificationService::AllSources());
183
184   // Begin the seach for all given profiles.
185   std::for_each(
186       profiles.begin(),
187       profiles.end(),
188       std::bind1st(std::mem_fun(&LastDownloadFinder::SearchInProfile), this));
189 }
190
191 void LastDownloadFinder::SearchInProfile(Profile* profile) {
192   // Do not look in OTR profiles or in profiles that do not participate in
193   // safe browsing.
194   if (profile->IsOffTheRecord() ||
195       !profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
196     return;
197   }
198
199   // Exit early if already processing this profile. This could happen if, for
200   // example, NOTIFICATION_PROFILE_ADDED arrives after construction while
201   // waiting for NOTIFICATION_HISTORY_LOADED.
202   if (profile_states_.count(profile))
203     return;
204
205   // Initiate a metadata search.
206   profile_states_[profile] = WAITING_FOR_METADATA;
207   download_details_getter_.Run(profile,
208                                base::Bind(&LastDownloadFinder::OnMetadataQuery,
209                                           weak_ptr_factory_.GetWeakPtr(),
210                                           profile));
211 }
212
213 void LastDownloadFinder::OnMetadataQuery(
214     Profile* profile,
215     scoped_ptr<ClientIncidentReport_DownloadDetails> details) {
216   auto iter = profile_states_.find(profile);
217   // Early-exit if the search for this profile was abandoned.
218   if (iter == profile_states_.end())
219     return;
220
221   if (details) {
222     if (IsMostInteresting(*details, details_.get(), most_recent_row_)) {
223       details_ = details.Pass();
224       most_recent_row_.end_time = base::Time();
225     }
226
227     RemoveProfileAndReportIfDone(iter);
228   } else {
229     // Search history since no metadata was found.
230     iter->second = WAITING_FOR_HISTORY;
231     HistoryService* history_service =
232         HistoryServiceFactory::GetForProfile(profile, Profile::IMPLICIT_ACCESS);
233     // No history service is returned for profiles that do not save history.
234     if (!history_service) {
235       RemoveProfileAndReportIfDone(iter);
236       return;
237     }
238     if (history_service->BackendLoaded()) {
239       history_service->QueryDownloads(
240           base::Bind(&LastDownloadFinder::OnDownloadQuery,
241                      weak_ptr_factory_.GetWeakPtr(),
242                      profile));
243     }  // else wait until history is loaded.
244   }
245 }
246
247 void LastDownloadFinder::OnProfileHistoryLoaded(
248     Profile* profile,
249     HistoryService* history_service) {
250   auto iter = profile_states_.find(profile);
251   if (iter == profile_states_.end())
252     return;
253
254   // Start the query in the history service if the finder was waiting for the
255   // service to load.
256   if (iter->second == WAITING_FOR_HISTORY) {
257     history_service->QueryDownloads(
258         base::Bind(&LastDownloadFinder::OnDownloadQuery,
259                    weak_ptr_factory_.GetWeakPtr(),
260                    profile));
261   }
262 }
263
264 void LastDownloadFinder::AbandonSearchInProfile(Profile* profile) {
265   // |profile| may not be present in the set of profiles.
266   auto iter = profile_states_.find(profile);
267   if (iter != profile_states_.end())
268     RemoveProfileAndReportIfDone(iter);
269 }
270
271 void LastDownloadFinder::OnDownloadQuery(
272     Profile* profile,
273     scoped_ptr<std::vector<history::DownloadRow> > downloads) {
274   // Early-exit if the history search for this profile was abandoned.
275   auto iter = profile_states_.find(profile);
276   if (iter == profile_states_.end())
277     return;
278
279   // Find the most recent from this profile and use it if it's better than
280   // anything else found so far.
281   const history::DownloadRow* profile_best = FindMostInteresting(*downloads);
282   if (profile_best &&
283       IsMostInteresting(*profile_best, details_.get(), most_recent_row_)) {
284     details_.reset();
285     most_recent_row_ = *profile_best;
286   }
287
288   RemoveProfileAndReportIfDone(iter);
289 }
290
291 void LastDownloadFinder::RemoveProfileAndReportIfDone(
292     std::map<Profile*, ProfileWaitState>::iterator iter) {
293   DCHECK(iter != profile_states_.end());
294   profile_states_.erase(iter);
295
296   // Finish processing if all results are in.
297   if (profile_states_.empty())
298     ReportResults();
299   // Do not touch this LastDownloadFinder after reporting results.
300 }
301
302 void LastDownloadFinder::ReportResults() {
303   DCHECK(profile_states_.empty());
304   if (details_) {
305     callback_.Run(make_scoped_ptr(new ClientIncidentReport_DownloadDetails(
306                                       *details_)).Pass());
307     // Do not touch this LastDownloadFinder after running the callback, since it
308     // may have been deleted.
309   } else if (!most_recent_row_.end_time.is_null()) {
310     scoped_ptr<ClientIncidentReport_DownloadDetails> details(
311         new ClientIncidentReport_DownloadDetails());
312     PopulateDetailsFromRow(most_recent_row_, details.get());
313     callback_.Run(details.Pass());
314     // Do not touch this LastDownloadFinder after running the callback, since it
315     // may have been deleted.
316   } else {
317     callback_.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>());
318     // Do not touch this LastDownloadFinder after running the callback, since it
319     // may have been deleted.
320   }
321 }
322
323 void LastDownloadFinder::Observe(int type,
324                                  const content::NotificationSource& source,
325                                  const content::NotificationDetails& details) {
326   switch (type) {
327     case chrome::NOTIFICATION_PROFILE_ADDED:
328       SearchInProfile(content::Source<Profile>(source).ptr());
329       break;
330     case chrome::NOTIFICATION_HISTORY_LOADED:
331       OnProfileHistoryLoaded(content::Source<Profile>(source).ptr(),
332                              content::Details<HistoryService>(details).ptr());
333       break;
334     case chrome::NOTIFICATION_PROFILE_DESTROYED:
335       AbandonSearchInProfile(content::Source<Profile>(source).ptr());
336       break;
337     default:
338       break;
339   }
340 }
341
342 }  // namespace safe_browsing