1 // Copyright 2013 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/utility/importer/firefox_importer.h" 6 7 #include <set> 8 9 #include "base/files/file_enumerator.h" 10 #include "base/files/file_util.h" 11 #include "base/json/json_file_value_serializer.h" 12 #include "base/memory/scoped_ptr.h" 13 #include "base/message_loop/message_loop.h" 14 #include "base/stl_util.h" 15 #include "base/strings/string_util.h" 16 #include "base/strings/utf_string_conversions.h" 17 #include "chrome/common/importer/firefox_importer_utils.h" 18 #include "chrome/common/importer/firefox_importer_utils.h" 19 #include "chrome/common/importer/imported_bookmark_entry.h" 20 #include "chrome/common/importer/imported_favicon_usage.h" 21 #include "chrome/common/importer/importer_autofill_form_data_entry.h" 22 #include "chrome/common/importer/importer_bridge.h" 23 #include "chrome/common/importer/importer_url_row.h" 24 #include "chrome/grit/generated_resources.h" 25 #include "chrome/utility/importer/bookmark_html_reader.h" 26 #include "chrome/utility/importer/favicon_reencode.h" 27 #include "chrome/utility/importer/nss_decryptor.h" 28 #include "components/autofill/core/common/password_form.h" 29 #include "sql/connection.h" 30 #include "sql/statement.h" 31 #include "url/gurl.h" 32 33 namespace { 34 35 // Original definition is in http://mxr.mozilla.org/firefox/source/toolkit/ 36 // components/places/public/nsINavBookmarksService.idl 37 enum BookmarkItemType { 38 TYPE_BOOKMARK = 1, 39 TYPE_FOLDER = 2, 40 TYPE_SEPARATOR = 3, 41 TYPE_DYNAMIC_CONTAINER = 4 42 }; 43 44 // Loads the default bookmarks in the Firefox installed at |app_path|, 45 // and stores their locations in |urls|. 46 void LoadDefaultBookmarks(const base::FilePath& app_path, 47 std::set<GURL>* urls) { 48 base::FilePath file = app_path.AppendASCII("defaults") 49 .AppendASCII("profile") 50 .AppendASCII("bookmarks.html"); 51 urls->clear(); 52 53 std::vector<ImportedBookmarkEntry> bookmarks; 54 bookmark_html_reader::ImportBookmarksFile(base::Callback<bool(void)>(), 55 base::Callback<bool(const GURL&)>(), 56 file, 57 &bookmarks, 58 NULL); 59 for (size_t i = 0; i < bookmarks.size(); ++i) 60 urls->insert(bookmarks[i].url); 61 } 62 63 // Returns true if |url| has a valid scheme that we allow to import. We 64 // filter out the URL with a unsupported scheme. 65 bool CanImportURL(const GURL& url) { 66 // The URL is not valid. 67 if (!url.is_valid()) 68 return false; 69 70 // Filter out the URLs with unsupported schemes. 71 const char* const kInvalidSchemes[] = {"wyciwyg", "place", "about", "chrome"}; 72 for (size_t i = 0; i < arraysize(kInvalidSchemes); ++i) { 73 if (url.SchemeIs(kInvalidSchemes[i])) 74 return false; 75 } 76 77 return true; 78 } 79 80 } // namespace 81 82 struct FirefoxImporter::BookmarkItem { 83 int parent; 84 int id; 85 GURL url; 86 base::string16 title; 87 BookmarkItemType type; 88 std::string keyword; 89 base::Time date_added; 90 int64 favicon; 91 bool empty_folder; 92 }; 93 94 FirefoxImporter::FirefoxImporter() { 95 } 96 97 FirefoxImporter::~FirefoxImporter() { 98 } 99 100 void FirefoxImporter::StartImport( 101 const importer::SourceProfile& source_profile, 102 uint16 items, 103 ImporterBridge* bridge) { 104 bridge_ = bridge; 105 source_path_ = source_profile.source_path; 106 app_path_ = source_profile.app_path; 107 108 #if defined(OS_POSIX) 109 locale_ = source_profile.locale; 110 #endif 111 112 // The order here is important! 113 bridge_->NotifyStarted(); 114 if ((items & importer::HOME_PAGE) && !cancelled()) { 115 bridge_->NotifyItemStarted(importer::HOME_PAGE); 116 ImportHomepage(); // Doesn't have a UI item. 117 bridge_->NotifyItemEnded(importer::HOME_PAGE); 118 } 119 120 // Note history should be imported before bookmarks because bookmark import 121 // will also import favicons and we store favicon for a URL only if the URL 122 // exist in history or bookmarks. 123 if ((items & importer::HISTORY) && !cancelled()) { 124 bridge_->NotifyItemStarted(importer::HISTORY); 125 ImportHistory(); 126 bridge_->NotifyItemEnded(importer::HISTORY); 127 } 128 129 if ((items & importer::FAVORITES) && !cancelled()) { 130 bridge_->NotifyItemStarted(importer::FAVORITES); 131 ImportBookmarks(); 132 bridge_->NotifyItemEnded(importer::FAVORITES); 133 } 134 if ((items & importer::SEARCH_ENGINES) && !cancelled()) { 135 bridge_->NotifyItemStarted(importer::SEARCH_ENGINES); 136 ImportSearchEngines(); 137 bridge_->NotifyItemEnded(importer::SEARCH_ENGINES); 138 } 139 if ((items & importer::PASSWORDS) && !cancelled()) { 140 bridge_->NotifyItemStarted(importer::PASSWORDS); 141 ImportPasswords(); 142 bridge_->NotifyItemEnded(importer::PASSWORDS); 143 } 144 if ((items & importer::AUTOFILL_FORM_DATA) && !cancelled()) { 145 bridge_->NotifyItemStarted(importer::AUTOFILL_FORM_DATA); 146 ImportAutofillFormData(); 147 bridge_->NotifyItemEnded(importer::AUTOFILL_FORM_DATA); 148 } 149 bridge_->NotifyEnded(); 150 } 151 152 void FirefoxImporter::ImportHistory() { 153 base::FilePath file = source_path_.AppendASCII("places.sqlite"); 154 if (!base::PathExists(file)) 155 return; 156 157 sql::Connection db; 158 if (!db.Open(file)) 159 return; 160 161 // |visit_type| represent the transition type of URLs (typed, click, 162 // redirect, bookmark, etc.) We eliminate some URLs like sub-frames and 163 // redirects, since we don't want them to appear in history. 164 // Firefox transition types are defined in: 165 // toolkit/components/places/public/nsINavHistoryService.idl 166 const char* query = "SELECT h.url, h.title, h.visit_count, " 167 "h.hidden, h.typed, v.visit_date " 168 "FROM moz_places h JOIN moz_historyvisits v " 169 "ON h.id = v.place_id " 170 "WHERE v.visit_type <= 3"; 171 172 sql::Statement s(db.GetUniqueStatement(query)); 173 174 std::vector<ImporterURLRow> rows; 175 while (s.Step() && !cancelled()) { 176 GURL url(s.ColumnString(0)); 177 178 // Filter out unwanted URLs. 179 if (!CanImportURL(url)) 180 continue; 181 182 ImporterURLRow row(url); 183 row.title = s.ColumnString16(1); 184 row.visit_count = s.ColumnInt(2); 185 row.hidden = s.ColumnInt(3) == 1; 186 row.typed_count = s.ColumnInt(4); 187 row.last_visit = base::Time::FromTimeT(s.ColumnInt64(5)/1000000); 188 189 rows.push_back(row); 190 } 191 192 if (!rows.empty() && !cancelled()) 193 bridge_->SetHistoryItems(rows, importer::VISIT_SOURCE_FIREFOX_IMPORTED); 194 } 195 196 void FirefoxImporter::ImportBookmarks() { 197 base::FilePath file = source_path_.AppendASCII("places.sqlite"); 198 if (!base::PathExists(file)) 199 return; 200 201 sql::Connection db; 202 if (!db.Open(file)) 203 return; 204 205 // Get the bookmark folders that we are interested in. 206 int toolbar_folder_id = -1; 207 int menu_folder_id = -1; 208 int unsorted_folder_id = -1; 209 LoadRootNodeID(&db, &toolbar_folder_id, &menu_folder_id, &unsorted_folder_id); 210 211 // Load livemark IDs. 212 std::set<int> livemark_id; 213 LoadLivemarkIDs(&db, &livemark_id); 214 215 // Load the default bookmarks. 216 std::set<GURL> default_urls; 217 LoadDefaultBookmarks(app_path_, &default_urls); 218 219 BookmarkList list; 220 GetTopBookmarkFolder(&db, toolbar_folder_id, &list); 221 GetTopBookmarkFolder(&db, menu_folder_id, &list); 222 GetTopBookmarkFolder(&db, unsorted_folder_id, &list); 223 size_t count = list.size(); 224 for (size_t i = 0; i < count; ++i) 225 GetWholeBookmarkFolder(&db, &list, i, NULL); 226 227 std::vector<ImportedBookmarkEntry> bookmarks; 228 std::vector<importer::URLKeywordInfo> url_keywords; 229 FaviconMap favicon_map; 230 231 // TODO(jcampan): http://b/issue?id=1196285 we do not support POST based 232 // keywords yet. We won't include them in the list. 233 std::set<int> post_keyword_ids; 234 const char* query = "SELECT b.id FROM moz_bookmarks b " 235 "INNER JOIN moz_items_annos ia ON ia.item_id = b.id " 236 "INNER JOIN moz_anno_attributes aa ON ia.anno_attribute_id = aa.id " 237 "WHERE aa.name = 'bookmarkProperties/POSTData'"; 238 sql::Statement s(db.GetUniqueStatement(query)); 239 240 if (!s.is_valid()) 241 return; 242 243 while (s.Step() && !cancelled()) 244 post_keyword_ids.insert(s.ColumnInt(0)); 245 246 for (size_t i = 0; i < list.size(); ++i) { 247 BookmarkItem* item = list[i]; 248 249 if (item->type == TYPE_FOLDER) { 250 // Folders are added implicitly on adding children, so we only explicitly 251 // add empty folders. 252 if (!item->empty_folder) 253 continue; 254 } else if (item->type == TYPE_BOOKMARK) { 255 // Import only valid bookmarks 256 if (!CanImportURL(item->url)) 257 continue; 258 } else { 259 continue; 260 } 261 262 // Skip the default bookmarks and unwanted URLs. 263 if (default_urls.find(item->url) != default_urls.end() || 264 post_keyword_ids.find(item->id) != post_keyword_ids.end()) 265 continue; 266 267 // Find the bookmark path by tracing their links to parent folders. 268 std::vector<base::string16> path; 269 BookmarkItem* child = item; 270 bool found_path = false; 271 bool is_in_toolbar = false; 272 while (child->parent >= 0) { 273 BookmarkItem* parent = list[child->parent]; 274 if (livemark_id.find(parent->id) != livemark_id.end()) { 275 // Don't import live bookmarks. 276 break; 277 } 278 279 if (parent->id != menu_folder_id) { 280 // To avoid excessive nesting, omit the name for the bookmarks menu 281 // folder. 282 path.insert(path.begin(), parent->title); 283 } 284 285 if (parent->id == toolbar_folder_id) 286 is_in_toolbar = true; 287 288 if (parent->id == toolbar_folder_id || 289 parent->id == menu_folder_id || 290 parent->id == unsorted_folder_id) { 291 // We've reached a root node, hooray! 292 found_path = true; 293 break; 294 } 295 296 child = parent; 297 } 298 299 if (!found_path) 300 continue; 301 302 ImportedBookmarkEntry entry; 303 entry.creation_time = item->date_added; 304 entry.title = item->title; 305 entry.url = item->url; 306 entry.path = path; 307 entry.in_toolbar = is_in_toolbar; 308 entry.is_folder = item->type == TYPE_FOLDER; 309 310 bookmarks.push_back(entry); 311 312 if (item->type == TYPE_BOOKMARK) { 313 if (item->favicon) 314 favicon_map[item->favicon].insert(item->url); 315 316 // This bookmark has a keyword, we should import it. 317 if (!item->keyword.empty() && item->url.is_valid()) { 318 importer::URLKeywordInfo url_keyword_info; 319 url_keyword_info.url = item->url; 320 url_keyword_info.keyword.assign(base::UTF8ToUTF16(item->keyword)); 321 url_keyword_info.display_name = item->title; 322 url_keywords.push_back(url_keyword_info); 323 } 324 } 325 } 326 327 STLDeleteElements(&list); 328 329 // Write into profile. 330 if (!bookmarks.empty() && !cancelled()) { 331 const base::string16& first_folder_name = 332 bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP_FROM_FIREFOX); 333 bridge_->AddBookmarks(bookmarks, first_folder_name); 334 } 335 if (!url_keywords.empty() && !cancelled()) { 336 bridge_->SetKeywords(url_keywords, false); 337 } 338 if (!favicon_map.empty() && !cancelled()) { 339 std::vector<ImportedFaviconUsage> favicons; 340 LoadFavicons(&db, favicon_map, &favicons); 341 bridge_->SetFavicons(favicons); 342 } 343 } 344 345 void FirefoxImporter::ImportPasswords() { 346 // Initializes NSS3. 347 NSSDecryptor decryptor; 348 if (!decryptor.Init(source_path_, source_path_) && 349 !decryptor.Init(app_path_, source_path_)) { 350 return; 351 } 352 353 std::vector<autofill::PasswordForm> forms; 354 base::FilePath source_path = source_path_; 355 base::FilePath file = source_path.AppendASCII("signons.sqlite"); 356 if (base::PathExists(file)) { 357 // Since Firefox 3.1, passwords are in signons.sqlite db. 358 decryptor.ReadAndParseSignons(file, &forms); 359 } else { 360 // Firefox 3.0 uses signons3.txt to store the passwords. 361 file = source_path.AppendASCII("signons3.txt"); 362 if (!base::PathExists(file)) 363 file = source_path.AppendASCII("signons2.txt"); 364 365 std::string content; 366 base::ReadFileToString(file, &content); 367 decryptor.ParseSignons(content, &forms); 368 } 369 370 if (!cancelled()) { 371 for (size_t i = 0; i < forms.size(); ++i) { 372 bridge_->SetPasswordForm(forms[i]); 373 } 374 } 375 } 376 377 void FirefoxImporter::ImportSearchEngines() { 378 std::vector<std::string> search_engine_data; 379 GetSearchEnginesXMLData(&search_engine_data); 380 381 bridge_->SetFirefoxSearchEnginesXMLData(search_engine_data); 382 } 383 384 void FirefoxImporter::ImportHomepage() { 385 GURL home_page = GetHomepage(source_path_); 386 if (home_page.is_valid() && !IsDefaultHomepage(home_page, app_path_)) { 387 bridge_->AddHomePage(home_page); 388 } 389 } 390 391 void FirefoxImporter::ImportAutofillFormData() { 392 base::FilePath file = source_path_.AppendASCII("formhistory.sqlite"); 393 if (!base::PathExists(file)) 394 return; 395 396 sql::Connection db; 397 if (!db.Open(file)) 398 return; 399 400 const char query[] = 401 "SELECT fieldname, value, timesUsed, firstUsed, lastUsed FROM " 402 "moz_formhistory"; 403 404 sql::Statement s(db.GetUniqueStatement(query)); 405 406 std::vector<ImporterAutofillFormDataEntry> form_entries; 407 while (s.Step() && !cancelled()) { 408 ImporterAutofillFormDataEntry form_entry; 409 form_entry.name = s.ColumnString16(0); 410 form_entry.value = s.ColumnString16(1); 411 form_entry.times_used = s.ColumnInt(2); 412 form_entry.first_used = base::Time::FromTimeT(s.ColumnInt64(3) / 1000000); 413 form_entry.last_used = base::Time::FromTimeT(s.ColumnInt64(4) / 1000000); 414 415 // Don't import search bar history. 416 if (base::UTF16ToUTF8(form_entry.name) == "searchbar-history") 417 continue; 418 419 form_entries.push_back(form_entry); 420 } 421 422 if (!form_entries.empty() && !cancelled()) 423 bridge_->SetAutofillFormData(form_entries); 424 } 425 426 void FirefoxImporter::GetSearchEnginesXMLData( 427 std::vector<std::string>* search_engine_data) { 428 base::FilePath file = source_path_.AppendASCII("search.sqlite"); 429 if (!base::PathExists(file)) { 430 // Since Firefox 3.5, search engines are no longer stored in search.sqlite. 431 // Instead, search.json is used for storing search engines. 432 GetSearchEnginesXMLDataFromJSON(search_engine_data); 433 return; 434 } 435 436 sql::Connection db; 437 if (!db.Open(file)) 438 return; 439 440 const char* query = "SELECT engineid FROM engine_data " 441 "WHERE engineid NOT IN " 442 "(SELECT engineid FROM engine_data " 443 "WHERE name='hidden') " 444 "ORDER BY value ASC"; 445 446 sql::Statement s(db.GetUniqueStatement(query)); 447 if (!s.is_valid()) 448 return; 449 450 const base::FilePath searchplugins_path(FILE_PATH_LITERAL("searchplugins")); 451 // Search engine definitions are XMLs stored in two directories. Default 452 // engines are in the app directory (app_path_) and custom engines are 453 // in the profile directory (source_path_). 454 455 // Since Firefox 21, app_path_ engines are in 'browser' subdirectory: 456 base::FilePath app_path = 457 app_path_.AppendASCII("browser").Append(searchplugins_path); 458 if (!base::PathExists(app_path)) { 459 // This might be an older Firefox, try old location without the 'browser' 460 // path component: 461 app_path = app_path_.Append(searchplugins_path); 462 } 463 464 base::FilePath profile_path = source_path_.Append(searchplugins_path); 465 466 // Firefox doesn't store a search engine in its sqlite database unless the 467 // user has added a engine. So we get search engines from sqlite db as well 468 // as from the file system. 469 if (s.Step()) { 470 const std::string kAppPrefix("[app]/"); 471 const std::string kProfilePrefix("[profile]/"); 472 do { 473 base::FilePath file; 474 std::string engine(s.ColumnString(0)); 475 476 // The string contains [app]/<name>.xml or [profile]/<name>.xml where 477 // the [app] and [profile] need to be replaced with the actual app or 478 // profile path. 479 size_t index = engine.find(kAppPrefix); 480 if (index != std::string::npos) { 481 // Remove '[app]/'. 482 file = app_path.AppendASCII(engine.substr(index + kAppPrefix.length())); 483 } else if ((index = engine.find(kProfilePrefix)) != std::string::npos) { 484 // Remove '[profile]/'. 485 file = profile_path.AppendASCII( 486 engine.substr(index + kProfilePrefix.length())); 487 } else { 488 // Looks like absolute path to the file. 489 file = base::FilePath::FromUTF8Unsafe(engine); 490 } 491 std::string file_data; 492 base::ReadFileToString(file, &file_data); 493 search_engine_data->push_back(file_data); 494 } while (s.Step() && !cancelled()); 495 } 496 497 #if defined(OS_POSIX) 498 // Ubuntu-flavored Firefox supports locale-specific search engines via 499 // locale-named subdirectories. They fall back to en-US. 500 // See http://crbug.com/53899 501 // TODO(jshin): we need to make sure our locale code matches that of 502 // Firefox. 503 DCHECK(!locale_.empty()); 504 base::FilePath locale_app_path = app_path.AppendASCII(locale_); 505 base::FilePath default_locale_app_path = app_path.AppendASCII("en-US"); 506 if (base::DirectoryExists(locale_app_path)) 507 app_path = locale_app_path; 508 else if (base::DirectoryExists(default_locale_app_path)) 509 app_path = default_locale_app_path; 510 #endif 511 512 // Get search engine definition from file system. 513 base::FileEnumerator engines(app_path, false, base::FileEnumerator::FILES); 514 for (base::FilePath engine_path = engines.Next(); 515 !engine_path.value().empty(); engine_path = engines.Next()) { 516 std::string file_data; 517 base::ReadFileToString(file, &file_data); 518 search_engine_data->push_back(file_data); 519 } 520 } 521 522 void FirefoxImporter::GetSearchEnginesXMLDataFromJSON( 523 std::vector<std::string>* search_engine_data) { 524 // search-metadata.json contains keywords for search engines. This 525 // file exists only if the user has set keywords for search engines. 526 base::FilePath search_metadata_json_file = 527 source_path_.AppendASCII("search-metadata.json"); 528 JSONFileValueSerializer metadata_serializer(search_metadata_json_file); 529 scoped_ptr<base::Value> metadata_root( 530 metadata_serializer.Deserialize(NULL, NULL)); 531 const base::DictionaryValue* search_metadata_root = NULL; 532 if (metadata_root) 533 metadata_root->GetAsDictionary(&search_metadata_root); 534 535 // search.json contains information about search engines to import. 536 base::FilePath search_json_file = source_path_.AppendASCII("search.json"); 537 if (!base::PathExists(search_json_file)) 538 return; 539 540 JSONFileValueSerializer serializer(search_json_file); 541 scoped_ptr<base::Value> root(serializer.Deserialize(NULL, NULL)); 542 const base::DictionaryValue* search_root = NULL; 543 if (!root || !root->GetAsDictionary(&search_root)) 544 return; 545 546 const std::string kDirectories("directories"); 547 const base::DictionaryValue* search_directories = NULL; 548 if (!search_root->GetDictionary(kDirectories, &search_directories)) 549 return; 550 551 // Dictionary |search_directories| contains a list of search engines 552 // (default and installed). The list can be found from key <engines> 553 // of the dictionary. Key <engines> is a grandchild of key <directories>. 554 // However, key <engines> parent's key is dynamic which depends on 555 // operating systems. For example, 556 // Ubuntu (for default search engine): 557 // /usr/lib/firefox/distribution/searchplugins/locale/en-US 558 // Ubuntu (for installed search engines): 559 // /home/<username>/.mozilla/firefox/lcd50n4n.default/searchplugins 560 // Windows (for default search engine): 561 // C:\\Program Files (x86)\\Mozilla Firefox\\browser\\searchplugins 562 // Therefore, it needs to be retrieved by searching. 563 564 for (base::DictionaryValue::Iterator it(*search_directories); !it.IsAtEnd(); 565 it.Advance()) { 566 // The key of |it| may contains dot (.) which cannot be used as <key> 567 // for retrieving <engines>. Hence, it is needed to get |it| as dictionary. 568 // The resulted dictionary can be used for retrieving <engines>. 569 const std::string kEngines("engines"); 570 const base::DictionaryValue* search_directory = NULL; 571 if (!it.value().GetAsDictionary(&search_directory)) 572 continue; 573 574 const base::ListValue* search_engines = NULL; 575 if (!search_directory->GetList(kEngines, &search_engines)) 576 continue; 577 578 const std::string kFilePath("filePath"); 579 const std::string kHidden("_hidden"); 580 for (size_t i = 0; i < search_engines->GetSize(); ++i) { 581 const base::DictionaryValue* engine_info = NULL; 582 if (!search_engines->GetDictionary(i, &engine_info)) 583 continue; 584 585 bool is_hidden = false; 586 std::string file_path; 587 if (!engine_info->GetBoolean(kHidden, &is_hidden) || 588 !engine_info->GetString(kFilePath, &file_path)) 589 continue; 590 591 if (!is_hidden) { 592 const std::string kAppPrefix("[app]/"); 593 const std::string kProfilePrefix("[profile]/"); 594 base::FilePath xml_file = base::FilePath::FromUTF8Unsafe(file_path); 595 596 // If |file_path| contains [app] or [profile] then they need to be 597 // replaced with the actual app or profile path. 598 size_t index = file_path.find(kAppPrefix); 599 if (index != std::string::npos) { 600 // Replace '[app]/' with actual app path. 601 xml_file = app_path_.AppendASCII("searchplugins").AppendASCII( 602 file_path.substr(index + kAppPrefix.length())); 603 } else if ((index = file_path.find(kProfilePrefix)) != 604 std::string::npos) { 605 // Replace '[profile]/' with actual profile path. 606 xml_file = source_path_.AppendASCII("searchplugins").AppendASCII( 607 file_path.substr(index + kProfilePrefix.length())); 608 } 609 610 std::string file_data; 611 base::ReadFileToString(xml_file, &file_data); 612 613 // If a keyword is mentioned for this search engine, then add 614 // it to the XML string as an <Alias> element and use this updated 615 // string. 616 const base::DictionaryValue* search_xml_path = NULL; 617 if (search_metadata_root && search_metadata_root->HasKey(file_path) && 618 search_metadata_root->GetDictionaryWithoutPathExpansion( 619 file_path, &search_xml_path)) { 620 std::string alias; 621 search_xml_path->GetString("alias", &alias); 622 623 // Add <Alias> element as the last child element. 624 size_t end_of_parent = file_data.find("</SearchPlugin>"); 625 if (end_of_parent != std::string::npos && !alias.empty()) 626 file_data.insert(end_of_parent, "<Alias>" + alias + "</Alias> \n"); 627 } 628 search_engine_data->push_back(file_data); 629 } 630 } 631 } 632 } 633 634 void FirefoxImporter::LoadRootNodeID(sql::Connection* db, 635 int* toolbar_folder_id, 636 int* menu_folder_id, 637 int* unsorted_folder_id) { 638 static const char* kToolbarFolderName = "toolbar"; 639 static const char* kMenuFolderName = "menu"; 640 static const char* kUnsortedFolderName = "unfiled"; 641 642 const char* query = "SELECT root_name, folder_id FROM moz_bookmarks_roots"; 643 sql::Statement s(db->GetUniqueStatement(query)); 644 645 while (s.Step()) { 646 std::string folder = s.ColumnString(0); 647 int id = s.ColumnInt(1); 648 if (folder == kToolbarFolderName) 649 *toolbar_folder_id = id; 650 else if (folder == kMenuFolderName) 651 *menu_folder_id = id; 652 else if (folder == kUnsortedFolderName) 653 *unsorted_folder_id = id; 654 } 655 } 656 657 void FirefoxImporter::LoadLivemarkIDs(sql::Connection* db, 658 std::set<int>* livemark) { 659 static const char* kFeedAnnotation = "livemark/feedURI"; 660 livemark->clear(); 661 662 const char* query = "SELECT b.item_id " 663 "FROM moz_anno_attributes a " 664 "JOIN moz_items_annos b ON a.id = b.anno_attribute_id " 665 "WHERE a.name = ? "; 666 sql::Statement s(db->GetUniqueStatement(query)); 667 s.BindString(0, kFeedAnnotation); 668 669 while (s.Step() && !cancelled()) 670 livemark->insert(s.ColumnInt(0)); 671 } 672 673 void FirefoxImporter::GetTopBookmarkFolder(sql::Connection* db, 674 int folder_id, 675 BookmarkList* list) { 676 const char* query = "SELECT b.title " 677 "FROM moz_bookmarks b " 678 "WHERE b.type = 2 AND b.id = ? " 679 "ORDER BY b.position"; 680 sql::Statement s(db->GetUniqueStatement(query)); 681 s.BindInt(0, folder_id); 682 683 if (s.Step()) { 684 BookmarkItem* item = new BookmarkItem; 685 item->parent = -1; // The top level folder has no parent. 686 item->id = folder_id; 687 item->title = s.ColumnString16(0); 688 item->type = TYPE_FOLDER; 689 item->favicon = 0; 690 item->empty_folder = true; 691 list->push_back(item); 692 } 693 } 694 695 void FirefoxImporter::GetWholeBookmarkFolder(sql::Connection* db, 696 BookmarkList* list, 697 size_t position, 698 bool* empty_folder) { 699 if (position >= list->size()) { 700 NOTREACHED(); 701 return; 702 } 703 704 const char* query = "SELECT b.id, h.url, COALESCE(b.title, h.title), " 705 "b.type, k.keyword, b.dateAdded, h.favicon_id " 706 "FROM moz_bookmarks b " 707 "LEFT JOIN moz_places h ON b.fk = h.id " 708 "LEFT JOIN moz_keywords k ON k.id = b.keyword_id " 709 "WHERE b.type IN (1,2) AND b.parent = ? " 710 "ORDER BY b.position"; 711 sql::Statement s(db->GetUniqueStatement(query)); 712 s.BindInt(0, (*list)[position]->id); 713 714 BookmarkList temp_list; 715 while (s.Step()) { 716 BookmarkItem* item = new BookmarkItem; 717 item->parent = static_cast<int>(position); 718 item->id = s.ColumnInt(0); 719 item->url = GURL(s.ColumnString(1)); 720 item->title = s.ColumnString16(2); 721 item->type = static_cast<BookmarkItemType>(s.ColumnInt(3)); 722 item->keyword = s.ColumnString(4); 723 item->date_added = base::Time::FromTimeT(s.ColumnInt64(5)/1000000); 724 item->favicon = s.ColumnInt64(6); 725 item->empty_folder = true; 726 727 temp_list.push_back(item); 728 if (empty_folder != NULL) 729 *empty_folder = false; 730 } 731 732 // Appends all items to the list. 733 for (BookmarkList::iterator i = temp_list.begin(); 734 i != temp_list.end(); ++i) { 735 list->push_back(*i); 736 // Recursive add bookmarks in sub-folders. 737 if ((*i)->type == TYPE_FOLDER) 738 GetWholeBookmarkFolder(db, list, list->size() - 1, &(*i)->empty_folder); 739 } 740 } 741 742 void FirefoxImporter::LoadFavicons( 743 sql::Connection* db, 744 const FaviconMap& favicon_map, 745 std::vector<ImportedFaviconUsage>* favicons) { 746 const char* query = "SELECT url, data FROM moz_favicons WHERE id=?"; 747 sql::Statement s(db->GetUniqueStatement(query)); 748 749 if (!s.is_valid()) 750 return; 751 752 for (FaviconMap::const_iterator i = favicon_map.begin(); 753 i != favicon_map.end(); ++i) { 754 s.BindInt64(0, i->first); 755 if (s.Step()) { 756 ImportedFaviconUsage usage; 757 758 usage.favicon_url = GURL(s.ColumnString(0)); 759 if (!usage.favicon_url.is_valid()) 760 continue; // Don't bother importing favicons with invalid URLs. 761 762 std::vector<unsigned char> data; 763 s.ColumnBlobAsVector(1, &data); 764 if (data.empty()) 765 continue; // Data definitely invalid. 766 767 if (!importer::ReencodeFavicon(&data[0], data.size(), &usage.png_data)) 768 continue; // Unable to decode. 769 770 usage.urls = i->second; 771 favicons->push_back(usage); 772 } 773 s.Reset(true); 774 } 775 } 776