Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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/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 copleted download in |downloads|. | |
|
mattm
2014/06/27 21:50:32
completed
grt (UTC plus 2)
2014/06/30 16:53:38
Done.
| |
| 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_CREATED, | |
|
mattm
2014/06/27 21:50:32
Is there a reason this is CREATED instead of ADDED
grt (UTC plus 2)
2014/06/30 16:53:38
Hmm. I think it should be ADDED, actually, since t
| |
| 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 HistoryService* history_service = | |
| 149 HistoryServiceFactory::GetForProfile(profile, Profile::IMPLICIT_ACCESS); | |
| 150 // No history service is returned for profiles that do not save history. | |
| 151 if (!history_service) | |
| 152 return; | |
| 153 | |
| 154 profiles_.push_back(profile); | |
| 155 if (history_service->BackendLoaded()) { | |
| 156 history_service->QueryDownloads( | |
| 157 base::Bind(&LastDownloadFinder::OnDownloadQuery, | |
| 158 weak_ptr_factory_.GetWeakPtr(), | |
| 159 profile)); | |
| 160 } // else wait until history is loaded. | |
| 161 } | |
| 162 | |
| 163 void LastDownloadFinder::OnProfileHistoryLoaded( | |
| 164 Profile* profile, | |
| 165 HistoryService* history_service) { | |
| 166 if (std::find(profiles_.begin(), profiles_.end(), profile) != | |
| 167 profiles_.end()) { | |
| 168 history_service->QueryDownloads( | |
| 169 base::Bind(&LastDownloadFinder::OnDownloadQuery, | |
| 170 weak_ptr_factory_.GetWeakPtr(), | |
| 171 profile)); | |
| 172 } | |
| 173 } | |
| 174 | |
| 175 void LastDownloadFinder::AbandonSearchInProfile(Profile* profile) { | |
| 176 RemoveProfileAndReportIfDone( | |
| 177 std::find(profiles_.begin(), profiles_.end(), profile)); | |
| 178 } | |
| 179 | |
| 180 void LastDownloadFinder::OnDownloadQuery( | |
| 181 Profile* profile, | |
| 182 scoped_ptr<std::vector<history::DownloadRow> > downloads) { | |
| 183 // Early-exit if the history search for this profile was abandoned. | |
| 184 std::vector<Profile*>::iterator it = | |
| 185 std::find(profiles_.begin(), profiles_.end(), profile); | |
| 186 if (it == profiles_.end()) | |
| 187 return; | |
| 188 | |
| 189 // Find the most recent from this profile and use it if it's better than | |
| 190 // anything else found so far. | |
| 191 const history::DownloadRow* profile_best = FindMostInteresting(*downloads); | |
| 192 if (profile_best && IsMoreInterestingThan(*profile_best, most_recent_row_)) | |
| 193 most_recent_row_ = *profile_best; | |
| 194 | |
| 195 RemoveProfileAndReportIfDone(it); | |
| 196 } | |
| 197 | |
| 198 void LastDownloadFinder::RemoveProfileAndReportIfDone( | |
| 199 std::vector<Profile*>::iterator it) { | |
| 200 if (it == profiles_.end()) | |
|
mattm
2014/06/27 21:50:32
should this be a DCHECK?
grt (UTC plus 2)
2014/06/30 16:53:37
No. Comment added.
| |
| 201 return; | |
| 202 | |
| 203 *it = profiles_.back(); | |
| 204 profiles_.resize(profiles_.size() - 1); | |
| 205 | |
| 206 // Finish processing if all results are in. | |
| 207 if (profiles_.empty()) | |
| 208 ReportResults(); | |
| 209 // Do not touch this instance after reporting results. | |
| 210 } | |
| 211 | |
| 212 void LastDownloadFinder::ReportResults() { | |
| 213 DCHECK(profiles_.empty()); | |
| 214 if (most_recent_row_.end_time.is_null()) { | |
| 215 callback_.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>()); | |
| 216 // Do not touch this instance after running the callback, since it may have | |
| 217 // been deleted. | |
| 218 } else { | |
| 219 scoped_ptr<ClientIncidentReport_DownloadDetails> details( | |
| 220 new ClientIncidentReport_DownloadDetails()); | |
| 221 PopulateDetails(most_recent_row_, details.get()); | |
| 222 callback_.Run(details.Pass()); | |
| 223 // Do not touch this instance after running the callback, since it may have | |
| 224 // been deleted. | |
| 225 } | |
| 226 } | |
| 227 | |
| 228 void LastDownloadFinder::Observe(int type, | |
| 229 const content::NotificationSource& source, | |
| 230 const content::NotificationDetails& details) { | |
| 231 switch (type) { | |
| 232 case chrome::NOTIFICATION_PROFILE_CREATED: | |
| 233 SearchInProfile(content::Source<Profile>(source).ptr()); | |
| 234 break; | |
| 235 case chrome::NOTIFICATION_HISTORY_LOADED: | |
| 236 OnProfileHistoryLoaded(content::Source<Profile>(source).ptr(), | |
| 237 content::Details<HistoryService>(details).ptr()); | |
| 238 break; | |
| 239 case chrome::NOTIFICATION_PROFILE_DESTROYED: | |
| 240 AbandonSearchInProfile(content::Source<Profile>(source).ptr()); | |
| 241 break; | |
| 242 default: | |
| 243 break; | |
| 244 } | |
| 245 } | |
| 246 | |
| 247 } // namespace safe_browsing | |
| OLD | NEW |