summaryrefslogtreecommitdiffstats
path: root/chrome/browser/importer
diff options
context:
space:
mode:
authorbeng@google.com <beng@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-13 00:56:27 +0000
committerbeng@google.com <beng@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-09-13 00:56:27 +0000
commit6c9851a40d3f6280dc322c2392d9cfcf8fba1b2d (patch)
tree91072da4d7f80596bcc437e82685cf7de7944dfe /chrome/browser/importer
parent231d5a36e476d013a91ca742bb8a0a2973cfee54 (diff)
downloadchromium_src-6c9851a40d3f6280dc322c2392d9cfcf8fba1b2d.zip
chromium_src-6c9851a40d3f6280dc322c2392d9cfcf8fba1b2d.tar.gz
chromium_src-6c9851a40d3f6280dc322c2392d9cfcf8fba1b2d.tar.bz2
Move importer files into an importer subdirectory.
Also delete title chomper no one uses it. B=2205 Review URL: http://codereview.chromium.org/3035 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@2154 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/importer')
-rw-r--r--chrome/browser/importer/firefox2_importer.cc515
-rw-r--r--chrome/browser/importer/firefox2_importer.h112
-rw-r--r--chrome/browser/importer/firefox3_importer.cc502
-rw-r--r--chrome/browser/importer/firefox3_importer.h85
-rw-r--r--chrome/browser/importer/firefox_importer_unittest.cc216
-rw-r--r--chrome/browser/importer/firefox_importer_utils.cc727
-rw-r--r--chrome/browser/importer/firefox_importer_utils.h211
-rw-r--r--chrome/browser/importer/firefox_profile_lock.cc87
-rw-r--r--chrome/browser/importer/firefox_profile_lock.h94
-rw-r--r--chrome/browser/importer/ie_importer.cc567
-rw-r--r--chrome/browser/importer/ie_importer.h89
-rw-r--r--chrome/browser/importer/importer.cc541
-rw-r--r--chrome/browser/importer/importer.h357
-rw-r--r--chrome/browser/importer/importer_unittest.cc819
-rw-r--r--chrome/browser/importer/mork_reader.cc581
-rw-r--r--chrome/browser/importer/mork_reader.h165
16 files changed, 5668 insertions, 0 deletions
diff --git a/chrome/browser/importer/firefox2_importer.cc b/chrome/browser/importer/firefox2_importer.cc
new file mode 100644
index 0000000..59f1778
--- /dev/null
+++ b/chrome/browser/importer/firefox2_importer.cc
@@ -0,0 +1,515 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/importer/firefox2_importer.h"
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/registry.h"
+#include "base/string_util.h"
+#include "base/values.h"
+#include "chrome/browser/importer/firefox_importer_utils.h"
+#include "chrome/browser/importer/mork_reader.h"
+#include "chrome/browser/template_url.h"
+#include "chrome/browser/template_url_parser.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/time_format.h"
+#include "generated_resources.h"
+#include "net/base/data_url.h"
+
+// Firefox2Importer.
+
+Firefox2Importer::Firefox2Importer() {
+}
+
+Firefox2Importer::~Firefox2Importer() {
+}
+
+void Firefox2Importer::StartImport(ProfileInfo profile_info,
+ uint16 items, ProfileWriter* writer,
+ ImporterHost* host) {
+ writer_ = writer;
+ source_path_ = profile_info.source_path;
+ app_path_ = profile_info.app_path;
+ importer_host_ = host;
+
+ // The order here is important!
+ NotifyStarted();
+ if ((items & HOME_PAGE) && !cancelled())
+ ImportHomepage(); // Doesn't have a UI item.
+ if ((items & FAVORITES) && !cancelled()) {
+ NotifyItemStarted(FAVORITES);
+ ImportBookmarks();
+ NotifyItemEnded(FAVORITES);
+ }
+ if ((items & SEARCH_ENGINES) && !cancelled()) {
+ NotifyItemStarted(SEARCH_ENGINES);
+ ImportSearchEngines();
+ NotifyItemEnded(SEARCH_ENGINES);
+ }
+ if ((items & PASSWORDS) && !cancelled()) {
+ NotifyItemStarted(PASSWORDS);
+ ImportPasswords();
+ NotifyItemEnded(PASSWORDS);
+ }
+ if ((items & HISTORY) && !cancelled()) {
+ NotifyItemStarted(HISTORY);
+ ImportHistory();
+ NotifyItemEnded(HISTORY);
+ }
+ NotifyEnded();
+}
+
+// static
+void Firefox2Importer::LoadDefaultBookmarks(const std::wstring& app_path,
+ std::set<GURL> *urls) {
+ // Firefox keeps its default bookmarks in a bookmarks.html file that
+ // lives at: <Firefox install dir>\defaults\profile\bookmarks.html
+ std::wstring file = app_path;
+ file_util::AppendToPath(&file, L"defaults\\profile\\bookmarks.html");
+
+ urls->clear();
+
+ // Read the whole file.
+ std::string content;
+ file_util::ReadFileToString(file, &content);
+ std::vector<std::string> lines;
+ SplitString(content, '\n', &lines);
+
+ std::string charset;
+ for (size_t i = 0; i < lines.size(); ++i) {
+ std::string line;
+ TrimString(lines[i], " ", &line);
+
+ // Get the encoding of the bookmark file.
+ if (ParseCharsetFromLine(line, &charset))
+ continue;
+
+ // Get the bookmark.
+ std::wstring title;
+ GURL url, favicon;
+ std::wstring shortcut;
+ Time add_date;
+ std::wstring post_data;
+ if (ParseBookmarkFromLine(line, charset, &title, &url,
+ &favicon, &shortcut, &add_date,
+ &post_data))
+ urls->insert(url);
+ }
+}
+
+// static
+TemplateURL* Firefox2Importer::CreateTemplateURL(const std::wstring& title,
+ const std::wstring& keyword,
+ const GURL& url) {
+ // Skip if the keyword or url is invalid.
+ if (keyword.empty() && url.is_valid())
+ return NULL;
+
+ TemplateURL* t_url = new TemplateURL();
+ // We set short name by using the title if it exists.
+ // Otherwise, we use the shortcut.
+ t_url->set_short_name(!title.empty() ? title : keyword);
+ t_url->set_keyword(keyword);
+ t_url->SetURL(TemplateURLRef::DisplayURLToURLRef(UTF8ToWide(url.spec())),
+ 0, 0);
+ return t_url;
+}
+
+void Firefox2Importer::ImportBookmarks() {
+ // Read the whole file.
+ std::wstring file = source_path_;
+ file_util::AppendToPath(&file, L"bookmarks.html");
+ std::string content;
+ file_util::ReadFileToString(file, &content);
+ std::vector<std::string> lines;
+ SplitString(content, '\n', &lines);
+
+ // Load the default bookmarks.
+ std::set<GURL> default_urls;
+ LoadDefaultBookmarks(app_path_, &default_urls);
+
+ // Parse the bookmarks.html file.
+ std::vector<ProfileWriter::BookmarkEntry> bookmarks, toolbar_bookmarks;
+ std::vector<TemplateURL*> template_urls;
+ std::vector<history::ImportedFavIconUsage> favicons;
+ std::wstring last_folder
+ = l10n_util::GetString(IDS_BOOKMARK_GROUP_FROM_FIREFOX);
+ bool last_folder_on_toolbar = false;
+ std::vector<std::wstring> path;
+ size_t toolbar_folder = 0;
+ std::string charset;
+ for (size_t i = 0; i < lines.size() && !cancelled(); ++i) {
+ std::string line;
+ TrimString(lines[i], " ", &line);
+
+ // Get the encoding of the bookmark file.
+ if (ParseCharsetFromLine(line, &charset))
+ continue;
+
+ // Get the folder name.
+ if (ParseFolderNameFromLine(line, charset, &last_folder,
+ &last_folder_on_toolbar))
+ continue;
+
+ // Get the bookmark entry.
+ std::wstring title, shortcut;
+ GURL url, favicon;
+ Time add_date;
+ std::wstring post_data;
+ // TODO(jcampan): http://b/issue?id=1196285 we do not support POST based
+ // keywords yet.
+ if (ParseBookmarkFromLine(line, charset, &title,
+ &url, &favicon, &shortcut, &add_date,
+ &post_data) &&
+ post_data.empty() &&
+ CanImportURL(GURL(url)) &&
+ default_urls.find(url) == default_urls.end()) {
+ if (toolbar_folder > path.size() && path.size() > 0) {
+ NOTREACHED(); // error in parsing.
+ break;
+ }
+
+ ProfileWriter::BookmarkEntry entry;
+ entry.creation_time = add_date;
+ entry.url = url;
+ entry.title = title;
+
+ if (first_run() && toolbar_folder) {
+ // Flatten the items in toolbar.
+ entry.in_toolbar = true;
+ entry.path.assign(path.begin() + toolbar_folder, path.end());
+ toolbar_bookmarks.push_back(entry);
+ } else {
+ // Insert the item into the "Imported from Firefox" folder after
+ // the first run.
+ entry.path.assign(path.begin(), path.end());
+ if (first_run())
+ entry.path.erase(entry.path.begin());
+ bookmarks.push_back(entry);
+ }
+
+ // Save the favicon. DataURLToFaviconUsage will handle the case where
+ // there is no favicon.
+ DataURLToFaviconUsage(url, favicon, &favicons);
+
+ // If there is a SHORTCUT attribute for this bookmark, we
+ // add it as our keywords.
+ TemplateURL* t_url = CreateTemplateURL(title, shortcut, url);
+ if (t_url)
+ template_urls.push_back(t_url);
+
+ continue;
+ }
+
+ // Bookmarks in sub-folder are encapsulated with <DL> tag.
+ if (StartsWithASCII(line, "<DL>", true)) {
+ path.push_back(last_folder);
+ last_folder.clear();
+ if (last_folder_on_toolbar && !toolbar_folder)
+ toolbar_folder = path.size();
+ } else if (StartsWithASCII(line, "</DL>", true)) {
+ if (path.empty())
+ break; // Mismatch <DL>.
+ path.pop_back();
+ if (toolbar_folder > path.size())
+ toolbar_folder = 0;
+ }
+ }
+
+ // Write data into profile.
+ bookmarks.insert(bookmarks.begin(), toolbar_bookmarks.begin(),
+ toolbar_bookmarks.end());
+ if (!bookmarks.empty() && !cancelled()) {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddBookmarkEntry, bookmarks));
+ }
+ if (!template_urls.empty() && !cancelled()) {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddKeywords, template_urls, -1, false));
+ } else {
+ STLDeleteContainerPointers(template_urls.begin(), template_urls.end());
+ }
+ if (!favicons.empty()) {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddFavicons, favicons));
+ }
+}
+
+void Firefox2Importer::ImportPasswords() {
+ // Initializes NSS3.
+ NSSDecryptor decryptor;
+ if (!decryptor.Init(source_path_, source_path_) &&
+ !decryptor.Init(app_path_, source_path_))
+ return;
+
+ // Firefox 2 uses signons2.txt to store the pssswords. If it doesn't
+ // exist, we try to find its older version.
+ std::wstring file = source_path_;
+ file_util::AppendToPath(&file, L"signons2.txt");
+ if (!file_util::PathExists(file)) {
+ file = source_path_;
+ file_util::AppendToPath(&file, L"signons.txt");
+ }
+
+ std::string content;
+ file_util::ReadFileToString(file, &content);
+ std::vector<PasswordForm> forms;
+ decryptor.ParseSignons(content, &forms);
+
+ if (!cancelled()) {
+ for (size_t i = 0; i < forms.size(); ++i) {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddPasswordForm, forms[i]));
+ }
+ }
+}
+
+void Firefox2Importer::ImportHistory() {
+ std::wstring file = source_path_;
+ file_util::AppendToPath(&file, L"history.dat");
+ ImportHistoryFromFirefox2(file, main_loop_, writer_);
+}
+
+void Firefox2Importer::ImportSearchEngines() {
+ std::vector<std::wstring> files;
+ GetSearchEnginesXMLFiles(&files);
+
+ std::vector<TemplateURL*> search_engines;
+ ParseSearchEnginesFromXMLFiles(files, &search_engines);
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddKeywords, search_engines,
+ GetFirefoxDefaultSearchEngineIndex(search_engines, source_path_),
+ true));
+}
+
+void Firefox2Importer::ImportHomepage() {
+ GURL homepage = GetHomepage(source_path_);
+ if (homepage.is_valid() && !IsDefaultHomepage(homepage, app_path_)) {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddHomepage, homepage));
+ }
+}
+
+void Firefox2Importer::GetSearchEnginesXMLFiles(
+ std::vector<std::wstring>* files) {
+ // Search engines are contained in XML files in a searchplugins directory that
+ // can be found in 2 locations:
+ // - Firefox install dir (default search engines)
+ // - the profile dir (user added search engines)
+ std::wstring dir(app_path_);
+ file_util::AppendToPath(&dir, L"searchplugins");
+ FindXMLFilesInDir(dir, files);
+
+ std::wstring profile_dir = source_path_;
+ file_util::AppendToPath(&profile_dir, L"searchplugins");
+ FindXMLFilesInDir(profile_dir, files);
+}
+
+// static
+bool Firefox2Importer::ParseCharsetFromLine(const std::string& line,
+ std::string* charset) {
+ const char kCharset[] = "charset=";
+ if (StartsWithASCII(line, "<META", true) &&
+ line.find("CONTENT=\"") != std::string::npos) {
+ size_t begin = line.find(kCharset);
+ if (begin == std::string::npos)
+ return false;
+ begin += std::string(kCharset).size();
+ size_t end = line.find_first_of('\"', begin);
+ *charset = line.substr(begin, end - begin);
+ return true;
+ }
+ return false;
+}
+
+// static
+bool Firefox2Importer::ParseFolderNameFromLine(const std::string& line,
+ const std::string& charset,
+ std::wstring* folder_name,
+ bool* is_toolbar_folder) {
+ const char kFolderOpen[] = "<DT><H3";
+ const char kFolderClose[] = "</H3>";
+ const char kToolbarFolderAttribute[] = "PERSONAL_TOOLBAR_FOLDER";
+
+ if (!StartsWithASCII(line, kFolderOpen, true))
+ return false;
+
+ size_t end = line.find(kFolderClose);
+ size_t tag_end = line.rfind('>', end) + 1;
+ // If no end tag or start tag is broken, we skip to find the folder name.
+ if (end == std::string::npos || tag_end < arraysize(kFolderOpen))
+ return false;
+
+ CodepageToWide(line.substr(tag_end, end - tag_end), charset.c_str(),
+ OnStringUtilConversionError::SKIP, folder_name);
+ HTMLUnescape(folder_name);
+
+ std::string attribute_list = line.substr(arraysize(kFolderOpen),
+ tag_end - arraysize(kFolderOpen) - 1);
+ std::string value;
+ if (GetAttribute(attribute_list, kToolbarFolderAttribute, &value) &&
+ LowerCaseEqualsASCII(value, "true"))
+ *is_toolbar_folder = true;
+ else
+ *is_toolbar_folder = false;
+
+ return true;
+}
+
+// static
+bool Firefox2Importer::ParseBookmarkFromLine(const std::string& line,
+ const std::string& charset,
+ std::wstring* title,
+ GURL* url,
+ GURL* favicon,
+ std::wstring* shortcut,
+ Time* add_date,
+ std::wstring* post_data) {
+ const char kItemOpen[] = "<DT><A";
+ const char kItemClose[] = "</A>";
+ const char kFeedURLAttribute[] = "FEEDURL";
+ const char kHrefAttribute[] = "HREF";
+ const char kIconAttribute[] = "ICON";
+ const char kShortcutURLAttribute[] = "SHORTCUTURL";
+ const char kAddDateAttribute[] = "ADD_DATE";
+ const char kPostDataAttribute[] = "POST_DATA";
+
+ title->clear();
+ *url = GURL();
+ *favicon = GURL();
+ shortcut->clear();
+ post_data->clear();
+ *add_date = Time();
+
+ if (!StartsWithASCII(line, kItemOpen, true))
+ return false;
+
+ size_t end = line.find(kItemClose);
+ size_t tag_end = line.rfind('>', end) + 1;
+ if (end == std::string::npos || tag_end < arraysize(kItemOpen))
+ return false; // No end tag or start tag is broken.
+
+ std::string attribute_list = line.substr(arraysize(kItemOpen),
+ tag_end - arraysize(kItemOpen) - 1);
+
+ // We don't import Live Bookmark folders, which is Firefox's RSS reading
+ // feature, since the user never necessarily bookmarked them and we don't
+ // have this feature to update their contents.
+ std::string value;
+ if (GetAttribute(attribute_list, kFeedURLAttribute, &value))
+ return false;
+
+ // Title
+ CodepageToWide(line.substr(tag_end, end - tag_end), charset.c_str(),
+ OnStringUtilConversionError::SKIP, title);
+ HTMLUnescape(title);
+
+ // URL
+ if (GetAttribute(attribute_list, kHrefAttribute, &value)) {
+ ReplaceSubstringsAfterOffset(&value, 0, "%22", "\"");
+ *url = GURL(value);
+ }
+
+ // Favicon
+ if (GetAttribute(attribute_list, kIconAttribute, &value))
+ *favicon = GURL(value);
+
+ // Keyword
+ if (GetAttribute(attribute_list, kShortcutURLAttribute, &value)) {
+ CodepageToWide(value, charset.c_str(), OnStringUtilConversionError::SKIP,
+ shortcut);
+ HTMLUnescape(shortcut);
+ }
+
+ // Add date
+ if (GetAttribute(attribute_list, kAddDateAttribute, &value)) {
+ int64 time = StringToInt64(value);
+ // Upper bound it at 32 bits.
+ if (0 < time && time < (1LL << 32))
+ *add_date = Time::FromTimeT(time);
+ }
+
+ // Post data.
+ if (GetAttribute(attribute_list, kPostDataAttribute, &value)) {
+ CodepageToWide(value, charset.c_str(),
+ OnStringUtilConversionError::SKIP, post_data);
+ HTMLUnescape(post_data);
+ }
+
+ return true;
+}
+
+// static
+bool Firefox2Importer::GetAttribute(const std::string& attribute_list,
+ const std::string& attribute,
+ std::string* value) {
+ const char kQuote[] = "\"";
+
+ size_t begin = attribute_list.find(attribute + "=" + kQuote);
+ if (begin == std::string::npos)
+ return false; // Can't find the attribute.
+
+ begin = attribute_list.find(kQuote, begin) + 1;
+ size_t end = attribute_list.find(kQuote, begin);
+ if (end == std::string::npos)
+ return false; // The value is not quoted.
+
+ *value = attribute_list.substr(begin, end - begin);
+ return true;
+}
+
+// static
+void Firefox2Importer::HTMLUnescape(std::wstring *text) {
+ ReplaceSubstringsAfterOffset(text, 0, L"&lt;", L"<");
+ ReplaceSubstringsAfterOffset(text, 0, L"&gt;", L">");
+ ReplaceSubstringsAfterOffset(text, 0, L"&amp;", L"&");
+ ReplaceSubstringsAfterOffset(text, 0, L"&quot;", L"\"");
+ ReplaceSubstringsAfterOffset(text, 0, L"&#39;", L"\'");
+}
+
+// static
+void Firefox2Importer::FindXMLFilesInDir(
+ const std::wstring& dir,
+ std::vector<std::wstring>* xml_files) {
+ file_util::FileEnumerator file_enum(dir, false,
+ file_util::FileEnumerator::FILES,
+ L"*.xml");
+ std::wstring file(file_enum.Next());
+ while (!file.empty()) {
+ xml_files->push_back(file);
+ file = file_enum.Next();
+ }
+}
+
+// static
+void Firefox2Importer::DataURLToFaviconUsage(
+ const GURL& link_url,
+ const GURL& favicon_data,
+ std::vector<history::ImportedFavIconUsage>* favicons) {
+ if (!link_url.is_valid() || !favicon_data.is_valid() ||
+ !favicon_data.SchemeIs("data"))
+ return;
+
+ // Parse the data URL.
+ std::string mime_type, char_set, data;
+ if (!net::DataURL::Parse(favicon_data, &mime_type, &char_set, &data) ||
+ data.empty())
+ return;
+
+ history::ImportedFavIconUsage usage;
+ if (!ReencodeFavicon(reinterpret_cast<const unsigned char*>(&data[0]),
+ data.size(), &usage.png_data))
+ return; // Unable to decode.
+
+ // We need to make up a URL for the favicon. We use a version of the page's
+ // URL so that we can be sure it will not collide.
+ usage.favicon_url = GURL(std::string("made-up-favicon:") + link_url.spec());
+
+ // We only have one URL per favicon for Firefox 2 bookmarks.
+ usage.urls.insert(link_url);
+
+ favicons->push_back(usage);
+}
+
diff --git a/chrome/browser/importer/firefox2_importer.h b/chrome/browser/importer/firefox2_importer.h
new file mode 100644
index 0000000..2c44993
--- /dev/null
+++ b/chrome/browser/importer/firefox2_importer.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_IMPORTER_FIREFOX2_IMPORTER_H_
+#define CHROME_BROWSER_IMPORTER_FIREFOX2_IMPORTER_H_
+
+#include "chrome/browser/importer/importer.h"
+
+class TemplateURL;
+
+// Importer for Mozilla Firefox 2.
+class Firefox2Importer : public Importer {
+ public:
+ Firefox2Importer();
+ virtual ~Firefox2Importer();
+
+ // Importer methods.
+ virtual void StartImport(ProfileInfo profile_info,
+ uint16 items,
+ ProfileWriter* writer,
+ ImporterHost* host);
+
+ // Loads the default bookmarks in the Firefox installed at |firefox_app_path|,
+ // and stores their locations in |urls|.
+ static void LoadDefaultBookmarks(const std::wstring& firefox_app_path,
+ std::set<GURL> *urls);
+
+ // Creates a TemplateURL with the |keyword| and |url|. |title| may be empty.
+ // This function transfers ownership of the created TemplateURL to the caller.
+ static TemplateURL* CreateTemplateURL(const std::wstring& title,
+ const std::wstring& keyword,
+ const GURL& url);
+
+ private:
+ FRIEND_TEST(FirefoxImporterTest, Firefox2BookmarkParse);
+ FRIEND_TEST(FirefoxImporterTest, Firefox2CookesParse);
+
+ void ImportBookmarks();
+ void ImportPasswords();
+ void ImportHistory();
+ void ImportSearchEngines();
+ // Import the user's home page, unless it is set to default home page as
+ // defined in browserconfig.properties.
+ void ImportHomepage();
+
+ // Fills |files| with the paths to the files containing the search engine
+ // descriptions.
+ void GetSearchEnginesXMLFiles(std::vector<std::wstring>* files);
+
+ // Helper methods for parsing bookmark file.
+ // Firefox 2 saves its bookmarks in a html file. We are interested in the
+ // bookmarks and folders, and their hierarchy. A folder starts with a
+ // heading tag, which contains it title. All bookmarks and sub-folders is
+ // following, and bracketed by a <DL> tag:
+ // <DT><H3 PERSONAL_TOOLBAR_FOLDER="true" ...>title</H3>
+ // <DL><p>
+ // ... container ...
+ // </DL><p>
+ // And a bookmark is presented by a <A> tag:
+ // <DT><A HREF="url" SHORTCUTURL="shortcut" ADD_DATE="11213014"...>name</A>
+ // Reference: http://kb.mozillazine.org/Bookmarks.html
+ static bool ParseCharsetFromLine(const std::string& line,
+ std::string* charset);
+ static bool ParseFolderNameFromLine(const std::string& line,
+ const std::string& charset,
+ std::wstring* folder_name,
+ bool* is_toolbar_folder);
+ // See above, this will also put the data: URL of the favicon into *favicon
+ // if there is a favicon given. |post_data| is set for POST base keywords to
+ // the contents of the actual POST (with %s for the search term).
+ static bool ParseBookmarkFromLine(const std::string& line,
+ const std::string& charset,
+ std::wstring* title,
+ GURL* url,
+ GURL* favicon,
+ std::wstring* shortcut,
+ Time* add_date,
+ std::wstring* post_data);
+
+ // Fetches the given attribute value from the |tag|. Returns true if
+ // successful, and |value| will contain the value.
+ static bool GetAttribute(const std::string& tag,
+ const std::string& attribute,
+ std::string* value);
+
+ // There are some characters in html file will be escaped:
+ // '<', '>', '"', '\', '&'
+ // Un-escapes them if the bookmark name has those characters.
+ static void HTMLUnescape(std::wstring* text);
+
+ // Fills |xml_files| with the file with an xml extension found under |dir|.
+ static void FindXMLFilesInDir(const std::wstring& dir,
+ std::vector<std::wstring>* xml_files);
+
+ // Given the URL of a page and a favicon data URL, adds an appropriate record
+ // to the given favicon usage vector. Will do nothing if the favicon is not
+ // valid.
+ static void DataURLToFaviconUsage(
+ const GURL& link_url,
+ const GURL& favicon_data,
+ std::vector<history::ImportedFavIconUsage>* favicons);
+
+ ProfileWriter* writer_;
+ std::wstring source_path_;
+ std::wstring app_path_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Firefox2Importer);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_FIREFOX2_IMPORTER_H_
+
diff --git a/chrome/browser/importer/firefox3_importer.cc b/chrome/browser/importer/firefox3_importer.cc
new file mode 100644
index 0000000..3279130
--- /dev/null
+++ b/chrome/browser/importer/firefox3_importer.cc
@@ -0,0 +1,502 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/importer/firefox3_importer.h"
+
+#include <set>
+
+#include "base/file_util.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "chrome/browser/importer/firefox2_importer.h"
+#include "chrome/browser/importer/firefox_importer_utils.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/time_format.h"
+#include "generated_resources.h"
+
+// Wraps the function sqlite3_close() in a class that is
+// used in scoped_ptr_malloc.
+
+namespace {
+
+class DBClose {
+ public:
+ inline void operator()(sqlite3* x) const {
+ sqlite3_close(x);
+ }
+};
+
+} // namespace
+
+void Firefox3Importer::StartImport(ProfileInfo profile_info,
+ uint16 items, ProfileWriter* writer,
+ ImporterHost* host) {
+ writer_ = writer;
+ source_path_ = profile_info.source_path;
+ app_path_ = profile_info.app_path;
+ importer_host_ = host;
+
+
+ // The order here is important!
+ NotifyStarted();
+ if ((items & HOME_PAGE) && !cancelled())
+ ImportHomepage(); // Doesn't have a UI item.
+ if ((items & FAVORITES) && !cancelled()) {
+ NotifyItemStarted(FAVORITES);
+ ImportBookmarks();
+ NotifyItemEnded(FAVORITES);
+ }
+ if ((items & SEARCH_ENGINES) && !cancelled()) {
+ NotifyItemStarted(SEARCH_ENGINES);
+ ImportSearchEngines();
+ NotifyItemEnded(SEARCH_ENGINES);
+ }
+ if ((items & PASSWORDS) && !cancelled()) {
+ NotifyItemStarted(PASSWORDS);
+ ImportPasswords();
+ NotifyItemEnded(PASSWORDS);
+ }
+ if ((items & HISTORY) && !cancelled()) {
+ NotifyItemStarted(HISTORY);
+ ImportHistory();
+ NotifyItemEnded(HISTORY);
+ }
+ NotifyEnded();
+}
+
+void Firefox3Importer::ImportHistory() {
+ std::wstring file = source_path_;
+ file_util::AppendToPath(&file, L"places.sqlite");
+ if (!file_util::PathExists(file))
+ return;
+
+ sqlite3* sqlite;
+ if (sqlite3_open(WideToUTF8(file).c_str(), &sqlite) != SQLITE_OK)
+ return;
+ scoped_ptr_malloc<sqlite3, DBClose> db(sqlite);
+
+ SQLStatement s;
+ // |visit_type| represent the transition type of URLs (typed, click,
+ // redirect, bookmark, etc.) We eliminate some URLs like sub-frames and
+ // redirects, since we don't want them to appear in history.
+ // Firefox transition types are defined in:
+ // toolkit/components/places/public/nsINavHistoryService.idl
+ const char* stmt = "SELECT h.url, h.title, h.visit_count, "
+ "h.hidden, h.typed, v.visit_date "
+ "FROM moz_places h JOIN moz_historyvisits v "
+ "ON h.id = v.place_id "
+ "WHERE v.visit_type <= 3";
+
+ if (s.prepare(db.get(), stmt) != SQLITE_OK)
+ return;
+
+ std::vector<history::URLRow> rows;
+ while (s.step() == SQLITE_ROW && !cancelled()) {
+ GURL url(s.column_string(0));
+
+ // Filter out unwanted URLs.
+ if (!CanImportURL(url))
+ continue;
+
+ history::URLRow row(url);
+ row.set_title(s.column_string16(1));
+ row.set_visit_count(s.column_int(2));
+ row.set_hidden(s.column_int(3) == 1);
+ row.set_typed_count(s.column_int(4));
+ row.set_last_visit(Time::FromTimeT(s.column_int64(5)/1000000));
+
+ rows.push_back(row);
+ }
+ if (!rows.empty() && !cancelled()) {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddHistoryPage, rows));
+ }
+}
+
+void Firefox3Importer::ImportBookmarks() {
+ std::wstring file = source_path_;
+ file_util::AppendToPath(&file, L"places.sqlite");
+ if (!file_util::PathExists(file))
+ return;
+
+ sqlite3* sqlite;
+ if (sqlite3_open(WideToUTF8(file).c_str(), &sqlite) != SQLITE_OK)
+ return;
+ scoped_ptr_malloc<sqlite3, DBClose> db(sqlite);
+
+ // Get the bookmark folders that we are interested in.
+ int toolbar_folder_id = -1;
+ int menu_folder_id = -1;
+ int unsorted_folder_id = -1;
+ LoadRootNodeID(db.get(), &toolbar_folder_id, &menu_folder_id,
+ &unsorted_folder_id);
+
+ // Load livemark IDs.
+ std::set<int> livemark_id;
+ LoadLivemarkIDs(db.get(), &livemark_id);
+
+ // Load the default bookmarks. Its storage is the same as Firefox 2.
+ std::set<GURL> default_urls;
+ Firefox2Importer::LoadDefaultBookmarks(app_path_, &default_urls);
+
+ BookmarkList list;
+ GetTopBookmarkFolder(db.get(), toolbar_folder_id, &list);
+ GetTopBookmarkFolder(db.get(), menu_folder_id, &list);
+ GetTopBookmarkFolder(db.get(), unsorted_folder_id, &list);
+ size_t count = list.size();
+ for (size_t i = 0; i < count; ++i)
+ GetWholeBookmarkFolder(db.get(), &list, i);
+
+ std::vector<ProfileWriter::BookmarkEntry> bookmarks;
+ std::vector<TemplateURL*> template_urls;
+ FaviconMap favicon_map;
+
+ // TODO(jcampan): http://b/issue?id=1196285 we do not support POST based
+ // keywords yet. We won't include them in the list.
+ std::set<int> post_keyword_ids;
+ SQLStatement s;
+ const char* stmt = "SELECT b.id FROM moz_bookmarks b "
+ "INNER JOIN moz_items_annos ia ON ia.item_id = b.id "
+ "INNER JOIN moz_anno_attributes aa ON ia.anno_attribute_id = aa.id "
+ "WHERE aa.name = 'bookmarkProperties/POSTData'";
+ if (s.prepare(db.get(), stmt) == SQLITE_OK) {
+ while (s.step() == SQLITE_ROW && !cancelled())
+ post_keyword_ids.insert(s.column_int(0));
+ } else {
+ NOTREACHED();
+ }
+
+ std::wstring firefox_folder =
+ l10n_util::GetString(IDS_BOOKMARK_GROUP_FROM_FIREFOX);
+ for (size_t i = 0; i < list.size(); ++i) {
+ BookmarkItem* item = list[i];
+
+ // The type of bookmark items is 1.
+ if (item->type != 1)
+ continue;
+
+ // Skip the default bookmarks and unwanted URLs.
+ if (!CanImportURL(item->url) ||
+ default_urls.find(item->url) != default_urls.end() ||
+ post_keyword_ids.find(item->id) != post_keyword_ids.end())
+ continue;
+
+ // Find the bookmark path by tracing their links to parent folders.
+ std::vector<std::wstring> path;
+ BookmarkItem* child = item;
+ bool found_path = false;
+ bool is_in_toolbar = false;
+ while (child->parent >= 0) {
+ BookmarkItem* parent = list[child->parent];
+ if (parent->id == toolbar_folder_id) {
+ // This bookmark entry should be put in the bookmark bar.
+ // But we put it in the Firefox group after first run, so
+ // that do not mess up the bookmark bar.
+ if (first_run()) {
+ is_in_toolbar = true;
+ } else {
+ path.insert(path.begin(), parent->title);
+ path.insert(path.begin(), firefox_folder);
+ }
+ found_path = true;
+ break;
+ } else if (parent->id == menu_folder_id ||
+ parent->id == unsorted_folder_id) {
+ // After the first run, the item will be placed in a folder in
+ // the "Other bookmarks".
+ if (!first_run())
+ path.insert(path.begin(), firefox_folder);
+ found_path = true;
+ break;
+ } else if (livemark_id.find(parent->id) != livemark_id.end()) {
+ // If the entry is under a livemark folder, we don't import it.
+ break;
+ }
+ path.insert(path.begin(), parent->title);
+ child = parent;
+ }
+
+ if (!found_path)
+ continue;
+
+ ProfileWriter::BookmarkEntry entry;
+ entry.creation_time = item->date_added;
+ entry.title = item->title;
+ entry.url = item->url;
+ entry.path = path;
+ entry.in_toolbar = is_in_toolbar;
+
+ bookmarks.push_back(entry);
+
+ if (item->favicon)
+ favicon_map[item->favicon].insert(item->url);
+
+ // This bookmark has a keyword, we import it to our TemplateURL model.
+ TemplateURL* t_url = Firefox2Importer::CreateTemplateURL(
+ item->title, UTF8ToWide(item->keyword), item->url);
+ if (t_url)
+ template_urls.push_back(t_url);
+ }
+
+ STLDeleteContainerPointers(list.begin(), list.end());
+
+ // Write into profile.
+ if (!bookmarks.empty() && !cancelled()) {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddBookmarkEntry, bookmarks));
+ }
+ if (!template_urls.empty() && !cancelled()) {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddKeywords, template_urls, -1, false));
+ } else {
+ STLDeleteContainerPointers(template_urls.begin(), template_urls.end());
+ }
+ if (!favicon_map.empty() && !cancelled()) {
+ std::vector<history::ImportedFavIconUsage> favicons;
+ LoadFavicons(db.get(), favicon_map, &favicons);
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddFavicons, favicons));
+ }
+}
+
+void Firefox3Importer::ImportPasswords() {
+ // Initializes NSS3.
+ NSSDecryptor decryptor;
+ if (!decryptor.Init(source_path_, source_path_) &&
+ !decryptor.Init(app_path_, source_path_))
+ return;
+
+ // Firefox 3 uses signons3.txt to store the passwords.
+ std::wstring file = source_path_;
+ file_util::AppendToPath(&file, L"signons3.txt");
+ if (!file_util::PathExists(file)) {
+ file = source_path_;
+ file_util::AppendToPath(&file, L"signons2.txt");
+ }
+
+ std::string content;
+ file_util::ReadFileToString(file, &content);
+ std::vector<PasswordForm> forms;
+ decryptor.ParseSignons(content, &forms);
+
+ if (!cancelled()) {
+ for (size_t i = 0; i < forms.size(); ++i) {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddPasswordForm, forms[i]));
+ }
+ }
+}
+
+void Firefox3Importer::ImportSearchEngines() {
+ std::vector<std::wstring> files;
+ GetSearchEnginesXMLFiles(&files);
+
+ std::vector<TemplateURL*> search_engines;
+ ParseSearchEnginesFromXMLFiles(files, &search_engines);
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddKeywords, search_engines,
+ GetFirefoxDefaultSearchEngineIndex(search_engines, source_path_), true));
+}
+
+void Firefox3Importer::ImportHomepage() {
+ GURL homepage = GetHomepage(source_path_);
+ if (homepage.is_valid() && !IsDefaultHomepage(homepage, app_path_)) {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddHomepage, homepage));
+ }
+}
+
+void Firefox3Importer::GetSearchEnginesXMLFiles(
+ std::vector<std::wstring>* files) {
+ std::wstring file = source_path_;
+ file_util::AppendToPath(&file, L"search.sqlite");
+ if (!file_util::PathExists(file))
+ return;
+
+ sqlite3* sqlite;
+ if (sqlite3_open(WideToUTF8(file).c_str(), &sqlite) != SQLITE_OK)
+ return;
+ scoped_ptr_malloc<sqlite3, DBClose> db(sqlite);
+
+ SQLStatement s;
+ const char* stmt = "SELECT engineid FROM engine_data ORDER BY value ASC";
+
+ if (s.prepare(db.get(), stmt) != SQLITE_OK)
+ return;
+
+ std::wstring app_path = app_path_;
+ file_util::AppendToPath(&app_path, L"searchplugins");
+ std::wstring profile_path = source_path_;
+ file_util::AppendToPath(&profile_path, L"searchplugins");
+
+ const std::wstring kAppPrefix = L"[app]/";
+ const std::wstring kProfilePrefix = L"[profile]/";
+ while (s.step() == SQLITE_ROW && !cancelled()) {
+ std::wstring file;
+ std::wstring engine = UTF8ToWide(s.column_string(0));
+ // The string contains [app]/<name>.xml or [profile]/<name>.xml where the
+ // [app] and [profile] need to be replaced with the actual app or profile
+ // path.
+ size_t index = engine.find(kAppPrefix);
+ if (index != std::wstring::npos) {
+ file = app_path;
+ file_util::AppendToPath(
+ &file,
+ engine.substr(index + kAppPrefix.length())); // Remove '[app]/'.
+ } else if ((index = engine.find(kProfilePrefix)) != std::wstring::npos) {
+ file = profile_path;
+ file_util::AppendToPath(
+ &file,
+ engine.substr(index + kProfilePrefix.length())); // Remove
+ // '[profile]/'.
+ } else {
+ NOTREACHED() << "Unexpected Firefox 3 search engine id";
+ continue;
+ }
+ files->push_back(file);
+ }
+}
+
+void Firefox3Importer::LoadRootNodeID(sqlite3* db,
+ int* toolbar_folder_id,
+ int* menu_folder_id,
+ int* unsorted_folder_id) {
+ const char kToolbarFolderName[] = "toolbar";
+ const char kMenuFolderName[] = "menu";
+ const char kUnsortedFolderName[] = "unfiled";
+
+ SQLStatement s;
+ const char* stmt = "SELECT root_name, folder_id FROM moz_bookmarks_roots";
+ if (s.prepare(db, stmt) != SQLITE_OK)
+ return;
+
+ while (s.step() == SQLITE_ROW) {
+ std::string folder = s.column_string(0);
+ int id = s.column_int(1);
+ if (folder == kToolbarFolderName)
+ *toolbar_folder_id = id;
+ else if (folder == kMenuFolderName)
+ *menu_folder_id = id;
+ else if (folder == kUnsortedFolderName)
+ *unsorted_folder_id = id;
+ }
+}
+
+void Firefox3Importer::LoadLivemarkIDs(sqlite3* db,
+ std::set<int>* livemark) {
+ const char kFeedAnnotation[] = "livemark/feedURI";
+ livemark->clear();
+
+ SQLStatement s;
+ const char* stmt = "SELECT b.item_id "
+ "FROM moz_anno_attributes a "
+ "JOIN moz_items_annos b ON a.id = b.anno_attribute_id "
+ "WHERE a.name = ? ";
+ if (s.prepare(db, stmt) != SQLITE_OK)
+ return;
+
+ s.bind_string(0, kFeedAnnotation);
+ while (s.step() == SQLITE_ROW && !cancelled())
+ livemark->insert(s.column_int(0));
+}
+
+void Firefox3Importer::GetTopBookmarkFolder(sqlite3* db, int folder_id,
+ BookmarkList* list) {
+ SQLStatement s;
+ const char* stmt = "SELECT b.title "
+ "FROM moz_bookmarks b "
+ "WHERE b.type = 2 AND b.id = ? "
+ "ORDER BY b.position";
+ if (s.prepare(db, stmt) != SQLITE_OK)
+ return;
+
+ s.bind_int(0, folder_id);
+ if (s.step() == SQLITE_ROW) {
+ BookmarkItem* item = new BookmarkItem;
+ item->parent = -1; // The top level folder has no parent.
+ item->id = folder_id;
+ item->title = s.column_string16(0);
+ item->type = 2;
+ item->favicon = 0;
+ list->push_back(item);
+ }
+}
+
+void Firefox3Importer::GetWholeBookmarkFolder(sqlite3* db, BookmarkList* list,
+ size_t position) {
+ if (position >= list->size()) {
+ NOTREACHED();
+ return;
+ }
+
+ SQLStatement s;
+ const char* stmt = "SELECT b.id, h.url, COALESCE(b.title, h.title), "
+ "b.type, k.keyword, b.dateAdded, h.favicon_id "
+ "FROM moz_bookmarks b "
+ "LEFT JOIN moz_places h ON b.fk = h.id "
+ "LEFT JOIN moz_keywords k ON k.id = b.keyword_id "
+ "WHERE b.type IN (1,2) AND b.parent = ? "
+ "ORDER BY b.position";
+ if (s.prepare(db, stmt) != SQLITE_OK)
+ return;
+
+ s.bind_int(0, (*list)[position]->id);
+ BookmarkList temp_list;
+ while (s.step() == SQLITE_ROW) {
+ BookmarkItem* item = new BookmarkItem;
+ item->parent = static_cast<int>(position);
+ item->id = s.column_int(0);
+ item->url = GURL(s.column_string(1));
+ item->title = s.column_string16(2);
+ item->type = s.column_int(3);
+ item->keyword = s.column_string(4);
+ item->date_added = Time::FromTimeT(s.column_int64(5)/1000000);
+ item->favicon = s.column_int64(6);
+
+ temp_list.push_back(item);
+ }
+
+ // Appends all items to the list.
+ for (BookmarkList::iterator i = temp_list.begin();
+ i != temp_list.end(); ++i) {
+ list->push_back(*i);
+ // Recursive add bookmarks in sub-folders.
+ if ((*i)->type == 2)
+ GetWholeBookmarkFolder(db, list, list->size() - 1);
+ }
+}
+
+void Firefox3Importer::LoadFavicons(
+ sqlite3* db,
+ const FaviconMap& favicon_map,
+ std::vector<history::ImportedFavIconUsage>* favicons) {
+ SQLStatement s;
+ const char* stmt = "SELECT url, data FROM moz_favicons WHERE id=?";
+ if (s.prepare(db, stmt) != SQLITE_OK)
+ return;
+
+ for (FaviconMap::const_iterator i = favicon_map.begin();
+ i != favicon_map.end(); ++i) {
+ s.bind_int64(0, i->first);
+ if (s.step() == SQLITE_ROW) {
+ history::ImportedFavIconUsage usage;
+
+ usage.favicon_url = GURL(s.column_string(0));
+ if (!usage.favicon_url.is_valid())
+ continue; // Don't bother importing favicons with invalid URLs.
+
+ std::vector<unsigned char> data;
+ if (!s.column_blob_as_vector(1, &data) || data.empty())
+ continue; // Data definitely invalid.
+
+ if (!ReencodeFavicon(&data[0], data.size(), &usage.png_data))
+ continue; // Unable to decode.
+
+ usage.urls = i->second;
+ favicons->push_back(usage);
+ }
+ s.reset();
+ }
+}
+
diff --git a/chrome/browser/importer/firefox3_importer.h b/chrome/browser/importer/firefox3_importer.h
new file mode 100644
index 0000000..d4cc0bc
--- /dev/null
+++ b/chrome/browser/importer/firefox3_importer.h
@@ -0,0 +1,85 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_IMPORTER_FIREFOX3_IMPORTER_H_
+#define CHROME_BROWSER_IMPORTER_FIREFOX3_IMPORTER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "chrome/browser/importer/importer.h"
+#include "chrome/common/sqlite_utils.h"
+#include "googleurl/src/gurl.h"
+
+// Importer for Mozilla Firefox 3.
+// Firefox 3 stores its persistent information in a new system called places.
+// http://wiki.mozilla.org/Places
+class Firefox3Importer : public Importer {
+ public:
+ Firefox3Importer() { }
+ virtual ~Firefox3Importer() { }
+
+ // Importer methods.
+ virtual void StartImport(ProfileInfo profile_info,
+ uint16 items,
+ ProfileWriter* writer,
+ ImporterHost* host);
+
+ private:
+ typedef std::map<int64, std::set<GURL> > FaviconMap;
+
+ void ImportBookmarks();
+ void ImportPasswords();
+ void ImportHistory();
+ void ImportSearchEngines();
+ // Import the user's home page, unless it is set to default home page as
+ // defined in browserconfig.properties.
+ void ImportHomepage();
+ void GetSearchEnginesXMLFiles(std::vector<std::wstring>* files);
+
+ // The struct stores the information about a bookmark item.
+ struct BookmarkItem {
+ int parent;
+ int id;
+ GURL url;
+ std::wstring title;
+ int type;
+ std::string keyword;
+ Time date_added;
+ int64 favicon;
+ };
+ typedef std::vector<BookmarkItem*> BookmarkList;
+
+ // Gets the specific IDs of bookmark root node from |db|.
+ void LoadRootNodeID(sqlite3* db, int* toolbar_folder_id,
+ int* menu_folder_id, int* unsorted_folder_id);
+
+ // Loads all livemark IDs from database |db|.
+ void LoadLivemarkIDs(sqlite3* db, std::set<int>* livemark);
+
+ // Gets the bookmark folder with given ID, and adds the entry in |list|
+ // if successful.
+ void GetTopBookmarkFolder(sqlite3* db, int folder_id, BookmarkList* list);
+
+ // Loads all children of the given folder, and appends them to the |list|.
+ void GetWholeBookmarkFolder(sqlite3* db, BookmarkList* list,
+ size_t position);
+
+ // Loads the favicons given in the map from the database, loads the data,
+ // and converts it into FaviconUsage structures.
+ void LoadFavicons(sqlite3* db,
+ const FaviconMap& favicon_map,
+ std::vector<history::ImportedFavIconUsage>* favicons);
+
+ ProfileWriter* writer_;
+ std::wstring source_path_;
+ std::wstring app_path_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Firefox3Importer);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_FIREFOX3_IMPORTER_H_
+
diff --git a/chrome/browser/importer/firefox_importer_unittest.cc b/chrome/browser/importer/firefox_importer_unittest.cc
new file mode 100644
index 0000000..d47b133
--- /dev/null
+++ b/chrome/browser/importer/firefox_importer_unittest.cc
@@ -0,0 +1,216 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "chrome/browser/importer/firefox2_importer.h"
+#include "chrome/browser/importer/firefox_importer_utils.h"
+#include "chrome/browser/importer/firefox_profile_lock.h"
+#include "chrome/common/chrome_paths.h"
+
+TEST(FirefoxImporterTest, Firefox2NSS3Decryptor) {
+ std::wstring nss_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &nss_path));
+ file_util::AppendToPath(&nss_path, L"firefox2_nss");
+ std::wstring db_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &db_path));
+ file_util::AppendToPath(&db_path, L"firefox2_profile");
+ NSSDecryptor decryptor;
+ EXPECT_TRUE(decryptor.Init(nss_path, db_path));
+ EXPECT_EQ(L"hello", decryptor.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAE"
+ "wFAYIKoZIhvcNAwcECBJM63MpT9rtBAjMCm7qo/EhlA=="));
+ // Test UTF-16 encoding.
+ EXPECT_EQ(L"\x4E2D", decryptor.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAE"
+ "wFAYIKoZIhvcNAwcECN9OQ5ZFmhb8BAiFo1Z+fUvaIQ=="));
+}
+
+TEST(FirefoxImporterTest, Firefox3NSS3Decryptor) {
+ std::wstring nss_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &nss_path));
+ file_util::AppendToPath(&nss_path, L"firefox3_nss");
+ std::wstring db_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &db_path));
+ file_util::AppendToPath(&db_path, L"firefox3_profile");
+ NSSDecryptor decryptor;
+ EXPECT_TRUE(decryptor.Init(nss_path, db_path));
+ EXPECT_EQ(L"hello", decryptor.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAE"
+ "wFAYIKoZIhvcNAwcECKajtRg4qFSHBAhv9luFkXgDJA=="));
+ // Test UTF-16 encoding.
+ EXPECT_EQ(L"\x4E2D", decryptor.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAE"
+ "wFAYIKoZIhvcNAwcECLWqqiccfQHWBAie74hxnULxlw=="));
+}
+
+TEST(FirefoxImporterTest, Firefox2BookmarkParse) {
+ bool result;
+
+ // Tests charset.
+ std::string charset;
+ result = Firefox2Importer::ParseCharsetFromLine(
+ "<META HTTP-EQUIV=\"Content-Type\" "
+ "CONTENT=\"text/html; charset=UTF-8\">",
+ &charset);
+ EXPECT_TRUE(result);
+ EXPECT_EQ("UTF-8", charset);
+
+ // Escaped characters in name.
+ std::wstring folder_name;
+ bool is_toolbar_folder;
+ result = Firefox2Importer::ParseFolderNameFromLine(
+ "<DT><H3 ADD_DATE=\"1207558707\" >&lt; &gt;"
+ " &amp; &quot; &#39; \\ /</H3>",
+ charset, &folder_name, &is_toolbar_folder);
+ EXPECT_TRUE(result);
+ EXPECT_EQ(L"< > & \" ' \\ /", folder_name);
+ EXPECT_EQ(false, is_toolbar_folder);
+
+ // Empty name and toolbar folder attribute.
+ result = Firefox2Importer::ParseFolderNameFromLine(
+ "<DT><H3 PERSONAL_TOOLBAR_FOLDER=\"true\"></H3>",
+ charset, &folder_name, &is_toolbar_folder);
+ EXPECT_TRUE(result);
+ EXPECT_EQ(L"", folder_name);
+ EXPECT_EQ(true, is_toolbar_folder);
+
+ // Unicode characters in title and shortcut.
+ std::wstring title;
+ GURL url, favicon;
+ std::wstring shortcut;
+ std::wstring post_data;
+ Time add_date;
+ result = Firefox2Importer::ParseBookmarkFromLine(
+ "<DT><A HREF=\"http://chinese.site.cn/path?query=1#ref\" "
+ "SHORTCUTURL=\"\xE4\xB8\xAD\">\xE4\xB8\xAD\xE6\x96\x87</A>",
+ charset, &title, &url, &favicon, &shortcut, &add_date, &post_data);
+ EXPECT_TRUE(result);
+ EXPECT_EQ(L"\x4E2D\x6587", title);
+ EXPECT_EQ("http://chinese.site.cn/path?query=1#ref", url.spec());
+ EXPECT_EQ(L"\x4E2D", shortcut);
+ EXPECT_EQ(L"", post_data);
+ EXPECT_TRUE(Time() == add_date);
+
+ // No shortcut, and url contains %22 ('"' character).
+ result = Firefox2Importer::ParseBookmarkFromLine(
+ "<DT><A HREF=\"http://domain.com/?q=%22<>%22\">name</A>",
+ charset, &title, &url, &favicon, &shortcut, &add_date, &post_data);
+ EXPECT_TRUE(result);
+ EXPECT_EQ(L"name", title);
+ EXPECT_EQ("http://domain.com/?q=\"%3C%3E\"", url.spec());
+ EXPECT_EQ(L"", shortcut);
+ EXPECT_EQ(L"", post_data);
+ EXPECT_TRUE(Time() == add_date);
+
+ // Creation date.
+ result = Firefox2Importer::ParseBookmarkFromLine(
+ "<DT><A HREF=\"http://site/\" ADD_DATE=\"1121301154\">name</A>",
+ charset, &title, &url, &favicon, &shortcut, &add_date, &post_data);
+ EXPECT_TRUE(result);
+ EXPECT_EQ(L"name", title);
+ EXPECT_EQ(GURL("http://site/"), url);
+ EXPECT_EQ(L"", shortcut);
+ EXPECT_EQ(L"", post_data);
+ EXPECT_TRUE(Time::FromTimeT(1121301154) == add_date);
+
+ // Post-data
+ result = Firefox2Importer::ParseBookmarkFromLine(
+ "<DT><A HREF=\"http://localhost:8080/test/hello.html\" ADD_DATE=\""
+ "1212447159\" LAST_VISIT=\"1212447251\" LAST_MODIFIED=\"1212447248\""
+ "SHORTCUTURL=\"post\" ICON=\"data:\" POST_DATA=\"lname%3D%25s\""
+ "LAST_CHARSET=\"UTF-8\" ID=\"rdf:#$weKaR3\">Test Post keyword</A>",
+ charset, &title, &url, &favicon, &shortcut, &add_date, &post_data);
+ EXPECT_TRUE(result);
+ EXPECT_EQ(L"Test Post keyword", title);
+ EXPECT_EQ("http://localhost:8080/test/hello.html", url.spec());
+ EXPECT_EQ(L"post", shortcut);
+ EXPECT_EQ(L"lname%3D%25s", post_data);
+ EXPECT_TRUE(Time::FromTimeT(1212447159) == add_date);
+
+ // Invalid case.
+ result = Firefox2Importer::ParseBookmarkFromLine(
+ "<DT><A HREF=\"http://domain.com/?q=%22",
+ charset, &title, &url, &favicon, &shortcut, &add_date, &post_data);
+ EXPECT_FALSE(result);
+ EXPECT_EQ(L"", title);
+ EXPECT_EQ("", url.spec());
+ EXPECT_EQ(L"", shortcut);
+ EXPECT_EQ(L"", post_data);
+ EXPECT_TRUE(Time() == add_date);
+}
+
+// Tests basic functionality and verifies that the lock file is deleted after
+// use.
+TEST(FirefoxImporterTest, ProfileLock) {
+ std::wstring test_path;
+ file_util::CreateNewTempDirectory(L"firefox_profile", &test_path);
+ std::wstring lock_file_path = test_path;
+ file_util::AppendToPath(&lock_file_path, FirefoxProfileLock::kLockFileName);
+
+ scoped_ptr<FirefoxProfileLock> lock;
+ EXPECT_EQ(static_cast<FirefoxProfileLock*>(NULL), lock.get());
+ EXPECT_FALSE(file_util::PathExists(lock_file_path));
+ lock.reset(new FirefoxProfileLock(test_path));
+ EXPECT_TRUE(lock->HasAcquired());
+ EXPECT_TRUE(file_util::PathExists(lock_file_path));
+ lock->Unlock();
+ EXPECT_FALSE(lock->HasAcquired());
+ EXPECT_FALSE(file_util::PathExists(lock_file_path));
+ lock->Lock();
+ EXPECT_TRUE(lock->HasAcquired());
+ EXPECT_TRUE(file_util::PathExists(lock_file_path));
+ lock->Lock();
+ EXPECT_TRUE(lock->HasAcquired());
+ lock->Unlock();
+ EXPECT_FALSE(lock->HasAcquired());
+ EXPECT_FALSE(file_util::PathExists(lock_file_path));
+}
+
+// If for some reason the lock file is left behind by the previous owner, we
+// should still be able to lock it, at least in the Windows implementation.
+TEST(FirefoxImporterTest, ProfileLockOrphaned) {
+ std::wstring test_path;
+ file_util::CreateNewTempDirectory(L"firefox_profile", &test_path);
+ std::wstring lock_file_path = test_path;
+ file_util::AppendToPath(&lock_file_path, FirefoxProfileLock::kLockFileName);
+
+ // Create the orphaned lock file.
+ HANDLE lock_file = CreateFile(lock_file_path.c_str(),
+ GENERIC_READ | GENERIC_WRITE,
+ 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,
+ NULL);
+ CloseHandle(lock_file);
+ EXPECT_TRUE(file_util::PathExists(lock_file_path));
+
+ scoped_ptr<FirefoxProfileLock> lock;
+ EXPECT_EQ(static_cast<FirefoxProfileLock*>(NULL), lock.get());
+ lock.reset(new FirefoxProfileLock(test_path));
+ EXPECT_TRUE(lock->HasAcquired());
+ lock->Unlock();
+ EXPECT_FALSE(lock->HasAcquired());
+}
+
+// Tests two locks contending for the same lock file.
+TEST(FirefoxImporterTest, ProfileLockContention) {
+ std::wstring test_path;
+ file_util::CreateNewTempDirectory(L"firefox_profile", &test_path);
+
+ scoped_ptr<FirefoxProfileLock> lock1;
+ EXPECT_EQ(static_cast<FirefoxProfileLock*>(NULL), lock1.get());
+ lock1.reset(new FirefoxProfileLock(test_path));
+ EXPECT_TRUE(lock1->HasAcquired());
+
+ scoped_ptr<FirefoxProfileLock> lock2;
+ EXPECT_EQ(static_cast<FirefoxProfileLock*>(NULL), lock2.get());
+ lock2.reset(new FirefoxProfileLock(test_path));
+ EXPECT_FALSE(lock2->HasAcquired());
+
+ lock1->Unlock();
+ EXPECT_FALSE(lock1->HasAcquired());
+
+ lock2->Lock();
+ EXPECT_TRUE(lock2->HasAcquired());
+ lock2->Unlock();
+ EXPECT_FALSE(lock2->HasAcquired());
+}
+
diff --git a/chrome/browser/importer/firefox_importer_utils.cc b/chrome/browser/importer/firefox_importer_utils.cc
new file mode 100644
index 0000000..c9d6caa
--- /dev/null
+++ b/chrome/browser/importer/firefox_importer_utils.cc
@@ -0,0 +1,727 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/importer/firefox_importer_utils.h"
+
+#include <shlobj.h>
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/registry.h"
+#include "base/string_util.h"
+#include "base/sys_string_conversions.h"
+#include "base/time.h"
+#include "chrome/browser/template_url.h"
+#include "chrome/browser/template_url_model.h"
+#include "chrome/browser/template_url_parser.h"
+#include "chrome/common/win_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/base64.h"
+
+namespace {
+
+// NOTE: Keep these in order since we need test all those paths according
+// to priority. For example. One machine has multiple users. One non-admin
+// user installs Firefox 2, which causes there is a Firefox2 entry under HKCU.
+// One admin user installs Firefox 3, which causes there is a Firefox 3 entry
+// under HKLM. So when the non-admin user log in, we should deal with Firefox 2
+// related data instead of Firefox 3.
+static const HKEY kFireFoxRegistryPaths[] = {
+ HKEY_CURRENT_USER,
+ HKEY_LOCAL_MACHINE
+};
+
+// FirefoxURLParameterFilter is used to remove parameter mentioning Firefox from
+// the search URL when importing search engines.
+class FirefoxURLParameterFilter : public TemplateURLParser::ParameterFilter {
+ public:
+ FirefoxURLParameterFilter() { }
+ ~FirefoxURLParameterFilter() { }
+
+ // TemplateURLParser::ParameterFilter method.
+ virtual bool KeepParameter(const std::string& key,
+ const std::string& value) {
+ std::string low_value = StringToLowerASCII(value);
+ if (low_value.find("mozilla") != -1 || low_value.find("firefox") != -1 ||
+ low_value.find("moz:") != -1 )
+ return false;
+ return true;
+ }
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(FirefoxURLParameterFilter);
+};
+
+typedef BOOL (WINAPI* SetDllDirectoryFunc)(LPCTSTR lpPathName);
+
+// A helper class whose destructor calls SetDllDirectory(NULL) to undo the
+// effects of a previous SetDllDirectory call.
+class SetDllDirectoryCaller {
+ public:
+ explicit SetDllDirectoryCaller() : func_(NULL) { }
+
+ ~SetDllDirectoryCaller() {
+ if (func_)
+ func_(NULL);
+ }
+
+ // Sets the SetDllDirectory function pointer to activates this object.
+ void set_func(SetDllDirectoryFunc func) { func_ = func; }
+
+ private:
+ SetDllDirectoryFunc func_;
+};
+
+} // namespace
+
+int GetCurrentFirefoxMajorVersion() {
+ TCHAR ver_buffer[128];
+ DWORD ver_buffer_length = sizeof(ver_buffer);
+ // When installing Firefox with admin account, the product keys will be
+ // written under HKLM\Mozilla. Otherwise it the keys will be written under
+ // HKCU\Mozilla.
+ for (int i = 0; i < arraysize(kFireFoxRegistryPaths); ++i) {
+ bool result = ReadFromRegistry(kFireFoxRegistryPaths[i],
+ L"Software\\Mozilla\\Mozilla Firefox",
+ L"CurrentVersion", ver_buffer, &ver_buffer_length);
+ if (!result)
+ continue;
+ return _wtoi(ver_buffer);
+ }
+ return 0;
+}
+
+std::wstring GetProfilesINI() {
+ // The default location of the profile folder containing user data is
+ // under the "Application Data" folder in Windows XP.
+ std::wstring ini_file;
+ wchar_t buffer[MAX_PATH] = {0};
+ if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL,
+ SHGFP_TYPE_CURRENT, buffer))) {
+ ini_file = buffer;
+ file_util::AppendToPath(&ini_file, L"Mozilla\\Firefox\\profiles.ini");
+ }
+ if (!file_util::PathExists(ini_file))
+ ini_file.clear();
+
+ return ini_file;
+}
+
+std::wstring GetFirefoxInstallPath() {
+ // Detects the path that Firefox is installed in.
+ std::wstring registry_path = L"Software\\Mozilla\\Mozilla Firefox";
+ TCHAR buffer[MAX_PATH];
+ DWORD buffer_length = sizeof(buffer);
+ bool result;
+ result = ReadFromRegistry(HKEY_LOCAL_MACHINE, registry_path.c_str(),
+ L"CurrentVersion", buffer, &buffer_length);
+ if (!result)
+ return std::wstring();
+ registry_path += L"\\" + std::wstring(buffer) + L"\\Main";
+ buffer_length = sizeof(buffer);
+ result = ReadFromRegistry(HKEY_LOCAL_MACHINE, registry_path.c_str(),
+ L"Install Directory", buffer, &buffer_length);
+ if (!result)
+ return std::wstring();
+ return buffer;
+}
+
+void ParseProfileINI(std::wstring file, DictionaryValue* root) {
+ // Reads the whole INI file.
+ std::string content;
+ file_util::ReadFileToString(file, &content);
+ ReplaceSubstringsAfterOffset(&content, 0, "\r\n", "\n");
+ std::vector<std::string> lines;
+ SplitString(content, '\n', &lines);
+
+ // Parses the file.
+ root->Clear();
+ std::wstring current_section;
+ for (size_t i = 0; i < lines.size(); ++i) {
+ std::wstring line = UTF8ToWide(lines[i]);
+ if (line.empty()) {
+ // Skips the empty line.
+ continue;
+ }
+ if (line[0] == L'#' || line[0] == L';') {
+ // This line is a comment.
+ continue;
+ }
+ if (line[0] == L'[') {
+ // It is a section header.
+ current_section = line.substr(1);
+ size_t end = current_section.rfind(L']');
+ if (end != std::wstring::npos)
+ current_section.erase(end);
+ } else {
+ std::wstring key, value;
+ size_t equal = line.find(L'=');
+ if (equal != std::wstring::npos) {
+ key = line.substr(0, equal);
+ value = line.substr(equal + 1);
+ // Checks whether the section and key contain a '.' character.
+ // Those sections and keys break DictionaryValue's path format,
+ // so we discard them.
+ if (current_section.find(L'.') == std::wstring::npos &&
+ key.find(L'.') == std::wstring::npos)
+ root->SetString(current_section + L"." + key, value);
+ }
+ }
+ }
+}
+
+bool CanImportURL(const GURL& url) {
+ const char* kInvalidSchemes[] = {"wyciwyg", "place", "about", "chrome"};
+
+ // The URL is not valid.
+ if (!url.is_valid())
+ return false;
+
+ // Filter out the URLs with unsupported schemes.
+ for (int i = 0; i < arraysize(kInvalidSchemes); ++i) {
+ if (url.SchemeIs(kInvalidSchemes[i]))
+ return false;
+ }
+
+ return true;
+}
+
+void ParseSearchEnginesFromXMLFiles(const std::vector<std::wstring>& xml_files,
+ std::vector<TemplateURL*>* search_engines) {
+ DCHECK(search_engines);
+
+ std::map<std::wstring, TemplateURL*> search_engine_for_url;
+ std::string content;
+ // The first XML file represents the default search engine in Firefox 3, so we
+ // need to keep it on top of the list.
+ TemplateURL* default_turl = NULL;
+ for (std::vector<std::wstring>::const_iterator iter = xml_files.begin();
+ iter != xml_files.end(); ++iter) {
+ file_util::ReadFileToString(*iter, &content);
+ TemplateURL* template_url = new TemplateURL();
+ FirefoxURLParameterFilter param_filter;
+ if (TemplateURLParser::Parse(
+ reinterpret_cast<const unsigned char*>(content.data()),
+ content.length(), &param_filter, template_url) &&
+ template_url->url()) {
+ std::wstring url = template_url->url()->url();
+ std::map<std::wstring, TemplateURL*>::iterator iter =
+ search_engine_for_url.find(url);
+ if (iter != search_engine_for_url.end()) {
+ // We have already found a search engine with the same URL. We give
+ // priority to the latest one found, as GetSearchEnginesXMLFiles()
+ // returns a vector with first Firefox default search engines and then
+ // the user's ones. We want to give priority to the user ones.
+ delete iter->second;
+ search_engine_for_url.erase(iter);
+ }
+ // Give this a keyword to facilitate tab-to-search, if possible.
+ template_url->set_keyword(TemplateURLModel::GenerateKeyword(GURL(url),
+ false));
+ template_url->set_show_in_default_list(true);
+ search_engine_for_url[url] = template_url;
+ if (!default_turl)
+ default_turl = template_url;
+ } else {
+ delete template_url;
+ }
+ content.clear();
+ }
+
+ // Put the results in the |search_engines| vector.
+ std::map<std::wstring, TemplateURL*>::iterator t_iter;
+ for (t_iter = search_engine_for_url.begin();
+ t_iter != search_engine_for_url.end(); ++t_iter) {
+ if (t_iter->second == default_turl)
+ search_engines->insert(search_engines->begin(), default_turl);
+ else
+ search_engines->push_back(t_iter->second);
+ }
+}
+
+bool ReadPrefFile(const std::wstring& path_name,
+ const std::wstring& file_name,
+ std::string* content) {
+ if (content == NULL)
+ return false;
+
+ std::wstring file = path_name;
+ file_util::AppendToPath(&file, file_name.c_str());
+
+ file_util::ReadFileToString(file, content);
+
+ if (content->empty()) {
+ NOTREACHED() << L"Firefox preference file " << file_name.c_str()
+ << L" is empty.";
+ return false;
+ }
+
+ return true;
+}
+
+std::string ReadBrowserConfigProp(const std::wstring& app_path,
+ const std::string& pref_key) {
+ std::string content;
+ if (!ReadPrefFile(app_path, L"browserconfig.properties", &content))
+ return "";
+
+ // This file has the syntax: key=value.
+ size_t prop_index = content.find(pref_key + "=");
+ if (prop_index == -1)
+ return "";
+
+ size_t start = prop_index + pref_key.length();
+ size_t stop = -1;
+ if (start != -1)
+ stop = content.find("\n", start + 1);
+
+ if (start == -1 || stop == -1 || (start == stop)) {
+ NOTREACHED() << "Firefox property " << pref_key << " could not be parsed.";
+ return "";
+ }
+
+ return content.substr(start + 1, stop - start - 1);
+}
+
+std::string ReadPrefsJsValue(const std::wstring& profile_path,
+ const std::string& pref_key) {
+ std::string content;
+ if (!ReadPrefFile(profile_path, L"prefs.js", &content))
+ return "";
+
+ // This file has the syntax: user_pref("key", value);
+ std::string search_for = std::string("user_pref(\"") + pref_key +
+ std::string("\", ");
+ size_t prop_index = content.find(search_for);
+ if (prop_index == -1)
+ return "";
+
+ size_t start = prop_index + search_for.length();
+ size_t stop = -1;
+ if (start != -1)
+ stop = content.find(")", start + 1);
+
+ if (start == -1 || stop == -1) {
+ NOTREACHED() << "Firefox property " << pref_key << " could not be parsed.";
+ return "";
+ }
+
+ // String values have double quotes we don't need to return to the caller.
+ if (content[start] == '\"' && content[stop - 1] == '\"') {
+ ++start;
+ --stop;
+ }
+
+ return content.substr(start, stop - start);
+}
+
+int GetFirefoxDefaultSearchEngineIndex(
+ const std::vector<TemplateURL*>& search_engines,
+ const std::wstring& profile_path) {
+ // The default search engine is contained in the file prefs.js found in the
+ // profile directory.
+ // It is the "browser.search.selectedEngine" property.
+ if (search_engines.empty())
+ return -1;
+
+ std::wstring default_se_name = UTF8ToWide(
+ ReadPrefsJsValue(profile_path, "browser.search.selectedEngine"));
+
+ int default_se_index = -1;
+ for (std::vector<TemplateURL*>::const_iterator iter = search_engines.begin();
+ iter != search_engines.end(); ++iter) {
+ if (default_se_name == (*iter)->short_name()) {
+ default_se_index = static_cast<int>(iter - search_engines.begin());
+ break;
+ }
+ }
+ if (default_se_index == -1) {
+ NOTREACHED() <<
+ "Firefox default search engine not found in search engine list";
+ }
+
+ return default_se_index;
+}
+
+GURL GetHomepage(const std::wstring& profile_path) {
+ std::string home_page_list =
+ ReadPrefsJsValue(profile_path, "browser.startup.homepage");
+
+ size_t seperator = home_page_list.find_first_of('|');
+ if (seperator == std::string::npos)
+ return GURL(home_page_list);
+
+ return GURL(home_page_list.substr(0, seperator));
+}
+
+bool IsDefaultHomepage(const GURL& homepage,
+ const std::wstring& app_path) {
+ if (!homepage.is_valid())
+ return false;
+
+ std::string default_homepages =
+ ReadBrowserConfigProp(app_path, "browser.startup.homepage");
+
+ size_t seperator = default_homepages.find_first_of('|');
+ if (seperator == std::string::npos)
+ return homepage.spec() == GURL(default_homepages).spec();
+
+ // Crack the string into separate homepage urls.
+ std::vector<std::string> urls;
+ SplitString(default_homepages, '|', &urls);
+
+ for (size_t i = 0; i < urls.size(); ++i) {
+ if (homepage.spec() == GURL(urls[i]).spec())
+ return true;
+ }
+
+ return false;
+}
+
+// class NSSDecryptor.
+
+// static
+const wchar_t NSSDecryptor::kNSS3Library[] = L"nss3.dll";
+const wchar_t NSSDecryptor::kSoftokn3Library[] = L"softokn3.dll";
+const wchar_t NSSDecryptor::kPLDS4Library[] = L"plds4.dll";
+const wchar_t NSSDecryptor::kNSPR4Library[] = L"nspr4.dll";
+
+NSSDecryptor::NSSDecryptor()
+ : NSS_Init(NULL), NSS_Shutdown(NULL), PK11_GetInternalKeySlot(NULL),
+ PK11_CheckUserPassword(NULL), PK11_FreeSlot(NULL),
+ PK11_Authenticate(NULL), PK11SDR_Decrypt(NULL), SECITEM_FreeItem(NULL),
+ PL_ArenaFinish(NULL), PR_Cleanup(NULL),
+ nss3_dll_(NULL), softokn3_dll_(NULL),
+ is_nss_initialized_(false) {
+}
+
+NSSDecryptor::~NSSDecryptor() {
+ Free();
+}
+
+bool NSSDecryptor::Init(const std::wstring& dll_path,
+ const std::wstring& db_path) {
+ // We call SetDllDirectory to work around a Purify bug (GetModuleHandle
+ // fails inside Purify under certain conditions). SetDllDirectory only
+ // exists on Windows XP SP1 or later, so we look up its address at run time.
+ HMODULE kernel32_dll = GetModuleHandle(L"kernel32.dll");
+ if (kernel32_dll == NULL)
+ return false;
+ SetDllDirectoryFunc set_dll_directory =
+ (SetDllDirectoryFunc)GetProcAddress(kernel32_dll, "SetDllDirectoryW");
+ SetDllDirectoryCaller caller;
+
+ if (set_dll_directory != NULL) {
+ if (!set_dll_directory(dll_path.c_str()))
+ return false;
+ caller.set_func(set_dll_directory);
+ nss3_dll_ = LoadLibrary(kNSS3Library);
+ if (nss3_dll_ == NULL)
+ return false;
+ } else {
+ // Fall back on LoadLibraryEx if SetDllDirectory isn't available. We
+ // actually prefer this method because it doesn't change the DLL search
+ // path, which is a process-wide property.
+ std::wstring path = dll_path;
+ file_util::AppendToPath(&path, kNSS3Library);
+ nss3_dll_ = LoadLibraryEx(path.c_str(), NULL,
+ LOAD_WITH_ALTERED_SEARCH_PATH);
+ if (nss3_dll_ == NULL)
+ return false;
+
+ // Firefox 2 uses NSS 3.11. Firefox 3 uses NSS 3.12. NSS 3.12 has two
+ // changes in its DLLs:
+ // 1. nss3.dll is not linked with softokn3.dll at build time, but rather
+ // loads softokn3.dll using LoadLibrary in NSS_Init.
+ // 2. softokn3.dll has a new dependency sqlite3.dll.
+ // NSS_Init's LoadLibrary call has trouble finding sqlite3.dll. To help
+ // it out, we preload softokn3.dll using LoadLibraryEx with the
+ // LOAD_WITH_ALTERED_SEARCH_PATH flag. This helps because LoadLibrary
+ // doesn't load a DLL again if it's already loaded. This workaround is
+ // harmless for NSS 3.11.
+ path = dll_path;
+ file_util::AppendToPath(&path, kSoftokn3Library);
+ softokn3_dll_ = LoadLibraryEx(path.c_str(), NULL,
+ LOAD_WITH_ALTERED_SEARCH_PATH);
+ if (softokn3_dll_ == NULL) {
+ Free();
+ return false;
+ }
+ }
+
+ // NSPR DLLs are already loaded now.
+ HMODULE plds4_dll = GetModuleHandle(kPLDS4Library);
+ HMODULE nspr4_dll = GetModuleHandle(kNSPR4Library);
+ if (plds4_dll == NULL || nspr4_dll == NULL) {
+ Free();
+ return false;
+ }
+
+ // Gets the function address.
+ NSS_Init = (NSSInitFunc)GetProcAddress(nss3_dll_, "NSS_Init");
+ NSS_Shutdown = (NSSShutdownFunc)GetProcAddress(nss3_dll_, "NSS_Shutdown");
+ PK11_GetInternalKeySlot = (PK11GetInternalKeySlotFunc)
+ GetProcAddress(nss3_dll_, "PK11_GetInternalKeySlot");
+ PK11_FreeSlot = (PK11FreeSlotFunc)GetProcAddress(nss3_dll_, "PK11_FreeSlot");
+ PK11_Authenticate = (PK11AuthenticateFunc)
+ GetProcAddress(nss3_dll_, "PK11_Authenticate");
+ PK11SDR_Decrypt = (PK11SDRDecryptFunc)
+ GetProcAddress(nss3_dll_, "PK11SDR_Decrypt");
+ SECITEM_FreeItem = (SECITEMFreeItemFunc)
+ GetProcAddress(nss3_dll_, "SECITEM_FreeItem");
+ PL_ArenaFinish = (PLArenaFinishFunc)
+ GetProcAddress(plds4_dll, "PL_ArenaFinish");
+ PR_Cleanup = (PRCleanupFunc)GetProcAddress(nspr4_dll, "PR_Cleanup");
+
+ if (NSS_Init == NULL || NSS_Shutdown == NULL ||
+ PK11_GetInternalKeySlot == NULL || PK11_FreeSlot == NULL ||
+ PK11_Authenticate == NULL || PK11SDR_Decrypt == NULL ||
+ SECITEM_FreeItem == NULL || PL_ArenaFinish == NULL ||
+ PR_Cleanup == NULL) {
+ Free();
+ return false;
+ }
+
+ SECStatus result = NSS_Init(base::SysWideToNativeMB(db_path).c_str());
+ if (result != SECSuccess) {
+ Free();
+ return false;
+ }
+
+ is_nss_initialized_ = true;
+ return true;
+}
+
+void NSSDecryptor::Free() {
+ if (is_nss_initialized_) {
+ NSS_Shutdown();
+ PL_ArenaFinish();
+ PR_Cleanup();
+ is_nss_initialized_ = false;
+ }
+ if (softokn3_dll_ != NULL)
+ FreeLibrary(softokn3_dll_);
+ softokn3_dll_ = NULL;
+ if (nss3_dll_ != NULL)
+ FreeLibrary(nss3_dll_);
+ nss3_dll_ = NULL;
+ NSS_Init = NULL;
+ NSS_Shutdown = NULL;
+ PK11_GetInternalKeySlot = NULL;
+ PK11_FreeSlot = NULL;
+ PK11_Authenticate = NULL;
+ PK11SDR_Decrypt = NULL;
+ SECITEM_FreeItem = NULL;
+ PL_ArenaFinish = NULL;
+ PR_Cleanup = NULL;
+}
+
+// This method is based on some Firefox code in
+// security/manager/ssl/src/nsSDR.cpp
+// The license block is:
+
+/* ***** BEGIN LICENSE BLOCK *****
+* Version: MPL 1.1/GPL 2.0/LGPL 2.1
+*
+* The contents of this file are subject to the Mozilla Public License Version
+* 1.1 (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+* http://www.mozilla.org/MPL/
+*
+* Software distributed under the License is distributed on an "AS IS" basis,
+* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+* for the specific language governing rights and limitations under the
+* License.
+*
+* The Original Code is the Netscape security libraries.
+*
+* The Initial Developer of the Original Code is
+* Netscape Communications Corporation.
+* Portions created by the Initial Developer are Copyright (C) 1994-2000
+* the Initial Developer. All Rights Reserved.
+*
+* Contributor(s):
+*
+* Alternatively, the contents of this file may be used under the terms of
+* either the GNU General Public License Version 2 or later (the "GPL"), or
+* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+* in which case the provisions of the GPL or the LGPL are applicable instead
+* of those above. If you wish to allow use of your version of this file only
+* under the terms of either the GPL or the LGPL, and not to allow others to
+* use your version of this file under the terms of the MPL, indicate your
+* decision by deleting the provisions above and replace them with the notice
+* and other provisions required by the GPL or the LGPL. If you do not delete
+* the provisions above, a recipient may use your version of this file under
+* the terms of any one of the MPL, the GPL or the LGPL.
+*
+* ***** END LICENSE BLOCK ***** */
+
+std::wstring NSSDecryptor::Decrypt(const std::string& crypt) const {
+ // Do nothing if NSS is not loaded.
+ if (!nss3_dll_)
+ return std::wstring();
+
+ std::string plain;
+
+ // The old style password is encoded in base64. They are identified
+ // by a leading '~'. Otherwise, we should decrypt the text.
+ if (crypt[0] != '~') {
+ std::string decoded_data;
+ net::Base64Decode(crypt, &decoded_data);
+ PK11SlotInfo* slot = NULL;
+ slot = PK11_GetInternalKeySlot();
+ SECStatus result = PK11_Authenticate(slot, PR_TRUE, NULL);
+ if (result != SECSuccess) {
+ PK11_FreeSlot(slot);
+ return std::wstring();
+ }
+
+ SECItem request;
+ request.data = reinterpret_cast<unsigned char*>(
+ const_cast<char*>(decoded_data.data()));
+ request.len = static_cast<unsigned int>(decoded_data.size());
+ SECItem reply;
+ reply.data = NULL;
+ reply.len = 0;
+ result = PK11SDR_Decrypt(&request, &reply, NULL);
+ if (result == SECSuccess)
+ plain.assign(reinterpret_cast<char*>(reply.data), reply.len);
+
+ SECITEM_FreeItem(&reply, PR_FALSE);
+ PK11_FreeSlot(slot);
+ } else {
+ // Deletes the leading '~' before decoding.
+ net::Base64Decode(crypt.substr(1), &plain);
+ }
+
+ return UTF8ToWide(plain);
+}
+
+// There are three versions of password filess. They store saved user
+// names and passwords.
+// References:
+// http://kb.mozillazine.org/Signons.txt
+// http://kb.mozillazine.org/Signons2.txt
+// http://kb.mozillazine.org/Signons3.txt
+void NSSDecryptor::ParseSignons(const std::string& content,
+ std::vector<PasswordForm>* forms) {
+ forms->clear();
+
+ // Splits the file content into lines.
+ std::vector<std::string> lines;
+ SplitString(content, '\n', &lines);
+
+ // The first line is the file version. We skip the unknown versions.
+ if (lines.empty())
+ return;
+ int version;
+ if (lines[0] == "#2c")
+ version = 1;
+ else if (lines[0] == "#2d")
+ version = 2;
+ else if (lines[0] == "#2e")
+ version = 3;
+ else
+ return;
+
+ GURL::Replacements rep;
+ rep.ClearQuery();
+ rep.ClearRef();
+ rep.ClearUsername();
+ rep.ClearPassword();
+
+ // Reads never-saved list. Domains are stored one per line.
+ size_t i;
+ for (i = 1; i < lines.size() && lines[i].compare(".") != 0; ++i) {
+ PasswordForm form;
+ form.origin = GURL(lines[i]).ReplaceComponents(rep);
+ form.signon_realm = form.origin.GetOrigin().spec();
+ form.blacklisted_by_user = true;
+ forms->push_back(form);
+ }
+ ++i;
+
+ // Reads saved passwords. The information is stored in blocks
+ // seperated by lines that only contain a dot. We find a block
+ // by the seperator and parse them one by one.
+ while (i < lines.size()) {
+ size_t begin = i;
+ size_t end = i + 1;
+ while (end < lines.size() && lines[end].compare(".") != 0)
+ ++end;
+ i = end + 1;
+
+ // A block has at least five lines.
+ if (end - begin < 5)
+ continue;
+
+ PasswordForm form;
+
+ // The first line is the site URL.
+ // For HTTP authentication logins, the URL may contain http realm,
+ // which will be in bracket:
+ // sitename:8080 (realm)
+ GURL url;
+ std::string realm;
+ const char kRealmBracketBegin[] = " (";
+ const char kRealmBracketEnd[] = ")";
+ if (lines[begin].find(kRealmBracketBegin) != std::string::npos) {
+ // In this case, the scheme may not exsit. We assume that the
+ // scheme is HTTP.
+ if (lines[begin].find("://") == std::string::npos)
+ lines[begin] = "http://" + lines[begin];
+
+ size_t start = lines[begin].find(kRealmBracketBegin);
+ url = GURL(lines[begin].substr(0, start));
+
+ start += std::string(kRealmBracketBegin).size();
+ size_t end = lines[begin].rfind(kRealmBracketEnd);
+ realm = lines[begin].substr(start, end - start);
+ } else {
+ // Don't have http realm. It is the URL that the following passwords
+ // belong to.
+ url = GURL(lines[begin]);
+ }
+ // Skips this block if the URL is not valid.
+ if (!url.is_valid())
+ continue;
+ form.origin = url.ReplaceComponents(rep);
+ form.signon_realm = form.origin.GetOrigin().spec();
+ if (!realm.empty())
+ form.signon_realm += realm;
+ form.ssl_valid = form.origin.SchemeIsSecure();
+ ++begin;
+
+ // There may be multiple username/password pairs for this site.
+ // In this case, they are saved in one block without a seperated
+ // line (contains a dot).
+ while (begin + 4 < end) {
+ // The user name.
+ form.username_element = UTF8ToWide(lines[begin++]);
+ form.username_value = Decrypt(lines[begin++]);
+ // The element name has a leading '*'.
+ if (lines[begin].at(0) == '*') {
+ form.password_element = UTF8ToWide(lines[begin++].substr(1));
+ form.password_value = Decrypt(lines[begin++]);
+ } else {
+ // Maybe the file is bad, we skip to next block.
+ break;
+ }
+ // The action attribute from the form element. This line exists
+ // in versin 2 or above.
+ if (version >= 2) {
+ if (begin < end)
+ form.action = GURL(lines[begin]).ReplaceComponents(rep);
+ ++begin;
+ }
+ // Version 3 has an extra line for further use.
+ if (version == 3) {
+ ++begin;
+ }
+
+ forms->push_back(form);
+ }
+ }
+}
+
diff --git a/chrome/browser/importer/firefox_importer_utils.h b/chrome/browser/importer/firefox_importer_utils.h
new file mode 100644
index 0000000..f69a27d
--- /dev/null
+++ b/chrome/browser/importer/firefox_importer_utils.h
@@ -0,0 +1,211 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_IMPORTER_FIREFOX_IMPORTER_UTILS_H_
+#define CHROME_BROWSER_IMPORTER_FIREFOX_IMPORTER_UTILS_H_
+
+#include "base/values.h"
+#include "webkit/glue/password_form.h"
+
+class GURL;
+class TemplateURL;
+
+// Detects which version of Firefox is installed. Returns its
+// major version, and drops the minor version. Returns 0 if
+// failed.
+int GetCurrentFirefoxMajorVersion();
+
+// Gets the full path of the profiles.ini file. This file records
+// the profiles that can be used by Firefox. Returns an empty
+// string if failed.
+std::wstring GetProfilesINI();
+
+// Detects where Firefox lives. Returns a empty string if Firefox
+// is not installed.
+std::wstring GetFirefoxInstallPath();
+
+// Parses the profile.ini file, and stores its information in |root|.
+// This file is a plain-text file. Key/value pairs are stored one per
+// line, and they are separeated in different sections. For example:
+// [General]
+// StartWithLastProfile=1
+//
+// [Profile0]
+// Name=default
+// IsRelative=1
+// Path=Profiles/abcdefeg.default
+// We set "[value]" in path "<Section>.<Key>". For example, the path
+// "Genenral.StartWithLastProfile" has the value "1".
+void ParseProfileINI(std::wstring file, DictionaryValue* root);
+
+// Returns true if we want to add the URL to the history. We filter
+// out the URL with a unsupported scheme.
+bool CanImportURL(const GURL& url);
+
+// Parses the OpenSearch XML files in |xml_files| and populates |search_engines|
+// with the resulting TemplateURLs.
+void ParseSearchEnginesFromXMLFiles(const std::vector<std::wstring>& xml_files,
+ std::vector<TemplateURL*>* search_engines);
+
+// Returns the index of the default search engine in the |search_engines| list.
+// If none is found, -1 is returned.
+int GetFirefoxDefaultSearchEngineIndex(
+ const std::vector<TemplateURL*>& search_engines,
+ const std::wstring& profile_path);
+
+// Returns the home page set in Firefox in a particular profile.
+GURL GetHomepage(const std::wstring& profile_path);
+
+// Checks to see if this home page is a default home page, as specified by
+// the resource file browserconfig.properties in the Firefox application
+// directory.
+bool IsDefaultHomepage(const GURL& homepage, const std::wstring& app_path);
+
+// The following declarations of functions and types are from Firefox
+// NSS library.
+// source code:
+// security/nss/lib/util/seccomon.h
+// security/nss/lib/nss/nss.h
+// The license block is:
+
+/* ***** BEGIN LICENSE BLOCK *****
+* Version: MPL 1.1/GPL 2.0/LGPL 2.1
+*
+* The contents of this file are subject to the Mozilla Public License Version
+* 1.1 (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+* http://www.mozilla.org/MPL/
+*
+* Software distributed under the License is distributed on an "AS IS" basis,
+* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+* for the specific language governing rights and limitations under the
+* License.
+*
+* The Original Code is the Netscape security libraries.
+*
+* The Initial Developer of the Original Code is
+* Netscape Communications Corporation.
+* Portions created by the Initial Developer are Copyright (C) 1994-2000
+* the Initial Developer. All Rights Reserved.
+*
+* Contributor(s):
+*
+* Alternatively, the contents of this file may be used under the terms of
+* either the GNU General Public License Version 2 or later (the "GPL"), or
+* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+* in which case the provisions of the GPL or the LGPL are applicable instead
+* of those above. If you wish to allow use of your version of this file only
+* under the terms of either the GPL or the LGPL, and not to allow others to
+* use your version of this file under the terms of the MPL, indicate your
+* decision by deleting the provisions above and replace them with the notice
+* and other provisions required by the GPL or the LGPL. If you do not delete
+* the provisions above, a recipient may use your version of this file under
+* the terms of any one of the MPL, the GPL or the LGPL.
+*
+* ***** END LICENSE BLOCK ***** */
+
+enum SECItemType {
+ siBuffer = 0,
+ siClearDataBuffer = 1,
+ siCipherDataBuffer = 2,
+ siDERCertBuffer = 3,
+ siEncodedCertBuffer = 4,
+ siDERNameBuffer = 5,
+ siEncodedNameBuffer = 6,
+ siAsciiNameString = 7,
+ siAsciiString = 8,
+ siDEROID = 9,
+ siUnsignedInteger = 10,
+ siUTCTime = 11,
+ siGeneralizedTime = 12
+};
+
+struct SECItem {
+ SECItemType type;
+ unsigned char *data;
+ unsigned int len;
+};
+
+enum SECStatus {
+ SECWouldBlock = -2,
+ SECFailure = -1,
+ SECSuccess = 0
+};
+
+typedef int PRBool;
+#define PR_TRUE 1
+#define PR_FALSE 0
+
+typedef enum { PR_FAILURE = -1, PR_SUCCESS = 0 } PRStatus;
+
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+
+typedef SECStatus (*NSSInitFunc)(const char *configdir);
+typedef SECStatus (*NSSShutdownFunc)(void);
+typedef PK11SlotInfo* (*PK11GetInternalKeySlotFunc)(void);
+typedef void (*PK11FreeSlotFunc)(PK11SlotInfo *slot);
+typedef SECStatus (*PK11CheckUserPasswordFunc)(PK11SlotInfo *slot, char *pw);
+typedef SECStatus
+ (*PK11AuthenticateFunc)(PK11SlotInfo *slot, PRBool loadCerts, void *wincx);
+typedef SECStatus
+ (*PK11SDRDecryptFunc)(SECItem *data, SECItem *result, void *cx);
+typedef void (*SECITEMFreeItemFunc)(SECItem *item, PRBool free_it);
+typedef void (*PLArenaFinishFunc)(void);
+typedef PRStatus (*PRCleanupFunc)(void);
+
+// A wrapper for Firefox NSS decrypt component.
+class NSSDecryptor {
+ public:
+ NSSDecryptor();
+ ~NSSDecryptor();
+
+ // Loads NSS3 library and returns true if successful.
+ // |dll_path| indicates the location of NSS3 DLL files, and |db_path|
+ // is the location of the database file that stores the keys.
+ bool Init(const std::wstring& dll_path, const std::wstring& db_path);
+
+ // Frees the libraries.
+ void Free();
+
+ // Decrypts Firefox stored passwords. Before using this method,
+ // make sure Init() returns true.
+ std::wstring Decrypt(const std::string& crypt) const;
+
+ // Parses the Firefox password file content, decrypts the
+ // username/password and reads other related information.
+ // The result will be stored in |forms|.
+ void ParseSignons(const std::string& content,
+ std::vector<PasswordForm>* forms);
+
+ private:
+ // Methods in Firefox security components.
+ NSSInitFunc NSS_Init;
+ NSSShutdownFunc NSS_Shutdown;
+ PK11GetInternalKeySlotFunc PK11_GetInternalKeySlot;
+ PK11CheckUserPasswordFunc PK11_CheckUserPassword;
+ PK11FreeSlotFunc PK11_FreeSlot;
+ PK11AuthenticateFunc PK11_Authenticate;
+ PK11SDRDecryptFunc PK11SDR_Decrypt;
+ SECITEMFreeItemFunc SECITEM_FreeItem;
+ PLArenaFinishFunc PL_ArenaFinish;
+ PRCleanupFunc PR_Cleanup;
+
+ // Libraries necessary for decrypting the passwords.
+ static const wchar_t kNSS3Library[];
+ static const wchar_t kSoftokn3Library[];
+ static const wchar_t kPLDS4Library[];
+ static const wchar_t kNSPR4Library[];
+
+ // NSS3 module handles.
+ HMODULE nss3_dll_;
+ HMODULE softokn3_dll_;
+
+ // True if NSS_Init() has been called
+ bool is_nss_initialized_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(NSSDecryptor);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_FIREFOX_IMPORTER_UTILS_H_
+
diff --git a/chrome/browser/importer/firefox_profile_lock.cc b/chrome/browser/importer/firefox_profile_lock.cc
new file mode 100644
index 0000000..aaab891
--- /dev/null
+++ b/chrome/browser/importer/firefox_profile_lock.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/importer/firefox_profile_lock.h"
+
+#include "base/file_util.h"
+
+// This class is based on Firefox code in:
+// profile/dirserviceprovider/src/nsProfileLock.cpp
+// The license block is:
+
+/* ***** BEGIN LICENSE BLOCK *****
+* Version: MPL 1.1/GPL 2.0/LGPL 2.1
+*
+* The contents of this file are subject to the Mozilla Public License Version
+* 1.1 (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+* http://www.mozilla.org/MPL/
+*
+* Software distributed under the License is distributed on an "AS IS" basis,
+* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+* for the specific language governing rights and limitations under the
+* License.
+*
+* The Original Code is mozilla.org code.
+*
+* The Initial Developer of the Original Code is
+* Netscape Communications Corporation.
+* Portions created by the Initial Developer are Copyright (C) 2002
+* the Initial Developer. All Rights Reserved.
+*
+* Contributor(s):
+* Conrad Carlen <ccarlen@netscape.com>
+* Brendan Eich <brendan@mozilla.org>
+* Colin Blake <colin@theblakes.com>
+* Javier Pedemonte <pedemont@us.ibm.com>
+* Mats Palmgren <mats.palmgren@bredband.net>
+*
+* Alternatively, the contents of this file may be used under the terms of
+* either the GNU General Public License Version 2 or later (the "GPL"), or
+* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+* in which case the provisions of the GPL or the LGPL are applicable instead
+* of those above. If you wish to allow use of your version of this file only
+* under the terms of either the GPL or the LGPL, and not to allow others to
+* use your version of this file under the terms of the MPL, indicate your
+* decision by deleting the provisions above and replace them with the notice
+* and other provisions required by the GPL or the LGPL. If you do not delete
+* the provisions above, a recipient may use your version of this file under
+* the terms of any one of the MPL, the GPL or the LGPL.
+*
+* ***** END LICENSE BLOCK ***** */
+
+// static
+wchar_t FirefoxProfileLock::kLockFileName[] = L"parent.lock";
+
+FirefoxProfileLock::FirefoxProfileLock(const std::wstring& path)
+ : lock_handle_(INVALID_HANDLE_VALUE) {
+ lock_file_ = path;
+ file_util::AppendToPath(&lock_file_, kLockFileName);
+ Lock();
+}
+
+FirefoxProfileLock::~FirefoxProfileLock() {
+ Unlock();
+}
+
+void FirefoxProfileLock::Lock() {
+ if (HasAcquired())
+ return;
+ lock_handle_ = CreateFile(lock_file_.c_str(), GENERIC_READ | GENERIC_WRITE,
+ 0, NULL, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE,
+ NULL);
+}
+
+void FirefoxProfileLock::Unlock() {
+ if (!HasAcquired())
+ return;
+ CloseHandle(lock_handle_);
+ lock_handle_ = INVALID_HANDLE_VALUE;
+}
+
+bool FirefoxProfileLock::HasAcquired() {
+ return (lock_handle_ != INVALID_HANDLE_VALUE);
+}
+
+
diff --git a/chrome/browser/importer/firefox_profile_lock.h b/chrome/browser/importer/firefox_profile_lock.h
new file mode 100644
index 0000000..c32d592
--- /dev/null
+++ b/chrome/browser/importer/firefox_profile_lock.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_IMPORTER_FIREFOX_PROFILE_LOCK_H__
+#define CHROME_BROWSER_IMPORTER_FIREFOX_PROFILE_LOCK_H__
+
+#include <windows.h>
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+// Firefox is designed to allow only one application to access its
+// profile at the same time.
+// Reference:
+// http://kb.mozillazine.org/Profile_in_use
+//
+// This class is based on Firefox code in:
+// profile/dirserviceprovider/src/nsProfileLock.cpp
+// The license block is:
+
+/* ***** BEGIN LICENSE BLOCK *****
+* Version: MPL 1.1/GPL 2.0/LGPL 2.1
+*
+* The contents of this file are subject to the Mozilla Public License Version
+* 1.1 (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+* http://www.mozilla.org/MPL/
+*
+* Software distributed under the License is distributed on an "AS IS" basis,
+* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+* for the specific language governing rights and limitations under the
+* License.
+*
+* The Original Code is mozilla.org code.
+*
+* The Initial Developer of the Original Code is
+* Netscape Communications Corporation.
+* Portions created by the Initial Developer are Copyright (C) 2002
+* the Initial Developer. All Rights Reserved.
+*
+* Contributor(s):
+* Conrad Carlen <ccarlen@netscape.com>
+* Brendan Eich <brendan@mozilla.org>
+* Colin Blake <colin@theblakes.com>
+* Javier Pedemonte <pedemont@us.ibm.com>
+* Mats Palmgren <mats.palmgren@bredband.net>
+*
+* Alternatively, the contents of this file may be used under the terms of
+* either the GNU General Public License Version 2 or later (the "GPL"), or
+* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+* in which case the provisions of the GPL or the LGPL are applicable instead
+* of those above. If you wish to allow use of your version of this file only
+* under the terms of either the GPL or the LGPL, and not to allow others to
+* use your version of this file under the terms of the MPL, indicate your
+* decision by deleting the provisions above and replace them with the notice
+* and other provisions required by the GPL or the LGPL. If you do not delete
+* the provisions above, a recipient may use your version of this file under
+* the terms of any one of the MPL, the GPL or the LGPL.
+*
+* ***** END LICENSE BLOCK ***** */
+
+class FirefoxProfileLock {
+ public:
+ explicit FirefoxProfileLock(const std::wstring& path);
+ ~FirefoxProfileLock();
+
+ // Locks and releases the profile.
+ void Lock();
+ void Unlock();
+
+ // Returns true if we lock the profile successfully.
+ bool HasAcquired();
+
+ private:
+ FRIEND_TEST(FirefoxImporterTest, ProfileLock);
+ FRIEND_TEST(FirefoxImporterTest, ProfileLockOrphaned);
+
+ // The name of the lock file.
+ static wchar_t kLockFileName[];
+
+ // Full path of the lock file in the profile folder.
+ std::wstring lock_file_;
+
+ // The handle of the lock file.
+ HANDLE lock_handle_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(FirefoxProfileLock);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_FIREFOX_PROFILE_LOCK_H__
+
diff --git a/chrome/browser/importer/ie_importer.cc b/chrome/browser/importer/ie_importer.cc
new file mode 100644
index 0000000..8e53807
--- /dev/null
+++ b/chrome/browser/importer/ie_importer.cc
@@ -0,0 +1,567 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/importer/ie_importer.h"
+
+#include <intshcut.h>
+#include <pstore.h>
+#include <shlobj.h>
+#include <urlhist.h>
+#include <algorithm>
+
+#include "base/file_util.h"
+#include "base/registry.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "base/win_util.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#include "chrome/browser/ie7_password.h"
+#include "chrome/browser/template_url_model.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/time_format.h"
+#include "chrome/common/win_util.h"
+#include "googleurl/src/gurl.h"
+
+#include "generated_resources.h"
+
+namespace {
+
+// Gets the creation time of the given file or directory.
+static Time GetFileCreationTime(const std::wstring& file) {
+ Time creation_time;
+ ScopedHandle file_handle(
+ CreateFile(file.c_str(), GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL));
+ FILETIME creation_filetime;
+ if (GetFileTime(file_handle, &creation_filetime, NULL, NULL))
+ creation_time = Time::FromFileTime(creation_filetime);
+ return creation_time;
+}
+
+} // namespace
+
+// static
+// {E161255A-37C3-11D2-BCAA-00C04fD929DB}
+const GUID IEImporter::kPStoreAutocompleteGUID = {0xe161255a, 0x37c3, 0x11d2,
+ {0xbc, 0xaa, 0x00, 0xc0, 0x4f, 0xd9, 0x29, 0xdb}};
+// {A79029D6-753E-4e27-B807-3D46AB1545DF}
+const GUID IEImporter::kUnittestGUID = { 0xa79029d6, 0x753e, 0x4e27,
+ {0xb8, 0x7, 0x3d, 0x46, 0xab, 0x15, 0x45, 0xdf}};
+
+void IEImporter::StartImport(ProfileInfo profile_info,
+ uint16 items,
+ ProfileWriter* writer,
+ ImporterHost* host) {
+ writer_ = writer;
+ source_path_ = profile_info.source_path;
+ importer_host_ = host;
+
+ NotifyStarted();
+
+ // Some IE settings (such as Protected Storage) is obtained via COM APIs.
+ win_util::ScopedCOMInitializer com_initializer;
+
+ if ((items & HOME_PAGE) && !cancelled())
+ ImportHomepage(); // Doesn't have a UI item.
+ // The order here is important!
+ if ((items & FAVORITES) && !cancelled()) {
+ NotifyItemStarted(FAVORITES);
+ ImportFavorites();
+ NotifyItemEnded(FAVORITES);
+ }
+ if ((items & SEARCH_ENGINES) && !cancelled()) {
+ NotifyItemStarted(SEARCH_ENGINES);
+ ImportSearchEngines();
+ NotifyItemEnded(SEARCH_ENGINES);
+ }
+ if ((items & PASSWORDS) && !cancelled()) {
+ NotifyItemStarted(PASSWORDS);
+ // Always import IE6 passwords.
+ ImportPasswordsIE6();
+
+ if (CurrentIEVersion() >= 7)
+ ImportPasswordsIE7();
+ NotifyItemEnded(PASSWORDS);
+ }
+ if ((items & HISTORY) && !cancelled()) {
+ NotifyItemStarted(HISTORY);
+ ImportHistory();
+ NotifyItemEnded(HISTORY);
+ }
+ NotifyEnded();
+}
+
+void IEImporter::ImportFavorites() {
+ std::wstring path;
+
+ FavoritesInfo info;
+ if (!GetFavoritesInfo(&info))
+ return;
+
+ BookmarkVector bookmarks;
+ ParseFavoritesFolder(info, &bookmarks);
+
+ if (!bookmarks.empty() && !cancelled()) {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddBookmarkEntry, bookmarks));
+ }
+}
+
+void IEImporter::ImportPasswordsIE6() {
+ GUID AutocompleteGUID = kPStoreAutocompleteGUID;
+ if (!source_path_.empty()) {
+ // We supply a fake GUID for testting.
+ AutocompleteGUID = kUnittestGUID;
+ }
+
+ // The PStoreCreateInstance function retrieves an interface pointer
+ // to a storage provider. But this function has no associated import
+ // library or header file, we must call it using the LoadLibrary()
+ // and GetProcAddress() functions.
+ typedef HRESULT (WINAPI *PStoreCreateFunc)(IPStore**, DWORD, DWORD, DWORD);
+ HMODULE pstorec_dll = LoadLibrary(L"pstorec.dll");
+ PStoreCreateFunc PStoreCreateInstance =
+ (PStoreCreateFunc)GetProcAddress(pstorec_dll, "PStoreCreateInstance");
+
+ CComPtr<IPStore> pstore;
+ HRESULT result = PStoreCreateInstance(&pstore, 0, 0, 0);
+ if (result != S_OK) {
+ FreeLibrary(pstorec_dll);
+ return;
+ }
+
+ std::vector<AutoCompleteInfo> ac_list;
+
+ // Enumerates AutoComplete items in the protected database.
+ CComPtr<IEnumPStoreItems> item;
+ result = pstore->EnumItems(0, &AutocompleteGUID,
+ &AutocompleteGUID, 0, &item);
+ if (result != PST_E_OK) {
+ pstore.Release();
+ FreeLibrary(pstorec_dll);
+ return;
+ }
+
+ wchar_t* item_name;
+ while (!cancelled() && SUCCEEDED(item->Next(1, &item_name, 0))) {
+ DWORD length = 0;
+ unsigned char* buffer = NULL;
+ result = pstore->ReadItem(0, &AutocompleteGUID, &AutocompleteGUID,
+ item_name, &length, &buffer, NULL, 0);
+ if (SUCCEEDED(result)) {
+ AutoCompleteInfo ac;
+ ac.key = item_name;
+ std::wstring data;
+ data.insert(0, reinterpret_cast<wchar_t*>(buffer),
+ length / sizeof(wchar_t));
+
+ // The key name is always ended with ":StringData".
+ const wchar_t kDataSuffix[] = L":StringData";
+ size_t i = ac.key.rfind(kDataSuffix);
+ if (i != std::wstring::npos && ac.key.substr(i) == kDataSuffix) {
+ ac.key.erase(i);
+ ac.is_url = (ac.key.find(L"://") != std::wstring::npos);
+ ac_list.push_back(ac);
+ SplitString(data, L'\0', &ac_list[ac_list.size() - 1].data);
+ }
+ CoTaskMemFree(buffer);
+ }
+ CoTaskMemFree(item_name);
+ }
+ // Releases them before unload the dll.
+ item.Release();
+ pstore.Release();
+ FreeLibrary(pstorec_dll);
+
+ size_t i;
+ for (i = 0; i < ac_list.size(); i++) {
+ if (!ac_list[i].is_url || ac_list[i].data.size() < 2)
+ continue;
+
+ GURL url(ac_list[i].key.c_str());
+ if (!(LowerCaseEqualsASCII(url.scheme(), "http") ||
+ LowerCaseEqualsASCII(url.scheme(), "https"))) {
+ continue;
+ }
+
+ PasswordForm form;
+ GURL::Replacements rp;
+ rp.ClearUsername();
+ rp.ClearPassword();
+ rp.ClearQuery();
+ rp.ClearRef();
+ form.origin = url.ReplaceComponents(rp);
+ form.username_value = ac_list[i].data[0];
+ form.password_value = ac_list[i].data[1];
+ form.signon_realm = url.GetOrigin().spec();
+
+ // This is not precise, because a scheme of https does not imply a valid
+ // certificate was presented; however we assign it this way so that if we
+ // import a password from IE whose scheme is https, we give it the benefit
+ // of the doubt and DONT auto-fill it unless the form appears under
+ // valid SSL conditions.
+ form.ssl_valid = url.SchemeIsSecure();
+
+ // Goes through the list to find out the username field
+ // of the web page.
+ size_t list_it, item_it;
+ for (list_it = 0; list_it < ac_list.size(); ++list_it) {
+ if (ac_list[list_it].is_url)
+ continue;
+
+ for (item_it = 0; item_it < ac_list[list_it].data.size(); ++item_it)
+ if (ac_list[list_it].data[item_it] == form.username_value) {
+ form.username_element = ac_list[list_it].key;
+ break;
+ }
+ }
+
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddPasswordForm, form));
+ }
+}
+
+void IEImporter::ImportPasswordsIE7() {
+ if (!source_path_.empty()) {
+ // We have been called from the unit tests. Don't import real passwords.
+ return;
+ }
+
+ const wchar_t kStorage2Path[] =
+ L"Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
+
+ RegKey key(HKEY_CURRENT_USER, kStorage2Path, KEY_READ);
+ RegistryValueIterator reg_iterator(HKEY_CURRENT_USER, kStorage2Path);
+ while (reg_iterator.Valid() && !cancelled()) {
+ // Get the size of the encrypted data.
+ DWORD value_len = 0;
+ if (key.ReadValue(reg_iterator.Name(), NULL, &value_len) && value_len) {
+ // Query the encrypted data.
+ std::vector<unsigned char> value;
+ value.resize(value_len);
+ if (key.ReadValue(reg_iterator.Name(), &value.front(), &value_len)) {
+ IE7PasswordInfo password_info;
+ password_info.url_hash = reg_iterator.Name();
+ password_info.encrypted_data = value;
+ password_info.date_created = Time::Now();
+ main_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(writer_,
+ &ProfileWriter::AddIE7PasswordInfo,
+ password_info));
+ }
+ }
+
+ ++reg_iterator;
+ }
+}
+
+// Reads history information from COM interface.
+void IEImporter::ImportHistory() {
+ const std::string kSchemes[] = {"http", "https", "ftp", "file"};
+ int total_schemes = arraysize(kSchemes);
+
+ CComPtr<IUrlHistoryStg2> url_history_stg2;
+ HRESULT result;
+ result = url_history_stg2.CoCreateInstance(CLSID_CUrlHistory, NULL,
+ CLSCTX_INPROC_SERVER);
+ if (FAILED(result))
+ return;
+ CComPtr<IEnumSTATURL> enum_url;
+ if (SUCCEEDED(result = url_history_stg2->EnumUrls(&enum_url))) {
+ std::vector<history::URLRow> rows;
+ STATURL stat_url;
+ ULONG fetched;
+ while (!cancelled() &&
+ (result = enum_url->Next(1, &stat_url, &fetched)) == S_OK) {
+ std::wstring url_string;
+ std::wstring title_string;
+ if (stat_url.pwcsUrl) {
+ url_string = stat_url.pwcsUrl;
+ CoTaskMemFree(stat_url.pwcsUrl);
+ }
+ if (stat_url.pwcsTitle) {
+ title_string = stat_url.pwcsTitle;
+ CoTaskMemFree(stat_url.pwcsTitle);
+ }
+
+ GURL url(url_string);
+ // Skips the URLs that are invalid or have other schemes.
+ if (!url.is_valid() ||
+ (std::find(kSchemes, kSchemes + total_schemes, url.scheme()) ==
+ kSchemes + total_schemes))
+ continue;
+
+ history::URLRow row(url);
+ row.set_title(title_string);
+ row.set_last_visit(Time::FromFileTime(stat_url.ftLastVisited));
+ if (stat_url.dwFlags == STATURL_QUERYFLAG_TOPLEVEL) {
+ row.set_visit_count(1);
+ row.set_hidden(false);
+ } else {
+ row.set_hidden(true);
+ }
+
+ rows.push_back(row);
+ }
+
+ if (!rows.empty() && !cancelled()) {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddHistoryPage, rows));
+ }
+ }
+}
+
+void IEImporter::ImportSearchEngines() {
+ // On IE, search engines are stored in the registry, under:
+ // Software\Microsoft\Internet Explorer\SearchScopes
+ // Each key represents a search engine. The URL value contains the URL and
+ // the DisplayName the name.
+ // The default key's name is contained under DefaultScope.
+ const wchar_t kSearchScopePath[] =
+ L"Software\\Microsoft\\Internet Explorer\\SearchScopes";
+
+ RegKey key(HKEY_CURRENT_USER, kSearchScopePath, KEY_READ);
+ std::wstring default_search_engine_name;
+ const TemplateURL* default_search_engine = NULL;
+ std::map<std::wstring, TemplateURL*> search_engines_map;
+ key.ReadValue(L"DefaultScope", &default_search_engine_name);
+ RegistryKeyIterator key_iterator(HKEY_CURRENT_USER, kSearchScopePath);
+ while (key_iterator.Valid()) {
+ std::wstring sub_key_name = kSearchScopePath;
+ sub_key_name.append(L"\\").append(key_iterator.Name());
+ RegKey sub_key(HKEY_CURRENT_USER, sub_key_name.c_str(), KEY_READ);
+ std::wstring url;
+ if (!sub_key.ReadValue(L"URL", &url) || url.empty()) {
+ LOG(INFO) << "No URL for IE search engine at " << key_iterator.Name();
+ ++key_iterator;
+ continue;
+ }
+ // For the name, we try the default value first (as Live Search uses a
+ // non displayable name in DisplayName, and the readable name under the
+ // default value).
+ std::wstring name;
+ if (!sub_key.ReadValue(NULL, &name) || name.empty()) {
+ // Try the displayable name.
+ if (!sub_key.ReadValue(L"DisplayName", &name) || name.empty()) {
+ LOG(INFO) << "No name for IE search engine at " << key_iterator.Name();
+ ++key_iterator;
+ continue;
+ }
+ }
+
+ std::map<std::wstring, TemplateURL*>::iterator t_iter =
+ search_engines_map.find(url);
+ TemplateURL* template_url =
+ (t_iter != search_engines_map.end()) ? t_iter->second : NULL;
+ if (!template_url) {
+ // First time we see that URL.
+ template_url = new TemplateURL();
+ template_url->set_short_name(name);
+ template_url->SetURL(url, 0, 0);
+ // Give this a keyword to facilitate tab-to-search, if possible.
+ template_url->set_keyword(TemplateURLModel::GenerateKeyword(GURL(url),
+ false));
+ template_url->set_show_in_default_list(true);
+ search_engines_map[url] = template_url;
+ }
+ if (template_url && key_iterator.Name() == default_search_engine_name) {
+ DCHECK(!default_search_engine);
+ default_search_engine = template_url;
+ }
+ ++key_iterator;
+ }
+
+ // ProfileWriter::AddKeywords() requires a vector and we have a map.
+ std::map<std::wstring, TemplateURL*>::iterator t_iter;
+ std::vector<TemplateURL*> search_engines;
+ int default_search_engine_index = -1;
+ for (t_iter = search_engines_map.begin(); t_iter != search_engines_map.end();
+ ++t_iter) {
+ search_engines.push_back(t_iter->second);
+ if (default_search_engine == t_iter->second) {
+ default_search_engine_index =
+ static_cast<int>(search_engines.size()) - 1;
+ }
+ }
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddKeywords, search_engines, default_search_engine_index,
+ true));
+}
+
+void IEImporter::ImportHomepage() {
+ const wchar_t kIESettingsMain[] =
+ L"Software\\Microsoft\\Internet Explorer\\Main";
+ const wchar_t kIEHomepage[] = L"Start Page";
+ const wchar_t kIEDefaultHomepage[] = L"Default_Page_URL";
+
+ RegKey key(HKEY_CURRENT_USER, kIESettingsMain, KEY_READ);
+ std::wstring homepage_url;
+ if (!key.ReadValue(kIEHomepage, &homepage_url) || homepage_url.empty())
+ return;
+
+ GURL homepage = GURL(homepage_url);
+ if (!homepage.is_valid())
+ return;
+
+ // Check to see if this is the default website and skip import.
+ RegKey keyDefault(HKEY_LOCAL_MACHINE, kIESettingsMain, KEY_READ);
+ std::wstring default_homepage_url;
+ if (keyDefault.ReadValue(kIEDefaultHomepage, &default_homepage_url) &&
+ !default_homepage_url.empty()) {
+ if (homepage.spec() == GURL(default_homepage_url).spec())
+ return;
+ }
+
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_,
+ &ProfileWriter::AddHomepage, homepage));
+}
+
+bool IEImporter::GetFavoritesInfo(IEImporter::FavoritesInfo *info) {
+ if (!source_path_.empty()) {
+ // Source path exists during testing.
+ info->path = source_path_;
+ file_util::AppendToPath(&info->path, L"Favorites");
+ info->links_folder = L"Links";
+ return true;
+ }
+
+ // IE stores the favorites in the Favorites under user profile's folder.
+ wchar_t buffer[MAX_PATH];
+ if (FAILED(SHGetFolderPath(NULL, CSIDL_FAVORITES, NULL,
+ SHGFP_TYPE_CURRENT, buffer)))
+ return false;
+ info->path = buffer;
+
+ // There is a Links folder under Favorites folder in Windows Vista, but it
+ // is not recording in Vista's registry. So in Vista, we assume the Links
+ // folder is under Favorites folder since it looks like there is not name
+ // different in every language version of Windows Vista.
+ if (win_util::GetWinVersion() != win_util::WINVERSION_VISTA) {
+ // The Link folder name is stored in the registry.
+ DWORD buffer_length = sizeof(buffer);
+ if (!ReadFromRegistry(HKEY_CURRENT_USER,
+ L"Software\\Microsoft\\Internet Explorer\\Toolbar",
+ L"LinksFolderName", buffer, &buffer_length))
+ return false;
+ info->links_folder = buffer;
+ } else {
+ info->links_folder = L"Links";
+ }
+
+ // Gets the creation time of the user's profile folder.
+ if (FAILED(SHGetFolderPath(NULL, CSIDL_PROFILE, NULL,
+ SHGFP_TYPE_CURRENT, buffer)))
+ return false;
+ info->profile_creation_time = GetFileCreationTime(buffer);
+
+ return true;
+}
+
+void IEImporter::ParseFavoritesFolder(const FavoritesInfo& info,
+ BookmarkVector* bookmarks) {
+ std::wstring ie_folder = l10n_util::GetString(IDS_BOOKMARK_GROUP_FROM_IE);
+ BookmarkVector toolbar_bookmarks;
+ std::wstring file;
+ std::vector<std::wstring> file_list;
+ file_util::FileEnumerator file_enumerator(info.path, true,
+ file_util::FileEnumerator::FILES);
+ while (!(file = file_enumerator.Next()).empty() && !cancelled())
+ file_list.push_back(file);
+
+ // Keep the bookmarks in alphabetical order.
+ std::sort(file_list.begin(), file_list.end());
+
+ for (std::vector<std::wstring>::iterator it = file_list.begin();
+ it != file_list.end(); ++it) {
+ std::wstring filename = file_util::GetFilenameFromPath(*it);
+ std::wstring extension = file_util::GetFileExtensionFromPath(filename);
+ if (!LowerCaseEqualsASCII(extension, "url"))
+ continue;
+
+ // We don't import default bookmarks from IE, e.g. "Customize links",
+ // "Free Hotmail". To detect these, we compare the creation time of the
+ // .url file with that of the profile folder. The file's creation time
+ // should be 2 minute later (to allow jitter).
+ Time creation = GetFileCreationTime(*it);
+ if (info.profile_creation_time != Time() &&
+ creation - info.profile_creation_time <= TimeDelta::FromMinutes(2))
+ continue;
+
+ // Skip the bookmark with invalid URL.
+ GURL url = GURL(ResolveInternetShortcut(*it));
+ if (!url.is_valid())
+ continue;
+
+ // Remove the dot and the file extension, and the directory path.
+ std::wstring relative_path = it->substr(info.path.size(),
+ it->size() - filename.size() - info.path.size());
+ TrimString(relative_path, L"\\", &relative_path);
+
+ ProfileWriter::BookmarkEntry entry;
+ entry.title = filename.substr(0, filename.size() - (extension.size() + 1));
+ entry.url = url;
+ entry.creation_time = creation;
+ if (!relative_path.empty())
+ SplitString(relative_path, file_util::kPathSeparator, &entry.path);
+
+ // Flatten the bookmarks in Link folder onto bookmark toolbar. Otherwise,
+ // put it into "Other bookmarks".
+ if (first_run() &&
+ (entry.path.size() > 0 && entry.path[0] == info.links_folder)) {
+ entry.in_toolbar = true;
+ entry.path.erase(entry.path.begin());
+ toolbar_bookmarks.push_back(entry);
+ } else {
+ // After the first run, we put the bookmarks in a "Imported From IE"
+ // folder, so that we don't mess up the "Other bookmarks".
+ if (!first_run())
+ entry.path.insert(entry.path.begin(), ie_folder);
+ bookmarks->push_back(entry);
+ }
+ }
+ bookmarks->insert(bookmarks->begin(), toolbar_bookmarks.begin(),
+ toolbar_bookmarks.end());
+}
+
+std::wstring IEImporter::ResolveInternetShortcut(const std::wstring& file) {
+ win_util::CoMemReleaser<wchar_t> url;
+ CComPtr<IUniformResourceLocator> url_locator;
+ HRESULT result = url_locator.CoCreateInstance(CLSID_InternetShortcut, NULL,
+ CLSCTX_INPROC_SERVER);
+ if (FAILED(result))
+ return std::wstring();
+
+ CComPtr<IPersistFile> persist_file;
+ result = url_locator.QueryInterface(&persist_file);
+ if (FAILED(result))
+ return std::wstring();
+
+ // Loads the Internet Shortcut from persistent storage.
+ result = persist_file->Load(file.c_str(), STGM_READ);
+ if (FAILED(result))
+ return std::wstring();
+
+ result = url_locator->GetURL(&url);
+ // GetURL can return S_FALSE (FAILED(S_FALSE) is false) when url == NULL.
+ if (FAILED(result) || (url == NULL))
+ return std::wstring();
+
+ return std::wstring(url);
+}
+
+int IEImporter::CurrentIEVersion() const {
+ static int version = -1;
+ if (version < 0) {
+ wchar_t buffer[128];
+ DWORD buffer_length = sizeof(buffer);
+ bool result = ReadFromRegistry(HKEY_LOCAL_MACHINE,
+ L"Software\\Microsoft\\Internet Explorer",
+ L"Version", buffer, &buffer_length);
+ version = (result ? _wtoi(buffer) : 0);
+ }
+ return version;
+}
+
diff --git a/chrome/browser/importer/ie_importer.h b/chrome/browser/importer/ie_importer.h
new file mode 100644
index 0000000..fce1f8e
--- /dev/null
+++ b/chrome/browser/importer/ie_importer.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_IMPORTER_IE_IMPORTER_H_
+#define CHROME_BROWSER_IMPORTER_IE_IMPORTER_H_
+
+#include "chrome/browser/importer/importer.h"
+
+class IEImporter : public Importer {
+ public:
+ IEImporter() {}
+ virtual ~IEImporter() {}
+
+ // Importer methods.
+ virtual void StartImport(ProfileInfo browser_info,
+ uint16 items,
+ ProfileWriter* writer,
+ ImporterHost* host);
+
+ private:
+ FRIEND_TEST(ImporterTest, IEImporter);
+
+ void ImportFavorites();
+ void ImportHistory();
+
+ // Import password for IE6 stored in protected storage.
+ void ImportPasswordsIE6();
+
+ // Import password for IE7 and IE8 stored in Storage2.
+ void ImportPasswordsIE7();
+
+ virtual void ImportSearchEngines();
+
+ // Import the homepage setting of IE. Note: IE supports multiple home pages,
+ // whereas Chrome doesn't, so we import only the one defined under the
+ // 'Start Page' registry key. We don't import if the homepage is set to the
+ // machine default.
+ void ImportHomepage();
+
+ // Resolves what's the .url file actually targets.
+ // Returns empty string if failed.
+ std::wstring ResolveInternetShortcut(const std::wstring& file);
+
+ // A struct hosts the information of IE Favorite folder.
+ struct FavoritesInfo {
+ std::wstring path;
+ std::wstring links_folder;
+ // The creation time of the user's profile folder.
+ Time profile_creation_time;
+ };
+ typedef std::vector<ProfileWriter::BookmarkEntry> BookmarkVector;
+
+ // Gets the information of Favorites folder. Returns true if successful.
+ bool GetFavoritesInfo(FavoritesInfo* info);
+
+ // This function will read the files in the Favorite folder, and store
+ // the bookmark items in |bookmarks|.
+ void ParseFavoritesFolder(const FavoritesInfo& info,
+ BookmarkVector* bookmarks);
+
+ // Determines which version of IE is in use.
+ int CurrentIEVersion() const;
+
+ // Hosts the writer used in this importer.
+ ProfileWriter* writer_;
+
+ // IE PStore subkey GUID: AutoComplete password & form data.
+ static const GUID kPStoreAutocompleteGUID;
+
+ // A fake GUID for unit test.
+ static const GUID kUnittestGUID;
+
+ // A struct hosts the information of AutoComplete data in PStore.
+ struct AutoCompleteInfo {
+ std::wstring key;
+ std::vector<std::wstring> data;
+ bool is_url;
+ };
+
+ // IE does not have source path. It's used in unit tests only for
+ // providing a fake source.
+ std::wstring source_path_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(IEImporter);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_IE_IMPORTER_H_
+
diff --git a/chrome/browser/importer/importer.cc b/chrome/browser/importer/importer.cc
new file mode 100644
index 0000000..45d317f
--- /dev/null
+++ b/chrome/browser/importer/importer.cc
@@ -0,0 +1,541 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/importer/importer.h"
+
+#include <map>
+
+#include "base/file_util.h"
+#include "base/gfx/image_operations.h"
+#include "base/gfx/png_encoder.h"
+#include "base/string_util.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/importer/firefox2_importer.h"
+#include "chrome/browser/importer/firefox3_importer.h"
+#include "chrome/browser/importer/firefox_importer_utils.h"
+#include "chrome/browser/importer/firefox_profile_lock.h"
+#include "chrome/browser/importer/ie_importer.h"
+#include "chrome/browser/template_url_model.h"
+#include "chrome/browser/shell_integration.h"
+#include "chrome/browser/webdata/web_data_service.h"
+#include "chrome/common/gfx/favicon_size.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/pref_service.h"
+#include "chrome/views/window.h"
+#include "webkit/glue/image_decoder.h"
+
+#include "generated_resources.h"
+
+// ProfileWriter.
+
+bool ProfileWriter::BookmarkModelIsLoaded() const {
+ return profile_->GetBookmarkModel()->IsLoaded();
+}
+
+void ProfileWriter::AddBookmarkModelObserver(BookmarkModelObserver* observer) {
+ profile_->GetBookmarkModel()->AddObserver(observer);
+}
+
+bool ProfileWriter::TemplateURLModelIsLoaded() const {
+ return profile_->GetTemplateURLModel()->loaded();
+}
+
+void ProfileWriter::AddTemplateURLModelObserver(
+ NotificationObserver* observer) {
+ TemplateURLModel* model = profile_->GetTemplateURLModel();
+ NotificationService::current()->AddObserver(
+ observer, TEMPLATE_URL_MODEL_LOADED,
+ Source<TemplateURLModel>(model));
+ model->Load();
+}
+
+void ProfileWriter::AddPasswordForm(const PasswordForm& form) {
+ profile_->GetWebDataService(Profile::EXPLICIT_ACCESS)->AddLogin(form);
+}
+
+void ProfileWriter::AddIE7PasswordInfo(const IE7PasswordInfo& info) {
+ profile_->GetWebDataService(Profile::EXPLICIT_ACCESS)->AddIE7Login(info);
+}
+
+void ProfileWriter::AddHistoryPage(const std::vector<history::URLRow>& page) {
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)->
+ AddPagesWithDetails(page);
+}
+
+void ProfileWriter::AddHomepage(const GURL& home_page) {
+ DCHECK(profile_);
+
+ PrefService* prefs = profile_->GetPrefs();
+ // NOTE: We set the kHomePage value, but keep the NewTab page as the homepage.
+ prefs->SetString(prefs::kHomePage, ASCIIToWide(home_page.spec()));
+ prefs->ScheduleSavePersistentPrefs(g_browser_process->file_thread());
+}
+
+void ProfileWriter::AddBookmarkEntry(
+ const std::vector<BookmarkEntry>& bookmark) {
+ BookmarkModel* model = profile_->GetBookmarkModel();
+ DCHECK(model->IsLoaded());
+
+ bool show_bookmark_toolbar = false;
+ std::set<BookmarkNode*> groups_added_to;
+ for (std::vector<BookmarkEntry>::const_iterator it = bookmark.begin();
+ it != bookmark.end(); ++it) {
+ // Don't insert this url if it exists in model or url is not valid.
+ if (model->GetNodeByURL(it->url) != NULL || !it->url.is_valid())
+ continue;
+
+ // Set up groups in BookmarkModel in such a way that path[i] is
+ // the subgroup of path[i-1]. Finally they construct a path in the
+ // model:
+ // path[0] \ path[1] \ ... \ path[size() - 1]
+ BookmarkNode* parent =
+ (it->in_toolbar ? model->GetBookmarkBarNode() : model->other_node());
+ for (std::vector<std::wstring>::const_iterator i = it->path.begin();
+ i != it->path.end(); ++i) {
+ BookmarkNode* child = NULL;
+ for (int index = 0; index < parent->GetChildCount(); ++index) {
+ BookmarkNode* node = parent->GetChild(index);
+ if ((node->GetType() == history::StarredEntry::BOOKMARK_BAR ||
+ node->GetType() == history::StarredEntry::USER_GROUP) &&
+ node->GetTitle() == *i) {
+ child = node;
+ break;
+ }
+ }
+ if (child == NULL)
+ child = model->AddGroup(parent, parent->GetChildCount(), *i);
+ parent = child;
+ }
+ groups_added_to.insert(parent);
+ model->AddURLWithCreationTime(parent, parent->GetChildCount(),
+ it->title, it->url, it->creation_time);
+
+ // If some items are put into toolbar, it looks like the user was using
+ // it in their last browser. We turn on the bookmarks toolbar.
+ if (it->in_toolbar)
+ show_bookmark_toolbar = true;
+ }
+
+ // Reset the date modified time of the groups we added to. We do this to
+ // make sure the 'recently added to' combobox in the bubble doesn't get random
+ // groups.
+ for (std::set<BookmarkNode*>::const_iterator i = groups_added_to.begin();
+ i != groups_added_to.end(); ++i) {
+ model->ResetDateGroupModified(*i);
+ }
+
+ if (show_bookmark_toolbar)
+ ShowBookmarkBar();
+}
+
+void ProfileWriter::AddFavicons(
+ const std::vector<history::ImportedFavIconUsage>& favicons) {
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)->
+ SetImportedFavicons(favicons);
+}
+
+typedef std::map<std::string, const TemplateURL*> HostPathMap;
+
+// Builds the key to use in HostPathMap for the specified TemplateURL. Returns
+// an empty string if a host+path can't be generated for the TemplateURL.
+// If an empty string is returned, it should not be added to HostPathMap.
+static std::string BuildHostPathKey(const TemplateURL* t_url) {
+ if (t_url->url() && t_url->url()->SupportsReplacement()) {
+ GURL search_url(t_url->url()->ReplaceSearchTerms(
+ *t_url, L"random string", TemplateURLRef::NO_SUGGESTIONS_AVAILABLE,
+ std::wstring()));
+ if (search_url.is_valid())
+ return search_url.host() + search_url.path();
+ }
+ return std::string();
+}
+
+// Builds a set that contains an entry of the host+path for each TemplateURL in
+// the TemplateURLModel that has a valid search url.
+static void BuildHostPathMap(const TemplateURLModel& model,
+ HostPathMap* host_path_map) {
+ std::vector<const TemplateURL*> template_urls = model.GetTemplateURLs();
+ for (size_t i = 0; i < template_urls.size(); ++i) {
+ const std::string host_path = BuildHostPathKey(template_urls[i]);
+ if (!host_path.empty()) {
+ const TemplateURL* existing_turl = (*host_path_map)[host_path];
+ if (!existing_turl ||
+ (template_urls[i]->show_in_default_list() &&
+ !existing_turl->show_in_default_list())) {
+ // If there are multiple TemplateURLs with the same host+path, favor
+ // those shown in the default list. If there are multiple potential
+ // defaults, favor the first one, which should be the more commonly used
+ // one.
+ (*host_path_map)[host_path] = template_urls[i];
+ }
+ } // else case, TemplateURL doesn't have a search url, doesn't support
+ // replacement, or doesn't have valid GURL. Ignore it.
+ }
+}
+
+void ProfileWriter::AddKeywords(const std::vector<TemplateURL*>& template_urls,
+ int default_keyword_index,
+ bool unique_on_host_and_path) {
+ TemplateURLModel* model = profile_->GetTemplateURLModel();
+ HostPathMap host_path_map;
+ if (unique_on_host_and_path)
+ BuildHostPathMap(*model, &host_path_map);
+
+ for (std::vector<TemplateURL*>::const_iterator i = template_urls.begin();
+ i != template_urls.end(); ++i) {
+ TemplateURL* t_url = *i;
+ bool default_keyword =
+ default_keyword_index >= 0 &&
+ (i - template_urls.begin() == default_keyword_index);
+
+ // TemplateURLModel requires keywords to be unique. If there is already a
+ // TemplateURL with this keyword, don't import it again.
+ const TemplateURL* turl_with_keyword =
+ model->GetTemplateURLForKeyword(t_url->keyword());
+ if (turl_with_keyword != NULL) {
+ if (default_keyword)
+ model->SetDefaultSearchProvider(turl_with_keyword);
+ delete t_url;
+ continue;
+ }
+
+ // For search engines if there is already a keyword with the same
+ // host+path, we don't import it. This is done to avoid both duplicate
+ // search providers (such as two Googles, or two Yahoos) as well as making
+ // sure the search engines we provide aren't replaced by those from the
+ // imported browser.
+ if (unique_on_host_and_path &&
+ host_path_map.find(BuildHostPathKey(t_url)) != host_path_map.end()) {
+ if (default_keyword) {
+ const TemplateURL* turl_with_host_path =
+ host_path_map[BuildHostPathKey(t_url)];
+ if (turl_with_host_path)
+ model->SetDefaultSearchProvider(turl_with_host_path);
+ else
+ NOTREACHED(); // BuildHostPathMap should only insert non-null values.
+ }
+ delete t_url;
+ continue;
+ }
+ model->Add(t_url);
+ if (default_keyword)
+ model->SetDefaultSearchProvider(t_url);
+ }
+}
+
+void ProfileWriter::ShowBookmarkBar() {
+ DCHECK(profile_);
+
+ PrefService* prefs = profile_->GetPrefs();
+ // Check whether the bookmark bar is shown in current pref.
+ if (!prefs->GetBoolean(prefs::kShowBookmarkBar)) {
+ // Set the pref and notify the notification service.
+ prefs->SetBoolean(prefs::kShowBookmarkBar, true);
+ prefs->ScheduleSavePersistentPrefs(g_browser_process->file_thread());
+ Source<Profile> source(profile_);
+ NotificationService::current()->Notify(
+ NOTIFY_BOOKMARK_BAR_VISIBILITY_PREF_CHANGED, source,
+ NotificationService::NoDetails());
+ }
+}
+
+// Importer.
+
+// static
+bool Importer::ReencodeFavicon(const unsigned char* src_data, size_t src_len,
+ std::vector<unsigned char>* png_data) {
+ // Decode the favicon using WebKit's image decoder.
+ webkit_glue::ImageDecoder decoder(gfx::Size(kFavIconSize, kFavIconSize));
+ SkBitmap decoded = decoder.Decode(src_data, src_len);
+ if (decoded.empty())
+ return false; // Unable to decode.
+
+ if (decoded.width() != kFavIconSize || decoded.height() != kFavIconSize) {
+ // The bitmap is not the correct size, re-sample.
+ int new_width = decoded.width();
+ int new_height = decoded.height();
+ calc_favicon_target_size(&new_width, &new_height);
+ decoded = gfx::ImageOperations::Resize(
+ decoded, gfx::ImageOperations::RESIZE_LANCZOS3,
+ gfx::Size(new_width, new_height));
+ }
+
+ // Encode our bitmap as a PNG.
+ SkAutoLockPixels decoded_lock(decoded);
+ PNGEncoder::Encode(reinterpret_cast<unsigned char*>(decoded.getPixels()),
+ PNGEncoder::FORMAT_BGRA, decoded.width(),
+ decoded.height(), decoded.width() * 4, false, png_data);
+ return true;
+}
+
+// ImporterHost.
+
+ImporterHost::ImporterHost()
+ : observer_(NULL),
+ task_(NULL),
+ importer_(NULL),
+ file_loop_(g_browser_process->file_thread()->message_loop()),
+ waiting_for_bookmarkbar_model_(false),
+ waiting_for_template_url_model_(false),
+ is_source_readable_(true) {
+ DetectSourceProfiles();
+}
+
+ImporterHost::ImporterHost(MessageLoop* file_loop)
+ : observer_(NULL),
+ task_(NULL),
+ importer_(NULL),
+ file_loop_(file_loop),
+ waiting_for_bookmarkbar_model_(false),
+ waiting_for_template_url_model_(false),
+ is_source_readable_(true) {
+ DetectSourceProfiles();
+}
+
+ImporterHost::~ImporterHost() {
+ STLDeleteContainerPointers(source_profiles_.begin(), source_profiles_.end());
+}
+
+void ImporterHost::Loaded(BookmarkModel* model) {
+ model->RemoveObserver(this);
+ waiting_for_bookmarkbar_model_ = false;
+ InvokeTaskIfDone();
+}
+
+void ImporterHost::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == TEMPLATE_URL_MODEL_LOADED);
+ TemplateURLModel* model = Source<TemplateURLModel>(source).ptr();
+ NotificationService::current()->RemoveObserver(
+ this, TEMPLATE_URL_MODEL_LOADED,
+ Source<TemplateURLModel>(model));
+ waiting_for_template_url_model_ = false;
+ InvokeTaskIfDone();
+}
+
+void ImporterHost::ShowWarningDialog() {
+ ChromeViews::Window::CreateChromeWindow(GetActiveWindow(), gfx::Rect(),
+ new ImporterLockView(this))->Show();
+}
+
+void ImporterHost::OnLockViewEnd(bool is_continue) {
+ if (is_continue) {
+ // User chose to continue, then we check the lock again to make
+ // sure that Firefox has been closed. Try to import the settings
+ // if successful. Otherwise, show a warning dialog.
+ firefox_lock_->Lock();
+ if (firefox_lock_->HasAcquired()) {
+ is_source_readable_ = true;
+ InvokeTaskIfDone();
+ } else {
+ ShowWarningDialog();
+ }
+ } else {
+ // User chose to skip the import process. We should delete
+ // the task and notify the ImporterHost to finish.
+ delete task_;
+ task_ = NULL;
+ importer_ = NULL;
+ ImportEnded();
+ }
+}
+
+void ImporterHost::StartImportSettings(const ProfileInfo& profile_info,
+ uint16 items,
+ ProfileWriter* writer,
+ bool first_run) {
+ // Preserves the observer and creates a task, since we do async import
+ // so that it doesn't block the UI. When the import is complete, observer
+ // will be notified.
+ writer_ = writer;
+ importer_ = CreateImporterByType(profile_info.browser_type);
+ importer_->set_first_run(first_run);
+ task_ = NewRunnableMethod(importer_, &Importer::StartImport,
+ profile_info, items, writer_.get(), this);
+
+ // We should lock the Firefox profile directory to prevent corruption.
+ if (profile_info.browser_type == FIREFOX2 ||
+ profile_info.browser_type == FIREFOX3) {
+ firefox_lock_.reset(new FirefoxProfileLock(profile_info.source_path));
+ if (!firefox_lock_->HasAcquired()) {
+ // If fail to acquire the lock, we set the source unreadable and
+ // show a warning dialog.
+ is_source_readable_ = false;
+ ShowWarningDialog();
+ }
+ }
+
+ // BookmarkModel should be loaded before adding IE favorites. So we observe
+ // the BookmarkModel if needed, and start the task after it has been loaded.
+ if ((items & FAVORITES) && !writer_->BookmarkModelIsLoaded()) {
+ writer_->AddBookmarkModelObserver(this);
+ waiting_for_bookmarkbar_model_ = true;
+ }
+
+ // Observes the TemplateURLModel if needed to import search engines from the
+ // other browser. We also check to see if we're importing bookmarks because
+ // we can import bookmark keywords from Firefox as search engines.
+ if ((items & SEARCH_ENGINES) || (items & FAVORITES)) {
+ if (!writer_->TemplateURLModelIsLoaded()) {
+ writer_->AddTemplateURLModelObserver(this);
+ waiting_for_template_url_model_ = true;
+ }
+ }
+
+ AddRef();
+ InvokeTaskIfDone();
+}
+
+void ImporterHost::Cancel() {
+ if (importer_)
+ importer_->Cancel();
+}
+
+void ImporterHost::SetObserver(Observer* observer) {
+ observer_ = observer;
+}
+
+void ImporterHost::InvokeTaskIfDone() {
+ if (waiting_for_bookmarkbar_model_ || waiting_for_template_url_model_ ||
+ !is_source_readable_)
+ return;
+ file_loop_->PostTask(FROM_HERE, task_);
+}
+
+void ImporterHost::ImportItemStarted(ImportItem item) {
+ if (observer_)
+ observer_->ImportItemStarted(item);
+}
+
+void ImporterHost::ImportItemEnded(ImportItem item) {
+ if (observer_)
+ observer_->ImportItemEnded(item);
+}
+
+void ImporterHost::ImportStarted() {
+ if (observer_)
+ observer_->ImportStarted();
+}
+
+void ImporterHost::ImportEnded() {
+ firefox_lock_.reset(); // Release the Firefox profile lock.
+ if (observer_)
+ observer_->ImportEnded();
+ Release();
+}
+
+Importer* ImporterHost::CreateImporterByType(ProfileType type) {
+ switch (type) {
+ case MS_IE:
+ return new IEImporter();
+ case FIREFOX2:
+ return new Firefox2Importer();
+ case FIREFOX3:
+ return new Firefox3Importer();
+ }
+ NOTREACHED();
+ return NULL;
+}
+
+int ImporterHost::GetAvailableProfileCount() {
+ return static_cast<int>(source_profiles_.size());
+}
+
+std::wstring ImporterHost::GetSourceProfileNameAt(int index) const {
+ DCHECK(index < static_cast<int>(source_profiles_.size()));
+ return source_profiles_[index]->description;
+}
+
+const ProfileInfo& ImporterHost::GetSourceProfileInfoAt(int index) const {
+ DCHECK(index < static_cast<int>(source_profiles_.size()));
+ return *source_profiles_[index];
+}
+
+void ImporterHost::DetectSourceProfiles() {
+ if (ShellIntegration::IsFirefoxDefaultBrowser()) {
+ DetectFirefoxProfiles();
+ DetectIEProfiles();
+ } else {
+ DetectIEProfiles();
+ DetectFirefoxProfiles();
+ }
+}
+
+void ImporterHost::DetectIEProfiles() {
+ // IE always exists and don't have multiple profiles.
+ ProfileInfo* ie = new ProfileInfo();
+ ie->description = l10n_util::GetString(IDS_IMPORT_FROM_IE);
+ ie->browser_type = MS_IE;
+ ie->source_path.clear();
+ ie->app_path.clear();
+ source_profiles_.push_back(ie);
+}
+
+void ImporterHost::DetectFirefoxProfiles() {
+ // Detects which version of Firefox is installed.
+ int version = GetCurrentFirefoxMajorVersion();
+ ProfileType firefox_type;
+ if (version == 2) {
+ firefox_type = FIREFOX2;
+ } else if (version == 3) {
+ firefox_type = FIREFOX3;
+ } else {
+ // Ignores other versions of firefox.
+ return;
+ }
+
+ std::wstring ini_file = GetProfilesINI();
+ DictionaryValue root;
+ ParseProfileINI(ini_file, &root);
+
+ std::wstring source_path;
+ for (int i = 0; ; ++i) {
+ std::wstring current_profile = L"Profile" + IntToWString(i);
+ if (!root.HasKey(current_profile)) {
+ // Profiles are continuously numbered. So we exit when we can't
+ // find the i-th one.
+ break;
+ }
+ std::wstring is_relative, path, profile_path;
+ if (root.GetString(current_profile + L".IsRelative", &is_relative) &&
+ root.GetString(current_profile + L".Path", &path)) {
+ ReplaceSubstringsAfterOffset(&path, 0, L"/", L"\\");
+
+ // IsRelative=1 means the folder path would be relative to the
+ // path of profiles.ini. IsRelative=0 refers to a custom profile
+ // location.
+ if (is_relative == L"1") {
+ profile_path = file_util::GetDirectoryFromPath(ini_file);
+ file_util::AppendToPath(&profile_path, path);
+ } else {
+ profile_path = path;
+ }
+
+ // We only import the default profile when multiple profiles exist,
+ // since the other profiles are used mostly by developers for testing.
+ // Otherwise, Profile0 will be imported.
+ std::wstring is_default;
+ if ((root.GetString(current_profile + L".Default", &is_default) &&
+ is_default == L"1") || i == 0) {
+ source_path = profile_path;
+ // We break out of the loop when we have found the default profile.
+ if (is_default == L"1")
+ break;
+ }
+ }
+ }
+
+ if (!source_path.empty()) {
+ ProfileInfo* firefox = new ProfileInfo();
+ firefox->description = l10n_util::GetString(IDS_IMPORT_FROM_FIREFOX);
+ firefox->browser_type = firefox_type;
+ firefox->source_path = source_path;
+ firefox->app_path = GetFirefoxInstallPath();
+ source_profiles_.push_back(firefox);
+ }
+}
+
diff --git a/chrome/browser/importer/importer.h b/chrome/browser/importer/importer.h
new file mode 100644
index 0000000..37be870
--- /dev/null
+++ b/chrome/browser/importer/importer.h
@@ -0,0 +1,357 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_IMPORTER_IMPORTER_H_
+#define CHROME_BROWSER_IMPORTER_IMPORTER_H_
+
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/message_loop.h"
+#include "base/ref_counted.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/ie7_password.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/template_url.h"
+#include "chrome/browser/views/importer_lock_view.h"
+#include "chrome/common/notification_service.h"
+#include "googleurl/src/gurl.h"
+#include "webkit/glue/password_form.h"
+
+// An enumeration of the type of browsers that we support to import
+// settings and data from them.
+enum ProfileType {
+ MS_IE = 0,
+ FIREFOX2,
+ FIREFOX3
+};
+
+// An enumeration of the type of data we want to import.
+enum ImportItem {
+ NONE = 0x0000,
+ HISTORY = 0x0001,
+ FAVORITES = 0x0002,
+ COOKIES = 0x0004, // not supported yet.
+ PASSWORDS = 0x0008,
+ SEARCH_ENGINES = 0x0010,
+ HOME_PAGE = 0x0020,
+};
+
+typedef struct {
+ std::wstring description;
+ ProfileType browser_type;
+ std::wstring source_path;
+ std::wstring app_path;
+} ProfileInfo;
+
+class FirefoxProfileLock;
+class Importer;
+
+// ProfileWriter encapsulates profile for writing entries into it.
+// This object must be invoked on UI thread.
+class ProfileWriter : public base::RefCounted<ProfileWriter> {
+ public:
+ explicit ProfileWriter(Profile* profile) : profile_(profile) { }
+ virtual ~ProfileWriter() { }
+
+ // Methods for monitoring BookmarkModel status.
+ virtual bool BookmarkModelIsLoaded() const;
+ virtual void AddBookmarkModelObserver(
+ BookmarkModelObserver* observer);
+
+ // Methods for monitoring TemplateURLModel status.
+ virtual bool TemplateURLModelIsLoaded() const;
+ virtual void AddTemplateURLModelObserver(
+ NotificationObserver* observer);
+
+ // A bookmark entry.
+ struct BookmarkEntry {
+ bool in_toolbar;
+ GURL url;
+ std::vector<std::wstring> path;
+ std::wstring title;
+ Time creation_time;
+
+ BookmarkEntry() : in_toolbar(false) {}
+ };
+
+ // Helper methods for adding data to local stores.
+ virtual void AddPasswordForm(const PasswordForm& form);
+ virtual void AddIE7PasswordInfo(const IE7PasswordInfo& info);
+ virtual void AddHistoryPage(const std::vector<history::URLRow>& page);
+ virtual void AddHomepage(const GURL& homepage);
+ virtual void AddBookmarkEntry(const std::vector<BookmarkEntry>& bookmark);
+ virtual void AddFavicons(
+ const std::vector<history::ImportedFavIconUsage>& favicons);
+ // Add the TemplateURLs in |template_urls| to the local store and make the
+ // TemplateURL at |default_keyword_index| the default keyword (does not set
+ // a default keyword if it is -1). The local store becomes the owner of the
+ // TemplateURLs. Some TemplateURLs in |template_urls| may conflict (same
+ // keyword or same host name in the URL) with existing TemplateURLs in the
+ // local store, in which case the existing ones takes precedence and the
+ // duplicate in |template_urls| are deleted.
+ // If unique_on_host_and_path a TemplateURL is only added if there is not an
+ // existing TemplateURL that has a replaceable search url with the same
+ // host+path combination.
+ virtual void AddKeywords(const std::vector<TemplateURL*>& template_urls,
+ int default_keyword_index,
+ bool unique_on_host_and_path);
+
+ // Shows the bookmarks toolbar.
+ void ShowBookmarkBar();
+
+ private:
+ Profile* profile_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ProfileWriter);
+};
+
+// This class hosts the importers. It enumerates profiles from other
+// browsers dynamically, and controls the process of importing. When
+// the import process is done, ImporterHost deletes itself.
+class ImporterHost : public base::RefCounted<ImporterHost>,
+ public BookmarkModelObserver,
+ public NotificationObserver {
+ public:
+ ImporterHost();
+ ~ImporterHost();
+
+ // This constructor only be used by unit-tests, where file thread does not
+ // exist.
+ explicit ImporterHost(MessageLoop* file_loop);
+
+ // BookmarkModelObserver methods.
+ virtual void Loaded(BookmarkModel* model);
+ virtual void BookmarkNodeMoved(BookmarkModel* model,
+ BookmarkNode* old_parent,
+ int old_index,
+ BookmarkNode* new_parent,
+ int new_index) {}
+ virtual void BookmarkNodeAdded(BookmarkModel* model,
+ BookmarkNode* parent,
+ int index) {}
+ virtual void BookmarkNodeRemoved(BookmarkModel* model,
+ BookmarkNode* parent,
+ int index) {}
+ virtual void BookmarkNodeChanged(BookmarkModel* model,
+ BookmarkNode* node) {}
+ virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model,
+ BookmarkNode* node) {}
+
+ // NotificationObserver method. Called when TemplateURLModel has been loaded.
+ void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ // ShowWarningDialog() asks user to close the application that is owning the
+ // lock. They can retry or skip the importing process.
+ void ShowWarningDialog();
+
+ // OnLockViewEnd() is called when user end the dialog by clicking a push
+ // button. |is_continue| is true when user clicked the "Continue" button.
+ void OnLockViewEnd(bool is_continue);
+
+ // Starts the process of importing the settings and data depending
+ // on what the user selected.
+ void StartImportSettings(const ProfileInfo& profile_info,
+ uint16 items,
+ ProfileWriter* writer,
+ bool first_run);
+
+ // Cancel
+ void Cancel();
+
+ // An interface which an object can implement to be notified of events during
+ // the import process.
+ class Observer {
+ public:
+ virtual ~Observer() {}
+ // Invoked when data for the specified item is about to be collected.
+ virtual void ImportItemStarted(ImportItem item) = 0;
+
+ // Invoked when data for the specified item has been collected from the
+ // source profile and is now ready for further processing.
+ virtual void ImportItemEnded(ImportItem item) = 0;
+
+ // Invoked when the import begins.
+ virtual void ImportStarted() = 0;
+
+ // Invoked when the source profile has been imported.
+ virtual void ImportEnded() = 0;
+ };
+ void SetObserver(Observer* observer);
+
+ // A series of functions invoked at the start, during and end of the end
+ // of the import process. The middle functions are notifications that the
+ // harvesting of a particular source of data (specified by |item|) is under
+ // way.
+ void ImportStarted();
+ void ImportItemStarted(ImportItem item);
+ void ImportItemEnded(ImportItem item);
+ void ImportEnded();
+
+ Importer* CreateImporterByType(ProfileType type);
+
+ // Returns the number of different browser profiles you can import from.
+ int GetAvailableProfileCount();
+
+ // Returns the name of the profile at the 'index' slot. The profiles are
+ // ordered such that the profile at index 0 is the likely default browser.
+ std::wstring GetSourceProfileNameAt(int index) const;
+
+ // Returns the ProfileInfo at the specified index. The ProfileInfo should be
+ // passed to StartImportSettings().
+ const ProfileInfo& GetSourceProfileInfoAt(int index) const;
+
+ private:
+ // If we're not waiting on any model to finish loading, invokes the task_.
+ void InvokeTaskIfDone();
+
+ // Detects the installed browsers and their associated profiles, then
+ // stores their information in a list. It returns the list of description
+ // of all profiles.
+ void DetectSourceProfiles();
+
+ // Helper methods for detecting available profiles.
+ void DetectIEProfiles();
+ void DetectFirefoxProfiles();
+
+ // The list of profiles with the default one first.
+ std::vector<ProfileInfo*> source_profiles_;
+
+ Observer* observer_;
+ scoped_refptr<ProfileWriter> writer_;
+
+ // The task is the process of importing settings from other browsers.
+ Task* task_;
+
+ // The importer used in the task;
+ Importer* importer_;
+
+ // The message loop for reading the source profiles.
+ MessageLoop* file_loop_;
+
+ // True if we're waiting for the model to finish loading.
+ bool waiting_for_bookmarkbar_model_;
+ bool waiting_for_template_url_model_;
+
+ // True if source profile is readable.
+ bool is_source_readable_;
+
+ // Firefox profile lock.
+ scoped_ptr<FirefoxProfileLock> firefox_lock_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ImporterHost);
+};
+
+// The base class of all importers.
+class Importer : public base::RefCounted<Importer> {
+ public:
+ virtual ~Importer() { }
+
+ // All importers should implement this method by adding their
+ // import logic. And it will be run in file thread by ImporterHost.
+ //
+ // Since we do async import, the importer should invoke
+ // ImporterHost::Finished() to notify its host that import
+ // stuff have been finished.
+ virtual void StartImport(ProfileInfo profile_info,
+ uint16 items,
+ ProfileWriter* writer,
+ ImporterHost* host) = 0;
+
+ // Cancels the import process.
+ void Cancel() { cancelled_ = true; }
+
+ void set_first_run(bool first_run) { first_run_ = first_run; }
+
+ protected:
+ Importer()
+ : main_loop_(MessageLoop::current()),
+ importer_host_(NULL),
+ cancelled_(false) {}
+
+ // Notifies the coordinator that the collection of data for the specified
+ // item has begun.
+ void NotifyItemStarted(ImportItem item) {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(importer_host_,
+ &ImporterHost::ImportItemStarted, item));
+ }
+
+ // Notifies the coordinator that the collection of data for the specified
+ // item has completed.
+ void NotifyItemEnded(ImportItem item) {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(importer_host_,
+ &ImporterHost::ImportItemEnded, item));
+ }
+
+ // Notifies the coordinator that the import operation has begun.
+ void NotifyStarted() {
+ main_loop_->PostTask(FROM_HERE, NewRunnableMethod(importer_host_,
+ &ImporterHost::ImportStarted));
+ }
+
+ // Notifies the coordinator that the entire import operation has completed.
+ void NotifyEnded() {
+ main_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(importer_host_, &ImporterHost::ImportEnded));
+ }
+
+ // Given raw image data, decodes the icon, re-sampling to the correct size as
+ // necessary, and re-encodes as PNG data in the given output vector. Returns
+ // true on success.
+ static bool ReencodeFavicon(const unsigned char* src_data, size_t src_len,
+ std::vector<unsigned char>* png_data);
+
+ bool cancelled() const { return cancelled_; }
+
+ bool first_run() const { return first_run_; }
+
+ // The importer should know the main thread so that ProfileWriter
+ // will be invoked in thread instead.
+ MessageLoop* main_loop_;
+
+ // The coordinator host for this importer.
+ ImporterHost* importer_host_;
+
+ private:
+ // True if the caller cancels the import process.
+ bool cancelled_;
+
+ // True if the importer is created in the first run UI.
+ bool first_run_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Importer);
+};
+
+// An interface an object that calls StartImportingWithUI can call to be
+// notified about the state of the import operation.
+class ImportObserver {
+ public:
+ virtual ~ImportObserver() {}
+ // The import operation was canceled by the user.
+ virtual void ImportCanceled() = 0;
+
+ // The import operation was completed successfully.
+ virtual void ImportComplete() = 0;
+};
+
+
+// Shows a UI for importing and begins importing the specified items from
+// source_profile to target_profile. observer is notified when the process is
+// complete, can be NULL. parent is the window to parent the UI to, can be NULL
+// if there's nothing to parent to. first_run is true if it's invoked in the
+// first run UI.
+void StartImportingWithUI(HWND parent_window,
+ int16 items,
+ ImporterHost* coordinator,
+ const ProfileInfo& source_profile,
+ Profile* target_profile,
+ ImportObserver* observer,
+ bool first_run);
+
+#endif // CHROME_BROWSER_IMPORTER_IMPORTER_H_
+
diff --git a/chrome/browser/importer/importer_unittest.cc b/chrome/browser/importer/importer_unittest.cc
new file mode 100644
index 0000000..e7f097e
--- /dev/null
+++ b/chrome/browser/importer/importer_unittest.cc
@@ -0,0 +1,819 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include <windows.h>
+#include <unknwn.h>
+#include <intshcut.h>
+#include <pstore.h>
+#include <urlhist.h>
+#include <shlguid.h>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "chrome/browser/ie7_password.h"
+#include "chrome/browser/importer/ie_importer.h"
+#include "chrome/browser/importer/importer.h"
+#include "chrome/browser/profile.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/env_util.h"
+#include "chrome/common/win_util.h"
+
+class ImporterTest : public testing::Test {
+ public:
+ protected:
+ virtual void SetUp() {
+ // Creates a new profile in a new subdirectory in the temp directory.
+ ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &test_path_));
+ file_util::AppendToPath(&test_path_, L"ImporterTest");
+ file_util::Delete(test_path_, true);
+ CreateDirectory(test_path_.c_str(), NULL);
+ profile_path_ = test_path_;
+ file_util::AppendToPath(&profile_path_, L"profile");
+ CreateDirectory(profile_path_.c_str(), NULL);
+ app_path_ = test_path_;
+ file_util::AppendToPath(&app_path_, L"app");
+ CreateDirectory(app_path_.c_str(), NULL);
+ }
+
+ virtual void TearDown() {
+ // Deletes the profile and cleans up the profile directory.
+ ASSERT_TRUE(file_util::Delete(test_path_, true));
+ ASSERT_FALSE(file_util::PathExists(test_path_));
+ }
+
+ MessageLoopForUI message_loop_;
+ std::wstring test_path_;
+ std::wstring profile_path_;
+ std::wstring app_path_;
+};
+
+const int kMaxPathSize = 5;
+
+typedef struct {
+ const bool in_toolbar;
+ const int path_size;
+ const wchar_t* path[kMaxPathSize];
+ const wchar_t* title;
+ const char* url;
+} BookmarkList;
+
+typedef struct {
+ const char* origin;
+ const char* action;
+ const char* realm;
+ const wchar_t* username_element;
+ const wchar_t* username;
+ const wchar_t* password_element;
+ const wchar_t* password;
+ bool blacklisted;
+} PasswordList;
+
+static const BookmarkList kIEBookmarks[] = {
+ {true, 0, {},
+ L"TheLink",
+ "http://www.links-thelink.com/"},
+ {true, 1, {L"SubFolderOfLinks"},
+ L"SubLink",
+ "http://www.links-sublink.com/"},
+ {false, 0, {},
+ L"Google Home Page",
+ "http://www.google.com/"},
+ {false, 0, {},
+ L"TheLink",
+ "http://www.links-thelink.com/"},
+ {false, 1, {L"SubFolder"},
+ L"Title",
+ "http://www.link.com/"},
+ {false, 0, {},
+ L"WithPortAndQuery",
+ "http://host:8080/cgi?q=query"},
+ {false, 1, {L"a"},
+ L"\x4E2D\x6587",
+ "http://chinese-title-favorite/"},
+};
+
+static const wchar_t* kIEIdentifyUrl =
+ L"http://A79029D6-753E-4e27-B807-3D46AB1545DF.com:8080/path?key=value";
+static const wchar_t* kIEIdentifyTitle =
+ L"Unittest GUID";
+
+bool IsWindowsVista() {
+ OSVERSIONINFO info = {0};
+ info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ GetVersionEx(&info);
+ return (info.dwMajorVersion >=6);
+}
+
+// Returns true if the |entry| is in the |list|.
+bool FindBookmarkEntry(const ProfileWriter::BookmarkEntry& entry,
+ const BookmarkList* list, int list_size) {
+ for (int i = 0; i < list_size; ++i)
+ if (list[i].in_toolbar == entry.in_toolbar &&
+ list[i].path_size == entry.path.size() &&
+ list[i].url == entry.url.spec() &&
+ list[i].title == entry.title) {
+ bool equal = true;
+ for (int k = 0; k < list[i].path_size; ++k)
+ if (list[i].path[k] != entry.path[k]) {
+ equal = false;
+ break;
+ }
+
+ if (equal)
+ return true;
+ }
+ return false;
+}
+
+class TestObserver : public ProfileWriter,
+ public ImporterHost::Observer {
+ public:
+ TestObserver() : ProfileWriter(NULL) {
+ bookmark_count_ = 0;
+ history_count_ = 0;
+ password_count_ = 0;
+ }
+
+ virtual void ImportItemStarted(ImportItem item) {}
+ virtual void ImportItemEnded(ImportItem item) {}
+ virtual void ImportStarted() {}
+ virtual void ImportEnded() {
+ MessageLoop::current()->Quit();
+ EXPECT_EQ(arraysize(kIEBookmarks), bookmark_count_);
+ EXPECT_EQ(1, history_count_);
+ if (IsWindowsVista())
+ EXPECT_EQ(0, password_count_);
+ else
+ EXPECT_EQ(1, password_count_);
+ }
+
+ virtual bool BookmarkModelIsLoaded() const {
+ // Profile is ready for writing.
+ return true;
+ }
+
+ virtual void AddBookmarkModelObserver(BookmarkModelObserver* observer) {
+ NOTREACHED();
+ }
+
+ virtual bool TemplateURLModelIsLoaded() const {
+ return true;
+ }
+
+ virtual void AddTemplateURLModelObserver(NotificationObserver* observer) {
+ NOTREACHED();
+ }
+
+ virtual void AddPasswordForm(const PasswordForm& form) {
+ // Importer should obtain this password form only.
+ EXPECT_EQ(GURL("http://localhost:8080/security/index.htm"), form.origin);
+ EXPECT_EQ("http://localhost:8080/", form.signon_realm);
+ EXPECT_EQ(L"user", form.username_element);
+ EXPECT_EQ(L"1", form.username_value);
+ EXPECT_EQ(L"", form.password_element);
+ EXPECT_EQ(L"2", form.password_value);
+ EXPECT_EQ("", form.action.spec());
+ ++password_count_;
+ }
+
+ virtual void AddHistoryPage(const std::vector<history::URLRow>& page) {
+ // Importer should read the specified URL.
+ for (size_t i = 0; i < page.size(); ++i)
+ if (page[i].title() == kIEIdentifyTitle &&
+ page[i].url() == GURL(kIEIdentifyUrl))
+ ++history_count_;
+ }
+
+ virtual void AddBookmarkEntry(const std::vector<BookmarkEntry>& bookmark) {
+ // Importer should import the IE Favorites folder the same as the list.
+ for (size_t i = 0; i < bookmark.size(); ++i) {
+ if (FindBookmarkEntry(bookmark[i], kIEBookmarks,
+ arraysize(kIEBookmarks)))
+ ++bookmark_count_;
+ }
+ }
+
+ virtual void AddKeyword(std::vector<TemplateURL*> template_url,
+ int default_keyword_index) {
+ // TODO(jcampan): bug 1169230: we should test keyword importing for IE.
+ // In order to do that we'll probably need to mock the Windows registry.
+ NOTREACHED();
+ STLDeleteContainerPointers(template_url.begin(), template_url.end());
+ }
+
+ private:
+ int bookmark_count_;
+ int history_count_;
+ int password_count_;
+};
+
+bool CreateUrlFile(std::wstring file, std::wstring url) {
+ CComPtr<IUniformResourceLocator> locator;
+ HRESULT result = locator.CoCreateInstance(CLSID_InternetShortcut, NULL,
+ CLSCTX_INPROC_SERVER);
+ if (FAILED(result))
+ return false;
+ CComPtr<IPersistFile> persist_file;
+ result = locator->QueryInterface(IID_IPersistFile,
+ reinterpret_cast<void**>(&persist_file));
+ if (FAILED(result))
+ return false;
+ result = locator->SetURL(url.c_str(), 0);
+ if (FAILED(result))
+ return false;
+ result = persist_file->Save(file.c_str(), TRUE);
+ if (FAILED(result))
+ return false;
+ return true;
+}
+
+void ClearPStoreType(IPStore* pstore, const GUID* type, const GUID* subtype) {
+ CComPtr<IEnumPStoreItems> item;
+ HRESULT result = pstore->EnumItems(0, type, subtype, 0, &item);
+ if (result == PST_E_OK) {
+ wchar_t* item_name;
+ while (SUCCEEDED(item->Next(1, &item_name, 0))) {
+ pstore->DeleteItem(0, type, subtype, item_name, NULL, 0);
+ CoTaskMemFree(item_name);
+ }
+ }
+ pstore->DeleteSubtype(0, type, subtype, 0);
+ pstore->DeleteType(0, type, 0);
+}
+
+void WritePStore(IPStore* pstore, const GUID* type, const GUID* subtype) {
+ struct PStoreItem {
+ wchar_t* name;
+ int data_size;
+ char* data;
+ } items[] = {
+ {L"http://localhost:8080/security/index.htm#ref:StringData", 8,
+ "\x31\x00\x00\x00\x32\x00\x00\x00"},
+ {L"http://localhost:8080/security/index.htm#ref:StringIndex", 20,
+ "\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00"
+ "\x00\x00\x2f\x00\x74\x00\x01\x00\x00\x00"},
+ {L"user:StringData", 4,
+ "\x31\x00\x00\x00"},
+ {L"user:StringIndex", 20,
+ "\x57\x49\x43\x4b\x18\x00\x00\x00\x01\x00"
+ "\x00\x00\x2f\x00\x74\x00\x00\x00\x00\x00"},
+ };
+
+ for (int i = 0; i < arraysize(items); ++i) {
+ HRESULT res = pstore->WriteItem(0, type, subtype, items[i].name,
+ items[i].data_size, reinterpret_cast<BYTE*>(items[i].data),
+ NULL, 0, 0);
+ ASSERT_TRUE(res == PST_E_OK);
+ }
+}
+
+TEST_F(ImporterTest, IEImporter) {
+ // Skips in Win2000 for the running environment can not be set up.
+ if (env_util::GetOperatingSystemVersion() == "5.0")
+ return;
+
+ // Sets up a favorites folder.
+ win_util::ScopedCOMInitializer com_init;
+ std::wstring path = test_path_;
+ file_util::AppendToPath(&path, L"Favorites");
+ CreateDirectory(path.c_str(), NULL);
+ CreateDirectory((path + L"\\SubFolder").c_str(), NULL);
+ CreateDirectory((path + L"\\Links").c_str(), NULL);
+ CreateDirectory((path + L"\\Links\\SubFolderOfLinks").c_str(), NULL);
+ CreateDirectory((path + L"\\\x0061").c_str(), NULL);
+ ASSERT_TRUE(CreateUrlFile(path + L"\\Google Home Page.url",
+ L"http://www.google.com/"));
+ ASSERT_TRUE(CreateUrlFile(path + L"\\SubFolder\\Title.url",
+ L"http://www.link.com/"));
+ ASSERT_TRUE(CreateUrlFile(path + L"\\TheLink.url",
+ L"http://www.links-thelink.com/"));
+ ASSERT_TRUE(CreateUrlFile(path + L"\\WithPortAndQuery.url",
+ L"http://host:8080/cgi?q=query"));
+ ASSERT_TRUE(CreateUrlFile(path + L"\\\x0061\\\x4E2D\x6587.url",
+ L"http://chinese-title-favorite/"));
+ ASSERT_TRUE(CreateUrlFile(path + L"\\Links\\TheLink.url",
+ L"http://www.links-thelink.com/"));
+ ASSERT_TRUE(CreateUrlFile(path + L"\\Links\\SubFolderOfLinks\\SubLink.url",
+ L"http://www.links-sublink.com/"));
+ file_util::WriteFile(path + L"\\InvalidUrlFile.url", "x", 1);
+ file_util::WriteFile(path + L"\\PlainTextFile.txt", "x", 1);
+
+ // Sets up dummy password data.
+ HRESULT res;
+ CComPtr<IPStore> pstore;
+ HMODULE pstorec_dll;
+ GUID type = IEImporter::kUnittestGUID;
+ GUID subtype = IEImporter::kUnittestGUID;
+ // PStore is read-only in Windows Vista.
+ if (!IsWindowsVista()) {
+ typedef HRESULT (WINAPI *PStoreCreateFunc)(IPStore**, DWORD, DWORD, DWORD);
+ pstorec_dll = LoadLibrary(L"pstorec.dll");
+ PStoreCreateFunc PStoreCreateInstance =
+ (PStoreCreateFunc)GetProcAddress(pstorec_dll, "PStoreCreateInstance");
+ res = PStoreCreateInstance(&pstore, 0, 0, 0);
+ ASSERT_TRUE(res == S_OK);
+ ClearPStoreType(pstore, &type, &subtype);
+ PST_TYPEINFO type_info;
+ type_info.szDisplayName = L"TestType";
+ type_info.cbSize = 8;
+ pstore->CreateType(0, &type, &type_info, 0);
+ pstore->CreateSubtype(0, &type, &subtype, &type_info, NULL, 0);
+ WritePStore(pstore, &type, &subtype);
+ }
+
+ // Sets up a special history link.
+ CComPtr<IUrlHistoryStg2> url_history_stg2;
+ res = url_history_stg2.CoCreateInstance(CLSID_CUrlHistory, NULL,
+ CLSCTX_INPROC_SERVER);
+ ASSERT_TRUE(res == S_OK);
+ res = url_history_stg2->AddUrl(kIEIdentifyUrl, kIEIdentifyTitle, 0);
+ ASSERT_TRUE(res == S_OK);
+
+ // Starts to import the above settings.
+ MessageLoop* loop = MessageLoop::current();
+ scoped_refptr<ImporterHost> host = new ImporterHost(loop);
+
+ TestObserver* observer = new TestObserver();
+ host->SetObserver(observer);
+ ProfileInfo profile_info;
+ profile_info.browser_type = MS_IE;
+ profile_info.source_path = test_path_;
+
+ loop->PostTask(FROM_HERE, NewRunnableMethod(host.get(),
+ &ImporterHost::StartImportSettings, profile_info,
+ HISTORY | PASSWORDS | FAVORITES, observer, true));
+ loop->Run();
+
+ // Cleans up.
+ url_history_stg2->DeleteUrl(kIEIdentifyUrl, 0);
+ url_history_stg2.Release();
+ if (!IsWindowsVista()) {
+ ClearPStoreType(pstore, &type, &subtype);
+ // Releases it befor unload the dll.
+ pstore.Release();
+ FreeLibrary(pstorec_dll);
+ }
+}
+
+TEST_F(ImporterTest, IE7Importer) {
+ // This is the unencrypted values of my keys under Storage2.
+ // The passwords have been manually changed to abcdef... but the size remains
+ // the same.
+ unsigned char data1[] = "\x0c\x00\x00\x00\x38\x00\x00\x00\x2c\x00\x00\x00"
+ "\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00\x00\x00"
+ "\x67\x00\x72\x00\x01\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x4e\xfa\x67\x76\x22\x94\xc8\x01"
+ "\x08\x00\x00\x00\x12\x00\x00\x00\x4e\xfa\x67\x76"
+ "\x22\x94\xc8\x01\x0c\x00\x00\x00\x61\x00\x62\x00"
+ "\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00"
+ "\x00\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00"
+ "\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00"
+ "\x6c\x00\x00\x00";
+
+ unsigned char data2[] = "\x0c\x00\x00\x00\x38\x00\x00\x00\x24\x00\x00\x00"
+ "\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00\x00\x00"
+ "\x67\x00\x72\x00\x01\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\xa8\xea\xf4\xe5\x9f\x9a\xc8\x01"
+ "\x09\x00\x00\x00\x14\x00\x00\x00\xa8\xea\xf4\xe5"
+ "\x9f\x9a\xc8\x01\x07\x00\x00\x00\x61\x00\x62\x00"
+ "\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00"
+ "\x69\x00\x00\x00\x61\x00\x62\x00\x63\x00\x64\x00"
+ "\x65\x00\x66\x00\x67\x00\x00\x00";
+
+
+
+ std::vector<unsigned char> decrypted_data1;
+ decrypted_data1.resize(arraysize(data1));
+ memcpy(&decrypted_data1.front(), data1, sizeof(data1));
+
+ std::vector<unsigned char> decrypted_data2;
+ decrypted_data2.resize(arraysize(data2));
+ memcpy(&decrypted_data2.front(), data2, sizeof(data2));
+
+ std::wstring password;
+ std::wstring username;
+ ASSERT_TRUE(ie7_password::GetUserPassFromData(decrypted_data1, &username,
+ &password));
+ EXPECT_EQ(L"abcdefgh", username);
+ EXPECT_EQ(L"abcdefghijkl", password);
+
+ ASSERT_TRUE(ie7_password::GetUserPassFromData(decrypted_data2, &username,
+ &password));
+ EXPECT_EQ(L"abcdefghi", username);
+ EXPECT_EQ(L"abcdefg", password);
+}
+
+static const BookmarkList kFirefox2Bookmarks[] = {
+ {true, 1, {L"Folder"},
+ L"On Toolbar's Subfolder",
+ "http://on.toolbar/bookmark/folder"},
+ {true, 0, {},
+ L"On Bookmark Toolbar",
+ "http://on.toolbar/bookmark"},
+ {false, 1, {L"Folder"},
+ L"New Bookmark",
+ "http://domain/"},
+ {false, 0, {},
+ L"<Name>",
+ "http://domain.com/q?a=\"er\"&b=%3C%20%20%3E"},
+ {false, 0, {},
+ L"Google Home Page",
+ "http://www.google.com/"},
+ {false, 0, {},
+ L"\x4E2D\x6587",
+ "http://chinese.site.cn/path?query=1#ref"},
+ {false, 0, {},
+ L"mail",
+ "mailto:username@host"},
+};
+
+static const PasswordList kFirefox2Passwords[] = {
+ {"https://www.google.com/", "", "https://www.google.com/",
+ L"", L"", L"", L"", true},
+ {"http://localhost:8080/", "", "http://localhost:8080/corp.google.com",
+ L"", L"http", L"", L"Http1+1abcdefg", false},
+ {"http://localhost:8080/", "http://localhost:8080/", "http://localhost:8080/",
+ L"loginuser", L"usr", L"loginpass", L"pwd", false},
+ {"http://localhost:8080/", "http://localhost:8080/", "http://localhost:8080/",
+ L"loginuser", L"firefox", L"loginpass", L"firefox", false},
+ {"http://localhost/", "", "http://localhost/",
+ L"loginuser", L"hello", L"", L"world", false},
+};
+
+typedef struct {
+ const wchar_t* keyword;
+ const wchar_t* url;
+} KeywordList;
+
+static const KeywordList kFirefox2Keywords[] = {
+ // Searh plugins
+ { L"amazon.com",
+ L"http://www.amazon.com/exec/obidos/external-search/?field-keywords="
+ L"{searchTerms}&mode=blended" },
+ { L"answers.com",
+ L"http://www.answers.com/main/ntquery?s={searchTerms}&gwp=13" },
+ { L"search.creativecommons.org",
+ L"http://search.creativecommons.org/?q={searchTerms}" },
+ { L"search.ebay.com",
+ L"http://search.ebay.com/search/search.dll?query={searchTerms}&"
+ L"MfcISAPICommand=GetResult&ht=1&ebaytag1=ebayreg&srchdesc=n&"
+ L"maxRecordsReturned=300&maxRecordsPerPage=50&SortProperty=MetaEndSort" },
+ { L"google.com",
+ L"http://www.google.com/search?q={searchTerms}&ie=utf-8&oe=utf-8&aq=t" },
+ { L"search.yahoo.com",
+ L"http://search.yahoo.com/search?p={searchTerms}&ei=UTF-8" },
+ { L"flickr.com",
+ L"http://www.flickr.com/photos/tags/?q={searchTerms}" },
+ { L"imdb.com",
+ L"http://www.imdb.com/find?q={searchTerms}" },
+ { L"webster.com",
+ L"http://www.webster.com/cgi-bin/dictionary?va={searchTerms}" },
+ // Search keywords.
+ { L"google", L"http://www.google.com/" },
+ { L"< > & \" ' \\ /", L"http://g.cn/"},
+};
+
+static const int kDefaultFirefox2KeywordIndex = 8;
+
+class FirefoxObserver : public ProfileWriter,
+ public ImporterHost::Observer {
+ public:
+ FirefoxObserver() : ProfileWriter(NULL) {
+ bookmark_count_ = 0;
+ history_count_ = 0;
+ password_count_ = 0;
+ keyword_count_ = 0;
+ }
+
+ virtual void ImportItemStarted(ImportItem item) {}
+ virtual void ImportItemEnded(ImportItem item) {}
+ virtual void ImportStarted() {}
+ virtual void ImportEnded() {
+ MessageLoop::current()->Quit();
+ EXPECT_EQ(arraysize(kFirefox2Bookmarks), bookmark_count_);
+ EXPECT_EQ(1, history_count_);
+ EXPECT_EQ(arraysize(kFirefox2Passwords), password_count_);
+ EXPECT_EQ(arraysize(kFirefox2Keywords), keyword_count_);
+ EXPECT_EQ(kFirefox2Keywords[kDefaultFirefox2KeywordIndex].keyword,
+ default_keyword_);
+ EXPECT_EQ(kFirefox2Keywords[kDefaultFirefox2KeywordIndex].url,
+ default_keyword_url_);
+ }
+
+ virtual bool BookmarkModelIsLoaded() const {
+ // Profile is ready for writing.
+ return true;
+ }
+
+ virtual void AddBookmarkModelObserver(BookmarkModelObserver* observer) {
+ NOTREACHED();
+ }
+
+ virtual bool TemplateURLModelIsLoaded() const {
+ return true;
+ }
+
+ virtual void AddTemplateURLModelObserver(NotificationObserver* observer) {
+ NOTREACHED();
+ }
+
+ virtual void AddPasswordForm(const PasswordForm& form) {
+ PasswordList p = kFirefox2Passwords[password_count_];
+ EXPECT_EQ(p.origin, form.origin.spec());
+ EXPECT_EQ(p.realm, form.signon_realm);
+ EXPECT_EQ(p.action, form.action.spec());
+ EXPECT_EQ(p.username_element, form.username_element);
+ EXPECT_EQ(p.username, form.username_value);
+ EXPECT_EQ(p.password_element, form.password_element);
+ EXPECT_EQ(p.password, form.password_value);
+ EXPECT_EQ(p.blacklisted, form.blacklisted_by_user);
+ ++password_count_;
+ }
+
+ virtual void AddHistoryPage(const std::vector<history::URLRow>& page) {
+ EXPECT_EQ(1, page.size());
+ EXPECT_EQ("http://en-us.www.mozilla.com/", page[0].url().spec());
+ EXPECT_EQ(L"Firefox Updated", page[0].title());
+ ++history_count_;
+ }
+
+ virtual void AddBookmarkEntry(const std::vector<BookmarkEntry>& bookmark) {
+ for (size_t i = 0; i < bookmark.size(); ++i) {
+ if (FindBookmarkEntry(bookmark[i], kFirefox2Bookmarks,
+ arraysize(kFirefox2Bookmarks)))
+ ++bookmark_count_;
+ }
+ }
+
+ virtual void AddKeywords(const std::vector<TemplateURL*>& template_urls,
+ int default_keyword_index,
+ bool unique_on_host_and_path) {
+ for (size_t i = 0; i < template_urls.size(); ++i) {
+ // The order might not be deterministic, look in the expected list for
+ // that template URL.
+ bool found = false;
+ std::wstring keyword = template_urls[i]->keyword();
+ for (int j = 0; j < arraysize(kFirefox2Keywords); ++j) {
+ const wchar_t* comp_keyword = kFirefox2Keywords[j].keyword;
+ bool equal = (keyword == comp_keyword);
+ if (template_urls[i]->keyword() == kFirefox2Keywords[j].keyword) {
+ EXPECT_EQ(kFirefox2Keywords[j].url, template_urls[i]->url()->url());
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+ ++keyword_count_;
+ }
+
+ if (default_keyword_index != -1) {
+ EXPECT_LT(default_keyword_index, static_cast<int>(template_urls.size()));
+ TemplateURL* default_turl = template_urls[default_keyword_index];
+ default_keyword_ = default_turl->keyword();
+ default_keyword_url_ = default_turl->url()->url();
+ }
+
+ STLDeleteContainerPointers(template_urls.begin(), template_urls.end());
+ }
+
+ void AddFavicons(const std::vector<history::ImportedFavIconUsage>& favicons) {
+ }
+
+ private:
+ int bookmark_count_;
+ int history_count_;
+ int password_count_;
+ int keyword_count_;
+ std::wstring default_keyword_;
+ std::wstring default_keyword_url_;
+};
+
+TEST_F(ImporterTest, Firefox2Importer) {
+ std::wstring data_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
+ file_util::AppendToPath(&data_path, L"firefox2_profile\\*");
+ file_util::CopyDirectory(data_path, profile_path_, true);
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
+ file_util::AppendToPath(&data_path, L"firefox2_nss");
+ file_util::CopyDirectory(data_path, profile_path_, false);
+
+ std::wstring search_engine_path = app_path_;
+ file_util::AppendToPath(&search_engine_path, L"searchplugins");
+ CreateDirectory(search_engine_path.c_str(), NULL);
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
+ file_util::AppendToPath(&data_path, L"firefox2_searchplugins");
+ file_util::CopyDirectory(data_path, search_engine_path, false);
+
+ MessageLoop* loop = MessageLoop::current();
+ scoped_refptr<ImporterHost> host = new ImporterHost(loop);
+ FirefoxObserver* observer = new FirefoxObserver();
+ host->SetObserver(observer);
+ ProfileInfo profile_info;
+ profile_info.browser_type = FIREFOX2;
+ profile_info.app_path = app_path_;
+ profile_info.source_path = profile_path_;
+
+ loop->PostTask(FROM_HERE, NewRunnableMethod(host.get(),
+ &ImporterHost::StartImportSettings, profile_info,
+ HISTORY | PASSWORDS | FAVORITES | SEARCH_ENGINES, observer, true));
+ loop->Run();
+}
+
+static const BookmarkList kFirefox3Bookmarks[] = {
+ {true, 0, {},
+ L"Toolbar",
+ "http://site/"},
+ {false, 0, {},
+ L"Title",
+ "http://www.google.com/"},
+};
+
+static const PasswordList kFirefox3Passwords[] = {
+ {"http://localhost:8080/", "http://localhost:8080/", "http://localhost:8080/",
+ L"loginuser", L"abc", L"loginpass", L"123", false},
+ {"http://localhost:8080/", "", "http://localhost:8080/localhost",
+ L"", L"http", L"", L"Http1+1abcdefg", false},
+};
+
+static const KeywordList kFirefox3Keywords[] = {
+ { L"amazon.com",
+ L"http://www.amazon.com/exec/obidos/external-search/?field-keywords="
+ L"{searchTerms}&mode=blended" },
+ { L"answers.com",
+ L"http://www.answers.com/main/ntquery?s={searchTerms}&gwp=13" },
+ { L"search.creativecommons.org",
+ L"http://search.creativecommons.org/?q={searchTerms}" },
+ { L"search.ebay.com",
+ L"http://search.ebay.com/search/search.dll?query={searchTerms}&"
+ L"MfcISAPICommand=GetResult&ht=1&ebaytag1=ebayreg&srchdesc=n&"
+ L"maxRecordsReturned=300&maxRecordsPerPage=50&SortProperty=MetaEndSort" },
+ { L"google.com",
+ L"http://www.google.com/search?q={searchTerms}&ie=utf-8&oe=utf-8&aq=t" },
+ { L"en.wikipedia.org",
+ L"http://en.wikipedia.org/wiki/Special:Search?search={searchTerms}" },
+ { L"search.yahoo.com",
+ L"http://search.yahoo.com/search?p={searchTerms}&ei=UTF-8" },
+ { L"flickr.com",
+ L"http://www.flickr.com/photos/tags/?q={searchTerms}" },
+ { L"imdb.com",
+ L"http://www.imdb.com/find?q={searchTerms}" },
+ { L"webster.com",
+ L"http://www.webster.com/cgi-bin/dictionary?va={searchTerms}" },
+ // Search keywords.
+ { L"\x4E2D\x6587", L"http://www.google.com/" },
+};
+
+static const int kDefaultFirefox3KeywordIndex = 8;
+
+class Firefox3Observer : public ProfileWriter,
+ public ImporterHost::Observer {
+ public:
+ Firefox3Observer() : ProfileWriter(NULL) {
+ bookmark_count_ = 0;
+ history_count_ = 0;
+ password_count_ = 0;
+ keyword_count_ = 0;
+ }
+
+ virtual void ImportItemStarted(ImportItem item) {}
+ virtual void ImportItemEnded(ImportItem item) {}
+ virtual void ImportStarted() {}
+ virtual void ImportEnded() {
+ MessageLoop::current()->Quit();
+ EXPECT_EQ(arraysize(kFirefox3Bookmarks), bookmark_count_);
+ EXPECT_EQ(1, history_count_);
+ EXPECT_EQ(arraysize(kFirefox3Passwords), password_count_);
+ EXPECT_EQ(arraysize(kFirefox3Keywords), keyword_count_);
+ EXPECT_EQ(kFirefox3Keywords[kDefaultFirefox3KeywordIndex].keyword,
+ default_keyword_);
+ EXPECT_EQ(kFirefox3Keywords[kDefaultFirefox3KeywordIndex].url,
+ default_keyword_url_);
+ }
+
+ virtual bool BookmarkModelIsLoaded() const {
+ // Profile is ready for writing.
+ return true;
+ }
+
+ virtual void AddBookmarkModelObserver(BookmarkModelObserver* observer) {
+ NOTREACHED();
+ }
+
+ virtual bool TemplateURLModelIsLoaded() const {
+ return true;
+ }
+
+ virtual void AddTemplateURLModelObserver(NotificationObserver* observer) {
+ NOTREACHED();
+ }
+
+ virtual void AddPasswordForm(const PasswordForm& form) {
+ PasswordList p = kFirefox3Passwords[password_count_];
+ EXPECT_EQ(p.origin, form.origin.spec());
+ EXPECT_EQ(p.realm, form.signon_realm);
+ EXPECT_EQ(p.action, form.action.spec());
+ EXPECT_EQ(p.username_element, form.username_element);
+ EXPECT_EQ(p.username, form.username_value);
+ EXPECT_EQ(p.password_element, form.password_element);
+ EXPECT_EQ(p.password, form.password_value);
+ EXPECT_EQ(p.blacklisted, form.blacklisted_by_user);
+ ++password_count_;
+ }
+
+ virtual void AddHistoryPage(const std::vector<history::URLRow>& page) {
+ ASSERT_EQ(3, page.size());
+ EXPECT_EQ("http://www.google.com/", page[0].url().spec());
+ EXPECT_EQ(L"Google", page[0].title());
+ EXPECT_EQ("http://www.google.com/", page[1].url().spec());
+ EXPECT_EQ(L"Google", page[1].title());
+ EXPECT_EQ("http://www.cs.unc.edu/~jbs/resources/perl/perl-cgi/programs/form1-POST.html",
+ page[2].url().spec());
+ EXPECT_EQ(L"example form (POST)", page[2].title());
+ ++history_count_;
+ }
+
+ virtual void AddBookmarkEntry(const std::vector<BookmarkEntry>& bookmark) {
+ for (size_t i = 0; i < bookmark.size(); ++i) {
+ if (FindBookmarkEntry(bookmark[i], kFirefox3Bookmarks,
+ arraysize(kFirefox3Bookmarks)))
+ ++bookmark_count_;
+ }
+ }
+
+ void AddKeywords(const std::vector<TemplateURL*>& template_urls,
+ int default_keyword_index,
+ bool unique_on_host_and_path) {
+ for (size_t i = 0; i < template_urls.size(); ++i) {
+ // The order might not be deterministic, look in the expected list for
+ // that template URL.
+ bool found = false;
+ std::wstring keyword = template_urls[i]->keyword();
+ for (int j = 0; j < arraysize(kFirefox3Keywords); ++j) {
+ const wchar_t* comp_keyword = kFirefox3Keywords[j].keyword;
+ bool equal = (keyword == comp_keyword);
+ if (template_urls[i]->keyword() == kFirefox3Keywords[j].keyword) {
+ EXPECT_EQ(kFirefox3Keywords[j].url, template_urls[i]->url()->url());
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found);
+ ++keyword_count_;
+ }
+
+ if (default_keyword_index != -1) {
+ EXPECT_LT(default_keyword_index, static_cast<int>(template_urls.size()));
+ TemplateURL* default_turl = template_urls[default_keyword_index];
+ default_keyword_ = default_turl->keyword();
+ default_keyword_url_ = default_turl->url()->url();
+ }
+
+ STLDeleteContainerPointers(template_urls.begin(), template_urls.end());
+ }
+
+ void AddFavicons(const std::vector<history::ImportedFavIconUsage>& favicons) {
+ }
+
+ private:
+ int bookmark_count_;
+ int history_count_;
+ int password_count_;
+ int keyword_count_;
+ std::wstring default_keyword_;
+ std::wstring default_keyword_url_;
+};
+
+TEST_F(ImporterTest, Firefox3Importer) {
+ std::wstring data_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
+ file_util::AppendToPath(&data_path, L"firefox3_profile\\*");
+ file_util::CopyDirectory(data_path, profile_path_, true);
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
+ file_util::AppendToPath(&data_path, L"firefox3_nss");
+ file_util::CopyDirectory(data_path, profile_path_, false);
+
+ std::wstring search_engine_path = app_path_;
+ file_util::AppendToPath(&search_engine_path, L"searchplugins");
+ CreateDirectory(search_engine_path.c_str(), NULL);
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
+ file_util::AppendToPath(&data_path, L"firefox3_searchplugins");
+ file_util::CopyDirectory(data_path, search_engine_path, false);
+
+ MessageLoop* loop = MessageLoop::current();
+ ProfileInfo profile_info;
+ profile_info.browser_type = FIREFOX3;
+ profile_info.app_path = app_path_;
+ profile_info.source_path = profile_path_;
+ scoped_refptr<ImporterHost> host = new ImporterHost(loop);
+ Firefox3Observer* observer = new Firefox3Observer();
+ host->SetObserver(observer);
+ loop->PostTask(FROM_HERE, NewRunnableMethod(host.get(),
+ &ImporterHost::StartImportSettings, profile_info,
+ HISTORY | PASSWORDS | FAVORITES | SEARCH_ENGINES, observer, true));
+ loop->Run();
+}
+
diff --git a/chrome/browser/importer/mork_reader.cc b/chrome/browser/importer/mork_reader.cc
new file mode 100644
index 0000000..3ae4b6c
--- /dev/null
+++ b/chrome/browser/importer/mork_reader.cc
@@ -0,0 +1,581 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Mork Reader.
+ *
+ * The Initial Developer of the Original Code is
+ * Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Brian Ryner <bryner@brianryner.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Source:
+// http://mxr.mozilla.org/firefox/source/db/morkreader/nsMorkReader.cpp
+// This file has been converted to google style.
+
+#include "chrome/browser/importer/mork_reader.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/importer/firefox_importer_utils.h"
+
+namespace {
+
+// Convert a hex character (0-9, A-F) to its corresponding byte value.
+// Returns -1 if the character is invalid.
+inline int HexCharToInt(char c) {
+ if ('0' <= c && c <= '9')
+ return c - '0';
+ if ('A' <= c && c <= 'F')
+ return c - 'A' + 10;
+ return -1;
+}
+
+// Unescape a Mork value. Mork uses $xx escaping to encode non-ASCII
+// characters. Additionally, '$' and '\' are backslash-escaped.
+// The result of the unescape is in returned.
+std::string MorkUnescape(const std::string& input) {
+ // We optimize for speed over space here -- size the result buffer to
+ // the size of the source, which is an upper bound on the size of the
+ // unescaped string.
+ std::string result;
+ size_t input_length = input.size();
+ result.reserve(input_length);
+
+ for (size_t i = 0; i < input_length; i++) {
+ char c = input[i];
+ if (c == '\\') {
+ // Escaped literal, slip the backslash, append the next character.
+ i++;
+ if (i < input_length)
+ result.push_back(input[i]);
+ } else if (c == '$') {
+ // Dollar sign denotes a hex character.
+ if (i < input_length - 2) {
+ // Would be nice to use ToInteger() here, but it currently
+ // requires a null-terminated string.
+ int first = HexCharToInt(input[++i]);
+ int second = HexCharToInt(input[++i]);
+ if (first >= 0 && second >= 0)
+ result.push_back((first << 4) | second);
+ }
+ } else {
+ // Regular character, just append.
+ result.push_back(input[i]);
+ }
+ }
+ return result;
+}
+
+} // namespace
+
+MorkReader::MorkReader() {
+}
+
+MorkReader::~MorkReader() {
+ // Need to delete all the pointers to vectors we have in the table.
+ for (RowMap::iterator i = table_.begin(); i != table_.end(); ++i)
+ delete i->second;
+}
+
+bool MorkReader::Read(const std::wstring& filename) {
+ stream_.open(filename.c_str());
+ if (!stream_.is_open())
+ return false;
+
+ std::string line;
+ if (!ReadLine(&line) ||
+ line.compare("// <!-- <mdb:mork:z v=\"1.4\"/> -->") != 0)
+ return false; // Unexpected file format.
+
+ IndexMap column_map;
+ while (ReadLine(&line)) {
+ // Trim off leading spaces
+ size_t idx = 0;
+ size_t len = line.size();
+ while (idx < len && line[idx] == ' ')
+ ++idx;
+ if (idx >= len)
+ continue;
+
+ // Look at the line to figure out what section type this is
+ if (StartsWithASCII(&line[idx], "< <(a=c)>", true)) {
+ // Column map. We begin by creating a hash of column id to column name.
+ StringMap column_name_map;
+ ParseMap(line, idx, &column_name_map);
+
+ // Now that we have the list of columns, we put them into a flat array.
+ // Rows will have value arrays of the same size, with indexes that
+ // correspond to the columns array. As we insert each column into the
+ // array, we also make an entry in columnMap so that we can look up the
+ // index given the column id.
+ columns_.reserve(column_name_map.size());
+
+ for (StringMap::const_iterator i = column_name_map.begin();
+ i != column_name_map.end(); ++i) {
+ column_map[i->first] = static_cast<int>(columns_.size());
+ MorkColumn col(i->first, i->second);
+ columns_.push_back(col);
+ }
+ } else if (StartsWithASCII(&line[idx], "<(", true)) {
+ // Value map.
+ ParseMap(line, idx, &value_map_);
+ } else if (line[idx] == '{' || line[idx] == '[') {
+ // Table / table row.
+ ParseTable(line, idx, &column_map);
+ } else {
+ // Don't know, hopefully don't care.
+ }
+ }
+ return true;
+}
+
+// Parses a key/value map of the form
+// <(k1=v1)(k2=v2)...>
+bool MorkReader::ParseMap(const std::string& first_line,
+ size_t start_index,
+ StringMap* map) {
+ // If the first line is the a=c line (column map), just skip over it.
+ std::string line(first_line);
+ if (StartsWithASCII(line, "< <(a=c)>", true))
+ ReadLine(&line);
+
+ std::string key;
+ do {
+ size_t idx = start_index;
+ size_t len = line.size();
+ size_t token_start;
+
+ while (idx < len) {
+ switch (line[idx++]) {
+ case '(':
+ // Beginning of a key/value pair.
+ if (!key.empty()) {
+ DLOG(WARNING) << "unterminated key/value pair?";
+ key.clear();
+ }
+
+ token_start = idx;
+ while (idx < len && line[idx] != '=')
+ ++idx;
+ key.assign(&line[token_start], idx - token_start);
+ break;
+
+ case '=': {
+ // Beginning of the value.
+ if (key.empty()) {
+ DLOG(WARNING) << "stray value";
+ break;
+ }
+
+ token_start = idx;
+ while (idx < len && line[idx] != ')') {
+ if (line[idx] == '\\')
+ ++idx; // Skip escaped ')' characters.
+ ++idx;
+ }
+ size_t token_end = std::min(idx, len);
+ ++idx;
+
+ std::string value = MorkUnescape(
+ std::string(&line[token_start], token_end - token_start));
+ (*map)[key] = value;
+ key.clear();
+ break;
+ }
+ case '>':
+ // End of the map.
+ DLOG_IF(WARNING, key.empty()) <<
+ "map terminates inside of key/value pair";
+ return true;
+ }
+ }
+
+ // We should start reading the next line at the beginning.
+ start_index = 0;
+ } while (ReadLine(&line));
+
+ // We ran out of lines and the map never terminated. This probably indicates
+ // a parsing error.
+ DLOG(WARNING) << "didn't find end of key/value map";
+ return false;
+}
+
+// Parses a table row of the form [123(^45^67)..]
+// (row id 123 has the value with id 67 for the column with id 45).
+// A '^' prefix for a column or value references an entry in the column or
+// value map. '=' is used as the separator when the value is a literal.
+void MorkReader::ParseTable(const std::string& first_line,
+ size_t start_index,
+ const IndexMap* column_map) {
+ std::string line(first_line);
+
+ // Column index of the cell we're parsing, minus one if invalid.
+ int column_index = -1;
+
+ // Points to the current row we're parsing inside of the |table_|, will be
+ // NULL if we're not inside a row.
+ ColumnDataList* current_row = NULL;
+
+ bool in_meta_row = false;
+
+ do {
+ size_t idx = start_index;
+ size_t len = line.size();
+
+ while (idx < len) {
+ switch (line[idx++]) {
+ case '{':
+ // This marks the beginning of a table section. There's a lot of
+ // junk before the first row that looks like cell values but isn't.
+ // Skip to the first '['.
+ while (idx < len && line[idx] != '[') {
+ if (line[idx] == '{') {
+ in_meta_row = true; // The meta row is enclosed in { }
+ } else if (line[idx] == '}') {
+ in_meta_row = false;
+ }
+ ++idx;
+ }
+ break;
+
+ case '[': {
+ // Start of a new row. Consume the row id, up to the first '('.
+ // Row edits also have a table namespace, separated from the row id
+ // by a colon. We don't make use of the namespace, but we need to
+ // make sure not to consider it part of the row id.
+ if (current_row) {
+ DLOG(WARNING) << "unterminated row?";
+ current_row = NULL;
+ }
+
+ // Check for a '-' at the start of the id. This signifies that
+ // if the row already exists, we should delete all columns from it
+ // before adding the new values.
+ bool cut_columns;
+ if (idx < len && line[idx] == '-') {
+ cut_columns = true;
+ ++idx;
+ } else {
+ cut_columns = false;
+ }
+
+ // Locate the range of the ID.
+ size_t token_start = idx; // Index of the first char of the token.
+ while (idx < len &&
+ line[idx] != '(' &&
+ line[idx] != ']' &&
+ line[idx] != ':') {
+ ++idx;
+ }
+ size_t token_end = idx; // Index of the char following the token.
+ while (idx < len && line[idx] != '(' && line[idx] != ']') {
+ ++idx;
+ }
+
+ if (in_meta_row) {
+ // Need to create the meta row.
+ meta_row_.resize(columns_.size());
+ current_row = &meta_row_;
+ } else {
+ // Find or create the regular row for this.
+ IDString row_id(&line[token_start], token_end - token_start);
+ RowMap::iterator found_row = table_.find(row_id);
+ if (found_row == table_.end()) {
+ // We don't already have this row, create a new one for it.
+ current_row = new ColumnDataList(columns_.size());
+ table_[row_id] = current_row;
+ } else {
+ // The row already exists and we're adding/replacing things.
+ current_row = found_row->second;
+ }
+ }
+ if (cut_columns) {
+ for (size_t i = 0; i < current_row->size(); ++i)
+ (*current_row)[i].clear();
+ }
+ break;
+ }
+
+ case ']':
+ // We're done with the row.
+ current_row = NULL;
+ in_meta_row = false;
+ break;
+
+ case '(': {
+ if (!current_row) {
+ DLOG(WARNING) << "cell value outside of row";
+ break;
+ }
+
+ bool column_is_atom;
+ if (line[idx] == '^') {
+ column_is_atom = true;
+ ++idx; // This is not part of the column id, advance past it.
+ } else {
+ column_is_atom = false;
+ }
+ size_t token_start = idx;
+ while (idx < len && line[idx] != '^' && line[idx] != '=') {
+ if (line[idx] == '\\')
+ ++idx; // Skip escaped characters.
+ ++idx;
+ }
+
+ size_t token_end = std::min(idx, len);
+
+ IDString column;
+ if (column_is_atom)
+ column.assign(&line[token_start], token_end - token_start);
+ else
+ column = MorkUnescape(line.substr(token_start,
+ token_end - token_start));
+
+ IndexMap::const_iterator found_column = column_map->find(column);
+ if (found_column == column_map->end()) {
+ DLOG(WARNING) << "Column not in column map, discarding it";
+ column_index = -1;
+ } else {
+ column_index = found_column->second;
+ }
+ break;
+ }
+
+ case '=':
+ case '^': {
+ if (column_index == -1) {
+ DLOG(WARNING) << "stray ^ or = marker";
+ break;
+ }
+
+ bool value_is_atom = (line[idx - 1] == '^');
+ size_t token_start = idx - 1; // Include the '=' or '^' marker.
+ while (idx < len && line[idx] != ')') {
+ if (line[idx] == '\\')
+ ++idx; // Skip escaped characters.
+ ++idx;
+ }
+ size_t token_end = std::min(idx, len);
+ ++idx;
+
+ if (value_is_atom) {
+ (*current_row)[column_index].assign(&line[token_start],
+ token_end - token_start);
+ } else {
+ (*current_row)[column_index] =
+ MorkUnescape(line.substr(token_start, token_end - token_start));
+ }
+ column_index = -1;
+ }
+ break;
+ }
+ }
+
+ // Start parsing the next line at the beginning.
+ start_index = 0;
+ } while (current_row && ReadLine(&line));
+}
+
+bool MorkReader::ReadLine(std::string* line) {
+ line->resize(256);
+ std::getline(stream_, *line);
+ if (stream_.eof() || stream_.bad())
+ return false;
+
+ while (!line->empty() && (*line)[line->size() - 1] == '\\') {
+ // There is a continuation for this line. Read it and append.
+ std::string new_line;
+ std::getline(stream_, new_line);
+ if (stream_.eof())
+ return false;
+ line->erase(line->size() - 1);
+ line->append(new_line);
+ }
+
+ return true;
+}
+
+void MorkReader::NormalizeValue(std::string* value) const {
+ if (value->empty())
+ return;
+ MorkReader::StringMap::const_iterator i;
+ switch (value->at(0)) {
+ case '^':
+ // Hex ID, lookup the name for it in the |value_map_|.
+ i = value_map_.find(value->substr(1));
+ if (i == value_map_.end())
+ value->clear();
+ else
+ *value = i->second;
+ break;
+ case '=':
+ // Just use the literal after the equals sign.
+ value->erase(value->begin());
+ break;
+ default:
+ // Anything else is invalid.
+ value->clear();
+ break;
+ }
+}
+
+// Source:
+// http://mxr.mozilla.org/firefox/source/toolkit/components/places/src/nsMorkHistoryImporter.cpp
+
+// Columns for entry (non-meta) history rows
+enum {
+ kURLColumn,
+ kNameColumn,
+ kVisitCountColumn,
+ kHiddenColumn,
+ kTypedColumn,
+ kLastVisitColumn,
+ kColumnCount // Keep me last.
+};
+
+static const char * const gColumnNames[] = {
+ "URL", "Name", "VisitCount", "Hidden", "Typed", "LastVisitDate"
+};
+
+struct TableReadClosure {
+ explicit TableReadClosure(const MorkReader& r)
+ : reader(r),
+ swap_bytes(false),
+ byte_order_column(-1) {
+ for (int i = 0; i < kColumnCount; ++i)
+ column_indexes[i] = -1;
+ }
+
+ // Backpointers to the reader and history we're operating on.
+ const MorkReader& reader;
+
+ // Whether we need to swap bytes (file format is other-endian).
+ bool swap_bytes;
+
+ // Indexes of the columns that we care about.
+ int column_indexes[kColumnCount];
+ int byte_order_column;
+};
+
+void AddToHistory(MorkReader::ColumnDataList* column_values,
+ const TableReadClosure& data,
+ std::vector<history::URLRow>* rows) {
+ std::string values[kColumnCount];
+
+ for (size_t i = 0; i < kColumnCount; ++i) {
+ if (data.column_indexes[i] != -1) {
+ values[i] = column_values->at(data.column_indexes[i]);
+ data.reader.NormalizeValue(&values[i]);
+ // Do not import hidden records.
+ if (i == kHiddenColumn && values[i] == "1")
+ return;
+ }
+ }
+
+ GURL url(values[kURLColumn]);
+
+ if (CanImportURL(url)) {
+ history::URLRow row(url);
+
+ // title is really a UTF-16 string at this point
+ std::wstring title;
+ if (data.swap_bytes) {
+ CodepageToWide(values[kNameColumn], "UTF-16BE",
+ OnStringUtilConversionError::SKIP, &title);
+ } else {
+ CodepageToWide(values[kNameColumn], "UTF-16LE",
+ OnStringUtilConversionError::SKIP, &title);
+ }
+ row.set_title(title);
+
+ int count = atoi(values[kVisitCountColumn].c_str());
+ if (count == 0)
+ count = 1;
+ row.set_visit_count(count);
+
+ time_t date = StringToInt64(values[kLastVisitColumn]);
+ if (date != 0)
+ row.set_last_visit(Time::FromTimeT(date/1000000));
+
+ bool is_typed = (values[kTypedColumn] == "1");
+ if (is_typed)
+ row.set_typed_count(1);
+
+ rows->push_back(row);
+ }
+}
+
+// It sets up the file stream and loops over the lines in the file to
+// parse them, then adds the resulting row set to history.
+void ImportHistoryFromFirefox2(std::wstring file, MessageLoop* loop,
+ ProfileWriter* writer) {
+ MorkReader reader;
+ reader.Read(file);
+
+ // Gather up the column ids so we don't need to find them on each row
+ TableReadClosure data(reader);
+ const MorkReader::MorkColumnList& columns = reader.columns();
+ for (size_t i = 0; i < columns.size(); ++i) {
+ for (int j = 0; j < kColumnCount; ++j)
+ if (columns[i].name == gColumnNames[j]) {
+ data.column_indexes[j] = static_cast<int>(i);
+ break;
+ }
+ if (columns[i].name == "ByteOrder")
+ data.byte_order_column = static_cast<int>(i);
+ }
+
+ // Determine the byte order from the table's meta-row.
+ const MorkReader::ColumnDataList& meta_row = reader.meta_row();
+ if (!meta_row.empty() && data.byte_order_column != -1) {
+ std::string byte_order = meta_row[data.byte_order_column];
+ if (!byte_order.empty()) {
+ // Note whether the file uses a non-native byte ordering.
+ // If it does, we'll have to swap bytes for PRUnichar values.
+ // "BE" and "LE" are the only recognized values, anything
+ // else is garbage and the file will be treated as native-endian
+ // (no swapping).
+ std::string byte_order_value(byte_order);
+ reader.NormalizeValue(&byte_order_value);
+ data.swap_bytes = (byte_order_value == "BE");
+ }
+ }
+
+ std::vector<history::URLRow> rows;
+ for (MorkReader::iterator i = reader.begin(); i != reader.end(); ++i)
+ AddToHistory(i->second, data, &rows);
+ if (!rows.empty())
+ loop->PostTask(FROM_HERE, NewRunnableMethod(writer,
+ &ProfileWriter::AddHistoryPage, rows));
+}
diff --git a/chrome/browser/importer/mork_reader.h b/chrome/browser/importer/mork_reader.h
new file mode 100644
index 0000000..1e54b93
--- /dev/null
+++ b/chrome/browser/importer/mork_reader.h
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Mork Reader.
+ *
+ * The Initial Developer of the Original Code is
+ * Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Brian Ryner <bryner@brianryner.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Source:
+// http://mxr.mozilla.org/firefox/source/db/morkreader/nsMorkReader.h
+
+#ifndef CHROME_BROWSER_IMPORTER_MORK_READER_H__
+#define CHROME_BROWSER_IMPORTER_MORK_READER_H__
+
+#include <map>
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/stack_container.h"
+#include "chrome/browser/importer/importer.h"
+
+// The nsMorkReader object allows a consumer to read in a mork-format
+// file and enumerate the rows that it contains. It does not provide
+// any functionality for modifying mork tables.
+
+// References:
+// http://www.mozilla.org/mailnews/arch/mork/primer.txt
+// http://www.mozilla.org/mailnews/arch/mork/grammar.txt
+// http://www.jwz.org/hacks/mork.pl
+
+class MorkReader {
+ public:
+ // The IDString type has built-in storage for the hex string representation
+ // of a 32-bit row id or atom map key, plus the terminating null.
+ // We use STL string here so that is can be operated with STL containers.
+ typedef std::string IDString;
+
+ // Lists the contents of a series of columns.
+ typedef std::vector<std::string> ColumnDataList;
+
+ // A MorkColumn describes a column of the table.
+ struct MorkColumn {
+ MorkColumn(IDString i, const std::string& n) : id(i), name(n) { }
+
+ IDString id;
+ std::string name;
+ };
+ typedef std::vector<MorkColumn> MorkColumnList;
+
+ // The key for each row is the identifier for it, and the data is a pointer
+ // to an array for each column.
+ typedef std::map<IDString, ColumnDataList*> RowMap;
+
+ typedef RowMap::const_iterator iterator;
+
+ MorkReader();
+ ~MorkReader();
+
+ // Read in the given mork file. Returns true on success.
+ // Note: currently, only single-table mork files are supported
+ bool Read(const std::wstring& filename);
+
+ // Returns the list of columns in the current table.
+ const MorkColumnList& columns() const { return columns_; }
+
+ // Get the "meta row" for the table. Each table has at most one meta row,
+ // which records information about the table. Like normal rows, the
+ // meta row contains columns in the same order as returned by columns().
+ // Returns null if there is no meta row for this table.
+ const ColumnDataList& meta_row() const { return meta_row_; }
+
+ // Normalizes the cell value (resolves references to the value map).
+ // |value| is modified in-place.
+ void NormalizeValue(std::string* value) const;
+
+ // Allow iteration over the table cells using STL iterators. The iterator's
+ // |first| will be the row ID, and the iterator's |second| will be a
+ // pointer to a ColumnDataList containing the cell data.
+ iterator begin() const { return table_.begin(); }
+ iterator end() const { return table_.end(); }
+
+ private:
+ // A convenience typedef for an ID-to-string mapping.
+ typedef std::map<IDString, std::string> StringMap;
+
+ // A convenience typdef for an ID-to-index mapping, used for the column index
+ // hashtable.
+ typedef std::map<IDString, int> IndexMap;
+
+ // Parses a line of the file which contains key/value pairs (either
+ // the column map or the value map). The starting line is parsed starting at
+ // the given index. Additional lines are read from stream_ if the line ends
+ // mid-entry. The pairs are added to the map.
+ bool ParseMap(const std::string& first_line,
+ size_t start_index,
+ StringMap* map);
+
+ // Parses a line of the file which contains a table or row definition,
+ // starting at the given offset within the line. Additional lines are read
+ // from |stream_| of the line ends mid-row. An entry is added to |table_|
+ // using the row ID as the key, which contains a column array for the row.
+ // The supplied column hash table maps from column id to an index in
+ // |columns_|.
+ void ParseTable(const std::string& first_line,
+ size_t start_index,
+ const IndexMap* column_map);
+
+ // Reads a single logical line from mStream into aLine.
+ // Any continuation lines are consumed and appended to the line.
+ bool ReadLine(std::string* line);
+
+ std::ifstream stream_;
+
+ // Lists the names of the columns for the table.
+ MorkColumnList columns_;
+
+ // Maps hex string IDs to the corrsponding names.
+ StringMap value_map_;
+
+ // The data of the columns in the meta row.
+ ColumnDataList meta_row_;
+
+ // The contents of the mork database. This array pointer is owned by this
+ // class and must be deleted.
+ RowMap table_;
+};
+
+// ImportHistoryFromFirefox2 is the main entry point to the importer.
+void ImportHistoryFromFirefox2(std::wstring file, MessageLoop* loop,
+ ProfileWriter* writer);
+
+#endif // CHROME_BROWSER_IMPORTER_MORK_READER_H__