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. 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 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 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 104 bool BookmarksFunction::EditBookmarksEnabled() { 105 if (profile_->GetPrefs()->GetBoolean(prefs::kEditBookmarksEnabled)) 106 return true; 107 error_ = keys::kEditBookmarksDisabled; 108 return false; 109 } 110 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 121 ExtensionBookmarkEventRouter* ExtensionBookmarkEventRouter::GetInstance() { 122 return Singleton<ExtensionBookmarkEventRouter>::get(); 123 } 124 125 ExtensionBookmarkEventRouter::ExtensionBookmarkEventRouter() { 126 } 127 128 ExtensionBookmarkEventRouter::~ExtensionBookmarkEventRouter() { 129 } 130 131 void ExtensionBookmarkEventRouter::Observe(BookmarkModel* model) { 132 if (models_.find(model) == models_.end()) { 133 model->AddObserver(this); 134 models_.insert(model); 135 } 136 } 137 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 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 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 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 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 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 229 void ExtensionBookmarkEventRouter::BookmarkNodeFaviconLoaded( 230 BookmarkModel* model, const BookmarkNode* node) { 231 // TODO(erikkay) anything we should do here? 232 } 233 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:: 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 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 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 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 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 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 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 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 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 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 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 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 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 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: 640 virtual ~BookmarkBucketMapper() { STLDeleteValues(&buckets_); } 641 protected: 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: 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. 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: 698 explicit RemoveBookmarksBucketMapper(Profile* profile) : profile_(profile) {} 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; 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> 748 static void Build(QuotaLimitHeuristics* heuristics) { 749 BuildWithMappers(heuristics, new BookmarkIdMapper<FunctionType>(), 750 new BookmarkIdMapper<FunctionType>()); 751 } 752 753 // For bookmarks.create. 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. 761 static void BuildForRemove(QuotaLimitHeuristics* heuristics, 762 Profile* profile) { 763 BuildWithMappers(heuristics, new RemoveBookmarksBucketMapper(profile), 764 new RemoveBookmarksBucketMapper(profile)); 765 } 766 767 private: 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. 796 void RemoveBookmarkFunction::GetQuotaLimitHeuristics( 797 QuotaLimitHeuristics* heuristics) const { 798 BookmarksQuotaLimitFactory::BuildForRemove(heuristics, profile()); 799 } 800 801 void MoveBookmarkFunction::GetQuotaLimitHeuristics( 802 QuotaLimitHeuristics* heuristics) const { 803 BookmarksQuotaLimitFactory::Build<MoveBookmarkFunction>(heuristics); 804 } 805 806 void UpdateBookmarkFunction::GetQuotaLimitHeuristics( 807 QuotaLimitHeuristics* heuristics) const { 808 BookmarksQuotaLimitFactory::Build<UpdateBookmarkFunction>(heuristics); 809 }; 810 811 void CreateBookmarkFunction::GetQuotaLimitHeuristics( 812 QuotaLimitHeuristics* heuristics) const { 813 BookmarksQuotaLimitFactory::BuildForCreate(heuristics, profile()); 814 } 815 816 BookmarksIOFunction::BookmarksIOFunction() {} 817 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 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 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 875 void BookmarksIOFunction::FileSelectionCanceled(void* params) { 876 Release(); // Balanced in BookmarksIOFunction::SelectFile() 877 } 878 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 885 bool ImportBookmarksFunction::RunImpl() { 886 if (!EditBookmarksEnabled()) 887 return false; 888 SelectFile(SelectFileDialog::SELECT_OPEN_FILE); 889 return true; 890 } 891 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 907 bool ExportBookmarksFunction::RunImpl() { 908 SelectFile(SelectFileDialog::SELECT_SAVEAS_FILE); 909 return true; 910 } 911 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