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