Home | History | Annotate | Download | only in importer
      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("&lt;"), ASCIIToUTF16("<"));
    626   ReplaceSubstringsAfterOffset(
    627       &text16, 0, ASCIIToUTF16("&gt;"), ASCIIToUTF16(">"));
    628   ReplaceSubstringsAfterOffset(
    629       &text16, 0, ASCIIToUTF16("&amp;"), ASCIIToUTF16("&"));
    630   ReplaceSubstringsAfterOffset(
    631       &text16, 0, ASCIIToUTF16("&quot;"), ASCIIToUTF16("\""));
    632   ReplaceSubstringsAfterOffset(
    633       &text16, 0, ASCIIToUTF16("&#39;"), 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