Home | History | Annotate | Download | only in incident_reporting
      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 // 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)
     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|.
     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)
     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|.
     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());
     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 
    101 LastDownloadFinder::~LastDownloadFinder() {
    102 }
    103 
    104 // static
    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();
    113 }
    114 
    115 LastDownloadFinder::LastDownloadFinder() : weak_ptr_factory_(this) {
    116 }
    117 
    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());
    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 
    140 void 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 
    171 void 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 
    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);
    189 }
    190 
    191 void 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 
    209 void 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 
    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
    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 
    238 void 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