Home | History | Annotate | Download | only in browser
      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 "components/bookmarks/browser/bookmark_utils.h"
      6 
      7 #include <utility>
      8 
      9 #include "base/basictypes.h"
     10 #include "base/files/file_path.h"
     11 #include "base/i18n/case_conversion.h"
     12 #include "base/i18n/string_search.h"
     13 #include "base/metrics/user_metrics_action.h"
     14 #include "base/prefs/pref_service.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "base/time/time.h"
     17 #include "components/bookmarks/browser/bookmark_client.h"
     18 #include "components/bookmarks/browser/bookmark_model.h"
     19 #include "components/bookmarks/browser/scoped_group_bookmark_actions.h"
     20 #include "components/bookmarks/common/bookmark_pref_names.h"
     21 #include "components/pref_registry/pref_registry_syncable.h"
     22 #include "components/query_parser/query_parser.h"
     23 #include "net/base/net_util.h"
     24 #include "ui/base/models/tree_node_iterator.h"
     25 #include "url/gurl.h"
     26 
     27 using base::Time;
     28 
     29 namespace {
     30 
     31 // The maximum length of URL or title returned by the Cleanup functions.
     32 const size_t kCleanedUpUrlMaxLength = 1024u;
     33 const size_t kCleanedUpTitleMaxLength = 1024u;
     34 
     35 void CloneBookmarkNodeImpl(BookmarkModel* model,
     36                            const BookmarkNodeData::Element& element,
     37                            const BookmarkNode* parent,
     38                            int index_to_add_at,
     39                            bool reset_node_times) {
     40   if (element.is_url) {
     41     Time date_added = reset_node_times ? Time::Now() : element.date_added;
     42     DCHECK(!date_added.is_null());
     43 
     44     model->AddURLWithCreationTimeAndMetaInfo(parent,
     45                                              index_to_add_at,
     46                                              element.title,
     47                                              element.url,
     48                                              date_added,
     49                                              &element.meta_info_map);
     50   } else {
     51     const BookmarkNode* cloned_node = model->AddFolderWithMetaInfo(
     52         parent, index_to_add_at, element.title, &element.meta_info_map);
     53     if (!reset_node_times) {
     54       DCHECK(!element.date_folder_modified.is_null());
     55       model->SetDateFolderModified(cloned_node, element.date_folder_modified);
     56     }
     57     for (int i = 0; i < static_cast<int>(element.children.size()); ++i)
     58       CloneBookmarkNodeImpl(model, element.children[i], cloned_node, i,
     59                             reset_node_times);
     60   }
     61 }
     62 
     63 // Comparison function that compares based on date modified of the two nodes.
     64 bool MoreRecentlyModified(const BookmarkNode* n1, const BookmarkNode* n2) {
     65   return n1->date_folder_modified() > n2->date_folder_modified();
     66 }
     67 
     68 // Returns true if |text| contains each string in |words|. This is used when
     69 // searching for bookmarks.
     70 bool DoesBookmarkTextContainWords(const base::string16& text,
     71                                   const std::vector<base::string16>& words) {
     72   for (size_t i = 0; i < words.size(); ++i) {
     73     if (!base::i18n::StringSearchIgnoringCaseAndAccents(
     74             words[i], text, NULL, NULL)) {
     75       return false;
     76     }
     77   }
     78   return true;
     79 }
     80 
     81 // Returns true if |node|s title or url contains the strings in |words|.
     82 // |languages| argument is user's accept-language setting to decode IDN.
     83 bool DoesBookmarkContainWords(const BookmarkNode* node,
     84                               const std::vector<base::string16>& words,
     85                               const std::string& languages) {
     86   return
     87       DoesBookmarkTextContainWords(node->GetTitle(), words) ||
     88       DoesBookmarkTextContainWords(
     89           base::UTF8ToUTF16(node->url().spec()), words) ||
     90       DoesBookmarkTextContainWords(net::FormatUrl(
     91           node->url(), languages, net::kFormatUrlOmitNothing,
     92           net::UnescapeRule::NORMAL, NULL, NULL, NULL), words);
     93 }
     94 
     95 // This is used with a tree iterator to skip subtrees which are not visible.
     96 bool PruneInvisibleFolders(const BookmarkNode* node) {
     97   return !node->IsVisible();
     98 }
     99 
    100 // This traces parents up to root, determines if node is contained in a
    101 // selected folder.
    102 bool HasSelectedAncestor(BookmarkModel* model,
    103                          const std::vector<const BookmarkNode*>& selected_nodes,
    104                          const BookmarkNode* node) {
    105   if (!node || model->is_permanent_node(node))
    106     return false;
    107 
    108   for (size_t i = 0; i < selected_nodes.size(); ++i)
    109     if (node->id() == selected_nodes[i]->id())
    110       return true;
    111 
    112   return HasSelectedAncestor(model, selected_nodes, node->parent());
    113 }
    114 
    115 const BookmarkNode* GetNodeByID(const BookmarkNode* node, int64 id) {
    116   if (node->id() == id)
    117     return node;
    118 
    119   for (int i = 0, child_count = node->child_count(); i < child_count; ++i) {
    120     const BookmarkNode* result = GetNodeByID(node->GetChild(i), id);
    121     if (result)
    122       return result;
    123   }
    124   return NULL;
    125 }
    126 
    127 // Attempts to shorten a URL safely (i.e., by preventing the end of the URL
    128 // from being in the middle of an escape sequence) to no more than
    129 // kCleanedUpUrlMaxLength characters, returning the result.
    130 std::string TruncateUrl(const std::string& url) {
    131   if (url.length() <= kCleanedUpUrlMaxLength)
    132     return url;
    133 
    134   // If we're in the middle of an escape sequence, truncate just before it.
    135   if (url[kCleanedUpUrlMaxLength - 1] == '%')
    136     return url.substr(0, kCleanedUpUrlMaxLength - 1);
    137   if (url[kCleanedUpUrlMaxLength - 2] == '%')
    138     return url.substr(0, kCleanedUpUrlMaxLength - 2);
    139 
    140   return url.substr(0, kCleanedUpUrlMaxLength);
    141 }
    142 
    143 }  // namespace
    144 
    145 namespace bookmark_utils {
    146 
    147 QueryFields::QueryFields() {}
    148 QueryFields::~QueryFields() {}
    149 
    150 void CloneBookmarkNode(BookmarkModel* model,
    151                        const std::vector<BookmarkNodeData::Element>& elements,
    152                        const BookmarkNode* parent,
    153                        int index_to_add_at,
    154                        bool reset_node_times) {
    155   if (!parent->is_folder() || !model) {
    156     NOTREACHED();
    157     return;
    158   }
    159   for (size_t i = 0; i < elements.size(); ++i) {
    160     CloneBookmarkNodeImpl(model, elements[i], parent,
    161                           index_to_add_at + static_cast<int>(i),
    162                           reset_node_times);
    163   }
    164 }
    165 
    166 void CopyToClipboard(BookmarkModel* model,
    167                      const std::vector<const BookmarkNode*>& nodes,
    168                      bool remove_nodes) {
    169   if (nodes.empty())
    170     return;
    171 
    172   // Create array of selected nodes with descendants filtered out.
    173   std::vector<const BookmarkNode*> filtered_nodes;
    174   for (size_t i = 0; i < nodes.size(); ++i)
    175     if (!HasSelectedAncestor(model, nodes, nodes[i]->parent()))
    176       filtered_nodes.push_back(nodes[i]);
    177 
    178   BookmarkNodeData(filtered_nodes).
    179       WriteToClipboard(ui::CLIPBOARD_TYPE_COPY_PASTE);
    180 
    181   if (remove_nodes) {
    182     bookmarks::ScopedGroupBookmarkActions group_cut(model);
    183     for (size_t i = 0; i < filtered_nodes.size(); ++i) {
    184       int index = filtered_nodes[i]->parent()->GetIndexOf(filtered_nodes[i]);
    185       if (index > -1)
    186         model->Remove(filtered_nodes[i]->parent(), index);
    187     }
    188   }
    189 }
    190 
    191 void PasteFromClipboard(BookmarkModel* model,
    192                         const BookmarkNode* parent,
    193                         int index) {
    194   if (!parent)
    195     return;
    196 
    197   BookmarkNodeData bookmark_data;
    198   if (!bookmark_data.ReadFromClipboard(ui::CLIPBOARD_TYPE_COPY_PASTE))
    199     return;
    200 
    201   if (index == -1)
    202     index = parent->child_count();
    203   bookmarks::ScopedGroupBookmarkActions group_paste(model);
    204   CloneBookmarkNode(model, bookmark_data.elements, parent, index, true);
    205 }
    206 
    207 bool CanPasteFromClipboard(BookmarkModel* model, const BookmarkNode* node) {
    208   if (!node || !model->client()->CanBeEditedByUser(node))
    209     return false;
    210   return BookmarkNodeData::ClipboardContainsBookmarks();
    211 }
    212 
    213 std::vector<const BookmarkNode*> GetMostRecentlyModifiedUserFolders(
    214     BookmarkModel* model,
    215     size_t max_count) {
    216   std::vector<const BookmarkNode*> nodes;
    217   ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node(),
    218                                                     PruneInvisibleFolders);
    219 
    220   while (iterator.has_next()) {
    221     const BookmarkNode* parent = iterator.Next();
    222     if (!model->client()->CanBeEditedByUser(parent))
    223       continue;
    224     if (parent->is_folder() && parent->date_folder_modified() > Time()) {
    225       if (max_count == 0) {
    226         nodes.push_back(parent);
    227       } else {
    228         std::vector<const BookmarkNode*>::iterator i =
    229             std::upper_bound(nodes.begin(), nodes.end(), parent,
    230                              &MoreRecentlyModified);
    231         if (nodes.size() < max_count || i != nodes.end()) {
    232           nodes.insert(i, parent);
    233           while (nodes.size() > max_count)
    234             nodes.pop_back();
    235         }
    236       }
    237     }  // else case, the root node, which we don't care about or imported nodes
    238        // (which have a time of 0).
    239   }
    240 
    241   if (nodes.size() < max_count) {
    242     // Add the permanent nodes if there is space. The permanent nodes are the
    243     // only children of the root_node.
    244     const BookmarkNode* root_node = model->root_node();
    245 
    246     for (int i = 0; i < root_node->child_count(); ++i) {
    247       const BookmarkNode* node = root_node->GetChild(i);
    248       if (node->IsVisible() && model->client()->CanBeEditedByUser(node) &&
    249           std::find(nodes.begin(), nodes.end(), node) == nodes.end()) {
    250         nodes.push_back(node);
    251 
    252         if (nodes.size() == max_count)
    253           break;
    254       }
    255     }
    256   }
    257   return nodes;
    258 }
    259 
    260 void GetMostRecentlyAddedEntries(BookmarkModel* model,
    261                                  size_t count,
    262                                  std::vector<const BookmarkNode*>* nodes) {
    263   ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
    264   while (iterator.has_next()) {
    265     const BookmarkNode* node = iterator.Next();
    266     if (node->is_url()) {
    267       std::vector<const BookmarkNode*>::iterator insert_position =
    268           std::upper_bound(nodes->begin(), nodes->end(), node,
    269                            &MoreRecentlyAdded);
    270       if (nodes->size() < count || insert_position != nodes->end()) {
    271         nodes->insert(insert_position, node);
    272         while (nodes->size() > count)
    273           nodes->pop_back();
    274       }
    275     }
    276   }
    277 }
    278 
    279 bool MoreRecentlyAdded(const BookmarkNode* n1, const BookmarkNode* n2) {
    280   return n1->date_added() > n2->date_added();
    281 }
    282 
    283 void GetBookmarksMatchingProperties(BookmarkModel* model,
    284                                     const QueryFields& query,
    285                                     size_t max_count,
    286                                     const std::string& languages,
    287                                     std::vector<const BookmarkNode*>* nodes) {
    288   std::vector<base::string16> query_words;
    289   query_parser::QueryParser parser;
    290   if (query.word_phrase_query) {
    291     parser.ParseQueryWords(base::i18n::ToLower(*query.word_phrase_query),
    292                            &query_words);
    293     if (query_words.empty())
    294       return;
    295   }
    296 
    297   ui::TreeNodeIterator<const BookmarkNode> iterator(model->root_node());
    298   while (iterator.has_next()) {
    299     const BookmarkNode* node = iterator.Next();
    300     if ((!query_words.empty() &&
    301         !DoesBookmarkContainWords(node, query_words, languages)) ||
    302         model->is_permanent_node(node)) {
    303       continue;
    304     }
    305     if (query.url) {
    306       // Check against bare url spec and IDN-decoded url.
    307       if (!node->is_url() ||
    308           !(base::UTF8ToUTF16(node->url().spec()) == *query.url ||
    309             net::FormatUrl(
    310                 node->url(), languages, net::kFormatUrlOmitNothing,
    311                 net::UnescapeRule::NORMAL, NULL, NULL, NULL) == *query.url)) {
    312         continue;
    313       }
    314     }
    315     if (query.title && node->GetTitle() != *query.title)
    316       continue;
    317 
    318     nodes->push_back(node);
    319     if (nodes->size() == max_count)
    320       return;
    321   }
    322 }
    323 
    324 void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
    325   registry->RegisterBooleanPref(
    326       prefs::kShowBookmarkBar,
    327       false,
    328       user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
    329   registry->RegisterBooleanPref(
    330       prefs::kEditBookmarksEnabled,
    331       true,
    332       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
    333   registry->RegisterBooleanPref(
    334       prefs::kShowAppsShortcutInBookmarkBar,
    335       true,
    336       user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
    337   registry->RegisterBooleanPref(
    338       prefs::kShowManagedBookmarksInBookmarkBar,
    339       true,
    340       user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
    341 }
    342 
    343 const BookmarkNode* GetParentForNewNodes(
    344     const BookmarkNode* parent,
    345     const std::vector<const BookmarkNode*>& selection,
    346     int* index) {
    347   const BookmarkNode* real_parent = parent;
    348 
    349   if (selection.size() == 1 && selection[0]->is_folder())
    350     real_parent = selection[0];
    351 
    352   if (index) {
    353     if (selection.size() == 1 && selection[0]->is_url()) {
    354       *index = real_parent->GetIndexOf(selection[0]) + 1;
    355       if (*index == 0) {
    356         // Node doesn't exist in parent, add to end.
    357         NOTREACHED();
    358         *index = real_parent->child_count();
    359       }
    360     } else {
    361       *index = real_parent->child_count();
    362     }
    363   }
    364 
    365   return real_parent;
    366 }
    367 
    368 void DeleteBookmarkFolders(BookmarkModel* model,
    369                            const std::vector<int64>& ids) {
    370   // Remove the folders that were removed. This has to be done after all the
    371   // other changes have been committed.
    372   for (std::vector<int64>::const_iterator iter = ids.begin();
    373        iter != ids.end();
    374        ++iter) {
    375     const BookmarkNode* node = GetBookmarkNodeByID(model, *iter);
    376     if (!node)
    377       continue;
    378     const BookmarkNode* parent = node->parent();
    379     model->Remove(parent, parent->GetIndexOf(node));
    380   }
    381 }
    382 
    383 void AddIfNotBookmarked(BookmarkModel* model,
    384                         const GURL& url,
    385                         const base::string16& title) {
    386   if (IsBookmarkedByUser(model, url))
    387     return;  // Nothing to do, a user bookmark with that url already exists.
    388   model->client()->RecordAction(base::UserMetricsAction("BookmarkAdded"));
    389   const BookmarkNode* parent = model->GetParentForNewNodes();
    390   model->AddURL(parent, parent->child_count(), title, url);
    391 }
    392 
    393 void RemoveAllBookmarks(BookmarkModel* model, const GURL& url) {
    394   std::vector<const BookmarkNode*> bookmarks;
    395   model->GetNodesByURL(url, &bookmarks);
    396 
    397   // Remove all the user bookmarks.
    398   for (size_t i = 0; i < bookmarks.size(); ++i) {
    399     const BookmarkNode* node = bookmarks[i];
    400     int index = node->parent()->GetIndexOf(node);
    401     if (index > -1 && model->client()->CanBeEditedByUser(node))
    402       model->Remove(node->parent(), index);
    403   }
    404 }
    405 
    406 base::string16 CleanUpUrlForMatching(
    407     const GURL& gurl,
    408     const std::string& languages,
    409     base::OffsetAdjuster::Adjustments* adjustments) {
    410   base::OffsetAdjuster::Adjustments tmp_adjustments;
    411   return base::i18n::ToLower(net::FormatUrlWithAdjustments(
    412       GURL(TruncateUrl(gurl.spec())), languages,
    413       net::kFormatUrlOmitUsernamePassword,
    414       net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS,
    415       NULL, NULL, adjustments ? adjustments : &tmp_adjustments));
    416 }
    417 
    418 base::string16 CleanUpTitleForMatching(const base::string16& title) {
    419   return base::i18n::ToLower(title.substr(0u, kCleanedUpTitleMaxLength));
    420 }
    421 
    422 bool CanAllBeEditedByUser(BookmarkClient* client,
    423                           const std::vector<const BookmarkNode*>& nodes) {
    424   for (size_t i = 0; i < nodes.size(); ++i) {
    425     if (!client->CanBeEditedByUser(nodes[i]))
    426       return false;
    427   }
    428   return true;
    429 }
    430 
    431 bool IsBookmarkedByUser(BookmarkModel* model, const GURL& url) {
    432   std::vector<const BookmarkNode*> nodes;
    433   model->GetNodesByURL(url, &nodes);
    434   for (size_t i = 0; i < nodes.size(); ++i) {
    435     if (model->client()->CanBeEditedByUser(nodes[i]))
    436       return true;
    437   }
    438   return false;
    439 }
    440 
    441 }  // namespace bookmark_utils
    442 
    443 const BookmarkNode* GetBookmarkNodeByID(const BookmarkModel* model, int64 id) {
    444   // TODO(sky): TreeNode needs a method that visits all nodes using a predicate.
    445   return GetNodeByID(model->root_node(), id);
    446 }
    447