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 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 | |
OLD | NEW |