Home | History | Annotate | Download | only in 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);
     41 template<> bool GetAs(const base::Value& in, bool* out) {
     42   return in.GetAsBoolean(out);
     43 }
     44 template<> bool GetAs(const base::Value& in, int* out) {
     45   return in.GetAsInteger(out);
     46 }
     47 template<> bool GetAs(const base::Value& in, std::string* out) {
     48   return in.GetAsString(out);
     49 }
     50 template<> bool GetAs(const base::Value& in, string16* out) {
     51   return in.GetAsString(out);
     52 }
     53 template<> bool GetAs(const base::Value& in, std::vector<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     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 
     72 static bool MatchesQuery(
     73     const std::vector<string16>& query_terms,
     74     const DownloadItem& item) {
     75   DCHECK(!query_terms.empty());
     76   string16 url_raw(UTF8ToUTF16(item.GetOriginalUrl().spec()));
     77   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   string16 path(item.GetTargetFilePath().LossyDisplayName());
     85 
     86   for (std::vector<string16>::const_iterator it = query_terms.begin();
     87        it != query_terms.end(); ++it) {
     88     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 
    101 static int64 GetStartTimeMsEpoch(const DownloadItem& item) {
    102   return (item.GetStartTime() - base::Time::UnixEpoch()).InMilliseconds();
    103 }
    104 
    105 static int64 GetEndTimeMsEpoch(const DownloadItem& item) {
    106   return (item.GetEndTime() - base::Time::UnixEpoch()).InMilliseconds();
    107 }
    108 
    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 
    118 static std::string GetStartTime(const DownloadItem& item) {
    119   return TimeToISO8601(item.GetStartTime());
    120 }
    121 
    122 static std::string GetEndTime(const DownloadItem& item) {
    123   return TimeToISO8601(item.GetEndTime());
    124 }
    125 
    126 static bool GetDangerAccepted(const DownloadItem& item) {
    127   return (item.GetDangerType() ==
    128           content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED);
    129 }
    130 
    131 static bool GetExists(const DownloadItem& item) {
    132   return !item.GetFileExternallyRemoved();
    133 }
    134 
    135 static 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 
    141 static std::string GetFilenameUTF8(const DownloadItem& item) {
    142   return UTF16ToUTF8(GetFilename(item));
    143 }
    144 
    145 static std::string GetUrl(const DownloadItem& item) {
    146   return item.GetOriginalUrl().spec();
    147 }
    148 
    149 static DownloadItem::DownloadState GetState(const DownloadItem& item) {
    150   return item.GetState();
    151 }
    152 
    153 static DownloadDangerType GetDangerType(const DownloadItem& item) {
    154   return item.GetDangerType();
    155 }
    156 
    157 static int GetReceivedBytes(const DownloadItem& item) {
    158   return item.GetReceivedBytes();
    159 }
    160 
    161 static int GetTotalBytes(const DownloadItem& item) {
    162   return item.GetTotalBytes();
    163 }
    164 
    165 static std::string GetMimeType(const DownloadItem& item) {
    166   return item.GetMimeType();
    167 }
    168 
    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>
    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<>().
    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|.
    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().
    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>
    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 
    240 DownloadQuery::DownloadQuery()
    241   : limit_(kuint32max) {
    242 }
    243 
    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 
    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 
    258 void DownloadQuery::AddFilter(DownloadItem::DownloadState state) {
    259   AddFilter(base::Bind(&FieldMatches<DownloadItem::DownloadState>, state, EQ,
    260       base::Bind(&GetState)));
    261 }
    262 
    263 void DownloadQuery::AddFilter(DownloadDangerType danger) {
    264   AddFilter(base::Bind(&FieldMatches<DownloadDangerType>, danger, EQ,
    265       base::Bind(&GetDangerType)));
    266 }
    267 
    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<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<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 
    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))
    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>
    343   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 
    349   Sorter(DownloadQuery::SortDirection adirection,
    350             const SortType& asorter)
    351     : direction(adirection),
    352       sorter(asorter) {
    353   }
    354   ~Sorter() {}
    355 
    356   DownloadQuery::SortDirection direction;
    357   SortType sorter;
    358 };
    359 
    360 class DownloadQuery::DownloadComparator {
    361  public:
    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 
    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 
    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(Sorter::Build<string16>(direction, &GetFilename));
    403       break;
    404     case SORT_DANGER:
    405       sorters_.push_back(Sorter::Build<DownloadDangerType>(
    406           direction, &GetDangerType));
    407       break;
    408     case SORT_DANGER_ACCEPTED:
    409       sorters_.push_back(Sorter::Build<bool>(direction, &GetDangerAccepted));
    410       break;
    411     case SORT_EXISTS:
    412       sorters_.push_back(Sorter::Build<bool>(direction, &GetExists));
    413       break;
    414     case SORT_STATE:
    415       sorters_.push_back(Sorter::Build<DownloadItem::DownloadState>(
    416           direction, &GetState));
    417       break;
    418     case SORT_PAUSED:
    419       sorters_.push_back(Sorter::Build<bool>(direction, &IsPaused));
    420       break;
    421     case SORT_MIME:
    422       sorters_.push_back(Sorter::Build<std::string>(direction, &GetMimeType));
    423       break;
    424     case SORT_BYTES_RECEIVED:
    425       sorters_.push_back(Sorter::Build<int>(direction, &GetReceivedBytes));
    426       break;
    427     case SORT_TOTAL_BYTES:
    428       sorters_.push_back(Sorter::Build<int>(direction, &GetTotalBytes));
    429       break;
    430   }
    431 }
    432 
    433 void DownloadQuery::FinishSearch(DownloadQuery::DownloadVector* results) const {
    434   if (!sorters_.empty())
    435     std::partial_sort(results->begin(),
    436                       results->begin() + std::min(limit_, results->size()),
    437                       results->end(),
    438                       DownloadComparator(sorters_));
    439   if (results->size() > limit_)
    440     results->resize(limit_);
    441 }
    442