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