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