firefox2_importer.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
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
34namespace {
35const char kItemOpen[] = "<DT><A";
36const char kItemClose[] = "</A>";
37const char kFeedURLAttribute[] = "FEEDURL";
38const char kHrefAttribute[] = "HREF";
39const char kIconAttribute[] = "ICON";
40const char kShortcutURLAttribute[] = "SHORTCUTURL";
41const char kAddDateAttribute[] = "ADD_DATE";
42const char kPostDataAttribute[] = "POST_DATA";
43}
44
45Firefox2Importer::Firefox2Importer() : parsing_bookmarks_html_file_(false) {}
46
47Firefox2Importer::~Firefox2Importer() {}
48
49void 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
93void 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
130TemplateURL* 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
148void 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          entry.in_toolbar = true;
270          entry.path.assign(path.begin() + toolbar_folder, path.end());
271          toolbar_bookmarks.push_back(entry);
272        } else {
273          // Insert the folder into the "Imported from Firefox" folder.
274          entry.path.assign(path.begin(), path.end());
275          if (import_to_bookmark_bar)
276            entry.path.erase(entry.path.begin());
277          bookmarks->push_back(entry);
278        }
279
280        // Parent folder include current one, so it's not empty.
281        last_folder_is_empty = false;
282      }
283
284      if (toolbar_folder > path.size())
285        toolbar_folder = 0;
286    }
287  }
288
289  bookmarks->insert(bookmarks->begin(), toolbar_bookmarks.begin(),
290                    toolbar_bookmarks.end());
291}
292
293void Firefox2Importer::ImportBookmarks() {
294  // Load the default bookmarks.
295  std::set<GURL> default_urls;
296  if (!parsing_bookmarks_html_file_)
297    LoadDefaultBookmarks(app_path_, &default_urls);
298
299  // Parse the bookmarks.html file.
300  std::vector<ProfileWriter::BookmarkEntry> bookmarks, toolbar_bookmarks;
301  std::vector<TemplateURL*> template_urls;
302  std::vector<history::ImportedFaviconUsage> favicons;
303  FilePath file = source_path_;
304  if (!parsing_bookmarks_html_file_)
305    file = file.AppendASCII("bookmarks.html");
306  string16 first_folder_name = bridge_->GetLocalizedString(
307      parsing_bookmarks_html_file_ ? IDS_BOOKMARK_GROUP :
308                                     IDS_BOOKMARK_GROUP_FROM_FIREFOX);
309
310  ImportBookmarksFile(file, default_urls, import_to_bookmark_bar(),
311                      first_folder_name, this, &bookmarks, &template_urls,
312                      &favicons);
313
314  // Write data into profile.
315  if (!bookmarks.empty() && !cancelled()) {
316    int options = 0;
317    if (import_to_bookmark_bar())
318      options |= ProfileWriter::IMPORT_TO_BOOKMARK_BAR;
319    if (bookmark_bar_disabled())
320      options |= ProfileWriter::BOOKMARK_BAR_DISABLED;
321    bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options);
322  }
323  if (!parsing_bookmarks_html_file_ && !template_urls.empty() &&
324      !cancelled()) {
325    bridge_->SetKeywords(template_urls, -1, false);
326  } else {
327    STLDeleteContainerPointers(template_urls.begin(), template_urls.end());
328  }
329  if (!favicons.empty()) {
330    bridge_->SetFavicons(favicons);
331  }
332}
333
334void Firefox2Importer::ImportPasswords() {
335  // Initializes NSS3.
336  NSSDecryptor decryptor;
337  if (!decryptor.Init(source_path_, source_path_) &&
338      !decryptor.Init(app_path_, source_path_)) {
339    return;
340  }
341
342  // Firefox 2 uses signons2.txt to store the pssswords. If it doesn't
343  // exist, we try to find its older version.
344  FilePath file = source_path_.AppendASCII("signons2.txt");
345  if (!file_util::PathExists(file)) {
346    file = source_path_.AppendASCII("signons.txt");
347  }
348
349  std::string content;
350  file_util::ReadFileToString(file, &content);
351  std::vector<webkit_glue::PasswordForm> forms;
352  decryptor.ParseSignons(content, &forms);
353
354  if (!cancelled()) {
355    for (size_t i = 0; i < forms.size(); ++i) {
356      bridge_->SetPasswordForm(forms[i]);
357    }
358  }
359}
360
361void Firefox2Importer::ImportHistory() {
362  FilePath file = source_path_.AppendASCII("history.dat");
363  ImportHistoryFromFirefox2(file, bridge_);
364}
365
366void Firefox2Importer::ImportSearchEngines() {
367  std::vector<FilePath> files;
368  GetSearchEnginesXMLFiles(&files);
369
370  std::vector<TemplateURL*> search_engines;
371  ParseSearchEnginesFromXMLFiles(files, &search_engines);
372
373  int default_index =
374      GetFirefoxDefaultSearchEngineIndex(search_engines, source_path_);
375  bridge_->SetKeywords(search_engines, default_index, true);
376}
377
378void Firefox2Importer::ImportHomepage() {
379  GURL home_page = GetHomepage(source_path_);
380  if (home_page.is_valid() && !IsDefaultHomepage(home_page, app_path_)) {
381    bridge_->AddHomePage(home_page);
382  }
383}
384
385void Firefox2Importer::GetSearchEnginesXMLFiles(
386    std::vector<FilePath>* files) {
387  // Search engines are contained in XML files in a searchplugins directory that
388  // can be found in 2 locations:
389  // - Firefox install dir (default search engines)
390  // - the profile dir (user added search engines)
391  FilePath dir = app_path_.AppendASCII("searchplugins");
392  FindXMLFilesInDir(dir, files);
393
394  FilePath profile_dir = source_path_.AppendASCII("searchplugins");
395  FindXMLFilesInDir(profile_dir, files);
396}
397
398// static
399bool Firefox2Importer::ParseCharsetFromLine(const std::string& line,
400                                            std::string* charset) {
401  const char kCharset[] = "charset=";
402  if (StartsWithASCII(line, "<META", false) &&
403      (line.find("CONTENT=\"") != std::string::npos ||
404          line.find("content=\"") != std::string::npos)) {
405    size_t begin = line.find(kCharset);
406    if (begin == std::string::npos)
407      return false;
408    begin += std::string(kCharset).size();
409    size_t end = line.find_first_of('\"', begin);
410    *charset = line.substr(begin, end - begin);
411    return true;
412  }
413  return false;
414}
415
416// static
417bool Firefox2Importer::ParseFolderNameFromLine(const std::string& line,
418                                               const std::string& charset,
419                                               string16* folder_name,
420                                               bool* is_toolbar_folder,
421                                               base::Time* add_date) {
422  const char kFolderOpen[] = "<DT><H3";
423  const char kFolderClose[] = "</H3>";
424  const char kToolbarFolderAttribute[] = "PERSONAL_TOOLBAR_FOLDER";
425  const char kAddDateAttribute[] = "ADD_DATE";
426
427  if (!StartsWithASCII(line, kFolderOpen, true))
428    return false;
429
430  size_t end = line.find(kFolderClose);
431  size_t tag_end = line.rfind('>', end) + 1;
432  // If no end tag or start tag is broken, we skip to find the folder name.
433  if (end == std::string::npos || tag_end < arraysize(kFolderOpen))
434    return false;
435
436  base::CodepageToUTF16(line.substr(tag_end, end - tag_end), charset.c_str(),
437                        base::OnStringConversionError::SKIP, folder_name);
438  HTMLUnescape(folder_name);
439
440  std::string attribute_list = line.substr(arraysize(kFolderOpen),
441      tag_end - arraysize(kFolderOpen) - 1);
442  std::string value;
443
444  // Add date
445  if (GetAttribute(attribute_list, kAddDateAttribute, &value)) {
446    int64 time;
447    base::StringToInt64(value, &time);
448    // Upper bound it at 32 bits.
449    if (0 < time && time < (1LL << 32))
450      *add_date = base::Time::FromTimeT(time);
451  }
452
453  if (GetAttribute(attribute_list, kToolbarFolderAttribute, &value) &&
454      LowerCaseEqualsASCII(value, "true"))
455    *is_toolbar_folder = true;
456  else
457    *is_toolbar_folder = false;
458
459  return true;
460}
461
462// static
463bool Firefox2Importer::ParseBookmarkFromLine(const std::string& line,
464                                             const std::string& charset,
465                                             string16* title,
466                                             GURL* url,
467                                             GURL* favicon,
468                                             string16* shortcut,
469                                             base::Time* add_date,
470                                             string16* post_data) {
471  title->clear();
472  *url = GURL();
473  *favicon = GURL();
474  shortcut->clear();
475  post_data->clear();
476  *add_date = base::Time();
477
478  if (!StartsWithASCII(line, kItemOpen, true))
479    return false;
480
481  size_t end = line.find(kItemClose);
482  size_t tag_end = line.rfind('>', end) + 1;
483  if (end == std::string::npos || tag_end < arraysize(kItemOpen))
484    return false;  // No end tag or start tag is broken.
485
486  std::string attribute_list = line.substr(arraysize(kItemOpen),
487      tag_end - arraysize(kItemOpen) - 1);
488
489  // We don't import Live Bookmark folders, which is Firefox's RSS reading
490  // feature, since the user never necessarily bookmarked them and we don't
491  // have this feature to update their contents.
492  std::string value;
493  if (GetAttribute(attribute_list, kFeedURLAttribute, &value))
494    return false;
495
496  // Title
497  base::CodepageToUTF16(line.substr(tag_end, end - tag_end), charset.c_str(),
498                        base::OnStringConversionError::SKIP, title);
499  HTMLUnescape(title);
500
501  // URL
502  if (GetAttribute(attribute_list, kHrefAttribute, &value)) {
503    string16 url16;
504    base::CodepageToUTF16(value, charset.c_str(),
505                          base::OnStringConversionError::SKIP, &url16);
506    HTMLUnescape(&url16);
507
508    *url = GURL(url16);
509  }
510
511  // Favicon
512  if (GetAttribute(attribute_list, kIconAttribute, &value))
513    *favicon = GURL(value);
514
515  // Keyword
516  if (GetAttribute(attribute_list, kShortcutURLAttribute, &value)) {
517    base::CodepageToUTF16(value, charset.c_str(),
518                          base::OnStringConversionError::SKIP, shortcut);
519    HTMLUnescape(shortcut);
520  }
521
522  // Add date
523  if (GetAttribute(attribute_list, kAddDateAttribute, &value)) {
524    int64 time;
525    base::StringToInt64(value, &time);
526    // Upper bound it at 32 bits.
527    if (0 < time && time < (1LL << 32))
528      *add_date = base::Time::FromTimeT(time);
529  }
530
531  // Post data.
532  if (GetAttribute(attribute_list, kPostDataAttribute, &value)) {
533    base::CodepageToUTF16(value, charset.c_str(),
534                          base::OnStringConversionError::SKIP, post_data);
535    HTMLUnescape(post_data);
536  }
537
538  return true;
539}
540
541// static
542bool Firefox2Importer::ParseMinimumBookmarkFromLine(const std::string& line,
543                                                    const std::string& charset,
544                                                    string16* title,
545                                                    GURL* url) {
546  const char kItemOpen[] = "<DT><A";
547  const char kItemClose[] = "</";
548  const char kHrefAttributeUpper[] = "HREF";
549  const char kHrefAttributeLower[] = "href";
550
551  title->clear();
552  *url = GURL();
553
554  // Case-insensitive check of open tag.
555  if (!StartsWithASCII(line, kItemOpen, false))
556    return false;
557
558  // Find any close tag.
559  size_t end = line.find(kItemClose);
560  size_t tag_end = line.rfind('>', end) + 1;
561  if (end == std::string::npos || tag_end < arraysize(kItemOpen))
562    return false;  // No end tag or start tag is broken.
563
564  std::string attribute_list = line.substr(arraysize(kItemOpen),
565      tag_end - arraysize(kItemOpen) - 1);
566
567  // Title
568  base::CodepageToUTF16(line.substr(tag_end, end - tag_end), charset.c_str(),
569                        base::OnStringConversionError::SKIP, title);
570  HTMLUnescape(title);
571
572  // URL
573  std::string value;
574  if (GetAttribute(attribute_list, kHrefAttributeUpper, &value) ||
575      GetAttribute(attribute_list, kHrefAttributeLower, &value)) {
576    if (charset.length() != 0) {
577      string16 url16;
578      base::CodepageToUTF16(value, charset.c_str(),
579                            base::OnStringConversionError::SKIP, &url16);
580      HTMLUnescape(&url16);
581
582      *url = GURL(url16);
583    } else {
584      *url = GURL(value);
585    }
586  }
587
588  return true;
589}
590
591// static
592bool Firefox2Importer::GetAttribute(const std::string& attribute_list,
593                                    const std::string& attribute,
594                                    std::string* value) {
595  const char kQuote[] = "\"";
596
597  size_t begin = attribute_list.find(attribute + "=" + kQuote);
598  if (begin == std::string::npos)
599    return false;  // Can't find the attribute.
600
601  begin = attribute_list.find(kQuote, begin) + 1;
602
603  size_t end = begin + 1;
604  while (end < attribute_list.size()) {
605    if (attribute_list[end] == '"' &&
606        attribute_list[end - 1] != '\\') {
607      break;
608    }
609    end++;
610  }
611
612  if (end == attribute_list.size())
613    return false;  // The value is not quoted.
614
615  *value = attribute_list.substr(begin, end - begin);
616  return true;
617}
618
619// static
620void Firefox2Importer::HTMLUnescape(string16* text) {
621  string16 text16 = *text;
622  ReplaceSubstringsAfterOffset(
623      &text16, 0, ASCIIToUTF16("&lt;"), ASCIIToUTF16("<"));
624  ReplaceSubstringsAfterOffset(
625      &text16, 0, ASCIIToUTF16("&gt;"), ASCIIToUTF16(">"));
626  ReplaceSubstringsAfterOffset(
627      &text16, 0, ASCIIToUTF16("&amp;"), ASCIIToUTF16("&"));
628  ReplaceSubstringsAfterOffset(
629      &text16, 0, ASCIIToUTF16("&quot;"), ASCIIToUTF16("\""));
630  ReplaceSubstringsAfterOffset(
631      &text16, 0, ASCIIToUTF16("&#39;"), ASCIIToUTF16("\'"));
632  text->assign(text16);
633}
634
635// static
636void Firefox2Importer::FindXMLFilesInDir(
637    const FilePath& dir,
638    std::vector<FilePath>* xml_files) {
639  file_util::FileEnumerator file_enum(dir, false,
640                                      file_util::FileEnumerator::FILES,
641                                      FILE_PATH_LITERAL("*.xml"));
642  FilePath file(file_enum.Next());
643  while (!file.empty()) {
644    xml_files->push_back(file);
645    file = file_enum.Next();
646  }
647}
648
649// static
650void Firefox2Importer::DataURLToFaviconUsage(
651    const GURL& link_url,
652    const GURL& favicon_data,
653    std::vector<history::ImportedFaviconUsage>* favicons) {
654  if (!link_url.is_valid() || !favicon_data.is_valid() ||
655      !favicon_data.SchemeIs(chrome::kDataScheme))
656    return;
657
658  // Parse the data URL.
659  std::string mime_type, char_set, data;
660  if (!net::DataURL::Parse(favicon_data, &mime_type, &char_set, &data) ||
661      data.empty())
662    return;
663
664  history::ImportedFaviconUsage usage;
665  if (!ReencodeFavicon(reinterpret_cast<const unsigned char*>(&data[0]),
666                       data.size(), &usage.png_data))
667    return;  // Unable to decode.
668
669  // We need to make up a URL for the favicon. We use a version of the page's
670  // URL so that we can be sure it will not collide.
671  usage.favicon_url = GURL(std::string("made-up-favicon:") + link_url.spec());
672
673  // We only have one URL per favicon for Firefox 2 bookmarks.
674  usage.urls.insert(link_url);
675
676  favicons->push_back(usage);
677}
678