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