1 // Copyright (c) 2012 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/download/download_query.h"
6
7 #include <algorithm>
8 #include <string>
9 #include <vector>
10
11 #include "base/bind.h"
12 #include "base/callback.h"
13 #include "base/files/file_path.h"
14 #include "base/i18n/case_conversion.h"
15 #include "base/i18n/string_search.h"
16 #include "base/logging.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/prefs/pref_service.h"
19 #include "base/stl_util.h"
20 #include "base/strings/string16.h"
21 #include "base/strings/string_split.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/time/time.h"
25 #include "base/values.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/common/pref_names.h"
28 #include "content/public/browser/content_browser_client.h"
29 #include "content/public/browser/download_item.h"
30 #include "net/base/net_util.h"
31 #include "third_party/re2/re2/re2.h"
32 #include "url/gurl.h"
33
34 using content::DownloadDangerType;
35 using content::DownloadItem;
36
37 namespace {
38
39 // Templatized base::Value::GetAs*().
40 template <typename T> bool GetAs(const base::Value& in, T* out);
GetAs(const base::Value & in,bool * out)41 template<> bool GetAs(const base::Value& in, bool* out) {
42 return in.GetAsBoolean(out);
43 }
GetAs(const base::Value & in,int * out)44 template<> bool GetAs(const base::Value& in, int* out) {
45 return in.GetAsInteger(out);
46 }
GetAs(const base::Value & in,std::string * out)47 template<> bool GetAs(const base::Value& in, std::string* out) {
48 return in.GetAsString(out);
49 }
GetAs(const base::Value & in,base::string16 * out)50 template<> bool GetAs(const base::Value& in, base::string16* out) {
51 return in.GetAsString(out);
52 }
GetAs(const base::Value & in,std::vector<base::string16> * out)53 template<> bool GetAs(const base::Value& in, std::vector<base::string16>* out) {
54 out->clear();
55 const base::ListValue* list = NULL;
56 if (!in.GetAsList(&list))
57 return false;
58 for (size_t i = 0; i < list->GetSize(); ++i) {
59 base::string16 element;
60 if (!list->GetString(i, &element)) {
61 out->clear();
62 return false;
63 }
64 out->push_back(element);
65 }
66 return true;
67 }
68
69 // The next several functions are helpers for making Callbacks that access
70 // DownloadItem fields.
71
MatchesQuery(const std::vector<base::string16> & query_terms,const DownloadItem & item)72 static bool MatchesQuery(
73 const std::vector<base::string16>& query_terms,
74 const DownloadItem& item) {
75 DCHECK(!query_terms.empty());
76 base::string16 url_raw(base::UTF8ToUTF16(item.GetOriginalUrl().spec()));
77 base::string16 url_formatted = url_raw;
78 if (item.GetBrowserContext()) {
79 Profile* profile = Profile::FromBrowserContext(item.GetBrowserContext());
80 url_formatted = net::FormatUrl(
81 item.GetOriginalUrl(),
82 profile->GetPrefs()->GetString(prefs::kAcceptLanguages));
83 }
84 base::string16 path(item.GetTargetFilePath().LossyDisplayName());
85
86 for (std::vector<base::string16>::const_iterator it = query_terms.begin();
87 it != query_terms.end(); ++it) {
88 base::string16 term = base::i18n::ToLower(*it);
89 if (!base::i18n::StringSearchIgnoringCaseAndAccents(
90 term, url_raw, NULL, NULL) &&
91 !base::i18n::StringSearchIgnoringCaseAndAccents(
92 term, url_formatted, NULL, NULL) &&
93 !base::i18n::StringSearchIgnoringCaseAndAccents(
94 term, path, NULL, NULL)) {
95 return false;
96 }
97 }
98 return true;
99 }
100
GetStartTimeMsEpoch(const DownloadItem & item)101 static int64 GetStartTimeMsEpoch(const DownloadItem& item) {
102 return (item.GetStartTime() - base::Time::UnixEpoch()).InMilliseconds();
103 }
104
GetEndTimeMsEpoch(const DownloadItem & item)105 static int64 GetEndTimeMsEpoch(const DownloadItem& item) {
106 return (item.GetEndTime() - base::Time::UnixEpoch()).InMilliseconds();
107 }
108
TimeToISO8601(const base::Time & t)109 std::string TimeToISO8601(const base::Time& t) {
110 base::Time::Exploded exploded;
111 t.UTCExplode(&exploded);
112 return base::StringPrintf(
113 "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", exploded.year, exploded.month,
114 exploded.day_of_month, exploded.hour, exploded.minute, exploded.second,
115 exploded.millisecond);
116 }
117
GetStartTime(const DownloadItem & item)118 static std::string GetStartTime(const DownloadItem& item) {
119 return TimeToISO8601(item.GetStartTime());
120 }
121
GetEndTime(const DownloadItem & item)122 static std::string GetEndTime(const DownloadItem& item) {
123 return TimeToISO8601(item.GetEndTime());
124 }
125
GetDangerAccepted(const DownloadItem & item)126 static bool GetDangerAccepted(const DownloadItem& item) {
127 return (item.GetDangerType() ==
128 content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED);
129 }
130
GetExists(const DownloadItem & item)131 static bool GetExists(const DownloadItem& item) {
132 return !item.GetFileExternallyRemoved();
133 }
134
GetFilename(const DownloadItem & item)135 static base::string16 GetFilename(const DownloadItem& item) {
136 // This filename will be compared with strings that could be passed in by the
137 // user, who only sees LossyDisplayNames.
138 return item.GetTargetFilePath().LossyDisplayName();
139 }
140
GetFilenameUTF8(const DownloadItem & item)141 static std::string GetFilenameUTF8(const DownloadItem& item) {
142 return base::UTF16ToUTF8(GetFilename(item));
143 }
144
GetUrl(const DownloadItem & item)145 static std::string GetUrl(const DownloadItem& item) {
146 return item.GetOriginalUrl().spec();
147 }
148
GetState(const DownloadItem & item)149 static DownloadItem::DownloadState GetState(const DownloadItem& item) {
150 return item.GetState();
151 }
152
GetDangerType(const DownloadItem & item)153 static DownloadDangerType GetDangerType(const DownloadItem& item) {
154 return item.GetDangerType();
155 }
156
GetReceivedBytes(const DownloadItem & item)157 static int GetReceivedBytes(const DownloadItem& item) {
158 return item.GetReceivedBytes();
159 }
160
GetTotalBytes(const DownloadItem & item)161 static int GetTotalBytes(const DownloadItem& item) {
162 return item.GetTotalBytes();
163 }
164
GetMimeType(const DownloadItem & item)165 static std::string GetMimeType(const DownloadItem& item) {
166 return item.GetMimeType();
167 }
168
IsPaused(const DownloadItem & item)169 static bool IsPaused(const DownloadItem& item) {
170 return item.IsPaused();
171 }
172
173 enum ComparisonType {LT, EQ, GT};
174
175 // Returns true if |item| matches the filter specified by |value|, |cmptype|,
176 // and |accessor|. |accessor| is conceptually a function that takes a
177 // DownloadItem and returns one of its fields, which is then compared to
178 // |value|.
179 template<typename ValueType>
FieldMatches(const ValueType & value,ComparisonType cmptype,const base::Callback<ValueType (const DownloadItem &)> & accessor,const DownloadItem & item)180 static bool FieldMatches(
181 const ValueType& value,
182 ComparisonType cmptype,
183 const base::Callback<ValueType(const DownloadItem&)>& accessor,
184 const DownloadItem& item) {
185 switch (cmptype) {
186 case LT: return accessor.Run(item) < value;
187 case EQ: return accessor.Run(item) == value;
188 case GT: return accessor.Run(item) > value;
189 }
190 NOTREACHED();
191 return false;
192 }
193
194 // Helper for building a Callback to FieldMatches<>().
BuildFilter(const base::Value & value,ComparisonType cmptype,ValueType (* accessor)(const DownloadItem &))195 template <typename ValueType> DownloadQuery::FilterCallback BuildFilter(
196 const base::Value& value, ComparisonType cmptype,
197 ValueType (*accessor)(const DownloadItem&)) {
198 ValueType cpp_value;
199 if (!GetAs(value, &cpp_value)) return DownloadQuery::FilterCallback();
200 return base::Bind(&FieldMatches<ValueType>, cpp_value, cmptype,
201 base::Bind(accessor));
202 }
203
204 // Returns true if |accessor.Run(item)| matches |pattern|.
FindRegex(RE2 * pattern,const base::Callback<std::string (const DownloadItem &)> & accessor,const DownloadItem & item)205 static bool FindRegex(
206 RE2* pattern,
207 const base::Callback<std::string(const DownloadItem&)>& accessor,
208 const DownloadItem& item) {
209 return RE2::PartialMatch(accessor.Run(item), *pattern);
210 }
211
212 // Helper for building a Callback to FindRegex().
BuildRegexFilter(const base::Value & regex_value,std::string (* accessor)(const DownloadItem &))213 DownloadQuery::FilterCallback BuildRegexFilter(
214 const base::Value& regex_value,
215 std::string (*accessor)(const DownloadItem&)) {
216 std::string regex_str;
217 if (!GetAs(regex_value, ®ex_str)) return DownloadQuery::FilterCallback();
218 scoped_ptr<RE2> pattern(new RE2(regex_str));
219 if (!pattern->ok()) return DownloadQuery::FilterCallback();
220 return base::Bind(&FindRegex, base::Owned(pattern.release()),
221 base::Bind(accessor));
222 }
223
224 // Returns a ComparisonType to indicate whether a field in |left| is less than,
225 // greater than or equal to the same field in |right|.
226 template<typename ValueType>
Compare(const base::Callback<ValueType (const DownloadItem &)> & accessor,const DownloadItem & left,const DownloadItem & right)227 static ComparisonType Compare(
228 const base::Callback<ValueType(const DownloadItem&)>& accessor,
229 const DownloadItem& left, const DownloadItem& right) {
230 ValueType left_value = accessor.Run(left);
231 ValueType right_value = accessor.Run(right);
232 if (left_value > right_value) return GT;
233 if (left_value < right_value) return LT;
234 DCHECK_EQ(left_value, right_value);
235 return EQ;
236 }
237
238 } // anonymous namespace
239
DownloadQuery()240 DownloadQuery::DownloadQuery()
241 : limit_(kuint32max) {
242 }
243
~DownloadQuery()244 DownloadQuery::~DownloadQuery() {
245 }
246
247 // AddFilter() pushes a new FilterCallback to filters_. Most FilterCallbacks are
248 // Callbacks to FieldMatches<>(). Search() iterates over given DownloadItems,
249 // discarding items for which any filter returns false. A DownloadQuery may have
250 // zero or more FilterCallbacks.
251
AddFilter(const DownloadQuery::FilterCallback & value)252 bool DownloadQuery::AddFilter(const DownloadQuery::FilterCallback& value) {
253 if (value.is_null()) return false;
254 filters_.push_back(value);
255 return true;
256 }
257
AddFilter(DownloadItem::DownloadState state)258 void DownloadQuery::AddFilter(DownloadItem::DownloadState state) {
259 AddFilter(base::Bind(&FieldMatches<DownloadItem::DownloadState>, state, EQ,
260 base::Bind(&GetState)));
261 }
262
AddFilter(DownloadDangerType danger)263 void DownloadQuery::AddFilter(DownloadDangerType danger) {
264 AddFilter(base::Bind(&FieldMatches<DownloadDangerType>, danger, EQ,
265 base::Bind(&GetDangerType)));
266 }
267
AddFilter(DownloadQuery::FilterType type,const base::Value & value)268 bool DownloadQuery::AddFilter(DownloadQuery::FilterType type,
269 const base::Value& value) {
270 switch (type) {
271 case FILTER_BYTES_RECEIVED:
272 return AddFilter(BuildFilter<int>(value, EQ, &GetReceivedBytes));
273 case FILTER_DANGER_ACCEPTED:
274 return AddFilter(BuildFilter<bool>(value, EQ, &GetDangerAccepted));
275 case FILTER_EXISTS:
276 return AddFilter(BuildFilter<bool>(value, EQ, &GetExists));
277 case FILTER_FILENAME:
278 return AddFilter(BuildFilter<base::string16>(value, EQ, &GetFilename));
279 case FILTER_FILENAME_REGEX:
280 return AddFilter(BuildRegexFilter(value, &GetFilenameUTF8));
281 case FILTER_MIME:
282 return AddFilter(BuildFilter<std::string>(value, EQ, &GetMimeType));
283 case FILTER_PAUSED:
284 return AddFilter(BuildFilter<bool>(value, EQ, &IsPaused));
285 case FILTER_QUERY: {
286 std::vector<base::string16> query_terms;
287 return GetAs(value, &query_terms) &&
288 (query_terms.empty() ||
289 AddFilter(base::Bind(&MatchesQuery, query_terms)));
290 }
291 case FILTER_ENDED_AFTER:
292 return AddFilter(BuildFilter<std::string>(value, GT, &GetEndTime));
293 case FILTER_ENDED_BEFORE:
294 return AddFilter(BuildFilter<std::string>(value, LT, &GetEndTime));
295 case FILTER_END_TIME:
296 return AddFilter(BuildFilter<std::string>(value, EQ, &GetEndTime));
297 case FILTER_STARTED_AFTER:
298 return AddFilter(BuildFilter<std::string>(value, GT, &GetStartTime));
299 case FILTER_STARTED_BEFORE:
300 return AddFilter(BuildFilter<std::string>(value, LT, &GetStartTime));
301 case FILTER_START_TIME:
302 return AddFilter(BuildFilter<std::string>(value, EQ, &GetStartTime));
303 case FILTER_TOTAL_BYTES:
304 return AddFilter(BuildFilter<int>(value, EQ, &GetTotalBytes));
305 case FILTER_TOTAL_BYTES_GREATER:
306 return AddFilter(BuildFilter<int>(value, GT, &GetTotalBytes));
307 case FILTER_TOTAL_BYTES_LESS:
308 return AddFilter(BuildFilter<int>(value, LT, &GetTotalBytes));
309 case FILTER_URL:
310 return AddFilter(BuildFilter<std::string>(value, EQ, &GetUrl));
311 case FILTER_URL_REGEX:
312 return AddFilter(BuildRegexFilter(value, &GetUrl));
313 }
314 return false;
315 }
316
Matches(const DownloadItem & item) const317 bool DownloadQuery::Matches(const DownloadItem& item) const {
318 for (FilterCallbackVector::const_iterator filter = filters_.begin();
319 filter != filters_.end(); ++filter) {
320 if (!filter->Run(item))
321 return false;
322 }
323 return true;
324 }
325
326 // AddSorter() creates a Sorter and pushes it onto sorters_. A Sorter is a
327 // direction and a Callback to Compare<>(). After filtering, Search() makes a
328 // DownloadComparator functor from the sorters_ and passes the
329 // DownloadComparator to std::partial_sort. std::partial_sort calls the
330 // DownloadComparator with different pairs of DownloadItems. DownloadComparator
331 // iterates over the sorters until a callback returns ComparisonType LT or GT.
332 // DownloadComparator returns true or false depending on that ComparisonType and
333 // the sorter's direction in order to indicate to std::partial_sort whether the
334 // left item is after or before the right item. If all sorters return EQ, then
335 // DownloadComparator compares GetId. A DownloadQuery may have zero or more
336 // Sorters, but there is one DownloadComparator per call to Search().
337
338 struct DownloadQuery::Sorter {
339 typedef base::Callback<ComparisonType(
340 const DownloadItem&, const DownloadItem&)> SortType;
341
342 template<typename ValueType>
BuildDownloadQuery::Sorter343 static Sorter Build(DownloadQuery::SortDirection adirection,
344 ValueType (*accessor)(const DownloadItem&)) {
345 return Sorter(adirection, base::Bind(&Compare<ValueType>,
346 base::Bind(accessor)));
347 }
348
SorterDownloadQuery::Sorter349 Sorter(DownloadQuery::SortDirection adirection,
350 const SortType& asorter)
351 : direction(adirection),
352 sorter(asorter) {
353 }
~SorterDownloadQuery::Sorter354 ~Sorter() {}
355
356 DownloadQuery::SortDirection direction;
357 SortType sorter;
358 };
359
360 class DownloadQuery::DownloadComparator {
361 public:
DownloadComparator(const DownloadQuery::SorterVector & terms)362 explicit DownloadComparator(const DownloadQuery::SorterVector& terms)
363 : terms_(terms) {
364 }
365
366 // Returns true if |left| sorts before |right|.
367 bool operator() (const DownloadItem* left, const DownloadItem* right);
368
369 private:
370 const DownloadQuery::SorterVector& terms_;
371
372 // std::sort requires this class to be copyable.
373 };
374
operator ()(const DownloadItem * left,const DownloadItem * right)375 bool DownloadQuery::DownloadComparator::operator() (
376 const DownloadItem* left, const DownloadItem* right) {
377 for (DownloadQuery::SorterVector::const_iterator term = terms_.begin();
378 term != terms_.end(); ++term) {
379 switch (term->sorter.Run(*left, *right)) {
380 case LT: return term->direction == DownloadQuery::ASCENDING;
381 case GT: return term->direction == DownloadQuery::DESCENDING;
382 case EQ: break; // break the switch but not the loop
383 }
384 }
385 CHECK_NE(left->GetId(), right->GetId());
386 return left->GetId() < right->GetId();
387 }
388
AddSorter(DownloadQuery::SortType type,DownloadQuery::SortDirection direction)389 void DownloadQuery::AddSorter(DownloadQuery::SortType type,
390 DownloadQuery::SortDirection direction) {
391 switch (type) {
392 case SORT_END_TIME:
393 sorters_.push_back(Sorter::Build<int64>(direction, &GetEndTimeMsEpoch));
394 break;
395 case SORT_START_TIME:
396 sorters_.push_back(Sorter::Build<int64>(direction, &GetStartTimeMsEpoch));
397 break;
398 case SORT_URL:
399 sorters_.push_back(Sorter::Build<std::string>(direction, &GetUrl));
400 break;
401 case SORT_FILENAME:
402 sorters_.push_back(
403 Sorter::Build<base::string16>(direction, &GetFilename));
404 break;
405 case SORT_DANGER:
406 sorters_.push_back(Sorter::Build<DownloadDangerType>(
407 direction, &GetDangerType));
408 break;
409 case SORT_DANGER_ACCEPTED:
410 sorters_.push_back(Sorter::Build<bool>(direction, &GetDangerAccepted));
411 break;
412 case SORT_EXISTS:
413 sorters_.push_back(Sorter::Build<bool>(direction, &GetExists));
414 break;
415 case SORT_STATE:
416 sorters_.push_back(Sorter::Build<DownloadItem::DownloadState>(
417 direction, &GetState));
418 break;
419 case SORT_PAUSED:
420 sorters_.push_back(Sorter::Build<bool>(direction, &IsPaused));
421 break;
422 case SORT_MIME:
423 sorters_.push_back(Sorter::Build<std::string>(direction, &GetMimeType));
424 break;
425 case SORT_BYTES_RECEIVED:
426 sorters_.push_back(Sorter::Build<int>(direction, &GetReceivedBytes));
427 break;
428 case SORT_TOTAL_BYTES:
429 sorters_.push_back(Sorter::Build<int>(direction, &GetTotalBytes));
430 break;
431 }
432 }
433
FinishSearch(DownloadQuery::DownloadVector * results) const434 void DownloadQuery::FinishSearch(DownloadQuery::DownloadVector* results) const {
435 if (!sorters_.empty())
436 std::partial_sort(results->begin(),
437 results->begin() + std::min(limit_, results->size()),
438 results->end(),
439 DownloadComparator(sorters_));
440 if (results->size() > limit_)
441 results->resize(limit_);
442 }
443