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.
5 #include "chrome/browser/download/download_query.h"
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"
34 using content::DownloadDangerType
;
35 using content::DownloadItem
;
39 // Templatized base::Value::GetAs*().
40 template <typename T
> bool GetAs(const base::Value
& in
, T
* out
);
41 template<> bool GetAs(const base::Value
& in
, bool* out
) {
42 return in
.GetAsBoolean(out
);
44 template<> bool GetAs(const base::Value
& in
, int* out
) {
45 return in
.GetAsInteger(out
);
47 template<> bool GetAs(const base::Value
& in
, std::string
* out
) {
48 return in
.GetAsString(out
);
50 template<> bool GetAs(const base::Value
& in
, base::string16
* out
) {
51 return in
.GetAsString(out
);
53 template<> bool GetAs(const base::Value
& in
, std::vector
<base::string16
>* out
) {
55 const base::ListValue
* list
= NULL
;
56 if (!in
.GetAsList(&list
))
58 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
59 base::string16 element
;
60 if (!list
->GetString(i
, &element
)) {
64 out
->push_back(element
);
69 // The next several functions are helpers for making Callbacks that access
70 // DownloadItem fields.
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
));
84 base::string16
path(item
.GetTargetFilePath().LossyDisplayName());
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
)) {
101 static int64
GetStartTimeMsEpoch(const DownloadItem
& item
) {
102 return (item
.GetStartTime() - base::Time::UnixEpoch()).InMilliseconds();
105 static int64
GetEndTimeMsEpoch(const DownloadItem
& item
) {
106 return (item
.GetEndTime() - base::Time::UnixEpoch()).InMilliseconds();
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
);
118 static std::string
GetStartTime(const DownloadItem
& item
) {
119 return TimeToISO8601(item
.GetStartTime());
122 static std::string
GetEndTime(const DownloadItem
& item
) {
123 return TimeToISO8601(item
.GetEndTime());
126 static bool GetDangerAccepted(const DownloadItem
& item
) {
127 return (item
.GetDangerType() ==
128 content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED
);
131 static bool GetExists(const DownloadItem
& item
) {
132 return !item
.GetFileExternallyRemoved();
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();
141 static std::string
GetFilenameUTF8(const DownloadItem
& item
) {
142 return base::UTF16ToUTF8(GetFilename(item
));
145 static std::string
GetUrl(const DownloadItem
& item
) {
146 return item
.GetOriginalUrl().spec();
149 static DownloadItem::DownloadState
GetState(const DownloadItem
& item
) {
150 return item
.GetState();
153 static DownloadDangerType
GetDangerType(const DownloadItem
& item
) {
154 return item
.GetDangerType();
157 static int GetReceivedBytes(const DownloadItem
& item
) {
158 return item
.GetReceivedBytes();
161 static int GetTotalBytes(const DownloadItem
& item
) {
162 return item
.GetTotalBytes();
165 static std::string
GetMimeType(const DownloadItem
& item
) {
166 return item
.GetMimeType();
169 static bool IsPaused(const DownloadItem
& item
) {
170 return item
.IsPaused();
173 enum ComparisonType
{LT
, EQ
, GT
};
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
179 template<typename ValueType
>
180 static bool FieldMatches(
181 const ValueType
& value
,
182 ComparisonType cmptype
,
183 const base::Callback
<ValueType(const DownloadItem
&)>& accessor
,
184 const DownloadItem
& item
) {
186 case LT
: return accessor
.Run(item
) < value
;
187 case EQ
: return accessor
.Run(item
) == value
;
188 case GT
: return accessor
.Run(item
) > value
;
194 // Helper for building a Callback to FieldMatches<>().
195 template <typename ValueType
> DownloadQuery::FilterCallback
BuildFilter(
196 const base::Value
& value
, ComparisonType cmptype
,
197 ValueType (*accessor
)(const DownloadItem
&)) {
199 if (!GetAs(value
, &cpp_value
)) return DownloadQuery::FilterCallback();
200 return base::Bind(&FieldMatches
<ValueType
>, cpp_value
, cmptype
,
201 base::Bind(accessor
));
204 // Returns true if |accessor.Run(item)| matches |pattern|.
205 static bool FindRegex(
207 const base::Callback
<std::string(const DownloadItem
&)>& accessor
,
208 const DownloadItem
& item
) {
209 return RE2::PartialMatch(accessor
.Run(item
), *pattern
);
212 // Helper for building a Callback to FindRegex().
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
));
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
>
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
);
238 } // anonymous namespace
240 DownloadQuery::DownloadQuery()
241 : limit_(kuint32max
) {
244 DownloadQuery::~DownloadQuery() {
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.
252 bool DownloadQuery::AddFilter(const DownloadQuery::FilterCallback
& value
) {
253 if (value
.is_null()) return false;
254 filters_
.push_back(value
);
258 void DownloadQuery::AddFilter(DownloadItem::DownloadState state
) {
259 AddFilter(base::Bind(&FieldMatches
<DownloadItem::DownloadState
>, state
, EQ
,
260 base::Bind(&GetState
)));
263 void DownloadQuery::AddFilter(DownloadDangerType danger
) {
264 AddFilter(base::Bind(&FieldMatches
<DownloadDangerType
>, danger
, EQ
,
265 base::Bind(&GetDangerType
)));
268 bool DownloadQuery::AddFilter(DownloadQuery::FilterType type
,
269 const base::Value
& value
) {
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
));
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
));
282 return AddFilter(BuildFilter
<std::string
>(value
, EQ
, &GetMimeType
));
284 return AddFilter(BuildFilter
<bool>(value
, EQ
, &IsPaused
));
286 std::vector
<base::string16
> query_terms
;
287 return GetAs(value
, &query_terms
) &&
288 (query_terms
.empty() ||
289 AddFilter(base::Bind(&MatchesQuery
, query_terms
)));
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
));
310 return AddFilter(BuildFilter
<std::string
>(value
, EQ
, &GetUrl
));
311 case FILTER_URL_REGEX
:
312 return AddFilter(BuildRegexFilter(value
, &GetUrl
));
317 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
))
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().
338 struct DownloadQuery::Sorter
{
339 typedef base::Callback
<ComparisonType(
340 const DownloadItem
&, const DownloadItem
&)> SortType
;
342 template<typename ValueType
>
343 static Sorter
Build(DownloadQuery::SortDirection adirection
,
344 ValueType (*accessor
)(const DownloadItem
&)) {
345 return Sorter(adirection
, base::Bind(&Compare
<ValueType
>,
346 base::Bind(accessor
)));
349 Sorter(DownloadQuery::SortDirection adirection
,
350 const SortType
& asorter
)
351 : direction(adirection
),
356 DownloadQuery::SortDirection direction
;
360 class DownloadQuery::DownloadComparator
{
362 explicit DownloadComparator(const DownloadQuery::SorterVector
& terms
)
366 // Returns true if |left| sorts before |right|.
367 bool operator() (const DownloadItem
* left
, const DownloadItem
* right
);
370 const DownloadQuery::SorterVector
& terms_
;
372 // std::sort requires this class to be copyable.
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
385 CHECK_NE(left
->GetId(), right
->GetId());
386 return left
->GetId() < right
->GetId();
389 void DownloadQuery::AddSorter(DownloadQuery::SortType type
,
390 DownloadQuery::SortDirection direction
) {
393 sorters_
.push_back(Sorter::Build
<int64
>(direction
, &GetEndTimeMsEpoch
));
395 case SORT_START_TIME
:
396 sorters_
.push_back(Sorter::Build
<int64
>(direction
, &GetStartTimeMsEpoch
));
399 sorters_
.push_back(Sorter::Build
<std::string
>(direction
, &GetUrl
));
403 Sorter::Build
<base::string16
>(direction
, &GetFilename
));
406 sorters_
.push_back(Sorter::Build
<DownloadDangerType
>(
407 direction
, &GetDangerType
));
409 case SORT_DANGER_ACCEPTED
:
410 sorters_
.push_back(Sorter::Build
<bool>(direction
, &GetDangerAccepted
));
413 sorters_
.push_back(Sorter::Build
<bool>(direction
, &GetExists
));
416 sorters_
.push_back(Sorter::Build
<DownloadItem::DownloadState
>(
417 direction
, &GetState
));
420 sorters_
.push_back(Sorter::Build
<bool>(direction
, &IsPaused
));
423 sorters_
.push_back(Sorter::Build
<std::string
>(direction
, &GetMimeType
));
425 case SORT_BYTES_RECEIVED
:
426 sorters_
.push_back(Sorter::Build
<int>(direction
, &GetReceivedBytes
));
428 case SORT_TOTAL_BYTES
:
429 sorters_
.push_back(Sorter::Build
<int>(direction
, &GetTotalBytes
));
434 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()),
439 DownloadComparator(sorters_
));
440 if (results
->size() > limit_
)
441 results
->resize(limit_
);