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/importer/firefox2_importer.h" 6 7 #include <string> 8 #include <vector> 9 10 #include "base/file_path.h" 11 #include "base/file_util.h" 12 #include "base/i18n/icu_string_conversions.h" 13 #include "base/message_loop.h" 14 #include "base/path_service.h" 15 #include "base/stl_util-inl.h" 16 #include "base/string_number_conversions.h" 17 #include "base/string_split.h" 18 #include "base/string_util.h" 19 #include "base/utf_string_conversions.h" 20 #include "chrome/browser/history/history_types.h" 21 #include "chrome/browser/importer/firefox_importer_utils.h" 22 #include "chrome/browser/importer/importer_bridge.h" 23 #include "chrome/browser/importer/mork_reader.h" 24 #include "chrome/browser/importer/nss_decryptor.h" 25 #include "chrome/browser/search_engines/template_url.h" 26 #include "chrome/browser/search_engines/template_url_parser.h" 27 #include "chrome/common/time_format.h" 28 #include "chrome/common/url_constants.h" 29 #include "googleurl/src/gurl.h" 30 #include "grit/generated_resources.h" 31 #include "net/base/data_url.h" 32 #include "webkit/glue/password_form.h" 33 34 namespace { 35 const char kItemOpen[] = "<DT><A"; 36 const char kItemClose[] = "</A>"; 37 const char kFeedURLAttribute[] = "FEEDURL"; 38 const char kHrefAttribute[] = "HREF"; 39 const char kIconAttribute[] = "ICON"; 40 const char kShortcutURLAttribute[] = "SHORTCUTURL"; 41 const char kAddDateAttribute[] = "ADD_DATE"; 42 const char kPostDataAttribute[] = "POST_DATA"; 43 } 44 45 Firefox2Importer::Firefox2Importer() : parsing_bookmarks_html_file_(false) {} 46 47 Firefox2Importer::~Firefox2Importer() {} 48 49 void Firefox2Importer::StartImport( 50 const importer::SourceProfile& source_profile, 51 uint16 items, 52 ImporterBridge* bridge) { 53 bridge_ = bridge; 54 source_path_ = source_profile.source_path; 55 app_path_ = source_profile.app_path; 56 57 parsing_bookmarks_html_file_ = 58 (source_profile.importer_type == importer::BOOKMARKS_HTML); 59 60 // The order here is important! 61 bridge_->NotifyStarted(); 62 if ((items & importer::HOME_PAGE) && !cancelled()) 63 ImportHomepage(); // Doesn't have a UI item. 64 65 // Note history should be imported before bookmarks because bookmark import 66 // will also import favicons and we store favicon for a URL only if the URL 67 // exist in history or bookmarks. 68 if ((items & importer::HISTORY) && !cancelled()) { 69 bridge_->NotifyItemStarted(importer::HISTORY); 70 ImportHistory(); 71 bridge_->NotifyItemEnded(importer::HISTORY); 72 } 73 74 if ((items & importer::FAVORITES) && !cancelled()) { 75 bridge_->NotifyItemStarted(importer::FAVORITES); 76 ImportBookmarks(); 77 bridge_->NotifyItemEnded(importer::FAVORITES); 78 } 79 if ((items & importer::SEARCH_ENGINES) && !cancelled()) { 80 bridge_->NotifyItemStarted(importer::SEARCH_ENGINES); 81 ImportSearchEngines(); 82 bridge_->NotifyItemEnded(importer::SEARCH_ENGINES); 83 } 84 if ((items & importer::PASSWORDS) && !cancelled()) { 85 bridge_->NotifyItemStarted(importer::PASSWORDS); 86 ImportPasswords(); 87 bridge_->NotifyItemEnded(importer::PASSWORDS); 88 } 89 bridge_->NotifyEnded(); 90 } 91 92 // static 93 void Firefox2Importer::LoadDefaultBookmarks(const FilePath& app_path, 94 std::set<GURL> *urls) { 95 FilePath file = app_path.AppendASCII("defaults") 96 .AppendASCII("profile") 97 .AppendASCII("bookmarks.html"); 98 99 urls->clear(); 100 101 // Read the whole file. 102 std::string content; 103 file_util::ReadFileToString(file, &content); 104 std::vector<std::string> lines; 105 base::SplitString(content, '\n', &lines); 106 107 std::string charset; 108 for (size_t i = 0; i < lines.size(); ++i) { 109 std::string line; 110 TrimString(lines[i], " ", &line); 111 112 // Get the encoding of the bookmark file. 113 if (ParseCharsetFromLine(line, &charset)) 114 continue; 115 116 // Get the bookmark. 117 string16 title; 118 GURL url, favicon; 119 string16 shortcut; 120 base::Time add_date; 121 string16 post_data; 122 if (ParseBookmarkFromLine(line, charset, &title, &url, 123 &favicon, &shortcut, &add_date, 124 &post_data)) 125 urls->insert(url); 126 } 127 } 128 129 // static 130 TemplateURL* Firefox2Importer::CreateTemplateURL(const string16& title, 131 const string16& keyword, 132 const GURL& url) { 133 // Skip if the keyword or url is invalid. 134 if (keyword.empty() && url.is_valid()) 135 return NULL; 136 137 TemplateURL* t_url = new TemplateURL(); 138 // We set short name by using the title if it exists. 139 // Otherwise, we use the shortcut. 140 t_url->set_short_name(!title.empty() ? title : keyword); 141 t_url->set_keyword(keyword); 142 t_url->SetURL(TemplateURLRef::DisplayURLToURLRef(UTF8ToUTF16(url.spec())), 143 0, 0); 144 return t_url; 145 } 146 147 // static 148 void Firefox2Importer::ImportBookmarksFile( 149 const FilePath& file_path, 150 const std::set<GURL>& default_urls, 151 bool import_to_bookmark_bar, 152 const string16& first_folder_name, 153 Importer* importer, 154 std::vector<ProfileWriter::BookmarkEntry>* bookmarks, 155 std::vector<TemplateURL*>* template_urls, 156 std::vector<history::ImportedFaviconUsage>* favicons) { 157 std::string content; 158 file_util::ReadFileToString(file_path, &content); 159 std::vector<std::string> lines; 160 base::SplitString(content, '\n', &lines); 161 162 std::vector<ProfileWriter::BookmarkEntry> toolbar_bookmarks; 163 string16 last_folder = first_folder_name; 164 bool last_folder_on_toolbar = false; 165 bool last_folder_is_empty = true; 166 base::Time last_folder_add_date; 167 std::vector<string16> path; 168 size_t toolbar_folder = 0; 169 std::string charset; 170 for (size_t i = 0; i < lines.size() && (!importer || !importer->cancelled()); 171 ++i) { 172 std::string line; 173 TrimString(lines[i], " ", &line); 174 175 // Get the encoding of the bookmark file. 176 if (ParseCharsetFromLine(line, &charset)) 177 continue; 178 179 // Get the folder name. 180 if (ParseFolderNameFromLine(line, charset, &last_folder, 181 &last_folder_on_toolbar, 182 &last_folder_add_date)) 183 continue; 184 185 // Get the bookmark entry. 186 string16 title; 187 string16 shortcut; 188 GURL url, favicon; 189 base::Time add_date; 190 string16 post_data; 191 bool is_bookmark; 192 // TODO(jcampan): http://b/issue?id=1196285 we do not support POST based 193 // keywords yet. 194 is_bookmark = ParseBookmarkFromLine(line, charset, &title, 195 &url, &favicon, &shortcut, &add_date, 196 &post_data) || 197 ParseMinimumBookmarkFromLine(line, charset, &title, &url); 198 199 if (is_bookmark) 200 last_folder_is_empty = false; 201 202 if (is_bookmark && 203 post_data.empty() && 204 CanImportURL(GURL(url)) && 205 default_urls.find(url) == default_urls.end()) { 206 if (toolbar_folder > path.size() && !path.empty()) { 207 NOTREACHED(); // error in parsing. 208 break; 209 } 210 211 ProfileWriter::BookmarkEntry entry; 212 entry.creation_time = add_date; 213 entry.url = url; 214 entry.title = title; 215 216 if (import_to_bookmark_bar && toolbar_folder) { 217 // Flatten the items in toolbar. 218 entry.in_toolbar = true; 219 entry.path.assign(path.begin() + toolbar_folder, path.end()); 220 toolbar_bookmarks.push_back(entry); 221 } else { 222 // Insert the item into the "Imported from Firefox" folder. 223 entry.path.assign(path.begin(), path.end()); 224 if (import_to_bookmark_bar) 225 entry.path.erase(entry.path.begin()); 226 bookmarks->push_back(entry); 227 } 228 229 // Save the favicon. DataURLToFaviconUsage will handle the case where 230 // there is no favicon. 231 if (favicons) 232 DataURLToFaviconUsage(url, favicon, favicons); 233 234 if (template_urls) { 235 // If there is a SHORTCUT attribute for this bookmark, we 236 // add it as our keywords. 237 TemplateURL* t_url = CreateTemplateURL(title, shortcut, url); 238 if (t_url) 239 template_urls->push_back(t_url); 240 } 241 242 continue; 243 } 244 245 // Bookmarks in sub-folder are encapsulated with <DL> tag. 246 if (StartsWithASCII(line, "<DL>", false)) { 247 path.push_back(last_folder); 248 last_folder.clear(); 249 if (last_folder_on_toolbar && !toolbar_folder) 250 toolbar_folder = path.size(); 251 252 // Mark next folder empty as initial state. 253 last_folder_is_empty = true; 254 } else if (StartsWithASCII(line, "</DL>", false)) { 255 if (path.empty()) 256 break; // Mismatch <DL>. 257 258 string16 folder_title = path.back(); 259 path.pop_back(); 260 261 if (last_folder_is_empty) { 262 // Empty folder should be added explicitly. 263 ProfileWriter::BookmarkEntry entry; 264 entry.is_folder = true; 265 entry.creation_time = last_folder_add_date; 266 entry.title = folder_title; 267 if (import_to_bookmark_bar && toolbar_folder) { 268 // Flatten the folder in toolbar. 269 if (toolbar_folder <= path.size()) { 270 entry.in_toolbar = true; 271 entry.path.assign(path.begin() + toolbar_folder, path.end()); 272 toolbar_bookmarks.push_back(entry); 273 } 274 } else { 275 // Insert the folder into the "Imported from Firefox" folder. 276 entry.path.assign(path.begin(), path.end()); 277 if (import_to_bookmark_bar) 278 entry.path.erase(entry.path.begin()); 279 bookmarks->push_back(entry); 280 } 281 282 // Parent folder include current one, so it's not empty. 283 last_folder_is_empty = false; 284 } 285 286 if (toolbar_folder > path.size()) 287 toolbar_folder = 0; 288 } 289 } 290 291 bookmarks->insert(bookmarks->begin(), toolbar_bookmarks.begin(), 292 toolbar_bookmarks.end()); 293 } 294 295 void Firefox2Importer::ImportBookmarks() { 296 // Load the default bookmarks. 297 std::set<GURL> default_urls; 298 if (!parsing_bookmarks_html_file_) 299 LoadDefaultBookmarks(app_path_, &default_urls); 300 301 // Parse the bookmarks.html file. 302 std::vector<ProfileWriter::BookmarkEntry> bookmarks, toolbar_bookmarks; 303 std::vector<TemplateURL*> template_urls; 304 std::vector<history::ImportedFaviconUsage> favicons; 305 FilePath file = source_path_; 306 if (!parsing_bookmarks_html_file_) 307 file = file.AppendASCII("bookmarks.html"); 308 string16 first_folder_name = bridge_->GetLocalizedString( 309 parsing_bookmarks_html_file_ ? IDS_BOOKMARK_GROUP : 310 IDS_BOOKMARK_GROUP_FROM_FIREFOX); 311 312 ImportBookmarksFile(file, default_urls, import_to_bookmark_bar(), 313 first_folder_name, this, &bookmarks, &template_urls, 314 &favicons); 315 316 // Write data into profile. 317 if (!bookmarks.empty() && !cancelled()) { 318 int options = 0; 319 if (import_to_bookmark_bar()) 320 options |= ProfileWriter::IMPORT_TO_BOOKMARK_BAR; 321 if (bookmark_bar_disabled()) 322 options |= ProfileWriter::BOOKMARK_BAR_DISABLED; 323 bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options); 324 } 325 if (!parsing_bookmarks_html_file_ && !template_urls.empty() && 326 !cancelled()) { 327 bridge_->SetKeywords(template_urls, -1, false); 328 } else { 329 STLDeleteContainerPointers(template_urls.begin(), template_urls.end()); 330 } 331 if (!favicons.empty()) { 332 bridge_->SetFavicons(favicons); 333 } 334 } 335 336 void Firefox2Importer::ImportPasswords() { 337 // Initializes NSS3. 338 NSSDecryptor decryptor; 339 if (!decryptor.Init(source_path_, source_path_) && 340 !decryptor.Init(app_path_, source_path_)) { 341 return; 342 } 343 344 // Firefox 2 uses signons2.txt to store the pssswords. If it doesn't 345 // exist, we try to find its older version. 346 FilePath file = source_path_.AppendASCII("signons2.txt"); 347 if (!file_util::PathExists(file)) { 348 file = source_path_.AppendASCII("signons.txt"); 349 } 350 351 std::string content; 352 file_util::ReadFileToString(file, &content); 353 std::vector<webkit_glue::PasswordForm> forms; 354 decryptor.ParseSignons(content, &forms); 355 356 if (!cancelled()) { 357 for (size_t i = 0; i < forms.size(); ++i) { 358 bridge_->SetPasswordForm(forms[i]); 359 } 360 } 361 } 362 363 void Firefox2Importer::ImportHistory() { 364 FilePath file = source_path_.AppendASCII("history.dat"); 365 ImportHistoryFromFirefox2(file, bridge_); 366 } 367 368 void Firefox2Importer::ImportSearchEngines() { 369 std::vector<FilePath> files; 370 GetSearchEnginesXMLFiles(&files); 371 372 std::vector<TemplateURL*> search_engines; 373 ParseSearchEnginesFromXMLFiles(files, &search_engines); 374 375 int default_index = 376 GetFirefoxDefaultSearchEngineIndex(search_engines, source_path_); 377 bridge_->SetKeywords(search_engines, default_index, true); 378 } 379 380 void Firefox2Importer::ImportHomepage() { 381 GURL home_page = GetHomepage(source_path_); 382 if (home_page.is_valid() && !IsDefaultHomepage(home_page, app_path_)) { 383 bridge_->AddHomePage(home_page); 384 } 385 } 386 387 void Firefox2Importer::GetSearchEnginesXMLFiles( 388 std::vector<FilePath>* files) { 389 // Search engines are contained in XML files in a searchplugins directory that 390 // can be found in 2 locations: 391 // - Firefox install dir (default search engines) 392 // - the profile dir (user added search engines) 393 FilePath dir = app_path_.AppendASCII("searchplugins"); 394 FindXMLFilesInDir(dir, files); 395 396 FilePath profile_dir = source_path_.AppendASCII("searchplugins"); 397 FindXMLFilesInDir(profile_dir, files); 398 } 399 400 // static 401 bool Firefox2Importer::ParseCharsetFromLine(const std::string& line, 402 std::string* charset) { 403 const char kCharset[] = "charset="; 404 if (StartsWithASCII(line, "<META", false) && 405 (line.find("CONTENT=\"") != std::string::npos || 406 line.find("content=\"") != std::string::npos)) { 407 size_t begin = line.find(kCharset); 408 if (begin == std::string::npos) 409 return false; 410 begin += std::string(kCharset).size(); 411 size_t end = line.find_first_of('\"', begin); 412 *charset = line.substr(begin, end - begin); 413 return true; 414 } 415 return false; 416 } 417 418 // static 419 bool Firefox2Importer::ParseFolderNameFromLine(const std::string& line, 420 const std::string& charset, 421 string16* folder_name, 422 bool* is_toolbar_folder, 423 base::Time* add_date) { 424 const char kFolderOpen[] = "<DT><H3"; 425 const char kFolderClose[] = "</H3>"; 426 const char kToolbarFolderAttribute[] = "PERSONAL_TOOLBAR_FOLDER"; 427 const char kAddDateAttribute[] = "ADD_DATE"; 428 429 if (!StartsWithASCII(line, kFolderOpen, true)) 430 return false; 431 432 size_t end = line.find(kFolderClose); 433 size_t tag_end = line.rfind('>', end) + 1; 434 // If no end tag or start tag is broken, we skip to find the folder name. 435 if (end == std::string::npos || tag_end < arraysize(kFolderOpen)) 436 return false; 437 438 base::CodepageToUTF16(line.substr(tag_end, end - tag_end), charset.c_str(), 439 base::OnStringConversionError::SKIP, folder_name); 440 HTMLUnescape(folder_name); 441 442 std::string attribute_list = line.substr(arraysize(kFolderOpen), 443 tag_end - arraysize(kFolderOpen) - 1); 444 std::string value; 445 446 // Add date 447 if (GetAttribute(attribute_list, kAddDateAttribute, &value)) { 448 int64 time; 449 base::StringToInt64(value, &time); 450 // Upper bound it at 32 bits. 451 if (0 < time && time < (1LL << 32)) 452 *add_date = base::Time::FromTimeT(time); 453 } 454 455 if (GetAttribute(attribute_list, kToolbarFolderAttribute, &value) && 456 LowerCaseEqualsASCII(value, "true")) 457 *is_toolbar_folder = true; 458 else 459 *is_toolbar_folder = false; 460 461 return true; 462 } 463 464 // static 465 bool Firefox2Importer::ParseBookmarkFromLine(const std::string& line, 466 const std::string& charset, 467 string16* title, 468 GURL* url, 469 GURL* favicon, 470 string16* shortcut, 471 base::Time* add_date, 472 string16* post_data) { 473 title->clear(); 474 *url = GURL(); 475 *favicon = GURL(); 476 shortcut->clear(); 477 post_data->clear(); 478 *add_date = base::Time(); 479 480 if (!StartsWithASCII(line, kItemOpen, true)) 481 return false; 482 483 size_t end = line.find(kItemClose); 484 size_t tag_end = line.rfind('>', end) + 1; 485 if (end == std::string::npos || tag_end < arraysize(kItemOpen)) 486 return false; // No end tag or start tag is broken. 487 488 std::string attribute_list = line.substr(arraysize(kItemOpen), 489 tag_end - arraysize(kItemOpen) - 1); 490 491 // We don't import Live Bookmark folders, which is Firefox's RSS reading 492 // feature, since the user never necessarily bookmarked them and we don't 493 // have this feature to update their contents. 494 std::string value; 495 if (GetAttribute(attribute_list, kFeedURLAttribute, &value)) 496 return false; 497 498 // Title 499 base::CodepageToUTF16(line.substr(tag_end, end - tag_end), charset.c_str(), 500 base::OnStringConversionError::SKIP, title); 501 HTMLUnescape(title); 502 503 // URL 504 if (GetAttribute(attribute_list, kHrefAttribute, &value)) { 505 string16 url16; 506 base::CodepageToUTF16(value, charset.c_str(), 507 base::OnStringConversionError::SKIP, &url16); 508 HTMLUnescape(&url16); 509 510 *url = GURL(url16); 511 } 512 513 // Favicon 514 if (GetAttribute(attribute_list, kIconAttribute, &value)) 515 *favicon = GURL(value); 516 517 // Keyword 518 if (GetAttribute(attribute_list, kShortcutURLAttribute, &value)) { 519 base::CodepageToUTF16(value, charset.c_str(), 520 base::OnStringConversionError::SKIP, shortcut); 521 HTMLUnescape(shortcut); 522 } 523 524 // Add date 525 if (GetAttribute(attribute_list, kAddDateAttribute, &value)) { 526 int64 time; 527 base::StringToInt64(value, &time); 528 // Upper bound it at 32 bits. 529 if (0 < time && time < (1LL << 32)) 530 *add_date = base::Time::FromTimeT(time); 531 } 532 533 // Post data. 534 if (GetAttribute(attribute_list, kPostDataAttribute, &value)) { 535 base::CodepageToUTF16(value, charset.c_str(), 536 base::OnStringConversionError::SKIP, post_data); 537 HTMLUnescape(post_data); 538 } 539 540 return true; 541 } 542 543 // static 544 bool Firefox2Importer::ParseMinimumBookmarkFromLine(const std::string& line, 545 const std::string& charset, 546 string16* title, 547 GURL* url) { 548 const char kItemOpen[] = "<DT><A"; 549 const char kItemClose[] = "</"; 550 const char kHrefAttributeUpper[] = "HREF"; 551 const char kHrefAttributeLower[] = "href"; 552 553 title->clear(); 554 *url = GURL(); 555 556 // Case-insensitive check of open tag. 557 if (!StartsWithASCII(line, kItemOpen, false)) 558 return false; 559 560 // Find any close tag. 561 size_t end = line.find(kItemClose); 562 size_t tag_end = line.rfind('>', end) + 1; 563 if (end == std::string::npos || tag_end < arraysize(kItemOpen)) 564 return false; // No end tag or start tag is broken. 565 566 std::string attribute_list = line.substr(arraysize(kItemOpen), 567 tag_end - arraysize(kItemOpen) - 1); 568 569 // Title 570 base::CodepageToUTF16(line.substr(tag_end, end - tag_end), charset.c_str(), 571 base::OnStringConversionError::SKIP, title); 572 HTMLUnescape(title); 573 574 // URL 575 std::string value; 576 if (GetAttribute(attribute_list, kHrefAttributeUpper, &value) || 577 GetAttribute(attribute_list, kHrefAttributeLower, &value)) { 578 if (charset.length() != 0) { 579 string16 url16; 580 base::CodepageToUTF16(value, charset.c_str(), 581 base::OnStringConversionError::SKIP, &url16); 582 HTMLUnescape(&url16); 583 584 *url = GURL(url16); 585 } else { 586 *url = GURL(value); 587 } 588 } 589 590 return true; 591 } 592 593 // static 594 bool Firefox2Importer::GetAttribute(const std::string& attribute_list, 595 const std::string& attribute, 596 std::string* value) { 597 const char kQuote[] = "\""; 598 599 size_t begin = attribute_list.find(attribute + "=" + kQuote); 600 if (begin == std::string::npos) 601 return false; // Can't find the attribute. 602 603 begin = attribute_list.find(kQuote, begin) + 1; 604 605 size_t end = begin + 1; 606 while (end < attribute_list.size()) { 607 if (attribute_list[end] == '"' && 608 attribute_list[end - 1] != '\\') { 609 break; 610 } 611 end++; 612 } 613 614 if (end == attribute_list.size()) 615 return false; // The value is not quoted. 616 617 *value = attribute_list.substr(begin, end - begin); 618 return true; 619 } 620 621 // static 622 void Firefox2Importer::HTMLUnescape(string16* text) { 623 string16 text16 = *text; 624 ReplaceSubstringsAfterOffset( 625 &text16, 0, ASCIIToUTF16("<"), ASCIIToUTF16("<")); 626 ReplaceSubstringsAfterOffset( 627 &text16, 0, ASCIIToUTF16(">"), ASCIIToUTF16(">")); 628 ReplaceSubstringsAfterOffset( 629 &text16, 0, ASCIIToUTF16("&"), ASCIIToUTF16("&")); 630 ReplaceSubstringsAfterOffset( 631 &text16, 0, ASCIIToUTF16("""), ASCIIToUTF16("\"")); 632 ReplaceSubstringsAfterOffset( 633 &text16, 0, ASCIIToUTF16("'"), ASCIIToUTF16("\'")); 634 text->assign(text16); 635 } 636 637 // static 638 void Firefox2Importer::FindXMLFilesInDir( 639 const FilePath& dir, 640 std::vector<FilePath>* xml_files) { 641 file_util::FileEnumerator file_enum(dir, false, 642 file_util::FileEnumerator::FILES, 643 FILE_PATH_LITERAL("*.xml")); 644 FilePath file(file_enum.Next()); 645 while (!file.empty()) { 646 xml_files->push_back(file); 647 file = file_enum.Next(); 648 } 649 } 650 651 // static 652 void Firefox2Importer::DataURLToFaviconUsage( 653 const GURL& link_url, 654 const GURL& favicon_data, 655 std::vector<history::ImportedFaviconUsage>* favicons) { 656 if (!link_url.is_valid() || !favicon_data.is_valid() || 657 !favicon_data.SchemeIs(chrome::kDataScheme)) 658 return; 659 660 // Parse the data URL. 661 std::string mime_type, char_set, data; 662 if (!net::DataURL::Parse(favicon_data, &mime_type, &char_set, &data) || 663 data.empty()) 664 return; 665 666 history::ImportedFaviconUsage usage; 667 if (!ReencodeFavicon(reinterpret_cast<const unsigned char*>(&data[0]), 668 data.size(), &usage.png_data)) 669 return; // Unable to decode. 670 671 // We need to make up a URL for the favicon. We use a version of the page's 672 // URL so that we can be sure it will not collide. 673 usage.favicon_url = GURL(std::string("made-up-favicon:") + link_url.spec()); 674 675 // We only have one URL per favicon for Firefox 2 bookmarks. 676 usage.urls.insert(link_url); 677 678 favicons->push_back(usage); 679 } 680