• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &regex_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