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
27namespace safe_browsing {
28
29namespace {
30
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.
34bool IsMoreInterestingThan(const history::DownloadRow& first,
35                           const history::DownloadRow& second) {
36  if (first.end_time < second.end_time)
37    return false;
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)) {
42    return false;
43  }
44  return (first.end_time != second.end_time ||
45          (first.opened && !second.opened));
46}
47
48// Returns a pointer to the most interesting completed download in |downloads|.
49const 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)
56      continue;
57    if (!most_recent_row || IsMoreInterestingThan(row, *most_recent_row))
58      most_recent_row = &row;
59  }
60  return most_recent_row;
61}
62
63// Populates the |details| protobuf with information pertaining to |download|.
64void 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());
83    }
84  }
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));
91
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
94  // opened in Chrome.
95  if (download.opened)
96    details->set_open_time_msec(download.end_time.ToJavaTime());
97}
98
99}  // namespace
100
101LastDownloadFinder::~LastDownloadFinder() {
102}
103
104// static
105scoped_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();
113}
114
115LastDownloadFinder::LastDownloadFinder() : weak_ptr_factory_(this) {
116}
117
118LastDownloadFinder::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());
132
133  // Begin the seach for all given profiles.
134  std::for_each(
135      profiles.begin(),
136      profiles.end(),
137      std::bind1st(std::mem_fun(&LastDownloadFinder::SearchInProfile), this));
138}
139
140void LastDownloadFinder::SearchInProfile(Profile* profile) {
141  // Do not look in OTR profiles or in profiles that do not participate in
142  // safe browsing.
143  if (profile->IsOffTheRecord() ||
144      !profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
145    return;
146  }
147
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) !=
152      profiles_.end()) {
153    return;
154  }
155
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)
160    return;
161
162  profiles_.push_back(profile);
163  if (history_service->BackendLoaded()) {
164    history_service->QueryDownloads(
165        base::Bind(&LastDownloadFinder::OnDownloadQuery,
166                   weak_ptr_factory_.GetWeakPtr(),
167                   profile));
168  }  // else wait until history is loaded.
169}
170
171void LastDownloadFinder::OnProfileHistoryLoaded(
172    Profile* profile,
173    HistoryService* history_service) {
174  if (std::find(profiles_.begin(), profiles_.end(), profile) !=
175      profiles_.end()) {
176    history_service->QueryDownloads(
177        base::Bind(&LastDownloadFinder::OnDownloadQuery,
178                   weak_ptr_factory_.GetWeakPtr(),
179                   profile));
180  }
181}
182
183void 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);
189}
190
191void LastDownloadFinder::OnDownloadQuery(
192    Profile* profile,
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())
198    return;
199
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;
205
206  RemoveProfileAndReportIfDone(it);
207}
208
209void LastDownloadFinder::RemoveProfileAndReportIfDone(
210    std::vector<Profile*>::iterator it) {
211  DCHECK(it != profiles_.end());
212
213  *it = profiles_.back();
214  profiles_.resize(profiles_.size() - 1);
215
216  // Finish processing if all results are in.
217  if (profiles_.empty())
218    ReportResults();
219  // Do not touch this instance after reporting results.
220}
221
222void 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
227    // been deleted.
228  } else {
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
234    // been deleted.
235  }
236}
237
238void LastDownloadFinder::Observe(int type,
239                                 const content::NotificationSource& source,
240                                 const content::NotificationDetails& details) {
241  switch (type) {
242    case chrome::NOTIFICATION_PROFILE_ADDED:
243      SearchInProfile(content::Source<Profile>(source).ptr());
244      break;
245    case chrome::NOTIFICATION_HISTORY_LOADED:
246      OnProfileHistoryLoaded(content::Source<Profile>(source).ptr(),
247                             content::Details<HistoryService>(details).ptr());
248      break;
249    case chrome::NOTIFICATION_PROFILE_DESTROYED:
250      AbandonSearchInProfile(content::Source<Profile>(source).ptr());
251      break;
252    default:
253      break;
254  }
255}
256
257}  // namespace safe_browsing
258