• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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/extensions/extension_bookmarks_module.h"
6 
7 #include "base/file_path.h"
8 #include "base/i18n/file_util_icu.h"
9 #include "base/i18n/time_formatting.h"
10 #include "base/json/json_writer.h"
11 #include "base/path_service.h"
12 #include "base/sha1.h"
13 #include "base/stl_util-inl.h"
14 #include "base/string16.h"
15 #include "base/string_number_conversions.h"
16 #include "base/string_util.h"
17 #include "base/time.h"
18 #include "base/utf_string_conversions.h"
19 #include "chrome/browser/bookmarks/bookmark_codec.h"
20 #include "chrome/browser/bookmarks/bookmark_html_writer.h"
21 #include "chrome/browser/bookmarks/bookmark_model.h"
22 #include "chrome/browser/bookmarks/bookmark_utils.h"
23 #include "chrome/browser/extensions/extension_bookmark_helpers.h"
24 #include "chrome/browser/extensions/extension_bookmarks_module_constants.h"
25 #include "chrome/browser/extensions/extension_event_router.h"
26 #include "chrome/browser/extensions/extensions_quota_service.h"
27 #include "chrome/browser/importer/importer_data_types.h"
28 #include "chrome/browser/importer/importer_host.h"
29 #include "chrome/browser/prefs/pref_service.h"
30 #include "chrome/browser/profiles/profile.h"
31 #include "chrome/browser/ui/browser_list.h"
32 #include "chrome/common/chrome_paths.h"
33 #include "chrome/common/pref_names.h"
34 #include "content/common/notification_service.h"
35 #include "grit/generated_resources.h"
36 #include "ui/base/l10n/l10n_util.h"
37 
38 namespace keys = extension_bookmarks_module_constants;
39 
40 using base::TimeDelta;
41 typedef QuotaLimitHeuristic::Bucket Bucket;
42 typedef QuotaLimitHeuristic::Config Config;
43 typedef QuotaLimitHeuristic::BucketList BucketList;
44 typedef ExtensionsQuotaService::TimedLimit TimedLimit;
45 typedef ExtensionsQuotaService::SustainedLimit SustainedLimit;
46 typedef QuotaLimitHeuristic::BucketMapper BucketMapper;
47 
48 namespace {
49 
50 // Generates a default path (including a default filename) that will be
51 // used for pre-populating the "Export Bookmarks" file chooser dialog box.
GetDefaultFilepathForBookmarkExport()52 FilePath GetDefaultFilepathForBookmarkExport() {
53   base::Time time = base::Time::Now();
54 
55   // Concatenate a date stamp to the filename.
56 #if defined(OS_POSIX)
57   FilePath::StringType filename =
58       l10n_util::GetStringFUTF8(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
59                                 base::TimeFormatShortDateNumeric(time));
60 #elif defined(OS_WIN)
61   FilePath::StringType filename =
62       l10n_util::GetStringFUTF16(IDS_EXPORT_BOOKMARKS_DEFAULT_FILENAME,
63                                  base::TimeFormatShortDateNumeric(time));
64 #endif
65 
66   file_util::ReplaceIllegalCharactersInPath(&filename, '_');
67 
68   FilePath default_path;
69   PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_path);
70   return default_path.Append(filename);
71 }
72 
73 }  // namespace
74 
Run()75 void BookmarksFunction::Run() {
76   BookmarkModel* model = profile()->GetBookmarkModel();
77   if (!model->IsLoaded()) {
78     // Bookmarks are not ready yet.  We'll wait.
79     registrar_.Add(this, NotificationType::BOOKMARK_MODEL_LOADED,
80                    NotificationService::AllSources());
81     AddRef();  // Balanced in Observe().
82     return;
83   }
84 
85   bool success = RunImpl();
86   if (success) {
87     NotificationService::current()->Notify(
88         NotificationType::EXTENSION_BOOKMARKS_API_INVOKED,
89         Source<const Extension>(GetExtension()),
90         Details<const BookmarksFunction>(this));
91   }
92   SendResponse(success);
93 }
94 
GetBookmarkIdAsInt64(const std::string & id_string,int64 * id)95 bool BookmarksFunction::GetBookmarkIdAsInt64(
96     const std::string& id_string, int64* id) {
97   if (base::StringToInt64(id_string, id))
98     return true;
99 
100   error_ = keys::kInvalidIdError;
101   return false;
102 }
103 
EditBookmarksEnabled()104 bool BookmarksFunction::EditBookmarksEnabled() {
105   if (profile_->GetPrefs()->GetBoolean(prefs::kEditBookmarksEnabled))
106     return true;
107   error_ = keys::kEditBookmarksDisabled;
108   return false;
109 }
110 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)111 void BookmarksFunction::Observe(NotificationType type,
112                                 const NotificationSource& source,
113                                 const NotificationDetails& details) {
114   DCHECK(type == NotificationType::BOOKMARK_MODEL_LOADED);
115   DCHECK(profile()->GetBookmarkModel()->IsLoaded());
116   Run();
117   Release();  // Balanced in Run().
118 }
119 
120 // static
GetInstance()121 ExtensionBookmarkEventRouter* ExtensionBookmarkEventRouter::GetInstance() {
122   return Singleton<ExtensionBookmarkEventRouter>::get();
123 }
124 
ExtensionBookmarkEventRouter()125 ExtensionBookmarkEventRouter::ExtensionBookmarkEventRouter() {
126 }
127 
~ExtensionBookmarkEventRouter()128 ExtensionBookmarkEventRouter::~ExtensionBookmarkEventRouter() {
129 }
130 
Observe(BookmarkModel * model)131 void ExtensionBookmarkEventRouter::Observe(BookmarkModel* model) {
132   if (models_.find(model) == models_.end()) {
133     model->AddObserver(this);
134     models_.insert(model);
135   }
136 }
137 
DispatchEvent(Profile * profile,const char * event_name,const std::string & json_args)138 void ExtensionBookmarkEventRouter::DispatchEvent(Profile *profile,
139                                                  const char* event_name,
140                                                  const std::string& json_args) {
141   if (profile->GetExtensionEventRouter()) {
142     profile->GetExtensionEventRouter()->DispatchEventToRenderers(
143         event_name, json_args, NULL, GURL());
144   }
145 }
146 
Loaded(BookmarkModel * model)147 void ExtensionBookmarkEventRouter::Loaded(BookmarkModel* model) {
148   // TODO(erikkay): Perhaps we should send this event down to the extension
149   // so they know when it's safe to use the API?
150 }
151 
BookmarkNodeMoved(BookmarkModel * model,const BookmarkNode * old_parent,int old_index,const BookmarkNode * new_parent,int new_index)152 void ExtensionBookmarkEventRouter::BookmarkNodeMoved(
153     BookmarkModel* model,
154     const BookmarkNode* old_parent,
155     int old_index,
156     const BookmarkNode* new_parent,
157     int new_index) {
158   ListValue args;
159   const BookmarkNode* node = new_parent->GetChild(new_index);
160   args.Append(new StringValue(base::Int64ToString(node->id())));
161   DictionaryValue* object_args = new DictionaryValue();
162   object_args->SetString(keys::kParentIdKey,
163                          base::Int64ToString(new_parent->id()));
164   object_args->SetInteger(keys::kIndexKey, new_index);
165   object_args->SetString(keys::kOldParentIdKey,
166                          base::Int64ToString(old_parent->id()));
167   object_args->SetInteger(keys::kOldIndexKey, old_index);
168   args.Append(object_args);
169 
170   std::string json_args;
171   base::JSONWriter::Write(&args, false, &json_args);
172   DispatchEvent(model->profile(), keys::kOnBookmarkMoved, json_args);
173 }
174 
BookmarkNodeAdded(BookmarkModel * model,const BookmarkNode * parent,int index)175 void ExtensionBookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model,
176                                                      const BookmarkNode* parent,
177                                                      int index) {
178   ListValue args;
179   const BookmarkNode* node = parent->GetChild(index);
180   args.Append(new StringValue(base::Int64ToString(node->id())));
181   DictionaryValue* obj =
182       extension_bookmark_helpers::GetNodeDictionary(node, false, false);
183   args.Append(obj);
184 
185   std::string json_args;
186   base::JSONWriter::Write(&args, false, &json_args);
187   DispatchEvent(model->profile(), keys::kOnBookmarkCreated, json_args);
188 }
189 
BookmarkNodeRemoved(BookmarkModel * model,const BookmarkNode * parent,int index,const BookmarkNode * node)190 void ExtensionBookmarkEventRouter::BookmarkNodeRemoved(
191     BookmarkModel* model,
192     const BookmarkNode* parent,
193     int index,
194     const BookmarkNode* node) {
195   ListValue args;
196   args.Append(new StringValue(base::Int64ToString(node->id())));
197   DictionaryValue* object_args = new DictionaryValue();
198   object_args->SetString(keys::kParentIdKey,
199                          base::Int64ToString(parent->id()));
200   object_args->SetInteger(keys::kIndexKey, index);
201   args.Append(object_args);
202 
203   std::string json_args;
204   base::JSONWriter::Write(&args, false, &json_args);
205   DispatchEvent(model->profile(), keys::kOnBookmarkRemoved, json_args);
206 }
207 
BookmarkNodeChanged(BookmarkModel * model,const BookmarkNode * node)208 void ExtensionBookmarkEventRouter::BookmarkNodeChanged(
209     BookmarkModel* model, const BookmarkNode* node) {
210   ListValue args;
211   args.Append(new StringValue(base::Int64ToString(node->id())));
212 
213   // TODO(erikkay) The only three things that BookmarkModel sends this
214   // notification for are title, url and favicon.  Since we're currently
215   // ignoring favicon and since the notification doesn't say which one anyway,
216   // for now we only include title and url.  The ideal thing would be to change
217   // BookmarkModel to indicate what changed.
218   DictionaryValue* object_args = new DictionaryValue();
219   object_args->SetString(keys::kTitleKey, node->GetTitle());
220   if (node->is_url())
221     object_args->SetString(keys::kUrlKey, node->GetURL().spec());
222   args.Append(object_args);
223 
224   std::string json_args;
225   base::JSONWriter::Write(&args, false, &json_args);
226   DispatchEvent(model->profile(), keys::kOnBookmarkChanged, json_args);
227 }
228 
BookmarkNodeFaviconLoaded(BookmarkModel * model,const BookmarkNode * node)229 void ExtensionBookmarkEventRouter::BookmarkNodeFaviconLoaded(
230     BookmarkModel* model, const BookmarkNode* node) {
231   // TODO(erikkay) anything we should do here?
232 }
233 
BookmarkNodeChildrenReordered(BookmarkModel * model,const BookmarkNode * node)234 void ExtensionBookmarkEventRouter::BookmarkNodeChildrenReordered(
235     BookmarkModel* model, const BookmarkNode* node) {
236   ListValue args;
237   args.Append(new StringValue(base::Int64ToString(node->id())));
238   int childCount = node->child_count();
239   ListValue* children = new ListValue();
240   for (int i = 0; i < childCount; ++i) {
241     const BookmarkNode* child = node->GetChild(i);
242     Value* child_id = new StringValue(base::Int64ToString(child->id()));
243     children->Append(child_id);
244   }
245   DictionaryValue* reorder_info = new DictionaryValue();
246   reorder_info->Set(keys::kChildIdsKey, children);
247   args.Append(reorder_info);
248 
249   std::string json_args;
250   base::JSONWriter::Write(&args, false, &json_args);
251   DispatchEvent(model->profile(),
252                 keys::kOnBookmarkChildrenReordered,
253                 json_args);
254 }
255 
256 void ExtensionBookmarkEventRouter::
BookmarkImportBeginning(BookmarkModel * model)257     BookmarkImportBeginning(BookmarkModel* model) {
258   ListValue args;
259   std::string json_args;
260   base::JSONWriter::Write(&args, false, &json_args);
261   DispatchEvent(model->profile(),
262                 keys::kOnBookmarkImportBegan,
263                 json_args);
264 }
265 
BookmarkImportEnding(BookmarkModel * model)266 void ExtensionBookmarkEventRouter::BookmarkImportEnding(BookmarkModel* model) {
267   ListValue args;
268   std::string json_args;
269   base::JSONWriter::Write(&args, false, &json_args);
270   DispatchEvent(model->profile(),
271                 keys::kOnBookmarkImportEnded,
272                 json_args);
273 }
274 
RunImpl()275 bool GetBookmarksFunction::RunImpl() {
276   BookmarkModel* model = profile()->GetBookmarkModel();
277   scoped_ptr<ListValue> json(new ListValue());
278   Value* arg0;
279   EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &arg0));
280   if (arg0->IsType(Value::TYPE_LIST)) {
281     const ListValue* ids = static_cast<const ListValue*>(arg0);
282     size_t count = ids->GetSize();
283     EXTENSION_FUNCTION_VALIDATE(count > 0);
284     for (size_t i = 0; i < count; ++i) {
285       int64 id;
286       std::string id_string;
287       EXTENSION_FUNCTION_VALIDATE(ids->GetString(i, &id_string));
288       if (!GetBookmarkIdAsInt64(id_string, &id))
289         return false;
290       const BookmarkNode* node = model->GetNodeByID(id);
291       if (!node) {
292         error_ = keys::kNoNodeError;
293         return false;
294       } else {
295         extension_bookmark_helpers::AddNode(node, json.get(), false);
296       }
297     }
298   } else {
299     int64 id;
300     std::string id_string;
301     EXTENSION_FUNCTION_VALIDATE(arg0->GetAsString(&id_string));
302     if (!GetBookmarkIdAsInt64(id_string, &id))
303       return false;
304     const BookmarkNode* node = model->GetNodeByID(id);
305     if (!node) {
306       error_ = keys::kNoNodeError;
307       return false;
308     }
309     extension_bookmark_helpers::AddNode(node, json.get(), false);
310   }
311 
312   result_.reset(json.release());
313   return true;
314 }
315 
RunImpl()316 bool GetBookmarkChildrenFunction::RunImpl() {
317   BookmarkModel* model = profile()->GetBookmarkModel();
318   int64 id;
319   std::string id_string;
320   EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id_string));
321   if (!GetBookmarkIdAsInt64(id_string, &id))
322     return false;
323   scoped_ptr<ListValue> json(new ListValue());
324   const BookmarkNode* node = model->GetNodeByID(id);
325   if (!node) {
326     error_ = keys::kNoNodeError;
327     return false;
328   }
329   int child_count = node->child_count();
330   for (int i = 0; i < child_count; ++i) {
331     const BookmarkNode* child = node->GetChild(i);
332     extension_bookmark_helpers::AddNode(child, json.get(), false);
333   }
334 
335   result_.reset(json.release());
336   return true;
337 }
338 
RunImpl()339 bool GetBookmarkRecentFunction::RunImpl() {
340   int number_of_items;
341   EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &number_of_items));
342   if (number_of_items < 1)
343     return false;
344 
345   BookmarkModel* model = profile()->GetBookmarkModel();
346   ListValue* json = new ListValue();
347   std::vector<const BookmarkNode*> nodes;
348   bookmark_utils::GetMostRecentlyAddedEntries(model, number_of_items, &nodes);
349   std::vector<const BookmarkNode*>::iterator i = nodes.begin();
350   for (; i != nodes.end(); ++i) {
351     const BookmarkNode* node = *i;
352     extension_bookmark_helpers::AddNode(node, json, false);
353   }
354   result_.reset(json);
355   return true;
356 }
357 
RunImpl()358 bool GetBookmarkTreeFunction::RunImpl() {
359   BookmarkModel* model = profile()->GetBookmarkModel();
360   scoped_ptr<ListValue> json(new ListValue());
361   const BookmarkNode* node = model->root_node();
362   extension_bookmark_helpers::AddNode(node, json.get(), true);
363   result_.reset(json.release());
364   return true;
365 }
366 
RunImpl()367 bool SearchBookmarksFunction::RunImpl() {
368   string16 query;
369   EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &query));
370 
371   BookmarkModel* model = profile()->GetBookmarkModel();
372   ListValue* json = new ListValue();
373   std::string lang = profile()->GetPrefs()->GetString(prefs::kAcceptLanguages);
374   std::vector<const BookmarkNode*> nodes;
375   bookmark_utils::GetBookmarksContainingText(model, query,
376                                              std::numeric_limits<int>::max(),
377                                              lang, &nodes);
378   std::vector<const BookmarkNode*>::iterator i = nodes.begin();
379   for (; i != nodes.end(); ++i) {
380     const BookmarkNode* node = *i;
381     extension_bookmark_helpers::AddNode(node, json, false);
382   }
383 
384   result_.reset(json);
385   return true;
386 }
387 
388 // static
ExtractIds(const ListValue * args,std::list<int64> * ids,bool * invalid_id)389 bool RemoveBookmarkFunction::ExtractIds(const ListValue* args,
390                                         std::list<int64>* ids,
391                                         bool* invalid_id) {
392   std::string id_string;
393   if (!args->GetString(0, &id_string))
394     return false;
395   int64 id;
396   if (base::StringToInt64(id_string, &id))
397     ids->push_back(id);
398   else
399     *invalid_id = true;
400   return true;
401 }
402 
RunImpl()403 bool RemoveBookmarkFunction::RunImpl() {
404   if (!EditBookmarksEnabled())
405     return false;
406   std::list<int64> ids;
407   bool invalid_id = false;
408   EXTENSION_FUNCTION_VALIDATE(ExtractIds(args_.get(), &ids, &invalid_id));
409   if (invalid_id) {
410     error_ = keys::kInvalidIdError;
411     return false;
412   }
413   bool recursive = false;
414   if (name() == RemoveTreeBookmarkFunction::function_name())
415     recursive = true;
416 
417   BookmarkModel* model = profile()->GetBookmarkModel();
418   size_t count = ids.size();
419   EXTENSION_FUNCTION_VALIDATE(count > 0);
420   for (std::list<int64>::iterator it = ids.begin(); it != ids.end(); ++it) {
421     if (!extension_bookmark_helpers::RemoveNode(model, *it, recursive, &error_))
422       return false;
423   }
424   return true;
425 }
426 
RunImpl()427 bool CreateBookmarkFunction::RunImpl() {
428   if (!EditBookmarksEnabled())
429     return false;
430   DictionaryValue* json;
431   EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &json));
432   EXTENSION_FUNCTION_VALIDATE(json != NULL);
433 
434   BookmarkModel* model = profile()->GetBookmarkModel();
435   int64 parentId;
436   if (!json->HasKey(keys::kParentIdKey)) {
437     // Optional, default to "other bookmarks".
438     parentId = model->other_node()->id();
439   } else {
440     std::string parentId_string;
441     EXTENSION_FUNCTION_VALIDATE(json->GetString(keys::kParentIdKey,
442                                                 &parentId_string));
443     if (!GetBookmarkIdAsInt64(parentId_string, &parentId))
444       return false;
445   }
446   const BookmarkNode* parent = model->GetNodeByID(parentId);
447   if (!parent) {
448     error_ = keys::kNoParentError;
449     return false;
450   }
451   if (parent->parent() == NULL) {  // Can't create children of the root.
452     error_ = keys::kModifySpecialError;
453     return false;
454   }
455 
456   int index;
457   if (!json->HasKey(keys::kIndexKey)) {  // Optional (defaults to end).
458     index = parent->child_count();
459   } else {
460     EXTENSION_FUNCTION_VALIDATE(json->GetInteger(keys::kIndexKey, &index));
461     if (index > parent->child_count() || index < 0) {
462       error_ = keys::kInvalidIndexError;
463       return false;
464     }
465   }
466 
467   string16 title;
468   json->GetString(keys::kTitleKey, &title);  // Optional.
469   std::string url_string;
470   json->GetString(keys::kUrlKey, &url_string);  // Optional.
471   GURL url(url_string);
472   if (!url.is_empty() && !url.is_valid()) {
473     error_ = keys::kInvalidUrlError;
474     return false;
475   }
476 
477   const BookmarkNode* node;
478   if (url_string.length())
479     node = model->AddURL(parent, index, title, url);
480   else
481     node = model->AddFolder(parent, index, title);
482   DCHECK(node);
483   if (!node) {
484     error_ = keys::kNoNodeError;
485     return false;
486   }
487 
488   DictionaryValue* ret =
489       extension_bookmark_helpers::GetNodeDictionary(node, false, false);
490   result_.reset(ret);
491 
492   return true;
493 }
494 
495 // static
ExtractIds(const ListValue * args,std::list<int64> * ids,bool * invalid_id)496 bool MoveBookmarkFunction::ExtractIds(const ListValue* args,
497                                       std::list<int64>* ids,
498                                       bool* invalid_id) {
499   // For now, Move accepts ID parameters in the same way as an Update.
500   return UpdateBookmarkFunction::ExtractIds(args, ids, invalid_id);
501 }
502 
RunImpl()503 bool MoveBookmarkFunction::RunImpl() {
504   if (!EditBookmarksEnabled())
505     return false;
506   std::list<int64> ids;
507   bool invalid_id = false;
508   EXTENSION_FUNCTION_VALIDATE(ExtractIds(args_.get(), &ids, &invalid_id));
509   if (invalid_id) {
510     error_ = keys::kInvalidIdError;
511     return false;
512   }
513   EXTENSION_FUNCTION_VALIDATE(ids.size() == 1);
514 
515   DictionaryValue* destination;
516   EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &destination));
517 
518   BookmarkModel* model = profile()->GetBookmarkModel();
519   const BookmarkNode* node = model->GetNodeByID(ids.front());
520   if (!node) {
521     error_ = keys::kNoNodeError;
522     return false;
523   }
524   if (node == model->root_node() ||
525       node == model->other_node() ||
526       node == model->GetBookmarkBarNode()) {
527     error_ = keys::kModifySpecialError;
528     return false;
529   }
530 
531   const BookmarkNode* parent = NULL;
532   if (!destination->HasKey(keys::kParentIdKey)) {
533     // Optional, defaults to current parent.
534     parent = node->parent();
535   } else {
536     std::string parentId_string;
537     EXTENSION_FUNCTION_VALIDATE(destination->GetString(keys::kParentIdKey,
538         &parentId_string));
539     int64 parentId;
540     if (!GetBookmarkIdAsInt64(parentId_string, &parentId))
541       return false;
542 
543     parent = model->GetNodeByID(parentId);
544   }
545   if (!parent) {
546     error_ = keys::kNoParentError;
547     // TODO(erikkay) return an error message.
548     return false;
549   }
550   if (parent == model->root_node()) {
551     error_ = keys::kModifySpecialError;
552     return false;
553   }
554 
555   int index;
556   if (destination->HasKey(keys::kIndexKey)) {  // Optional (defaults to end).
557     EXTENSION_FUNCTION_VALIDATE(destination->GetInteger(keys::kIndexKey,
558                                                         &index));
559     if (index > parent->child_count() || index < 0) {
560       error_ = keys::kInvalidIndexError;
561       return false;
562     }
563   } else {
564     index = parent->child_count();
565   }
566 
567   model->Move(node, parent, index);
568 
569   DictionaryValue* ret =
570       extension_bookmark_helpers::GetNodeDictionary(node, false, false);
571   result_.reset(ret);
572 
573   return true;
574 }
575 
576 // static
ExtractIds(const ListValue * args,std::list<int64> * ids,bool * invalid_id)577 bool UpdateBookmarkFunction::ExtractIds(const ListValue* args,
578                                         std::list<int64>* ids,
579                                         bool* invalid_id) {
580   // For now, Update accepts ID parameters in the same way as an Remove.
581   return RemoveBookmarkFunction::ExtractIds(args, ids, invalid_id);
582 }
583 
RunImpl()584 bool UpdateBookmarkFunction::RunImpl() {
585   if (!EditBookmarksEnabled())
586     return false;
587   std::list<int64> ids;
588   bool invalid_id = false;
589   EXTENSION_FUNCTION_VALIDATE(ExtractIds(args_.get(), &ids, &invalid_id));
590   if (invalid_id) {
591     error_ = keys::kInvalidIdError;
592     return false;
593   }
594   EXTENSION_FUNCTION_VALIDATE(ids.size() == 1);
595 
596   DictionaryValue* updates;
597   EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &updates));
598 
599   // Optional but we need to distinguish non present from an empty title.
600   string16 title;
601   const bool has_title = updates->GetString(keys::kTitleKey, &title);
602 
603   // Optional.
604   std::string url_string;
605   updates->GetString(keys::kUrlKey, &url_string);
606   GURL url(url_string);
607   if (!url_string.empty() && !url.is_valid()) {
608     error_ = keys::kInvalidUrlError;
609     return false;
610   }
611 
612   BookmarkModel* model = profile()->GetBookmarkModel();
613   const BookmarkNode* node = model->GetNodeByID(ids.front());
614   if (!node) {
615     error_ = keys::kNoNodeError;
616     return false;
617   }
618   if (node == model->root_node() ||
619       node == model->other_node() ||
620       node == model->GetBookmarkBarNode()) {
621     error_ = keys::kModifySpecialError;
622     return false;
623   }
624   if (has_title)
625     model->SetTitle(node, title);
626   if (!url.is_empty())
627     model->SetURL(node, url);
628 
629   DictionaryValue* ret =
630       extension_bookmark_helpers::GetNodeDictionary(node, false, false);
631   result_.reset(ret);
632 
633   return true;
634 }
635 
636 // Mapper superclass for BookmarkFunctions.
637 template <typename BucketIdType>
638 class BookmarkBucketMapper : public BucketMapper {
639  public:
~BookmarkBucketMapper()640   virtual ~BookmarkBucketMapper() { STLDeleteValues(&buckets_); }
641  protected:
GetBucket(const BucketIdType & id)642   Bucket* GetBucket(const BucketIdType& id) {
643     Bucket* b = buckets_[id];
644     if (b == NULL) {
645       b = new Bucket();
646       buckets_[id] = b;
647     }
648     return b;
649   }
650  private:
651   std::map<BucketIdType, Bucket*> buckets_;
652 };
653 
654 // Mapper for 'bookmarks.create'.  Maps "same input to bookmarks.create" to a
655 // unique bucket.
656 class CreateBookmarkBucketMapper : public BookmarkBucketMapper<std::string> {
657  public:
CreateBookmarkBucketMapper(Profile * profile)658   explicit CreateBookmarkBucketMapper(Profile* profile) : profile_(profile) {}
659   // TODO(tim): This should share code with CreateBookmarkFunction::RunImpl,
660   // but I can't figure out a good way to do that with all the macros.
GetBucketsForArgs(const ListValue * args,BucketList * buckets)661   virtual void GetBucketsForArgs(const ListValue* args, BucketList* buckets) {
662     DictionaryValue* json;
663     if (!args->GetDictionary(0, &json))
664       return;
665 
666     std::string parent_id;
667     if (json->HasKey(keys::kParentIdKey)) {
668       if (!json->GetString(keys::kParentIdKey, &parent_id))
669         return;
670     }
671     BookmarkModel* model = profile_->GetBookmarkModel();
672 
673     int64 parent_id_int64;
674     base::StringToInt64(parent_id, &parent_id_int64);
675     const BookmarkNode* parent = model->GetNodeByID(parent_id_int64);
676     if (!parent)
677       return;
678 
679     std::string bucket_id = UTF16ToUTF8(parent->GetTitle());
680     std::string title;
681     json->GetString(keys::kTitleKey, &title);
682     std::string url_string;
683     json->GetString(keys::kUrlKey, &url_string);
684 
685     bucket_id += title;
686     bucket_id += url_string;
687     // 20 bytes (SHA1 hash length) is very likely less than most of the
688     // |bucket_id| strings we construct here, so we hash it to save space.
689     buckets->push_back(GetBucket(base::SHA1HashString(bucket_id)));
690   }
691  private:
692   Profile* profile_;
693 };
694 
695 // Mapper for 'bookmarks.remove'.
696 class RemoveBookmarksBucketMapper : public BookmarkBucketMapper<std::string> {
697  public:
RemoveBookmarksBucketMapper(Profile * profile)698   explicit RemoveBookmarksBucketMapper(Profile* profile) : profile_(profile) {}
GetBucketsForArgs(const ListValue * args,BucketList * buckets)699   virtual void GetBucketsForArgs(const ListValue* args, BucketList* buckets) {
700     typedef std::list<int64> IdList;
701     IdList ids;
702     bool invalid_id = false;
703     if (!RemoveBookmarkFunction::ExtractIds(args, &ids, &invalid_id) ||
704         invalid_id) {
705       return;
706     }
707 
708     for (IdList::iterator it = ids.begin(); it != ids.end(); ++it) {
709       BookmarkModel* model = profile_->GetBookmarkModel();
710       const BookmarkNode* node = model->GetNodeByID(*it);
711       if (!node || !node->parent())
712         return;
713 
714       std::string bucket_id;
715       bucket_id += UTF16ToUTF8(node->parent()->GetTitle());
716       bucket_id += UTF16ToUTF8(node->GetTitle());
717       bucket_id += node->GetURL().spec();
718       buckets->push_back(GetBucket(base::SHA1HashString(bucket_id)));
719     }
720   }
721  private:
722   Profile* profile_;
723 };
724 
725 // Mapper for any bookmark function accepting bookmark IDs as parameters, where
726 // a distinct ID corresponds to a single item in terms of quota limiting.  This
727 // is inappropriate for bookmarks.remove, for example, since repeated removals
728 // of the same item will actually have a different ID each time.
729 template <class FunctionType>
730 class BookmarkIdMapper : public BookmarkBucketMapper<int64> {
731  public:
732   typedef std::list<int64> IdList;
GetBucketsForArgs(const ListValue * args,BucketList * buckets)733   virtual void GetBucketsForArgs(const ListValue* args, BucketList* buckets) {
734     IdList ids;
735     bool invalid_id = false;
736     if (!FunctionType::ExtractIds(args, &ids, &invalid_id) || invalid_id)
737       return;
738     for (IdList::iterator it = ids.begin(); it != ids.end(); ++it)
739       buckets->push_back(GetBucket(*it));
740   }
741 };
742 
743 // Builds heuristics for all BookmarkFunctions using specialized BucketMappers.
744 class BookmarksQuotaLimitFactory {
745  public:
746   // For id-based bookmark functions.
747   template <class FunctionType>
Build(QuotaLimitHeuristics * heuristics)748   static void Build(QuotaLimitHeuristics* heuristics) {
749     BuildWithMappers(heuristics, new BookmarkIdMapper<FunctionType>(),
750                                  new BookmarkIdMapper<FunctionType>());
751   }
752 
753   // For bookmarks.create.
BuildForCreate(QuotaLimitHeuristics * heuristics,Profile * profile)754   static void BuildForCreate(QuotaLimitHeuristics* heuristics,
755                              Profile* profile) {
756     BuildWithMappers(heuristics, new CreateBookmarkBucketMapper(profile),
757                                  new CreateBookmarkBucketMapper(profile));
758   }
759 
760   // For bookmarks.remove.
BuildForRemove(QuotaLimitHeuristics * heuristics,Profile * profile)761   static void BuildForRemove(QuotaLimitHeuristics* heuristics,
762                              Profile* profile) {
763     BuildWithMappers(heuristics, new RemoveBookmarksBucketMapper(profile),
764                                  new RemoveBookmarksBucketMapper(profile));
765   }
766 
767  private:
BuildWithMappers(QuotaLimitHeuristics * heuristics,BucketMapper * short_mapper,BucketMapper * long_mapper)768   static void BuildWithMappers(QuotaLimitHeuristics* heuristics,
769       BucketMapper* short_mapper, BucketMapper* long_mapper) {
770     TimedLimit* timed = new TimedLimit(kLongLimitConfig, long_mapper);
771     // A max of two operations per minute, sustained over 10 minutes.
772     SustainedLimit* sustained = new SustainedLimit(TimeDelta::FromMinutes(10),
773         kShortLimitConfig, short_mapper);
774     heuristics->push_back(timed);
775     heuristics->push_back(sustained);
776   }
777 
778   // The quota configurations used for all BookmarkFunctions.
779   static const Config kShortLimitConfig;
780   static const Config kLongLimitConfig;
781 
782   DISALLOW_IMPLICIT_CONSTRUCTORS(BookmarksQuotaLimitFactory);
783 };
784 
785 const Config BookmarksQuotaLimitFactory::kShortLimitConfig = {
786   2,                         // 2 tokens per interval.
787   TimeDelta::FromMinutes(1)  // 1 minute long refill interval.
788 };
789 
790 const Config BookmarksQuotaLimitFactory::kLongLimitConfig = {
791   100,                       // 100 tokens per interval.
792   TimeDelta::FromHours(1)    // 1 hour long refill interval.
793 };
794 
795 // And finally, building the individual heuristics for each function.
GetQuotaLimitHeuristics(QuotaLimitHeuristics * heuristics) const796 void RemoveBookmarkFunction::GetQuotaLimitHeuristics(
797     QuotaLimitHeuristics* heuristics) const {
798   BookmarksQuotaLimitFactory::BuildForRemove(heuristics, profile());
799 }
800 
GetQuotaLimitHeuristics(QuotaLimitHeuristics * heuristics) const801 void MoveBookmarkFunction::GetQuotaLimitHeuristics(
802     QuotaLimitHeuristics* heuristics) const {
803   BookmarksQuotaLimitFactory::Build<MoveBookmarkFunction>(heuristics);
804 }
805 
GetQuotaLimitHeuristics(QuotaLimitHeuristics * heuristics) const806 void UpdateBookmarkFunction::GetQuotaLimitHeuristics(
807     QuotaLimitHeuristics* heuristics) const {
808   BookmarksQuotaLimitFactory::Build<UpdateBookmarkFunction>(heuristics);
809 };
810 
GetQuotaLimitHeuristics(QuotaLimitHeuristics * heuristics) const811 void CreateBookmarkFunction::GetQuotaLimitHeuristics(
812     QuotaLimitHeuristics* heuristics) const {
813   BookmarksQuotaLimitFactory::BuildForCreate(heuristics, profile());
814 }
815 
BookmarksIOFunction()816 BookmarksIOFunction::BookmarksIOFunction() {}
817 
~BookmarksIOFunction()818 BookmarksIOFunction::~BookmarksIOFunction() {
819   // There may be pending file dialogs, we need to tell them that we've gone
820   // away so they don't try and call back to us.
821   if (select_file_dialog_.get())
822     select_file_dialog_->ListenerDestroyed();
823 }
824 
SelectFile(SelectFileDialog::Type type)825 void BookmarksIOFunction::SelectFile(SelectFileDialog::Type type) {
826   // GetDefaultFilepathForBookmarkExport() might have to touch the filesystem
827   // (stat or access, for example), so this requires a thread with IO allowed.
828   if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
829     BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
830         NewRunnableMethod(this, &BookmarksIOFunction::SelectFile, type));
831     return;
832   }
833 
834   // Pre-populating the filename field in case this is a SELECT_SAVEAS_FILE
835   // dialog. If not, there is no filename field in the dialog box.
836   FilePath default_path;
837   if (type == SelectFileDialog::SELECT_SAVEAS_FILE)
838     default_path = GetDefaultFilepathForBookmarkExport();
839   else
840     DCHECK(type == SelectFileDialog::SELECT_OPEN_FILE);
841 
842   // After getting the |default_path|, ask the UI to display the file dialog.
843   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
844       NewRunnableMethod(this, &BookmarksIOFunction::ShowSelectFileDialog,
845                         type, default_path));
846 }
847 
ShowSelectFileDialog(SelectFileDialog::Type type,FilePath default_path)848 void BookmarksIOFunction::ShowSelectFileDialog(SelectFileDialog::Type type,
849                                                FilePath default_path) {
850   // Balanced in one of the three callbacks of SelectFileDialog:
851   // either FileSelectionCanceled, MultiFilesSelected, or FileSelected
852   AddRef();
853   select_file_dialog_ = SelectFileDialog::Create(this);
854   SelectFileDialog::FileTypeInfo file_type_info;
855   file_type_info.extensions.resize(1);
856   file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("html"));
857 
858   TabContents* tab_contents = dispatcher()->delegate()->
859       associated_tab_contents();
860 
861   // |tab_contents| can be NULL (for background pages), which is fine. In such
862   // a case if file-selection dialogs are forbidden by policy, we will not
863   // show an InfoBar, which is better than letting one appear out of the blue.
864   select_file_dialog_->SelectFile(type,
865                                   string16(),
866                                   default_path,
867                                   &file_type_info,
868                                   0,
869                                   FILE_PATH_LITERAL(""),
870                                   tab_contents,
871                                   NULL,
872                                   NULL);
873 }
874 
FileSelectionCanceled(void * params)875 void BookmarksIOFunction::FileSelectionCanceled(void* params) {
876   Release();  // Balanced in BookmarksIOFunction::SelectFile()
877 }
878 
MultiFilesSelected(const std::vector<FilePath> & files,void * params)879 void BookmarksIOFunction::MultiFilesSelected(
880     const std::vector<FilePath>& files, void* params) {
881   Release();  // Balanced in BookmarsIOFunction::SelectFile()
882   NOTREACHED() << "Should not be able to select multiple files";
883 }
884 
RunImpl()885 bool ImportBookmarksFunction::RunImpl() {
886   if (!EditBookmarksEnabled())
887     return false;
888   SelectFile(SelectFileDialog::SELECT_OPEN_FILE);
889   return true;
890 }
891 
FileSelected(const FilePath & path,int index,void * params)892 void ImportBookmarksFunction::FileSelected(const FilePath& path,
893                                            int index,
894                                            void* params) {
895   scoped_refptr<ImporterHost> importer_host(new ImporterHost);
896   importer::SourceProfile source_profile;
897   source_profile.importer_type = importer::BOOKMARKS_HTML;
898   source_profile.source_path = path;
899   importer_host->StartImportSettings(source_profile,
900                                      profile(),
901                                      importer::FAVORITES,
902                                      new ProfileWriter(profile()),
903                                      true);
904   Release();  // Balanced in BookmarksIOFunction::SelectFile()
905 }
906 
RunImpl()907 bool ExportBookmarksFunction::RunImpl() {
908   SelectFile(SelectFileDialog::SELECT_SAVEAS_FILE);
909   return true;
910 }
911 
FileSelected(const FilePath & path,int index,void * params)912 void ExportBookmarksFunction::FileSelected(const FilePath& path,
913                                            int index,
914                                            void* params) {
915   bookmark_html_writer::WriteBookmarks(profile(), path, NULL);
916   Release();  // Balanced in BookmarksIOFunction::SelectFile()
917 }
918