Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/safe_browsing/incident_reporting/last_download_finder.h " | 5 #include "chrome/browser/safe_browsing/incident_reporting/last_download_finder.h " |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <functional> | 8 #include <functional> |
| 9 #include <utility> | 9 #include <utility> |
| 10 | 10 |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 21 #include "chrome/common/safe_browsing/download_protection_util.h" | 21 #include "chrome/common/safe_browsing/download_protection_util.h" |
| 22 #include "content/public/browser/download_item.h" | 22 #include "content/public/browser/download_item.h" |
| 23 #include "content/public/browser/notification_details.h" | 23 #include "content/public/browser/notification_details.h" |
| 24 #include "content/public/browser/notification_service.h" | 24 #include "content/public/browser/notification_service.h" |
| 25 #include "content/public/browser/notification_source.h" | 25 #include "content/public/browser/notification_source.h" |
| 26 | 26 |
| 27 namespace safe_browsing { | 27 namespace safe_browsing { |
| 28 | 28 |
| 29 namespace { | 29 namespace { |
| 30 | 30 |
| 31 // Function templates to access members of either a DownloadRow or a | |
| 32 // ClientIncidentReport_DownloadDetails instance. | |
| 33 | |
| 34 // Returns the end time of a download. | |
| 35 template <class D> | |
| 36 int64 GetEndTime(const D&); | |
| 37 | |
| 38 // Returns true if a download was of a binary file. | |
| 39 template <class D> | |
| 40 bool IsBinaryDownload(const D&); | |
| 41 | |
| 42 // Returns true if a download has been opened. | |
| 43 template <class D> | |
| 44 bool HasBeenOpened(const D&); | |
| 45 | |
| 46 // Specializations for a history::DownloadRow. | |
|
robertshield
2014/10/22 02:28:56
Why are template specializations better than overl
grt (UTC plus 2)
2014/10/23 19:53:13
um, because they're harder to read and write?
| |
| 47 | |
| 48 template <> | |
| 49 int64 GetEndTime(const history::DownloadRow& row) { | |
| 50 return row.end_time.ToJavaTime(); | |
| 51 } | |
| 52 | |
| 53 template <> | |
| 54 bool IsBinaryDownload(const history::DownloadRow& row) { | |
| 55 // TODO(grt): Peek into archives to see if they contain binaries; | |
| 56 // http://crbug.com/386915. | |
| 57 return (download_protection_util::IsBinaryFile(row.target_path) && | |
| 58 !download_protection_util::IsArchiveFile(row.target_path)); | |
| 59 } | |
| 60 | |
| 61 template <> | |
| 62 bool HasBeenOpened(const history::DownloadRow& row) { | |
| 63 return row.opened; | |
| 64 } | |
| 65 | |
| 66 // Specializations for a ClientIncidentReport_DownloadDetails. | |
| 67 | |
| 68 template <> | |
| 69 int64 GetEndTime(const ClientIncidentReport_DownloadDetails& details) { | |
| 70 return details.download_time_msec(); | |
| 71 } | |
| 72 | |
| 73 template <> | |
| 74 bool IsBinaryDownload(const ClientIncidentReport_DownloadDetails& details) { | |
| 75 return true; | |
| 76 } | |
| 77 | |
| 78 template <> | |
| 79 bool HasBeenOpened(const ClientIncidentReport_DownloadDetails& details) { | |
| 80 return details.has_open_time_msec() && details.open_time_msec(); | |
| 81 } | |
| 82 | |
| 31 // Returns true if |first| is more recent than |second|, preferring opened over | 83 // Returns true if |first| is more recent than |second|, preferring opened over |
| 32 // non-opened for downloads that completed at the same time (extraordinarily | 84 // non-opened for downloads that completed at the same time (extraordinarily |
| 33 // unlikely). Only files that look like some kind of executable are considered. | 85 // unlikely). Only files that look like some kind of executable are considered. |
| 34 bool IsMoreInterestingThan(const history::DownloadRow& first, | 86 template <class A, class B> |
| 35 const history::DownloadRow& second) { | 87 bool IsMoreInterestingThan(const A& first, const B& second) { |
| 36 if (first.end_time < second.end_time) | 88 if (GetEndTime(first) < GetEndTime(second) || !IsBinaryDownload(first)) |
| 37 return false; | 89 return false; |
| 38 // TODO(grt): Peek into archives to see if they contain binaries; | 90 return (GetEndTime(first) != GetEndTime(second) || |
| 39 // http://crbug.com/386915. | 91 (HasBeenOpened(first) && !HasBeenOpened(second))); |
| 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 } | 92 } |
| 47 | 93 |
| 48 // Returns a pointer to the most interesting completed download in |downloads|. | 94 // Returns a pointer to the most interesting completed download in |downloads|. |
| 49 const history::DownloadRow* FindMostInteresting( | 95 const history::DownloadRow* FindMostInteresting( |
| 50 const std::vector<history::DownloadRow>& downloads) { | 96 const std::vector<history::DownloadRow>& downloads) { |
| 51 const history::DownloadRow* most_recent_row = NULL; | 97 const history::DownloadRow* most_recent_row = NULL; |
| 52 for (size_t i = 0; i < downloads.size(); ++i) { | 98 for (size_t i = 0; i < downloads.size(); ++i) { |
| 53 const history::DownloadRow& row = downloads[i]; | 99 const history::DownloadRow& row = downloads[i]; |
| 54 // Ignore incomplete downloads. | 100 // Ignore incomplete downloads. |
| 55 if (row.state != content::DownloadItem::COMPLETE) | 101 if (row.state != content::DownloadItem::COMPLETE) |
| 56 continue; | 102 continue; |
| 57 if (!most_recent_row || IsMoreInterestingThan(row, *most_recent_row)) | 103 if (!most_recent_row || IsMoreInterestingThan(row, *most_recent_row)) |
| 58 most_recent_row = &row; | 104 most_recent_row = &row; |
| 59 } | 105 } |
| 60 return most_recent_row; | 106 return most_recent_row; |
| 61 } | 107 } |
| 62 | 108 |
| 109 // Returns true if |candidate| is more interesting than whichever of |details| | |
| 110 // or |best_row| is present. | |
| 111 template <class D> | |
| 112 bool IsMostInteresting(const D& candidate, | |
| 113 const ClientIncidentReport_DownloadDetails* details, | |
| 114 const history::DownloadRow& best_row) { | |
| 115 return details ? | |
| 116 IsMoreInterestingThan(candidate, *details) : | |
| 117 IsMoreInterestingThan(candidate, best_row); | |
| 118 } | |
| 119 | |
| 63 // Populates the |details| protobuf with information pertaining to |download|. | 120 // Populates the |details| protobuf with information pertaining to |download|. |
| 64 void PopulateDetails(const history::DownloadRow& download, | 121 void PopulateDetailsFromRow(const history::DownloadRow& download, |
| 65 ClientIncidentReport_DownloadDetails* details) { | 122 ClientIncidentReport_DownloadDetails* details) { |
| 66 ClientDownloadRequest* download_request = details->mutable_download(); | 123 ClientDownloadRequest* download_request = details->mutable_download(); |
| 67 download_request->set_url(download.url_chain.back().spec()); | 124 download_request->set_url(download.url_chain.back().spec()); |
| 68 // digests is a required field, so force it to exist. | 125 // digests is a required field, so force it to exist. |
| 69 // TODO(grt): Include digests in reports; http://crbug.com/389123. | 126 // TODO(grt): Include digests in reports; http://crbug.com/389123. |
| 70 ignore_result(download_request->mutable_digests()); | 127 ignore_result(download_request->mutable_digests()); |
| 71 download_request->set_length(download.received_bytes); | 128 download_request->set_length(download.received_bytes); |
| 72 for (size_t i = 0; i < download.url_chain.size(); ++i) { | 129 for (size_t i = 0; i < download.url_chain.size(); ++i) { |
| 73 const GURL& url = download.url_chain[i]; | 130 const GURL& url = download.url_chain[i]; |
| 74 ClientDownloadRequest_Resource* resource = | 131 ClientDownloadRequest_Resource* resource = |
| 75 download_request->add_resources(); | 132 download_request->add_resources(); |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 96 details->set_open_time_msec(download.end_time.ToJavaTime()); | 153 details->set_open_time_msec(download.end_time.ToJavaTime()); |
| 97 } | 154 } |
| 98 | 155 |
| 99 } // namespace | 156 } // namespace |
| 100 | 157 |
| 101 LastDownloadFinder::~LastDownloadFinder() { | 158 LastDownloadFinder::~LastDownloadFinder() { |
| 102 } | 159 } |
| 103 | 160 |
| 104 // static | 161 // static |
| 105 scoped_ptr<LastDownloadFinder> LastDownloadFinder::Create( | 162 scoped_ptr<LastDownloadFinder> LastDownloadFinder::Create( |
| 163 const DownloadDetailsGetter& download_details_getter, | |
| 106 const LastDownloadCallback& callback) { | 164 const LastDownloadCallback& callback) { |
| 107 scoped_ptr<LastDownloadFinder> finder(make_scoped_ptr(new LastDownloadFinder( | 165 scoped_ptr<LastDownloadFinder> finder(make_scoped_ptr(new LastDownloadFinder( |
| 108 g_browser_process->profile_manager()->GetLoadedProfiles(), callback))); | 166 download_details_getter, |
| 167 g_browser_process->profile_manager()->GetLoadedProfiles(), | |
| 168 callback))); | |
| 109 // Return NULL if there is no work to do. | 169 // Return NULL if there is no work to do. |
| 110 if (finder->profiles_.empty()) | 170 if (finder->profiles_.empty()) |
| 111 return scoped_ptr<LastDownloadFinder>(); | 171 return scoped_ptr<LastDownloadFinder>(); |
| 112 return finder.Pass(); | 172 return finder.Pass(); |
| 113 } | 173 } |
| 114 | 174 |
| 115 LastDownloadFinder::LastDownloadFinder() : weak_ptr_factory_(this) { | 175 LastDownloadFinder::LastDownloadFinder() : weak_ptr_factory_(this) { |
| 116 } | 176 } |
| 117 | 177 |
| 118 LastDownloadFinder::LastDownloadFinder(const std::vector<Profile*>& profiles, | 178 LastDownloadFinder::LastDownloadFinder( |
| 119 const LastDownloadCallback& callback) | 179 const DownloadDetailsGetter& download_details_getter, |
| 120 : callback_(callback), weak_ptr_factory_(this) { | 180 const std::vector<Profile*>& profiles, |
| 181 const LastDownloadCallback& callback) | |
| 182 : download_details_getter_(download_details_getter), | |
| 183 callback_(callback), | |
| 184 weak_ptr_factory_(this) { | |
| 121 // Observe profile lifecycle events so that the finder can begin or abandon | 185 // Observe profile lifecycle events so that the finder can begin or abandon |
| 122 // the search in profiles while it is running. | 186 // the search in profiles while it is running. |
| 123 notification_registrar_.Add(this, | 187 notification_registrar_.Add(this, |
| 124 chrome::NOTIFICATION_PROFILE_ADDED, | 188 chrome::NOTIFICATION_PROFILE_ADDED, |
| 125 content::NotificationService::AllSources()); | 189 content::NotificationService::AllSources()); |
| 126 notification_registrar_.Add(this, | 190 notification_registrar_.Add(this, |
| 127 chrome::NOTIFICATION_HISTORY_LOADED, | 191 chrome::NOTIFICATION_HISTORY_LOADED, |
| 128 content::NotificationService::AllSources()); | 192 content::NotificationService::AllSources()); |
| 129 notification_registrar_.Add(this, | 193 notification_registrar_.Add(this, |
| 130 chrome::NOTIFICATION_PROFILE_DESTROYED, | 194 chrome::NOTIFICATION_PROFILE_DESTROYED, |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 141 // Do not look in OTR profiles or in profiles that do not participate in | 205 // Do not look in OTR profiles or in profiles that do not participate in |
| 142 // safe browsing. | 206 // safe browsing. |
| 143 if (profile->IsOffTheRecord() || | 207 if (profile->IsOffTheRecord() || |
| 144 !profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) { | 208 !profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) { |
| 145 return; | 209 return; |
| 146 } | 210 } |
| 147 | 211 |
| 148 // Exit early if already processing this profile. This could happen if, for | 212 // Exit early if already processing this profile. This could happen if, for |
| 149 // example, NOTIFICATION_PROFILE_ADDED arrives after construction while | 213 // example, NOTIFICATION_PROFILE_ADDED arrives after construction while |
| 150 // waiting for NOTIFICATION_HISTORY_LOADED. | 214 // waiting for NOTIFICATION_HISTORY_LOADED. |
| 151 if (std::find(profiles_.begin(), profiles_.end(), profile) != | 215 if (profiles_.count(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; | 216 return; |
| 161 | 217 |
| 162 profiles_.push_back(profile); | 218 // Initiate a metadata search. |
| 163 if (history_service->BackendLoaded()) { | 219 profiles_[profile] = WAITING_FOR_METADATA; |
| 164 history_service->QueryDownloads( | 220 download_details_getter_.Run(profile, |
| 165 base::Bind(&LastDownloadFinder::OnDownloadQuery, | 221 base::Bind(&LastDownloadFinder::OnMetadataQuery, |
| 166 weak_ptr_factory_.GetWeakPtr(), | 222 weak_ptr_factory_.GetWeakPtr(), |
| 167 profile)); | 223 profile)); |
| 168 } // else wait until history is loaded. | 224 } |
| 225 | |
| 226 void LastDownloadFinder::OnMetadataQuery( | |
| 227 Profile* profile, | |
| 228 scoped_ptr<ClientIncidentReport_DownloadDetails> details) { | |
| 229 auto iter = profiles_.find(profile); | |
| 230 // Early-exit if the search for this profile was abandoned. | |
| 231 if (iter == profiles_.end()) | |
| 232 return; | |
| 233 | |
| 234 if (details) { | |
| 235 if (IsMostInteresting(*details, details_.get(), most_recent_row_)) { | |
| 236 details_ = details.Pass(); | |
| 237 most_recent_row_.end_time = base::Time(); | |
| 238 } | |
| 239 | |
| 240 RemoveProfileAndReportIfDone(iter); | |
| 241 } else { | |
| 242 // Search history since no metadata was found. | |
| 243 iter->second = WAITING_FOR_HISTORY; | |
| 244 HistoryService* history_service = | |
| 245 HistoryServiceFactory::GetForProfile(profile, Profile::IMPLICIT_ACCESS); | |
| 246 // No history service is returned for profiles that do not save history. | |
| 247 if (!history_service) { | |
| 248 RemoveProfileAndReportIfDone(iter); | |
| 249 return; | |
| 250 } | |
| 251 if (history_service->BackendLoaded()) { | |
| 252 history_service->QueryDownloads( | |
| 253 base::Bind(&LastDownloadFinder::OnDownloadQuery, | |
| 254 weak_ptr_factory_.GetWeakPtr(), | |
| 255 profile)); | |
| 256 } // else wait until history is loaded. | |
| 257 } | |
| 169 } | 258 } |
| 170 | 259 |
| 171 void LastDownloadFinder::OnProfileHistoryLoaded( | 260 void LastDownloadFinder::OnProfileHistoryLoaded( |
| 172 Profile* profile, | 261 Profile* profile, |
| 173 HistoryService* history_service) { | 262 HistoryService* history_service) { |
| 174 if (std::find(profiles_.begin(), profiles_.end(), profile) != | 263 auto iter = profiles_.find(profile); |
| 175 profiles_.end()) { | 264 if (iter == profiles_.end()) |
| 265 return; | |
| 266 | |
| 267 // Start the query in the history service if the finder was waiting for the | |
| 268 // service to load. | |
| 269 if (iter->second == WAITING_FOR_HISTORY) { | |
| 176 history_service->QueryDownloads( | 270 history_service->QueryDownloads( |
| 177 base::Bind(&LastDownloadFinder::OnDownloadQuery, | 271 base::Bind(&LastDownloadFinder::OnDownloadQuery, |
| 178 weak_ptr_factory_.GetWeakPtr(), | 272 weak_ptr_factory_.GetWeakPtr(), |
| 179 profile)); | 273 profile)); |
| 180 } | 274 } |
| 181 } | 275 } |
| 182 | 276 |
| 183 void LastDownloadFinder::AbandonSearchInProfile(Profile* profile) { | 277 void LastDownloadFinder::AbandonSearchInProfile(Profile* profile) { |
| 184 // |profile| may not be present in the set of profiles. | 278 // |profile| may not be present in the set of profiles. |
| 185 std::vector<Profile*>::iterator it = | 279 auto iter = profiles_.find(profile); |
| 186 std::find(profiles_.begin(), profiles_.end(), profile); | 280 if (iter != profiles_.end()) |
| 187 if (it != profiles_.end()) | 281 RemoveProfileAndReportIfDone(iter); |
| 188 RemoveProfileAndReportIfDone(it); | |
| 189 } | 282 } |
| 190 | 283 |
| 191 void LastDownloadFinder::OnDownloadQuery( | 284 void LastDownloadFinder::OnDownloadQuery( |
| 192 Profile* profile, | 285 Profile* profile, |
| 193 scoped_ptr<std::vector<history::DownloadRow> > downloads) { | 286 scoped_ptr<std::vector<history::DownloadRow> > downloads) { |
| 194 // Early-exit if the history search for this profile was abandoned. | 287 // Early-exit if the history search for this profile was abandoned. |
| 195 std::vector<Profile*>::iterator it = | 288 auto iter = profiles_.find(profile); |
| 196 std::find(profiles_.begin(), profiles_.end(), profile); | 289 if (iter == profiles_.end()) |
| 197 if (it == profiles_.end()) | |
| 198 return; | 290 return; |
| 199 | 291 |
| 200 // Find the most recent from this profile and use it if it's better than | 292 // Find the most recent from this profile and use it if it's better than |
| 201 // anything else found so far. | 293 // anything else found so far. |
| 202 const history::DownloadRow* profile_best = FindMostInteresting(*downloads); | 294 const history::DownloadRow* profile_best = FindMostInteresting(*downloads); |
| 203 if (profile_best && IsMoreInterestingThan(*profile_best, most_recent_row_)) | 295 if (profile_best && |
| 296 IsMostInteresting(*profile_best, details_.get(), most_recent_row_)) { | |
| 297 details_.reset(); | |
| 204 most_recent_row_ = *profile_best; | 298 most_recent_row_ = *profile_best; |
| 299 } | |
| 205 | 300 |
| 206 RemoveProfileAndReportIfDone(it); | 301 RemoveProfileAndReportIfDone(iter); |
| 207 } | 302 } |
| 208 | 303 |
| 209 void LastDownloadFinder::RemoveProfileAndReportIfDone( | 304 void LastDownloadFinder::RemoveProfileAndReportIfDone( |
| 210 std::vector<Profile*>::iterator it) { | 305 std::map<Profile*, ProfileWaitState>::iterator iter) { |
| 211 DCHECK(it != profiles_.end()); | 306 DCHECK(iter != profiles_.end()); |
| 212 | 307 profiles_.erase(iter); |
| 213 *it = profiles_.back(); | |
| 214 profiles_.resize(profiles_.size() - 1); | |
| 215 | 308 |
| 216 // Finish processing if all results are in. | 309 // Finish processing if all results are in. |
| 217 if (profiles_.empty()) | 310 if (profiles_.empty()) |
| 218 ReportResults(); | 311 ReportResults(); |
| 219 // Do not touch this instance after reporting results. | 312 // Do not touch this instance after reporting results. |
| 220 } | 313 } |
| 221 | 314 |
| 222 void LastDownloadFinder::ReportResults() { | 315 void LastDownloadFinder::ReportResults() { |
| 223 DCHECK(profiles_.empty()); | 316 DCHECK(profiles_.empty()); |
| 224 if (most_recent_row_.end_time.is_null()) { | 317 if (details_) { |
| 225 callback_.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>()); | 318 callback_.Run(make_scoped_ptr(new ClientIncidentReport_DownloadDetails( |
| 319 *details_)).Pass()); | |
| 320 // Do not touch this instance after running the callback, since it may have | |
| 321 // been deleted. | |
|
robertshield
2014/10/22 02:28:56
which instance?
grt (UTC plus 2)
2014/10/23 19:53:13
|this|. Comment tweaked.
| |
| 322 } else if (!most_recent_row_.end_time.is_null()) { | |
| 323 scoped_ptr<ClientIncidentReport_DownloadDetails> details( | |
| 324 new ClientIncidentReport_DownloadDetails()); | |
| 325 PopulateDetailsFromRow(most_recent_row_, details.get()); | |
| 326 callback_.Run(details.Pass()); | |
| 226 // Do not touch this instance after running the callback, since it may have | 327 // Do not touch this instance after running the callback, since it may have |
| 227 // been deleted. | 328 // been deleted. |
| 228 } else { | 329 } else { |
| 229 scoped_ptr<ClientIncidentReport_DownloadDetails> details( | 330 callback_.Run(scoped_ptr<ClientIncidentReport_DownloadDetails>()); |
| 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 | 331 // Do not touch this instance after running the callback, since it may have |
| 234 // been deleted. | 332 // been deleted. |
| 235 } | 333 } |
| 236 } | 334 } |
| 237 | 335 |
| 238 void LastDownloadFinder::Observe(int type, | 336 void LastDownloadFinder::Observe(int type, |
| 239 const content::NotificationSource& source, | 337 const content::NotificationSource& source, |
| 240 const content::NotificationDetails& details) { | 338 const content::NotificationDetails& details) { |
| 241 switch (type) { | 339 switch (type) { |
| 242 case chrome::NOTIFICATION_PROFILE_ADDED: | 340 case chrome::NOTIFICATION_PROFILE_ADDED: |
| 243 SearchInProfile(content::Source<Profile>(source).ptr()); | 341 SearchInProfile(content::Source<Profile>(source).ptr()); |
| 244 break; | 342 break; |
| 245 case chrome::NOTIFICATION_HISTORY_LOADED: | 343 case chrome::NOTIFICATION_HISTORY_LOADED: |
| 246 OnProfileHistoryLoaded(content::Source<Profile>(source).ptr(), | 344 OnProfileHistoryLoaded(content::Source<Profile>(source).ptr(), |
| 247 content::Details<HistoryService>(details).ptr()); | 345 content::Details<HistoryService>(details).ptr()); |
| 248 break; | 346 break; |
| 249 case chrome::NOTIFICATION_PROFILE_DESTROYED: | 347 case chrome::NOTIFICATION_PROFILE_DESTROYED: |
| 250 AbandonSearchInProfile(content::Source<Profile>(source).ptr()); | 348 AbandonSearchInProfile(content::Source<Profile>(source).ptr()); |
| 251 break; | 349 break; |
| 252 default: | 350 default: |
| 253 break; | 351 break; |
| 254 } | 352 } |
| 255 } | 353 } |
| 256 | 354 |
| 257 } // namespace safe_browsing | 355 } // namespace safe_browsing |
| OLD | NEW |