summaryrefslogtreecommitdiffstats
path: root/chrome/browser/importer
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2010-07-29 17:14:53 +0100
committerBen Murdoch <benm@google.com>2010-08-04 14:29:45 +0100
commitc407dc5cd9bdc5668497f21b26b09d988ab439de (patch)
tree7eaf8707c0309516bdb042ad976feedaf72b0bb1 /chrome/browser/importer
parent0998b1cdac5733f299c12d88bc31ef9c8035b8fa (diff)
downloadexternal_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.zip
external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.gz
external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.bz2
Merge Chromium src@r53293
Change-Id: Ia79acf8670f385cee48c45b0a75371d8e950af34
Diffstat (limited to 'chrome/browser/importer')
-rw-r--r--chrome/browser/importer/firefox2_importer.cc587
-rw-r--r--chrome/browser/importer/firefox2_importer.h130
-rw-r--r--chrome/browser/importer/firefox3_importer.cc532
-rw-r--r--chrome/browser/importer/firefox3_importer.h87
-rw-r--r--chrome/browser/importer/firefox_importer_unittest.cc178
-rw-r--r--chrome/browser/importer/firefox_importer_unittest_messages_internal.h33
-rw-r--r--chrome/browser/importer/firefox_importer_unittest_utils.h84
-rw-r--r--chrome/browser/importer/firefox_importer_unittest_utils_mac.cc265
-rw-r--r--chrome/browser/importer/firefox_importer_utils.cc450
-rw-r--r--chrome/browser/importer/firefox_importer_utils.h97
-rw-r--r--chrome/browser/importer/firefox_importer_utils_linux.cc21
-rw-r--r--chrome/browser/importer/firefox_importer_utils_mac.mm40
-rw-r--r--chrome/browser/importer/firefox_importer_utils_win.cc76
-rw-r--r--chrome/browser/importer/firefox_profile_lock.cc79
-rw-r--r--chrome/browser/importer/firefox_profile_lock.h114
-rw-r--r--chrome/browser/importer/firefox_profile_lock_posix.cc123
-rw-r--r--chrome/browser/importer/firefox_profile_lock_unittest.cc133
-rw-r--r--chrome/browser/importer/firefox_profile_lock_win.cc75
-rw-r--r--chrome/browser/importer/firefox_proxy_settings.cc226
-rw-r--r--chrome/browser/importer/firefox_proxy_settings.h110
-rw-r--r--chrome/browser/importer/firefox_proxy_settings_unittest.cc124
-rw-r--r--chrome/browser/importer/ie_importer.cc578
-rw-r--r--chrome/browser/importer/ie_importer.h84
-rw-r--r--chrome/browser/importer/importer.cc588
-rw-r--r--chrome/browser/importer/importer.h522
-rw-r--r--chrome/browser/importer/importer_bridge.cc190
-rw-r--r--chrome/browser/importer/importer_bridge.h168
-rw-r--r--chrome/browser/importer/importer_data_types.h58
-rw-r--r--chrome/browser/importer/importer_list.cc186
-rw-r--r--chrome/browser/importer/importer_list.h61
-rw-r--r--chrome/browser/importer/importer_messages.h369
-rw-r--r--chrome/browser/importer/importer_messages_internal.h80
-rw-r--r--chrome/browser/importer/importer_unittest.cc885
-rw-r--r--chrome/browser/importer/mork_reader.cc585
-rw-r--r--chrome/browser/importer/mork_reader.h167
-rw-r--r--chrome/browser/importer/nss_decryptor.cc301
-rw-r--r--chrome/browser/importer/nss_decryptor.h18
-rw-r--r--chrome/browser/importer/nss_decryptor_mac.h162
-rw-r--r--chrome/browser/importer/nss_decryptor_mac.mm75
-rw-r--r--chrome/browser/importer/nss_decryptor_system_nss.cc269
-rw-r--r--chrome/browser/importer/nss_decryptor_system_nss.h59
-rw-r--r--chrome/browser/importer/nss_decryptor_win.cc178
-rw-r--r--chrome/browser/importer/nss_decryptor_win.h177
-rw-r--r--chrome/browser/importer/profile_writer.cc348
-rw-r--r--chrome/browser/importer/profile_writer.h136
-rw-r--r--chrome/browser/importer/safari_importer.h100
-rw-r--r--chrome/browser/importer/safari_importer.mm395
-rw-r--r--chrome/browser/importer/safari_importer_unittest.mm171
-rw-r--r--chrome/browser/importer/toolbar_importer.cc597
-rw-r--r--chrome/browser/importer/toolbar_importer.h172
-rw-r--r--chrome/browser/importer/toolbar_importer_unittest.cc482
51 files changed, 11725 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..d551105
--- /dev/null
+++ b/chrome/browser/importer/firefox2_importer.cc
@@ -0,0 +1,587 @@
+// Copyright (c) 2010 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 <string>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/i18n/icu_string_conversions.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/stl_util-inl.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/importer/firefox_importer_utils.h"
+#include "chrome/browser/importer/importer_bridge.h"
+#include "chrome/browser/importer/importer_data_types.h"
+#include "chrome/browser/importer/mork_reader.h"
+#include "chrome/browser/importer/nss_decryptor.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/browser/search_engines/template_url_parser.h"
+#include "chrome/common/time_format.h"
+#include "chrome/common/url_constants.h"
+#include "grit/generated_resources.h"
+#include "net/base/data_url.h"
+#include "webkit/glue/password_form.h"
+
+using base::Time;
+using importer::BOOKMARKS_HTML;
+using importer::FAVORITES;
+using importer::HISTORY;
+using importer::HOME_PAGE;
+using importer::PASSWORDS;
+using importer::ProfileInfo;
+using importer::SEARCH_ENGINES;
+using webkit_glue::PasswordForm;
+
+// Firefox2Importer.
+
+Firefox2Importer::Firefox2Importer() : parsing_bookmarks_html_file_(false) {
+}
+
+Firefox2Importer::~Firefox2Importer() {
+}
+
+void Firefox2Importer::StartImport(importer::ProfileInfo profile_info,
+ uint16 items,
+ ImporterBridge* bridge) {
+ bridge_ = bridge;
+ source_path_ = profile_info.source_path;
+ app_path_ = profile_info.app_path;
+
+ parsing_bookmarks_html_file_ =
+ (profile_info.browser_type == importer::BOOKMARKS_HTML);
+
+ // The order here is important!
+ bridge_->NotifyStarted();
+ if ((items & importer::HOME_PAGE) && !cancelled())
+ ImportHomepage(); // Doesn't have a UI item.
+
+ // Note history should be imported before bookmarks because bookmark import
+ // will also import favicons and we store favicon for a URL only if the URL
+ // exist in history or bookmarks.
+ if ((items & importer::HISTORY) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::HISTORY);
+ ImportHistory();
+ bridge_->NotifyItemEnded(importer::HISTORY);
+ }
+
+ if ((items & importer::FAVORITES) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::FAVORITES);
+ ImportBookmarks();
+ bridge_->NotifyItemEnded(importer::FAVORITES);
+ }
+ if ((items & importer::SEARCH_ENGINES) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::SEARCH_ENGINES);
+ ImportSearchEngines();
+ bridge_->NotifyItemEnded(importer::SEARCH_ENGINES);
+ }
+ if ((items & importer::PASSWORDS) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::PASSWORDS);
+ ImportPasswords();
+ bridge_->NotifyItemEnded(importer::PASSWORDS);
+ }
+ bridge_->NotifyEnded();
+}
+
+// static
+void Firefox2Importer::LoadDefaultBookmarks(const FilePath& app_path,
+ std::set<GURL> *urls) {
+ FilePath file = app_path.AppendASCII("defaults")
+ .AppendASCII("profile")
+ .AppendASCII("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;
+}
+
+// static
+void Firefox2Importer::ImportBookmarksFile(
+ const FilePath& file_path,
+ const std::set<GURL>& default_urls,
+ bool import_to_bookmark_bar,
+ const std::wstring& first_folder_name,
+ Importer* importer,
+ std::vector<ProfileWriter::BookmarkEntry>* bookmarks,
+ std::vector<TemplateURL*>* template_urls,
+ std::vector<history::ImportedFavIconUsage>* favicons) {
+ std::string content;
+ file_util::ReadFileToString(file_path, &content);
+ std::vector<std::string> lines;
+ SplitString(content, '\n', &lines);
+
+ std::vector<ProfileWriter::BookmarkEntry> toolbar_bookmarks;
+ std::wstring last_folder = first_folder_name;
+ 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() && (!importer || !importer->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 (import_to_bookmark_bar && 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.
+ entry.path.assign(path.begin(), path.end());
+ if (import_to_bookmark_bar)
+ entry.path.erase(entry.path.begin());
+ bookmarks->push_back(entry);
+ }
+
+ // Save the favicon. DataURLToFaviconUsage will handle the case where
+ // there is no favicon.
+ if (favicons)
+ DataURLToFaviconUsage(url, favicon, favicons);
+
+ if (template_urls) {
+ // 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;
+ }
+ }
+
+ bookmarks->insert(bookmarks->begin(), toolbar_bookmarks.begin(),
+ toolbar_bookmarks.end());
+}
+
+void Firefox2Importer::ImportBookmarks() {
+ // Load the default bookmarks.
+ std::set<GURL> default_urls;
+ if (!parsing_bookmarks_html_file_)
+ 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;
+ FilePath file = source_path_;
+ if (!parsing_bookmarks_html_file_)
+ file = file.AppendASCII("bookmarks.html");
+ std::wstring first_folder_name;
+ first_folder_name = bridge_->GetLocalizedString(
+ parsing_bookmarks_html_file_ ? IDS_BOOKMARK_GROUP :
+ IDS_BOOKMARK_GROUP_FROM_FIREFOX);
+
+ ImportBookmarksFile(file, default_urls, import_to_bookmark_bar(),
+ first_folder_name, this, &bookmarks, &template_urls,
+ &favicons);
+
+ // Write data into profile.
+ if (!bookmarks.empty() && !cancelled()) {
+ int options = 0;
+ if (import_to_bookmark_bar())
+ options |= ProfileWriter::IMPORT_TO_BOOKMARK_BAR;
+ if (bookmark_bar_disabled())
+ options |= ProfileWriter::BOOKMARK_BAR_DISABLED;
+ bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options);
+ }
+ if (!parsing_bookmarks_html_file_ && !template_urls.empty() &&
+ !cancelled()) {
+ bridge_->SetKeywords(template_urls, -1, false);
+ } else {
+ STLDeleteContainerPointers(template_urls.begin(), template_urls.end());
+ }
+ if (!favicons.empty()) {
+ bridge_->SetFavIcons(favicons);
+ }
+}
+
+void Firefox2Importer::ImportPasswords() {
+ // Initializes NSS3.
+ NSSDecryptor decryptor;
+ if (!decryptor.Init(source_path_.ToWStringHack(),
+ source_path_.ToWStringHack()) &&
+ !decryptor.Init(app_path_.ToWStringHack(),
+ source_path_.ToWStringHack())) {
+ return;
+ }
+
+ // Firefox 2 uses signons2.txt to store the pssswords. If it doesn't
+ // exist, we try to find its older version.
+ FilePath file = source_path_.AppendASCII("signons2.txt");
+ if (!file_util::PathExists(file)) {
+ file = source_path_.AppendASCII("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) {
+ bridge_->SetPasswordForm(forms[i]);
+ }
+ }
+}
+
+void Firefox2Importer::ImportHistory() {
+ FilePath file = source_path_.AppendASCII("history.dat");
+ ImportHistoryFromFirefox2(file, bridge_);
+}
+
+void Firefox2Importer::ImportSearchEngines() {
+ std::vector<FilePath> files;
+ GetSearchEnginesXMLFiles(&files);
+
+ std::vector<TemplateURL*> search_engines;
+ ParseSearchEnginesFromXMLFiles(files, &search_engines);
+
+ int default_index =
+ GetFirefoxDefaultSearchEngineIndex(search_engines, source_path_);
+ bridge_->SetKeywords(search_engines, default_index, true);
+}
+
+void Firefox2Importer::ImportHomepage() {
+ GURL home_page = GetHomepage(source_path_);
+ if (home_page.is_valid() && !IsDefaultHomepage(home_page, app_path_)) {
+ bridge_->AddHomePage(home_page);
+ }
+}
+
+void Firefox2Importer::GetSearchEnginesXMLFiles(
+ std::vector<FilePath>* 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)
+ FilePath dir = app_path_.AppendASCII("searchplugins");
+ FindXMLFilesInDir(dir, files);
+
+ FilePath profile_dir = source_path_.AppendASCII("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;
+
+ base::CodepageToWide(line.substr(tag_end, end - tag_end), charset.c_str(),
+ base::OnStringConversionError::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
+ base::CodepageToWide(line.substr(tag_end, end - tag_end), charset.c_str(),
+ base::OnStringConversionError::SKIP, title);
+ HTMLUnescape(title);
+
+ // URL
+ if (GetAttribute(attribute_list, kHrefAttribute, &value)) {
+ std::wstring w_url;
+ base::CodepageToWide(value, charset.c_str(),
+ base::OnStringConversionError::SKIP, &w_url);
+ HTMLUnescape(&w_url);
+
+ string16 url16 = WideToUTF16Hack(w_url);
+
+ *url = GURL(url16);
+ }
+
+ // Favicon
+ if (GetAttribute(attribute_list, kIconAttribute, &value))
+ *favicon = GURL(value);
+
+ // Keyword
+ if (GetAttribute(attribute_list, kShortcutURLAttribute, &value)) {
+ base::CodepageToWide(value, charset.c_str(),
+ base::OnStringConversionError::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)) {
+ base::CodepageToWide(value, charset.c_str(),
+ base::OnStringConversionError::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 = begin + 1;
+ while (end < attribute_list.size()) {
+ if (attribute_list[end] == '"' &&
+ attribute_list[end - 1] != '\\') {
+ break;
+ }
+ end++;
+ }
+
+ if (end == attribute_list.size())
+ return false; // The value is not quoted.
+
+ *value = attribute_list.substr(begin, end - begin);
+ return true;
+}
+
+// static
+void Firefox2Importer::HTMLUnescape(std::wstring *text) {
+ string16 text16 = WideToUTF16Hack(*text);
+ ReplaceSubstringsAfterOffset(
+ &text16, 0, ASCIIToUTF16("&lt;"), ASCIIToUTF16("<"));
+ ReplaceSubstringsAfterOffset(
+ &text16, 0, ASCIIToUTF16("&gt;"), ASCIIToUTF16(">"));
+ ReplaceSubstringsAfterOffset(
+ &text16, 0, ASCIIToUTF16("&amp;"), ASCIIToUTF16("&"));
+ ReplaceSubstringsAfterOffset(
+ &text16, 0, ASCIIToUTF16("&quot;"), ASCIIToUTF16("\""));
+ ReplaceSubstringsAfterOffset(
+ &text16, 0, ASCIIToUTF16("&#39;"), ASCIIToUTF16("\'"));
+ text->assign(UTF16ToWideHack(text16));
+}
+
+// static
+void Firefox2Importer::FindXMLFilesInDir(
+ const FilePath& dir,
+ std::vector<FilePath>* xml_files) {
+ file_util::FileEnumerator file_enum(dir, false,
+ file_util::FileEnumerator::FILES,
+ FILE_PATH_LITERAL("*.xml"));
+ FilePath 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(chrome::kDataScheme))
+ 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..c320e38
--- /dev/null
+++ b/chrome/browser/importer/firefox2_importer.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2010 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 <set>
+
+#include "base/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "chrome/browser/importer/importer.h"
+#include "chrome/browser/importer/importer_data_types.h"
+
+class TemplateURL;
+
+// Importer for Mozilla Firefox 2.
+class Firefox2Importer : public Importer {
+ public:
+ Firefox2Importer();
+
+ // Importer methods.
+ virtual void StartImport(importer::ProfileInfo profile_info,
+ uint16 items,
+ ImporterBridge* bridge);
+
+ // Loads the default bookmarks in the Firefox installed at |firefox_app_path|,
+ // and stores their locations in |urls|.
+ static void LoadDefaultBookmarks(const FilePath& 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);
+
+ // Imports the bookmarks from the specified file. |template_urls| and
+ // |favicons| may be null, in which case TemplateURLs and favicons are
+ // not parsed. Any bookmarks in |default_urls| are ignored.
+ static void ImportBookmarksFile(
+ const FilePath& file_path,
+ const std::set<GURL>& default_urls,
+ bool import_to_bookmark_bar,
+ const std::wstring& first_folder_name,
+ Importer* importer,
+ std::vector<ProfileWriter::BookmarkEntry>* bookmarks,
+ std::vector<TemplateURL*>* template_urls,
+ std::vector<history::ImportedFavIconUsage>* favicons);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(FirefoxImporterTest, Firefox2BookmarkParse);
+ FRIEND_TEST_ALL_PREFIXES(FirefoxImporterTest, Firefox2CookesParse);
+
+ virtual ~Firefox2Importer();
+
+ 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<FilePath>* 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,
+ base::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 FilePath& dir,
+ std::vector<FilePath>* 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);
+
+ FilePath source_path_;
+ FilePath app_path_;
+ // If true, we only parse the bookmarks.html file specified as source_path_.
+ bool parsing_bookmarks_html_file_;
+
+ DISALLOW_COPY_AND_ASSIGN(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..2b95cc3
--- /dev/null
+++ b/chrome/browser/importer/firefox3_importer.cc
@@ -0,0 +1,532 @@
+// Copyright (c) 2010 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/message_loop.h"
+#include "base/scoped_ptr.h"
+#include "base/stl_util-inl.h"
+#include "base/string_util.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/importer/firefox2_importer.h"
+#include "chrome/browser/importer/firefox_importer_utils.h"
+#include "chrome/browser/importer/importer_bridge.h"
+#include "chrome/browser/importer/importer_data_types.h"
+#include "chrome/browser/importer/nss_decryptor.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/common/time_format.h"
+#include "chrome/common/sqlite_utils.h"
+#include "grit/generated_resources.h"
+#include "webkit/glue/password_form.h"
+
+using base::Time;
+using importer::BOOKMARKS_HTML;
+using importer::FAVORITES;
+using importer::HISTORY;
+using importer::HOME_PAGE;
+using importer::PASSWORDS;
+using importer::ProfileInfo;
+using importer::SEARCH_ENGINES;
+using webkit_glue::PasswordForm;
+
+void Firefox3Importer::StartImport(importer::ProfileInfo profile_info,
+ uint16 items,
+ ImporterBridge* bridge) {
+ bridge_ = bridge;
+ source_path_ = profile_info.source_path;
+ app_path_ = profile_info.app_path;
+
+
+ // The order here is important!
+ bridge_->NotifyStarted();
+ if ((items & importer::HOME_PAGE) && !cancelled())
+ ImportHomepage(); // Doesn't have a UI item.
+
+ // Note history should be imported before bookmarks because bookmark import
+ // will also import favicons and we store favicon for a URL only if the URL
+ // exist in history or bookmarks.
+ if ((items & importer::HISTORY) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::HISTORY);
+ ImportHistory();
+ bridge_->NotifyItemEnded(importer::HISTORY);
+ }
+
+ if ((items & importer::FAVORITES) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::FAVORITES);
+ ImportBookmarks();
+ bridge_->NotifyItemEnded(importer::FAVORITES);
+ }
+ if ((items & importer::SEARCH_ENGINES) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::SEARCH_ENGINES);
+ ImportSearchEngines();
+ bridge_->NotifyItemEnded(importer::SEARCH_ENGINES);
+ }
+ if ((items & importer::PASSWORDS) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::PASSWORDS);
+ ImportPasswords();
+ bridge_->NotifyItemEnded(importer::PASSWORDS);
+ }
+ bridge_->NotifyEnded();
+}
+
+void Firefox3Importer::ImportHistory() {
+ FilePath file = source_path_.AppendASCII("places.sqlite");
+ if (!file_util::PathExists(file))
+ return;
+
+ sqlite3* sqlite;
+ if (sqlite3_open(WideToUTF8(file.ToWStringHack()).c_str(),
+ &sqlite) != SQLITE_OK) {
+ return;
+ }
+ sqlite_utils::scoped_sqlite_db_ptr 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(UTF8ToUTF16(s.column_string(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()) {
+ bridge_->SetHistoryItems(rows);
+ }
+}
+
+void Firefox3Importer::ImportBookmarks() {
+ FilePath file = source_path_.AppendASCII("places.sqlite");
+ if (!file_util::PathExists(file))
+ return;
+
+ sqlite3* sqlite;
+ if (sqlite3_open(WideToUTF8(file.ToWStringHack()).c_str(),
+ &sqlite) != SQLITE_OK) {
+ return;
+ }
+ sqlite_utils::scoped_sqlite_db_ptr 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 =
+ bridge_->GetLocalizedString(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 (import_to_bookmark_bar()) {
+ 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 (!import_to_bookmark_bar())
+ 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()) {
+ const std::wstring& first_folder_name =
+ bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP_FROM_FIREFOX);
+ int options = 0;
+ if (import_to_bookmark_bar())
+ options = ProfileWriter::IMPORT_TO_BOOKMARK_BAR;
+ bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options);
+ }
+ if (!template_urls.empty() && !cancelled()) {
+ bridge_->SetKeywords(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);
+ bridge_->SetFavIcons(favicons);
+ }
+}
+
+void Firefox3Importer::ImportPasswords() {
+ // Initializes NSS3.
+ NSSDecryptor decryptor;
+ if (!decryptor.Init(source_path_.ToWStringHack(),
+ source_path_.ToWStringHack()) &&
+ !decryptor.Init(app_path_.ToWStringHack(),
+ source_path_.ToWStringHack())) {
+ return;
+ }
+
+ std::vector<PasswordForm> forms;
+ FilePath source_path = source_path_;
+ FilePath file = source_path.AppendASCII("signons.sqlite");
+ if (file_util::PathExists(file)) {
+ // Since Firefox 3.1, passwords are in signons.sqlite db.
+ decryptor.ReadAndParseSignons(file, &forms);
+ } else {
+ // Firefox 3.0 uses signons3.txt to store the passwords.
+ file = source_path.AppendASCII("signons3.txt");
+ if (!file_util::PathExists(file))
+ file = source_path.AppendASCII("signons2.txt");
+
+ std::string content;
+ file_util::ReadFileToString(file, &content);
+ decryptor.ParseSignons(content, &forms);
+ }
+
+ if (!cancelled()) {
+ for (size_t i = 0; i < forms.size(); ++i) {
+ bridge_->SetPasswordForm(forms[i]);
+ }
+ }
+}
+
+void Firefox3Importer::ImportSearchEngines() {
+ std::vector<FilePath> files;
+ GetSearchEnginesXMLFiles(&files);
+
+ std::vector<TemplateURL*> search_engines;
+ ParseSearchEnginesFromXMLFiles(files, &search_engines);
+ int default_index =
+ GetFirefoxDefaultSearchEngineIndex(search_engines, source_path_);
+ bridge_->SetKeywords(search_engines, default_index, true);
+}
+
+void Firefox3Importer::ImportHomepage() {
+ GURL home_page = GetHomepage(source_path_);
+ if (home_page.is_valid() && !IsDefaultHomepage(home_page, app_path_)) {
+ bridge_->AddHomePage(home_page);
+ }
+}
+
+void Firefox3Importer::GetSearchEnginesXMLFiles(
+ std::vector<FilePath>* files) {
+ FilePath file = source_path_.AppendASCII("search.sqlite");
+ if (!file_util::PathExists(file))
+ return;
+
+ sqlite3* sqlite;
+ if (sqlite3_open(WideToUTF8(file.ToWStringHack()).c_str(),
+ &sqlite) != SQLITE_OK) {
+ return;
+ }
+ sqlite_utils::scoped_sqlite_db_ptr db(sqlite);
+
+ SQLStatement s;
+ const char* stmt = "SELECT engineid FROM engine_data "
+ "WHERE engineid NOT IN "
+ "(SELECT engineid FROM engine_data "
+ "WHERE name='hidden') "
+ "ORDER BY value ASC";
+
+ if (s.prepare(db.get(), stmt) != SQLITE_OK)
+ return;
+
+ FilePath app_path = app_path_.AppendASCII("searchplugins");
+ FilePath profile_path = source_path_.AppendASCII("searchplugins");
+
+ // Firefox doesn't store a search engine in its sqlite database unless
+ // the user has changed the default definition of engine. So we get search
+ // engines from sqlite db as well as from file system.
+ if (s.step() == SQLITE_ROW) {
+ const std::wstring kAppPrefix = L"[app]/";
+ const std::wstring kProfilePrefix = L"[profile]/";
+ do {
+ FilePath 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) {
+ // Remove '[app]/'.
+ file = app_path.Append(FilePath::FromWStringHack(
+ engine.substr(index + kAppPrefix.length())));
+ } else if ((index = engine.find(kProfilePrefix)) != std::wstring::npos) {
+ // Remove '[profile]/'.
+ file = profile_path.Append(
+ FilePath::FromWStringHack(
+ engine.substr(index + kProfilePrefix.length())));
+ } else {
+ // Looks like absolute path to the file.
+ file = FilePath::FromWStringHack(engine);
+ }
+ files->push_back(file);
+ } while (s.step() == SQLITE_ROW && !cancelled());
+ }
+
+ // Get search engine definition from file system.
+ file_util::FileEnumerator engines(app_path, false,
+ file_util::FileEnumerator::FILES);
+ for (FilePath engine_path = engines.Next(); !engine_path.value().empty();
+ engine_path = engines.Next()) {
+ files->push_back(engine_path);
+ }
+}
+
+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_wstring(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_wstring(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..f8bfa2f
--- /dev/null
+++ b/chrome/browser/importer/firefox3_importer.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2010 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 <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "chrome/browser/importer/importer.h"
+#include "chrome/browser/importer/importer_data_types.h"
+#include "googleurl/src/gurl.h"
+
+struct sqlite3;
+
+// 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() { }
+
+ // Importer methods.
+ virtual void StartImport(importer::ProfileInfo profile_info,
+ uint16 items,
+ ImporterBridge* bridge);
+
+ private:
+ typedef std::map<int64, std::set<GURL> > FaviconMap;
+
+ virtual ~Firefox3Importer() { }
+
+ 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<FilePath>* 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;
+ base::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);
+
+ FilePath source_path_;
+ FilePath app_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(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..42c23ac
--- /dev/null
+++ b/chrome/browser/importer/firefox_importer_unittest.cc
@@ -0,0 +1,178 @@
+// 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_path.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/importer/firefox2_importer.h"
+#include "chrome/browser/importer/firefox_importer_unittest_utils.h"
+#include "chrome/browser/importer/firefox_importer_utils.h"
+#include "chrome/browser/importer/nss_decryptor.h"
+#include "chrome/common/chrome_paths.h"
+
+using base::Time;
+
+// The following 2 tests require the use of the NSSDecryptor, on OSX this needs
+// to run in a separate process, so we use a proxy object so we can share the
+// same test between platforms.
+TEST(FirefoxImporterTest, Firefox2NSS3Decryptor) {
+ FilePath nss_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &nss_path));
+#ifdef OS_MACOSX
+ nss_path = nss_path.AppendASCII("firefox2_nss_mac");
+#else
+ nss_path = nss_path.AppendASCII("firefox2_nss");
+#endif // !OS_MACOSX
+ FilePath db_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &db_path));
+ db_path = db_path.AppendASCII("firefox2_profile");
+
+ FFUnitTestDecryptorProxy decryptor_proxy;
+ ASSERT_TRUE(decryptor_proxy.Setup(nss_path.ToWStringHack()));
+
+ EXPECT_TRUE(decryptor_proxy.DecryptorInit(nss_path.ToWStringHack(),
+ db_path.ToWStringHack()));
+ EXPECT_EQ(ASCIIToUTF16("hello"),
+ decryptor_proxy.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECBJ"
+ "M63MpT9rtBAjMCm7qo/EhlA=="));
+ // Test UTF-16 encoding.
+ EXPECT_EQ(WideToUTF16(L"\x4E2D"),
+ decryptor_proxy.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECN9"
+ "OQ5ZFmhb8BAiFo1Z+fUvaIQ=="));
+}
+
+TEST(FirefoxImporterTest, Firefox3NSS3Decryptor) {
+ FilePath nss_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &nss_path));
+#ifdef OS_MACOSX
+ nss_path = nss_path.AppendASCII("firefox3_nss_mac");
+#else
+ nss_path = nss_path.AppendASCII("firefox3_nss");
+#endif // !OS_MACOSX
+ FilePath db_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &db_path));
+ db_path = db_path.AppendASCII("firefox3_profile");
+
+ FFUnitTestDecryptorProxy decryptor_proxy;
+ ASSERT_TRUE(decryptor_proxy.Setup(nss_path.ToWStringHack()));
+
+ EXPECT_TRUE(decryptor_proxy.DecryptorInit(nss_path.ToWStringHack(),
+ db_path.ToWStringHack()));
+ EXPECT_EQ(ASCIIToUTF16("hello"),
+ decryptor_proxy.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECKa"
+ "jtRg4qFSHBAhv9luFkXgDJA=="));
+ // Test UTF-16 encoding.
+ EXPECT_EQ(WideToUTF16(L"\x4E2D"),
+ decryptor_proxy.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECLW"
+ "qqiccfQHWBAie74hxnULxlw=="));
+}
+
+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=%22%3C%3E%22", url.spec());
+ EXPECT_EQ(L"", shortcut);
+ EXPECT_EQ(L"", post_data);
+ EXPECT_TRUE(Time() == add_date);
+
+ result = Firefox2Importer::ParseBookmarkFromLine(
+ "<DT><A HREF=\"http://domain.com/?g=&quot;\"\">name</A>",
+ charset, &title, &url, &favicon, &shortcut, &add_date, &post_data);
+ EXPECT_TRUE(result);
+ EXPECT_EQ(L"name", title);
+ EXPECT_EQ("http://domain.com/?g=%22", 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);
+}
diff --git a/chrome/browser/importer/firefox_importer_unittest_messages_internal.h b/chrome/browser/importer/firefox_importer_unittest_messages_internal.h
new file mode 100644
index 0000000..68b2582
--- /dev/null
+++ b/chrome/browser/importer/firefox_importer_unittest_messages_internal.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2009 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.
+
+// This header is meant to be included in multiple passes, hence no traditional
+// header guard.
+// See ipc_message_macros.h for explanation of the macros and passes.
+
+#include "ipc/ipc_message_macros.h"
+
+// Messages definitions for messages sent between the unit test binary and
+// a child process by FFUnitTestDecryptorProxy.
+IPC_BEGIN_MESSAGES(Test)
+
+// Server->Child: Initialize the decrytor with the following paramters.
+IPC_MESSAGE_CONTROL2(Msg_Decryptor_Init,
+ std::wstring /* dll_path */,
+ std::wstring /* db_path */)
+// Child->Server: Return paramter from init call.
+IPC_MESSAGE_CONTROL1(Msg_Decryptor_InitReturnCode,
+ bool /* ret */)
+
+// Server->Child: Decrypt a given string.
+IPC_MESSAGE_CONTROL1(Msg_Decrypt,
+ std::string /* crypt */)
+// Child->Server: Decrypted String.
+IPC_MESSAGE_CONTROL1(Msg_Decryptor_Response,
+ string16 /* unencrypted_str */)
+
+// Server->Child: Die.
+IPC_MESSAGE_CONTROL0(Msg_Decryptor_Quit)
+
+IPC_END_MESSAGES(Test)
diff --git a/chrome/browser/importer/firefox_importer_unittest_utils.h b/chrome/browser/importer/firefox_importer_unittest_utils.h
new file mode 100644
index 0000000..3eb12d6
--- /dev/null
+++ b/chrome/browser/importer/firefox_importer_unittest_utils.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2009 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_UNITTEST_UTILS_H_
+#define CHROME_BROWSER_IMPORTER_FIREFOX_IMPORTER_UNITTEST_UTILS_H_
+
+#include "base/basictypes.h"
+#include "base/process_util.h"
+#include "base/scoped_ptr.h"
+#include "chrome/browser/importer/nss_decryptor.h"
+
+class FFDecryptorServerChannelListener;
+namespace IPC {
+ class Channel;
+} // namespace IPC
+class MessageLoopForIO;
+
+// On OS X NSSDecryptor needs to run in a separate process. To allow us to use
+// the same unit test on all platforms we use a proxy class which spawns a
+// child process to do decryption on OS X, and calls through directly
+// to NSSDecryptor on other platforms.
+// On OS X:
+// 2 IPC messages are sent for every method of NSSDecryptor, one containing the
+// input arguments sent from Server->Child and one coming back containing
+// the return argument e.g.
+//
+// -> Msg_Decryptor_Init(dll_path, db_path)
+// <- Msg_Decryptor_InitReturnCode(bool)
+class FFUnitTestDecryptorProxy {
+ public:
+ FFUnitTestDecryptorProxy();
+ virtual ~FFUnitTestDecryptorProxy();
+
+ // Initialize a decryptor, returns true if the object was
+ // constructed successfully.
+ bool Setup(const std::wstring& nss_path);
+
+ // This match the parallel functions in NSSDecryptor.
+ bool DecryptorInit(const std::wstring& dll_path, const std::wstring& db_path);
+ string16 Decrypt(const std::string& crypt);
+
+ private:
+#if defined(OS_MACOSX)
+ // Blocks until either a timeout is reached, or until the client process
+ // responds to an IPC message.
+ // Returns true if a reply was received successfully and false if the
+ // the operation timed out.
+ bool WaitForClientResponse();
+
+ base::ProcessHandle child_process_;
+ scoped_ptr<IPC::Channel> channel_;
+ scoped_ptr<FFDecryptorServerChannelListener> listener_;
+ scoped_ptr<MessageLoopForIO> message_loop_;
+#else
+ NSSDecryptor decryptor_;
+#endif // !OS_MACOSX
+ DISALLOW_COPY_AND_ASSIGN(FFUnitTestDecryptorProxy);
+};
+
+// On Non-OSX platforms FFUnitTestDecryptorProxy simply calls through to
+// NSSDecryptor.
+#if !defined(OS_MACOSX)
+FFUnitTestDecryptorProxy::FFUnitTestDecryptorProxy() {
+}
+
+FFUnitTestDecryptorProxy::~FFUnitTestDecryptorProxy() {
+}
+
+bool FFUnitTestDecryptorProxy::Setup(const std::wstring& /* nss_path */) {
+ return true;
+}
+
+bool FFUnitTestDecryptorProxy::DecryptorInit(const std::wstring& dll_path,
+ const std::wstring& db_path) {
+ return decryptor_.Init(dll_path, db_path);
+}
+
+string16 FFUnitTestDecryptorProxy::Decrypt(const std::string& crypt) {
+ return decryptor_.Decrypt(crypt);
+}
+#endif // !OS_MACOSX
+
+#endif // CHROME_BROWSER_IMPORTER_FIREFOX_IMPORTER_UNITTEST_UTILS_H_
diff --git a/chrome/browser/importer/firefox_importer_unittest_utils_mac.cc b/chrome/browser/importer/firefox_importer_unittest_utils_mac.cc
new file mode 100644
index 0000000..03dddc7
--- /dev/null
+++ b/chrome/browser/importer/firefox_importer_unittest_utils_mac.cc
@@ -0,0 +1,265 @@
+// Copyright (c) 2009 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_unittest_utils.h"
+
+#include "base/command_line.h"
+#include "base/debug_on_start.h"
+#include "base/file_path.h"
+#include "base/message_loop.h"
+#include "chrome/browser/importer/firefox_importer_utils.h"
+#include "ipc/ipc_channel.h"
+#include "ipc/ipc_descriptors.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_utils.h"
+#include "ipc/ipc_switches.h"
+#include "testing/multiprocess_func_list.h"
+
+// Definition of IPC Messages used for this test.
+#define MESSAGES_INTERNAL_FILE \
+ "chrome/browser/importer/firefox_importer_unittest_messages_internal.h"
+#include "ipc/ipc_message_macros.h"
+
+namespace {
+
+// Name of IPC Channel to use for Server<-> Child Communications.
+const char kTestChannelID[] = "T1";
+
+// Launch the child process:
+// |nss_path| - path to the NSS directory holding the decryption libraries.
+// |channel| - IPC Channel to use for communication.
+// |handle| - On return, the process handle to use to communicate with the
+// child.
+bool LaunchNSSDecrypterChildProcess(const std::wstring& nss_path,
+ const IPC::Channel& channel, base::ProcessHandle* handle) {
+ CommandLine cl(*CommandLine::ForCurrentProcess());
+ cl.AppendSwitchWithValue("client", "NSSDecrypterChildProcess");
+
+ FilePath ff_dylib_dir = FilePath::FromWStringHack(nss_path);
+ // Set env variable needed for FF encryption libs to load.
+ // See "chrome/browser/importer/nss_decryptor_mac.mm" for an explanation of
+ // why we need this.
+ base::environment_vector env;
+ std::pair<std::string, std::string> dyld_override;
+ dyld_override.first = "DYLD_FALLBACK_LIBRARY_PATH";
+ dyld_override.second = ff_dylib_dir.value();
+ env.push_back(dyld_override);
+
+ base::file_handle_mapping_vector fds_to_map;
+ const int ipcfd = channel.GetClientFileDescriptor();
+ if (ipcfd > -1) {
+ fds_to_map.push_back(std::pair<int,int>(ipcfd, kPrimaryIPCChannel + 3));
+ } else {
+ return false;
+ }
+
+ bool debug_on_start = CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDebugChildren);
+ return base::LaunchApp(cl.argv(), env, fds_to_map, debug_on_start, handle);
+}
+
+} // namespace
+
+//----------------------- Server --------------------
+
+// Class to communicate on the server side of the IPC Channel.
+// Method calls are sent over IPC and replies are read back into class
+// variables.
+// This class needs to be called on a single thread.
+class FFDecryptorServerChannelListener : public IPC::Channel::Listener {
+ public:
+ FFDecryptorServerChannelListener()
+ : got_result(false), sender_(NULL) {}
+
+ void SetSender(IPC::Message::Sender* sender) {
+ sender_ = sender;
+ }
+
+ void OnInitDecryptorResponse(bool result) {
+ DCHECK(!got_result);
+ result_bool = result;
+ got_result = true;
+ MessageLoop::current()->Quit();
+ }
+
+ void OnDecryptedTextResonse(const string16& decrypted_text) {
+ DCHECK(!got_result);
+ result_string = decrypted_text;
+ got_result = true;
+ MessageLoop::current()->Quit();
+ }
+
+ void QuitClient() {
+ if (sender_)
+ sender_->Send(new Msg_Decryptor_Quit());
+ }
+
+ virtual void OnMessageReceived(const IPC::Message& msg) {
+ IPC_BEGIN_MESSAGE_MAP(FFDecryptorServerChannelListener, msg)
+ IPC_MESSAGE_HANDLER(Msg_Decryptor_InitReturnCode, OnInitDecryptorResponse)
+ IPC_MESSAGE_HANDLER(Msg_Decryptor_Response, OnDecryptedTextResonse)
+ IPC_END_MESSAGE_MAP()
+ }
+
+ // If an error occured, just kill the message Loop.
+ virtual void OnChannelError() {
+ got_result = false;
+ MessageLoop::current()->Quit();
+ }
+
+ // Results of IPC calls.
+ string16 result_string;
+ bool result_bool;
+ // True if IPC call succeeded and data in above variables is valid.
+ bool got_result;
+
+ private:
+ IPC::Message::Sender* sender_; // weak
+};
+
+FFUnitTestDecryptorProxy::FFUnitTestDecryptorProxy()
+ : child_process_(0) {
+}
+
+bool FFUnitTestDecryptorProxy::Setup(const std::wstring& nss_path) {
+ // Create a new message loop and spawn the child process.
+ message_loop_.reset(new MessageLoopForIO());
+
+ listener_.reset(new FFDecryptorServerChannelListener());
+ channel_.reset(new IPC::Channel(kTestChannelID,
+ IPC::Channel::MODE_SERVER,
+ listener_.get()));
+ channel_->Connect();
+ listener_->SetSender(channel_.get());
+
+ // Spawn child and set up sync IPC connection.
+ bool ret = LaunchNSSDecrypterChildProcess(nss_path,
+ *(channel_.get()),
+ &child_process_);
+ return ret && (child_process_ != 0);
+}
+
+FFUnitTestDecryptorProxy::~FFUnitTestDecryptorProxy() {
+ listener_->QuitClient();
+ channel_->Close();
+
+ if (child_process_) {
+ base::WaitForSingleProcess(child_process_, 5000);
+ base::CloseProcessHandle(child_process_);
+ }
+}
+
+// A message_loop task that quits the message loop when invoked, setting cancel
+// causes the task to do nothing when invoked.
+class CancellableQuitMsgLoop : public base::RefCounted<CancellableQuitMsgLoop> {
+ public:
+ CancellableQuitMsgLoop() : cancelled_(false) {}
+ void QuitNow() {
+ if (!cancelled_)
+ MessageLoop::current()->Quit();
+ }
+ bool cancelled_;
+};
+
+// Spin until either a client response arrives or a timeout occurs.
+bool FFUnitTestDecryptorProxy::WaitForClientResponse() {
+ const int64 kLoopTimeoutMS = 10 * 1000; // 10 seconds.
+
+ // What we're trying to do here is to wait for an RPC message to go over the
+ // wire and the client to reply. If the client does not replyy by a given
+ // timeout we kill the message loop.
+ // The way we do this is to post a CancellableQuitMsgLoop for 3 seconds in
+ // the future and cancel it if an RPC message comes back earlier.
+ // This relies on the IPC listener class to quit the message loop itself when
+ // a message comes in.
+ scoped_refptr<CancellableQuitMsgLoop> quit_task =
+ new CancellableQuitMsgLoop();
+ MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod(
+ quit_task.get(), &CancellableQuitMsgLoop::QuitNow), kLoopTimeoutMS);
+
+ message_loop_->Run();
+ bool ret = !quit_task->cancelled_;
+ quit_task->cancelled_ = false;
+ return ret;
+}
+
+bool FFUnitTestDecryptorProxy::DecryptorInit(const std::wstring& dll_path,
+ const std::wstring& db_path) {
+ channel_->Send(new Msg_Decryptor_Init(dll_path, db_path));
+ bool ok = WaitForClientResponse();
+ if (ok && listener_->got_result) {
+ listener_->got_result = false;
+ return listener_->result_bool;
+ }
+ return false;
+}
+
+string16 FFUnitTestDecryptorProxy::Decrypt(const std::string& crypt) {
+ channel_->Send(new Msg_Decrypt(crypt));
+ bool ok = WaitForClientResponse();
+ if (ok && listener_->got_result) {
+ listener_->got_result = false;
+ return listener_->result_string;
+ }
+ return string16();
+}
+
+//---------------------------- Child Process -----------------------
+
+// Class to listen on the client side of the ipc channel, it calls through
+// to the NSSDecryptor and sends back a reply.
+class FFDecryptorClientChannelListener : public IPC::Channel::Listener {
+ public:
+ FFDecryptorClientChannelListener()
+ : sender_(NULL) {}
+
+ void SetSender(IPC::Message::Sender* sender) {
+ sender_ = sender;
+ }
+
+ void OnDecryptor_Init(std::wstring dll_path, std::wstring db_path) {
+ bool ret = decryptor_.Init(dll_path, db_path);
+ sender_->Send(new Msg_Decryptor_InitReturnCode(ret));
+ }
+
+ void OnDecrypt(std::string crypt) {
+ string16 unencrypted_str = decryptor_.Decrypt(crypt);
+ sender_->Send(new Msg_Decryptor_Response(unencrypted_str));
+ }
+
+ void OnQuitRequest() {
+ MessageLoop::current()->Quit();
+ }
+
+ virtual void OnMessageReceived(const IPC::Message& msg) {
+ IPC_BEGIN_MESSAGE_MAP(FFDecryptorClientChannelListener, msg)
+ IPC_MESSAGE_HANDLER(Msg_Decryptor_Init, OnDecryptor_Init)
+ IPC_MESSAGE_HANDLER(Msg_Decrypt, OnDecrypt)
+ IPC_MESSAGE_HANDLER(Msg_Decryptor_Quit, OnQuitRequest)
+ IPC_END_MESSAGE_MAP()
+ }
+
+ virtual void OnChannelError() {
+ MessageLoop::current()->Quit();
+ }
+
+ private:
+ NSSDecryptor decryptor_;
+ IPC::Message::Sender* sender_;
+};
+
+// Entry function in child process.
+MULTIPROCESS_TEST_MAIN(NSSDecrypterChildProcess) {
+ MessageLoopForIO main_message_loop;
+ FFDecryptorClientChannelListener listener;
+
+ IPC::Channel channel(kTestChannelID, IPC::Channel::MODE_CLIENT, &listener);
+ channel.Connect();
+ listener.SetSender(&channel);
+
+ // run message loop
+ MessageLoop::current()->Run();
+
+ return 0;
+}
diff --git a/chrome/browser/importer/firefox_importer_utils.cc b/chrome/browser/importer/firefox_importer_utils.cc
new file mode 100644
index 0000000..b9a7a74
--- /dev/null
+++ b/chrome/browser/importer/firefox_importer_utils.cc
@@ -0,0 +1,450 @@
+// Copyright (c) 2010 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 <algorithm>
+#include <map>
+#include <string>
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/browser/search_engines/template_url_model.h"
+#include "chrome/browser/search_engines/template_url_parser.h"
+#include "googleurl/src/gurl.h"
+
+namespace {
+
+// FirefoxURLParameterFilter is used to remove parameter mentioning Firefox from
+// the search URL when importing search engines.
+class FirefoxURLParameterFilter : public TemplateURLParser::ParameterFilter {
+ public:
+ FirefoxURLParameterFilter() {}
+ virtual ~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") != std::string::npos ||
+ low_value.find("firefox") != std::string::npos ||
+ low_value.find("moz:") != std::string::npos )
+ return false;
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FirefoxURLParameterFilter);
+};
+} // namespace
+
+FilePath GetFirefoxProfilePath() {
+ DictionaryValue root;
+ FilePath ini_file = GetProfilesINI();
+ ParseProfileINI(ini_file, &root);
+
+ FilePath source_path;
+ for (int i = 0; ; ++i) {
+ std::string current_profile = StringPrintf("Profile%d", i);
+ if (!root.HasKeyASCII(current_profile)) {
+ // Profiles are continuously numbered. So we exit when we can't
+ // find the i-th one.
+ break;
+ }
+ std::string is_relative;
+ string16 path16;
+ if (root.GetStringASCII(current_profile + ".IsRelative", &is_relative) &&
+ root.GetString(current_profile + ".Path", &path16)) {
+#if defined(OS_WIN)
+ ReplaceSubstringsAfterOffset(
+ &path16, 0, ASCIIToUTF16("/"), ASCIIToUTF16("\\"));
+#endif
+ FilePath path = FilePath::FromWStringHack(UTF16ToWide(path16));
+
+ // 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 == "1") {
+ path = ini_file.DirName().Append(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::string is_default;
+ if ((root.GetStringASCII(current_profile + ".Default", &is_default) &&
+ is_default == "1") || i == 0) {
+ // We have found the default profile.
+ return path;
+ }
+ }
+ }
+ return FilePath();
+}
+
+
+bool GetFirefoxVersionAndPathFromProfile(const FilePath& profile_path,
+ int* version,
+ FilePath* app_path) {
+ bool ret = false;
+ FilePath compatibility_file = profile_path.AppendASCII("compatibility.ini");
+ std::string content;
+ file_util::ReadFileToString(compatibility_file, &content);
+ ReplaceSubstringsAfterOffset(&content, 0, "\r\n", "\n");
+ std::vector<std::string> lines;
+ SplitString(content, '\n', &lines);
+
+ for (size_t i = 0; i < lines.size(); ++i) {
+ const std::string& line = lines[i];
+ if (line.empty() || line[0] == '#' || line[0] == ';')
+ continue;
+ size_t equal = line.find('=');
+ if (equal != std::string::npos) {
+ std::string key = line.substr(0, equal);
+ if (key == "LastVersion") {
+ *version = line.substr(equal + 1)[0] - '0';
+ ret = true;
+ } else if (key == "LastAppDir") {
+ // TODO(evanm): If the path in question isn't convertible to
+ // UTF-8, what does Firefox do? If it puts raw bytes in the
+ // file, we could go straight from bytes -> filepath;
+ // otherwise, we're out of luck here.
+ *app_path = FilePath::FromWStringHack(
+ UTF8ToWide(line.substr(equal + 1)));
+ }
+ }
+ }
+ return ret;
+}
+
+void ParseProfileINI(const FilePath& 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 (size_t i = 0; i < arraysize(kInvalidSchemes); ++i) {
+ if (url.SchemeIs(kInvalidSchemes[i]))
+ return false;
+ }
+
+ return true;
+}
+
+void ParseSearchEnginesFromXMLFiles(const std::vector<FilePath>& xml_files,
+ std::vector<TemplateURL*>* search_engines) {
+ DCHECK(search_engines);
+
+ std::map<std::string, 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<FilePath>::const_iterator file_iter = xml_files.begin();
+ file_iter != xml_files.end(); ++file_iter) {
+ file_util::ReadFileToString(*file_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::string url = template_url->url()->url();
+ std::map<std::string, 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::string, 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 FilePath& path, std::string* content) {
+ if (content == NULL)
+ return false;
+
+ file_util::ReadFileToString(path, content);
+
+ if (content->empty()) {
+ NOTREACHED() << "Firefox preference file " << path.value()
+ << " is empty.";
+ return false;
+ }
+
+ return true;
+}
+
+std::string ReadBrowserConfigProp(const FilePath& app_path,
+ const std::string& pref_key) {
+ std::string content;
+ if (!ReadPrefFile(app_path.AppendASCII("browserconfig.properties"), &content))
+ return "";
+
+ // This file has the syntax: key=value.
+ size_t prop_index = content.find(pref_key + "=");
+ if (prop_index == std::string::npos)
+ return "";
+
+ size_t start = prop_index + pref_key.length();
+ size_t stop = std::string::npos;
+ if (start != std::string::npos)
+ stop = content.find("\n", start + 1);
+
+ if (start == std::string::npos ||
+ stop == std::string::npos || (start == stop)) {
+ NOTREACHED() << "Firefox property " << pref_key << " could not be parsed.";
+ return "";
+ }
+
+ return content.substr(start + 1, stop - start - 1);
+}
+
+std::string ReadPrefsJsValue(const FilePath& profile_path,
+ const std::string& pref_key) {
+ std::string content;
+ if (!ReadPrefFile(profile_path.AppendASCII("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 == std::string::npos)
+ return "";
+
+ size_t start = prop_index + search_for.length();
+ size_t stop = std::string::npos;
+ if (start != std::string::npos)
+ stop = content.find(")", start + 1);
+
+ if (start == std::string::npos || stop == std::string::npos) {
+ 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 FilePath& 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"));
+
+ if (default_se_name.empty()) {
+ // browser.search.selectedEngine does not exist if the user has not changed
+ // from the default (or has selected the default).
+ // TODO: should fallback to 'browser.search.defaultengine' if selectedEngine
+ // is empty.
+ return -1;
+ }
+
+ 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 FilePath& 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 FilePath& 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;
+}
+
+bool ParsePrefFile(const FilePath& pref_file, DictionaryValue* prefs) {
+ // The string that is before a pref key.
+ const std::string kUserPrefString = "user_pref(\"";
+ std::string contents;
+ if (!file_util::ReadFileToString(pref_file, &contents))
+ return false;
+
+ std::vector<std::string> lines;
+ Tokenize(contents, "\n", &lines);
+
+ for (std::vector<std::string>::const_iterator iter = lines.begin();
+ iter != lines.end(); ++iter) {
+ const std::string& line = *iter;
+ size_t start_key = line.find(kUserPrefString);
+ if (start_key == std::string::npos)
+ continue; // Could be a comment or a blank line.
+ start_key += kUserPrefString.length();
+ size_t stop_key = line.find('"', start_key);
+ if (stop_key == std::string::npos) {
+ LOG(ERROR) << "Invalid key found in Firefox pref file '" <<
+ pref_file.value() << "' line is '" << line << "'.";
+ continue;
+ }
+ std::string key = line.substr(start_key, stop_key - start_key);
+ size_t start_value = line.find(',', stop_key + 1);
+ if (start_value == std::string::npos) {
+ LOG(ERROR) << "Invalid value found in Firefox pref file '" <<
+ pref_file.value() << "' line is '" << line << "'.";
+ continue;
+ }
+ size_t stop_value = line.find(");", start_value + 1);
+ if (stop_value == std::string::npos) {
+ LOG(ERROR) << "Invalid value found in Firefox pref file '" <<
+ pref_file.value() << "' line is '" << line << "'.";
+ continue;
+ }
+ std::string value = line.substr(start_value + 1,
+ stop_value - start_value - 1);
+ TrimWhitespace(value, TRIM_ALL, &value);
+ // Value could be a boolean.
+ bool is_value_true = LowerCaseEqualsASCII(value, "true");
+ if (is_value_true || LowerCaseEqualsASCII(value, "false")) {
+ prefs->SetBoolean(ASCIIToWide(key), is_value_true);
+ continue;
+ }
+
+ // Value could be a string.
+ if (value.size() >= 2U &&
+ value[0] == '"' && value[value.size() - 1] == '"') {
+ value = value.substr(1, value.size() - 2);
+ // ValueString only accept valid UTF-8. Simply ignore that entry if it is
+ // not UTF-8.
+ if (IsStringUTF8(value))
+ prefs->SetString(ASCIIToWide(key), value);
+ else
+ LOG(INFO) << "Non UTF8 value for key " << key << ", ignored.";
+ continue;
+ }
+
+ // Or value could be an integer.
+ int int_value = 0;
+ if (StringToInt(value, &int_value)) {
+ prefs->SetInteger(ASCIIToWide(key), int_value);
+ continue;
+ }
+
+ LOG(ERROR) << "Invalid value found in Firefox pref file '" <<
+ pref_file.value() << "' value is '" << value << "'.";
+ }
+ return true;
+}
diff --git a/chrome/browser/importer/firefox_importer_utils.h b/chrome/browser/importer/firefox_importer_utils.h
new file mode 100644
index 0000000..1ad7ddb
--- /dev/null
+++ b/chrome/browser/importer/firefox_importer_utils.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2009 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 <vector>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "build/build_config.h"
+
+class DictionaryValue;
+class GURL;
+class TemplateURL;
+
+#if defined(OS_WIN)
+// Detects which version of Firefox is installed from registry. Returns its
+// major version, and drops the minor version. Returns 0 if
+// failed. If there are indicators of both FF2 and FF3 it is
+// biased to return the biggest version.
+int GetCurrentFirefoxMajorVersionFromRegistry();
+
+// Detects where Firefox lives. Returns a empty string if Firefox
+// is not installed.
+std::wstring GetFirefoxInstallPathFromRegistry();
+#endif // OS_WIN
+
+#if defined(OS_MACOSX)
+
+// Get the directory in which the Firefox .dylibs live, we need to load these
+// in order to decoded FF profile passwords.
+// The Path is usuall FF App Bundle/Contents/Mac OS/
+// Returns empty path on failure.
+FilePath GetFirefoxDylibPath();
+#endif // OS_MACOSX
+
+// Returns the path to the Firefox profile.
+FilePath GetFirefoxProfilePath();
+
+// Detects version of Firefox and installation path from given Firefox profile
+bool GetFirefoxVersionAndPathFromProfile(const FilePath& profile_path,
+ int* version,
+ FilePath* app_path);
+
+// 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.
+FilePath GetProfilesINI();
+
+// 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(const FilePath& 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<FilePath>& 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 FilePath& profile_path);
+
+// Returns the home page set in Firefox in a particular profile.
+GURL GetHomepage(const FilePath& 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 FilePath& app_path);
+
+// Parses the prefs found in the file |pref_file| and puts the key/value pairs
+// in |prefs|. Keys are strings, and values can be strings, booleans or
+// integers. Returns true if it succeeded, false otherwise (in which case
+// |prefs| is not filled).
+// Note: for strings, only valid UTF-8 string values are supported. If a
+// key/pair is not valid UTF-8, it is ignored and will not appear in |prefs|.
+bool ParsePrefFile(const FilePath& pref_file, DictionaryValue* prefs);
+
+#endif // CHROME_BROWSER_IMPORTER_FIREFOX_IMPORTER_UTILS_H_
diff --git a/chrome/browser/importer/firefox_importer_utils_linux.cc b/chrome/browser/importer/firefox_importer_utils_linux.cc
new file mode 100644
index 0000000..eeefd38
--- /dev/null
+++ b/chrome/browser/importer/firefox_importer_utils_linux.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2009 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 "base/file_util.h"
+
+FilePath GetProfilesINI() {
+ FilePath ini_file;
+ // The default location of the profile folder containing user data is
+ // under user HOME directory in .mozilla/firefox folder on Linux.
+ FilePath home = file_util::GetHomeDir();
+ if (!home.empty()) {
+ ini_file = home.Append(".mozilla/firefox/profiles.ini");
+ }
+ if (file_util::PathExists(ini_file))
+ return ini_file;
+
+ return FilePath();
+}
diff --git a/chrome/browser/importer/firefox_importer_utils_mac.mm b/chrome/browser/importer/firefox_importer_utils_mac.mm
new file mode 100644
index 0000000..08f222d
--- /dev/null
+++ b/chrome/browser/importer/firefox_importer_utils_mac.mm
@@ -0,0 +1,40 @@
+// Copyright (c) 2009 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 <Cocoa/Cocoa.h>
+
+#include "chrome/browser/importer/firefox_importer_utils.h"
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+
+FilePath GetProfilesINI() {
+ FilePath app_data_path;
+ if (!PathService::Get(base::DIR_APP_DATA, &app_data_path)) {
+ return FilePath();
+ }
+ FilePath ini_file = app_data_path.Append("Firefox").Append("profiles.ini");
+ if (!file_util::PathExists(ini_file)) {
+ return FilePath();
+ }
+ return ini_file;
+}
+
+FilePath GetFirefoxDylibPath() {
+ CFURLRef appURL = nil;
+ if (LSFindApplicationForInfo(kLSUnknownCreator,
+ CFSTR("org.mozilla.firefox"),
+ NULL,
+ NULL,
+ &appURL) != noErr) {
+ return FilePath();
+ }
+ NSBundle *ff_bundle = [NSBundle
+ bundleWithPath:[reinterpret_cast<const NSURL*>(appURL) path]];
+ CFRelease(appURL);
+ NSString *ff_library_path = [[ff_bundle executablePath]
+ stringByDeletingLastPathComponent];
+
+ return FilePath([ff_library_path fileSystemRepresentation]);
+}
diff --git a/chrome/browser/importer/firefox_importer_utils_win.cc b/chrome/browser/importer/firefox_importer_utils_win.cc
new file mode 100644
index 0000000..77e3fcb
--- /dev/null
+++ b/chrome/browser/importer/firefox_importer_utils_win.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2010 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/registry.h"
+
+// 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
+};
+
+int GetCurrentFirefoxMajorVersionFromRegistry() {
+ TCHAR ver_buffer[128];
+ DWORD ver_buffer_length = sizeof(ver_buffer);
+ int highest_version = 0;
+ // 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) {
+ RegKey reg_key(kFireFoxRegistryPaths[i],
+ L"Software\\Mozilla\\Mozilla Firefox");
+
+ bool result = reg_key.ReadValue(L"CurrentVersion", ver_buffer,
+ &ver_buffer_length, NULL);
+ if (!result)
+ continue;
+ highest_version = std::max(highest_version, _wtoi(ver_buffer));
+ }
+ return highest_version;
+}
+
+std::wstring GetFirefoxInstallPathFromRegistry() {
+ // 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);
+ RegKey reg_key(HKEY_LOCAL_MACHINE, registry_path.c_str());
+ bool result = reg_key.ReadValue(L"CurrentVersion", buffer,
+ &buffer_length, NULL);
+ if (!result)
+ return std::wstring();
+ registry_path += L"\\" + std::wstring(buffer) + L"\\Main";
+ buffer_length = sizeof(buffer);
+ RegKey reg_key_directory = RegKey(HKEY_LOCAL_MACHINE, registry_path.c_str());
+ result = reg_key_directory.ReadValue(L"Install Directory", buffer,
+ &buffer_length, NULL);
+ if (!result)
+ return std::wstring();
+ return buffer;
+}
+
+FilePath GetProfilesINI() {
+ FilePath ini_file;
+ // The default location of the profile folder containing user data is
+ // under the "Application Data" folder in Windows XP.
+ wchar_t buffer[MAX_PATH] = {0};
+ if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL,
+ SHGFP_TYPE_CURRENT, buffer))) {
+ ini_file = FilePath(buffer).Append(L"Mozilla\\Firefox\\profiles.ini");
+ }
+ if (file_util::PathExists(ini_file))
+ return ini_file;
+
+ return FilePath();
+}
diff --git a/chrome/browser/importer/firefox_profile_lock.cc b/chrome/browser/importer/firefox_profile_lock.cc
new file mode 100644
index 0000000..fff771d
--- /dev/null
+++ b/chrome/browser/importer/firefox_profile_lock.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2010 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_path.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
+#if defined(OS_MACOSX)
+const FilePath::CharType* FirefoxProfileLock::kLockFileName =
+ FILE_PATH_LITERAL(".parentlock");
+const FilePath::CharType* FirefoxProfileLock::kOldLockFileName =
+ FILE_PATH_LITERAL("parent.lock");
+#elif defined(OS_POSIX)
+// http://www.google.com/codesearch/p?hl=en#e_ObwTAVPyo/profile/dirserviceprovider/src/nsProfileLock.cpp&l=433
+const FilePath::CharType* FirefoxProfileLock::kLockFileName =
+ FILE_PATH_LITERAL(".parentlock");
+const FilePath::CharType* FirefoxProfileLock::kOldLockFileName =
+ FILE_PATH_LITERAL("lock");
+#else
+const FilePath::CharType* FirefoxProfileLock::kLockFileName =
+ FILE_PATH_LITERAL("parent.lock");
+#endif
+
+FirefoxProfileLock::FirefoxProfileLock(const FilePath& path) {
+ Init();
+ lock_file_ = path.Append(kLockFileName);
+ Lock();
+}
+
+FirefoxProfileLock::~FirefoxProfileLock() {
+ Unlock();
+}
diff --git a/chrome/browser/importer/firefox_profile_lock.h b/chrome/browser/importer/firefox_profile_lock.h
new file mode 100644
index 0000000..20b9065
--- /dev/null
+++ b/chrome/browser/importer/firefox_profile_lock.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2010 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 "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/gtest_prod_util.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 FilePath& path);
+ ~FirefoxProfileLock();
+
+ // Locks and releases the profile.
+ void Lock();
+ void Unlock();
+
+ // Returns true if we lock the profile successfully.
+ bool HasAcquired();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(FirefoxProfileLockTest, ProfileLock);
+ FRIEND_TEST_ALL_PREFIXES(FirefoxProfileLockTest, ProfileLockOrphaned);
+
+ static const FilePath::CharType* kLockFileName;
+ static const FilePath::CharType* kOldLockFileName;
+
+ void Init();
+
+ // Full path of the lock file in the profile folder.
+ FilePath lock_file_;
+
+ // The handle of the lock file.
+#if defined(OS_WIN)
+ HANDLE lock_handle_;
+#elif defined(OS_POSIX)
+ int lock_fd_;
+
+ // On Posix systems Firefox apparently first tries to put a fcntl lock
+ // on a file and if that fails, it does a regular exculsive open on another
+ // file. This variable contains the location of this other file.
+ FilePath old_lock_file_;
+
+ // Method that tries to put a fcntl lock on file specified by |lock_file_|.
+ // Returns false if lock is already held by another process. true in all
+ // other cases.
+ bool LockWithFcntl();
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(FirefoxProfileLock);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_FIREFOX_PROFILE_LOCK_H__
diff --git a/chrome/browser/importer/firefox_profile_lock_posix.cc b/chrome/browser/importer/firefox_profile_lock_posix.cc
new file mode 100644
index 0000000..919b04a
--- /dev/null
+++ b/chrome/browser/importer/firefox_profile_lock_posix.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 2009 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 <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.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 ***** */
+
+void FirefoxProfileLock::Init() {
+ lock_fd_ = -1;
+}
+
+void FirefoxProfileLock::Lock() {
+ if (HasAcquired())
+ return;
+
+ bool fcntl_lock = LockWithFcntl();
+ if (!fcntl_lock) {
+ return;
+ } else if (!HasAcquired()) {
+ old_lock_file_ = lock_file_.DirName().Append(kOldLockFileName);
+ lock_fd_ = open(old_lock_file_.value().c_str(), O_CREAT | O_EXCL, 0644);
+ }
+}
+
+void FirefoxProfileLock::Unlock() {
+ if (!HasAcquired())
+ return;
+ close(lock_fd_);
+ lock_fd_ = -1;
+ file_util::Delete(old_lock_file_, false);
+}
+
+bool FirefoxProfileLock::HasAcquired() {
+ return (lock_fd_ >= 0);
+}
+
+// This function tries to lock Firefox profile using fcntl(). The return
+// value of this function together with HasAcquired() tells the current status
+// of lock.
+// if return == false: Another process has lock to the profile.
+// if return == true && HasAcquired() == true: successfully acquired the lock.
+// if return == false && HasAcquired() == false: Failed to acquire lock due
+// to some error (so that we can try alternate method of profile lock).
+bool FirefoxProfileLock::LockWithFcntl() {
+ lock_fd_ = open(lock_file_.value().c_str(), O_WRONLY | O_CREAT | O_TRUNC,
+ 0666);
+ if (lock_fd_ == -1)
+ return true;
+
+ struct flock lock;
+ lock.l_start = 0;
+ lock.l_len = 0;
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+
+ struct flock testlock = lock;
+ if (fcntl(lock_fd_, F_GETLK, &testlock) == -1) {
+ close(lock_fd_);
+ lock_fd_ = -1;
+ return true;
+ } else if (fcntl(lock_fd_, F_SETLK, &lock) == -1) {
+ close(lock_fd_);
+ lock_fd_ = -1;
+ if (errno == EAGAIN || errno == EACCES)
+ return false;
+ else
+ return true;
+ } else {
+ // We have the lock.
+ return true;
+ }
+}
diff --git a/chrome/browser/importer/firefox_profile_lock_unittest.cc b/chrome/browser/importer/firefox_profile_lock_unittest.cc
new file mode 100644
index 0000000..09214b7
--- /dev/null
+++ b/chrome/browser/importer/firefox_profile_lock_unittest.cc
@@ -0,0 +1,133 @@
+// Copyright (c) 2010 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 "base/process_util.h"
+#include "base/string_util.h"
+#include "build/build_config.h"
+#include "chrome/browser/importer/firefox_profile_lock.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/file_test_utils.h"
+
+class FirefoxProfileLockTest : public testing::Test {
+ public:
+ protected:
+ virtual void SetUp() {
+ ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &test_path_));
+ std::wstring dir_name(L"FirefoxProfileLockTest");
+ dir_name.append(StringPrintf(L"-%d", base::GetCurrentProcId()));
+ test_path_ = test_path_.Append(FilePath::FromWStringHack(dir_name));
+ file_util::Delete(test_path_, true);
+ file_util::CreateDirectory(test_path_);
+ }
+
+ virtual void TearDown() {
+ ASSERT_TRUE(file_util::Delete(test_path_, true));
+ ASSERT_FALSE(file_util::PathExists(test_path_));
+ }
+
+ FilePath test_path_;
+};
+
+TEST_F(FirefoxProfileLockTest, LockTest) {
+ FirefoxProfileLock lock1(test_path_);
+ ASSERT_TRUE(lock1.HasAcquired());
+ lock1.Unlock();
+ ASSERT_FALSE(lock1.HasAcquired());
+ lock1.Lock();
+ ASSERT_TRUE(lock1.HasAcquired());
+}
+
+// Tests basic functionality and verifies that the lock file is deleted after
+// use.
+TEST_F(FirefoxProfileLockTest, ProfileLock) {
+ FilePath test_path;
+ ASSERT_TRUE(file_util::CreateNewTempDirectory(
+ FILE_PATH_LITERAL("firefox_profile"), &test_path));
+ FilePath lock_file_path = test_path;
+ FileAutoDeleter deleter(lock_file_path);
+ lock_file_path = lock_file_path.Append(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());
+
+ // In the posix code, we don't delete the file when releasing the lock.
+#if !defined(OS_POSIX)
+ EXPECT_FALSE(file_util::PathExists(lock_file_path));
+#endif // !defined(OS_POSIX)
+ 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());
+ // In the posix code, we don't delete the file when releasing the lock.
+#if !defined(OS_POSIX)
+ EXPECT_FALSE(file_util::PathExists(lock_file_path));
+#endif // !defined(OS_POSIX)
+}
+
+// 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_F(FirefoxProfileLockTest, ProfileLockOrphaned) {
+ FilePath test_path;
+ ASSERT_TRUE(file_util::CreateNewTempDirectory(
+ FILE_PATH_LITERAL("firefox_profile"), &test_path));
+ FilePath lock_file_path = test_path;
+ FileAutoDeleter deleter(lock_file_path);
+ lock_file_path = lock_file_path.Append(FirefoxProfileLock::kLockFileName);
+
+ // Create the orphaned lock file.
+ FILE* lock_file = file_util::OpenFile(lock_file_path, "w");
+ ASSERT_TRUE(lock_file);
+ file_util::CloseFile(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());
+}
+
+// This is broken on POSIX since the same process is allowed to reacquire a
+// lock.
+#if !defined(OS_POSIX)
+// Tests two locks contending for the same lock file.
+TEST_F(FirefoxProfileLockTest, ProfileLockContention) {
+ FilePath test_path;
+ ASSERT_TRUE(file_util::CreateNewTempDirectory(
+ FILE_PATH_LITERAL("firefox_profile"), &test_path));
+ FileAutoDeleter deleter(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());
+}
+#endif
diff --git a/chrome/browser/importer/firefox_profile_lock_win.cc b/chrome/browser/importer/firefox_profile_lock_win.cc
new file mode 100644
index 0000000..e0c46d9
--- /dev/null
+++ b/chrome/browser/importer/firefox_profile_lock_win.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 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 <windows.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 ***** */
+
+void FirefoxProfileLock::Init() {
+ lock_handle_ = INVALID_HANDLE_VALUE;
+}
+
+void FirefoxProfileLock::Lock() {
+ if (HasAcquired())
+ return;
+ lock_handle_ = CreateFile(lock_file_.value().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_proxy_settings.cc b/chrome/browser/importer/firefox_proxy_settings.cc
new file mode 100644
index 0000000..0f8ff39
--- /dev/null
+++ b/chrome/browser/importer/firefox_proxy_settings.cc
@@ -0,0 +1,226 @@
+// Copyright (c) 2010 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_proxy_settings.h"
+
+#include "base/string_tokenizer.h"
+#include "base/string_util.h"
+#include "base/values.h"
+#include "chrome/browser/importer/firefox_importer_utils.h"
+#include "net/proxy/proxy_config.h"
+
+namespace {
+
+const wchar_t* const kNetworkProxyTypeKey = L"network.proxy.type";
+const char* const kHTTPProxyKey = "network.proxy.http";
+const wchar_t* const kHTTPProxyPortKey = L"network.proxy.http_port";
+const char* const kSSLProxyKey = "network.proxy.ssl";
+const wchar_t* const kSSLProxyPortKey = L"network.proxy.ssl_port";
+const char* const kFTPProxyKey = "network.proxy.ftp";
+const wchar_t* const kFTPProxyPortKey = L"network.proxy.ftp_port";
+const char* const kGopherProxyKey = "network.proxy.gopher";
+const wchar_t* const kGopherProxyPortKey = L"network.proxy.gopher_port";
+const char* const kSOCKSHostKey = "network.proxy.socks";
+const wchar_t* const kSOCKSHostPortKey = L"network.proxy.socks_port";
+const wchar_t* const kSOCKSVersionKey = L"network.proxy.socks_version";
+const char* const kAutoconfigURL = "network.proxy.autoconfig_url";
+const char* const kNoProxyListKey = "network.proxy.no_proxies_on";
+const char* const kPrefFileName = "prefs.js";
+
+FirefoxProxySettings::ProxyConfig IntToProxyConfig(int type) {
+ switch (type) {
+ case 1:
+ return FirefoxProxySettings::MANUAL;
+ case 2:
+ return FirefoxProxySettings::AUTO_FROM_URL;
+ case 4:
+ return FirefoxProxySettings::AUTO_DETECT;
+ case 5:
+ return FirefoxProxySettings::SYSTEM;
+ default:
+ LOG(ERROR) << "Unknown Firefox proxy config type: " << type;
+ return FirefoxProxySettings::NO_PROXY;
+ }
+}
+
+FirefoxProxySettings::SOCKSVersion IntToSOCKSVersion(int type) {
+ switch (type) {
+ case 4:
+ return FirefoxProxySettings::V4;
+ case 5:
+ return FirefoxProxySettings::V5;
+ default:
+ LOG(ERROR) << "Unknown Firefox proxy config type: " << type;
+ return FirefoxProxySettings::UNKNONW;
+ }
+}
+
+} // namespace
+
+FirefoxProxySettings::FirefoxProxySettings() {
+ Reset();
+}
+
+FirefoxProxySettings::~FirefoxProxySettings() {
+}
+
+void FirefoxProxySettings::Reset() {
+ config_type_ = NO_PROXY;
+ http_proxy_.clear();
+ http_proxy_port_ = 0;
+ ssl_proxy_.clear();
+ ssl_proxy_port_ = 0;
+ ftp_proxy_.clear();
+ ftp_proxy_port_ = 0;
+ gopher_proxy_.clear();
+ gopher_proxy_port_ = 0;
+ socks_host_.clear();
+ socks_port_ = 0;
+ socks_version_ = UNKNONW;
+ proxy_bypass_list_.clear();
+ autoconfig_url_.clear();
+}
+
+// static
+bool FirefoxProxySettings::GetSettings(FirefoxProxySettings* settings) {
+ DCHECK(settings);
+ settings->Reset();
+
+ FilePath profile_path = GetFirefoxProfilePath();
+ if (profile_path.empty())
+ return false;
+ FilePath pref_file = profile_path.AppendASCII(kPrefFileName);
+ return GetSettingsFromFile(pref_file, settings);
+}
+
+bool FirefoxProxySettings::ToProxyConfig(net::ProxyConfig* config) {
+ switch (config_type()) {
+ case NO_PROXY:
+ *config = net::ProxyConfig::CreateDirect();
+ return true;
+ case AUTO_DETECT:
+ *config = net::ProxyConfig::CreateAutoDetect();
+ return true;
+ case AUTO_FROM_URL:
+ *config = net::ProxyConfig::CreateFromCustomPacURL(
+ GURL(autoconfig_url()));
+ return true;
+ case SYSTEM:
+ // Can't convert this directly to a ProxyConfig.
+ return false;
+ case MANUAL:
+ // Handled outside of the switch (since it is a lot of code.)
+ break;
+ default:
+ NOTREACHED();
+ return false;
+ }
+
+ // The rest of this funciton is for handling the MANUAL case.
+ DCHECK_EQ(MANUAL, config_type());
+
+ *config = net::ProxyConfig();
+ config->proxy_rules().type =
+ net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+
+ if (!http_proxy().empty()) {
+ config->proxy_rules().proxy_for_http = net::ProxyServer(
+ net::ProxyServer::SCHEME_HTTP,
+ http_proxy(),
+ http_proxy_port());
+ }
+
+ if (!ftp_proxy().empty()) {
+ config->proxy_rules().proxy_for_ftp = net::ProxyServer(
+ net::ProxyServer::SCHEME_HTTP,
+ ftp_proxy(),
+ ftp_proxy_port());
+ }
+
+ if (!ssl_proxy().empty()) {
+ config->proxy_rules().proxy_for_https = net::ProxyServer(
+ net::ProxyServer::SCHEME_HTTP,
+ ssl_proxy(),
+ ssl_proxy_port());
+ }
+
+ if (!socks_host().empty()) {
+ net::ProxyServer::Scheme proxy_scheme = V5 == socks_version() ?
+ net::ProxyServer::SCHEME_SOCKS5 : net::ProxyServer::SCHEME_SOCKS4;
+
+ config->proxy_rules().socks_proxy = net::ProxyServer(
+ proxy_scheme,
+ socks_host(),
+ socks_port());
+ }
+
+ config->proxy_rules().bypass_rules.ParseFromStringUsingSuffixMatching(
+ JoinString(proxy_bypass_list_, ';'));
+
+ return true;
+}
+
+// static
+bool FirefoxProxySettings::GetSettingsFromFile(const FilePath& pref_file,
+ FirefoxProxySettings* settings) {
+ DictionaryValue dictionary;
+ if (!ParsePrefFile(pref_file, &dictionary))
+ return false;
+
+ int proxy_type = 0;
+ if (!dictionary.GetInteger(kNetworkProxyTypeKey, &proxy_type))
+ return true; // No type means no proxy.
+
+ settings->config_type_ = IntToProxyConfig(proxy_type);
+ if (settings->config_type_ == AUTO_FROM_URL) {
+ if (!dictionary.GetStringASCII(kAutoconfigURL,
+ &(settings->autoconfig_url_))) {
+ LOG(ERROR) << "Failed to retrieve Firefox proxy autoconfig URL";
+ }
+ return true;
+ }
+
+ if (settings->config_type_ == MANUAL) {
+ if (!dictionary.GetStringASCII(kHTTPProxyKey, &(settings->http_proxy_)))
+ LOG(ERROR) << "Failed to retrieve Firefox proxy HTTP host";
+ if (!dictionary.GetInteger(kHTTPProxyPortKey,
+ &(settings->http_proxy_port_))) {
+ LOG(ERROR) << "Failed to retrieve Firefox proxy HTTP port";
+ }
+ if (!dictionary.GetStringASCII(kSSLProxyKey, &(settings->ssl_proxy_)))
+ LOG(ERROR) << "Failed to retrieve Firefox proxy SSL host";
+ if (!dictionary.GetInteger(kSSLProxyPortKey, &(settings->ssl_proxy_port_)))
+ LOG(ERROR) << "Failed to retrieve Firefox proxy SSL port";
+ if (!dictionary.GetStringASCII(kFTPProxyKey, &(settings->ftp_proxy_)))
+ LOG(ERROR) << "Failed to retrieve Firefox proxy FTP host";
+ if (!dictionary.GetInteger(kFTPProxyPortKey, &(settings->ftp_proxy_port_)))
+ LOG(ERROR) << "Failed to retrieve Firefox proxy SSL port";
+ if (!dictionary.GetStringASCII(kGopherProxyKey, &(settings->gopher_proxy_)))
+ LOG(ERROR) << "Failed to retrieve Firefox proxy gopher host";
+ if (!dictionary.GetInteger(kGopherProxyPortKey,
+ &(settings->gopher_proxy_port_))) {
+ LOG(ERROR) << "Failed to retrieve Firefox proxy gopher port";
+ }
+ if (!dictionary.GetStringASCII(kSOCKSHostKey, &(settings->socks_host_)))
+ LOG(ERROR) << "Failed to retrieve Firefox SOCKS host";
+ if (!dictionary.GetInteger(kSOCKSHostPortKey, &(settings->socks_port_)))
+ LOG(ERROR) << "Failed to retrieve Firefox SOCKS port";
+ int socks_version;
+ if (dictionary.GetInteger(kSOCKSVersionKey, &socks_version))
+ settings->socks_version_ = IntToSOCKSVersion(socks_version);
+
+ std::string proxy_bypass;
+ if (dictionary.GetStringASCII(kNoProxyListKey, &proxy_bypass) &&
+ !proxy_bypass.empty()) {
+ StringTokenizer string_tok(proxy_bypass, ",");
+ while (string_tok.GetNext()) {
+ std::string token = string_tok.token();
+ TrimWhitespaceASCII(token, TRIM_ALL, &token);
+ if (!token.empty())
+ settings->proxy_bypass_list_.push_back(token);
+ }
+ }
+ }
+ return true;
+}
diff --git a/chrome/browser/importer/firefox_proxy_settings.h b/chrome/browser/importer/firefox_proxy_settings.h
new file mode 100644
index 0000000..80e1e24
--- /dev/null
+++ b/chrome/browser/importer/firefox_proxy_settings.h
@@ -0,0 +1,110 @@
+// Copyright (c) 2010 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_PROXY_SETTINGS_H_
+#define CHROME_BROWSER_IMPORTER_FIREFOX_PROXY_SETTINGS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+
+class FilePath;
+
+namespace net {
+class ProxyConfig;
+}
+
+class FirefoxProxySettings {
+ public:
+ enum ProxyConfig {
+ NO_PROXY = 0, // No proxy are used.
+ AUTO_DETECT, // Automatically detected.
+ SYSTEM, // Using system proxy settings.
+ AUTO_FROM_URL, // Automatically configured from a URL.
+ MANUAL // User specified settings.
+ };
+
+ enum SOCKSVersion {
+ UNKNONW = 0,
+ V4,
+ V5
+ };
+
+ FirefoxProxySettings();
+ ~FirefoxProxySettings();
+
+ // Sets |settings| to the proxy settings for the current installed version of
+ // Firefox and returns true if successful.
+ // Returns false if Firefox is not installed or if the settings could not be
+ // retrieved.
+ static bool GetSettings(FirefoxProxySettings* settings);
+
+ // Resets all the states of this FirefoxProxySettings to no proxy.
+ void Reset();
+
+ ProxyConfig config_type() const { return config_type_; }
+
+ std::string http_proxy() const { return http_proxy_; }
+ int http_proxy_port() const { return http_proxy_port_; }
+
+ std::string ssl_proxy() const { return ssl_proxy_; }
+ int ssl_proxy_port() const { return ssl_proxy_port_; }
+
+ std::string ftp_proxy() const { return ftp_proxy_; }
+ int ftp_proxy_port() const { return ftp_proxy_port_; }
+
+ std::string gopher_proxy() const { return gopher_proxy_; }
+ int gopher_proxy_port() const { return gopher_proxy_port_; }
+
+ std::string socks_host() const { return socks_host_; }
+ int socks_port() const { return socks_port_; }
+ SOCKSVersion socks_version() const { return socks_version_; }
+
+ std::vector<std::string> proxy_bypass_list() const {
+ return proxy_bypass_list_;
+ }
+
+ const std::string autoconfig_url() const {
+ return autoconfig_url_;
+ }
+
+ // Converts a FirefoxProxySettings object to a net::ProxyConfig.
+ // On success returns true and fills |config| with the result.
+ bool ToProxyConfig(net::ProxyConfig* config);
+
+ protected:
+ // Gets the settings from the passed prefs.js file and returns true if
+ // successful.
+ // Protected for tests.
+ static bool GetSettingsFromFile(const FilePath& pref_file,
+ FirefoxProxySettings* settings);
+
+ private:
+ ProxyConfig config_type_;
+
+ std::string http_proxy_;
+ int http_proxy_port_;
+
+ std::string ssl_proxy_;
+ int ssl_proxy_port_;
+
+ std::string ftp_proxy_;
+ int ftp_proxy_port_;
+
+ std::string gopher_proxy_;
+ int gopher_proxy_port_;
+
+ std::string socks_host_;
+ int socks_port_;
+ SOCKSVersion socks_version_;
+
+ std::vector<std::string> proxy_bypass_list_;
+
+ std::string autoconfig_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(FirefoxProxySettings);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_FIREFOX_PROXY_SETTINGS_H_
diff --git a/chrome/browser/importer/firefox_proxy_settings_unittest.cc b/chrome/browser/importer/firefox_proxy_settings_unittest.cc
new file mode 100644
index 0000000..ed2a66c
--- /dev/null
+++ b/chrome/browser/importer/firefox_proxy_settings_unittest.cc
@@ -0,0 +1,124 @@
+// Copyright (c) 2010 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 <sstream>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "base/file_path.h"
+#include "base/path_service.h"
+#include "chrome/browser/importer/firefox_proxy_settings.h"
+#include "chrome/common/chrome_paths.h"
+#include "net/proxy/proxy_config.h"
+
+class FirefoxProxySettingsTest : public testing::Test {
+};
+
+class TestFirefoxProxySettings : public FirefoxProxySettings {
+ public:
+ TestFirefoxProxySettings() {}
+
+ static bool TestGetSettingsFromFile(const FilePath& pref_file,
+ FirefoxProxySettings* settings) {
+ return GetSettingsFromFile(pref_file, settings);
+ }
+};
+
+TEST_F(FirefoxProxySettingsTest, TestParse) {
+ FirefoxProxySettings settings;
+
+ FilePath js_pref_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &js_pref_path));
+ js_pref_path = js_pref_path.AppendASCII("firefox3_pref.js");
+
+ EXPECT_TRUE(TestFirefoxProxySettings::TestGetSettingsFromFile(js_pref_path,
+ &settings));
+ EXPECT_EQ(FirefoxProxySettings::MANUAL, settings.config_type());
+ EXPECT_EQ("http_proxy", settings.http_proxy());
+ EXPECT_EQ(1111, settings.http_proxy_port());
+ EXPECT_EQ("ssl_proxy", settings.ssl_proxy());
+ EXPECT_EQ(2222, settings.ssl_proxy_port());
+ EXPECT_EQ("ftp_proxy", settings.ftp_proxy());
+ EXPECT_EQ(3333, settings.ftp_proxy_port());
+ EXPECT_EQ("gopher_proxy", settings.gopher_proxy());
+ EXPECT_EQ(4444, settings.gopher_proxy_port());
+ EXPECT_EQ("socks_host", settings.socks_host());
+ EXPECT_EQ(5555, settings.socks_port());
+ EXPECT_EQ(FirefoxProxySettings::V4, settings.socks_version());
+ ASSERT_EQ(3U, settings.proxy_bypass_list().size());
+ EXPECT_EQ("localhost", settings.proxy_bypass_list()[0]);
+ EXPECT_EQ("127.0.0.1", settings.proxy_bypass_list()[1]);
+ EXPECT_EQ("noproxy.com", settings.proxy_bypass_list()[2]);
+ EXPECT_EQ("", settings.autoconfig_url());
+
+ // Test that ToProxyConfig() properly translates into a net::ProxyConfig.
+ net::ProxyConfig config;
+ EXPECT_TRUE(settings.ToProxyConfig(&config));
+
+ // Pretty-print |config| to a string (easy way to define the expectations).
+ std::ostringstream stream;
+ stream << config;
+ std::string pretty_printed_config = stream.str();
+
+ EXPECT_EQ(
+ "Automatic settings:\n"
+ " Auto-detect: No\n"
+ " Custom PAC script: [None]\n"
+ "Manual settings:\n"
+ " Proxy server: \n"
+ " HTTP: http_proxy:1111\n"
+ " HTTPS: ssl_proxy:2222\n"
+ " FTP: ftp_proxy:3333\n"
+ " SOCKS: socks4://socks_host:5555\n"
+ " Bypass list: \n"
+ " *localhost\n"
+ " 127.0.0.1\n"
+ " *noproxy.com",
+ pretty_printed_config);
+}
+
+TEST_F(FirefoxProxySettingsTest, TestParseAutoConfigUrl) {
+ FirefoxProxySettings settings;
+
+ FilePath js_pref_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &js_pref_path));
+ js_pref_path = js_pref_path.AppendASCII("firefox3_pref_pac_url.js");
+
+ EXPECT_TRUE(TestFirefoxProxySettings::TestGetSettingsFromFile(js_pref_path,
+ &settings));
+ EXPECT_EQ(FirefoxProxySettings::AUTO_FROM_URL, settings.config_type());
+
+ // Everything should be empty except for the autoconfig URL.
+ EXPECT_EQ("http://custom-pac-url/", settings.autoconfig_url());
+ EXPECT_EQ("", settings.http_proxy());
+ EXPECT_EQ(0, settings.http_proxy_port());
+ EXPECT_EQ("", settings.ssl_proxy());
+ EXPECT_EQ(0, settings.ssl_proxy_port());
+ EXPECT_EQ("", settings.ftp_proxy());
+ EXPECT_EQ(0, settings.ftp_proxy_port());
+ EXPECT_EQ("", settings.gopher_proxy());
+ EXPECT_EQ(0, settings.gopher_proxy_port());
+ EXPECT_EQ("", settings.socks_host());
+ EXPECT_EQ(0, settings.socks_port());
+ EXPECT_EQ(0, settings.socks_port());
+ EXPECT_EQ(0U, settings.proxy_bypass_list().size());
+
+ // Test that ToProxyConfig() properly translates into a net::ProxyConfig.
+ net::ProxyConfig config;
+ EXPECT_TRUE(settings.ToProxyConfig(&config));
+
+ // Pretty-print |config| to a string (easy way to define the expectations).
+ std::ostringstream stream;
+ stream << config;
+ std::string pretty_printed_config = stream.str();
+
+ EXPECT_EQ(
+ "Automatic settings:\n"
+ " Auto-detect: No\n"
+ " Custom PAC script: http://custom-pac-url/\n"
+ "Manual settings:\n"
+ " Proxy server: [None]\n"
+ " Bypass list: [None]",
+ pretty_printed_config);
+}
diff --git a/chrome/browser/importer/ie_importer.cc b/chrome/browser/importer/ie_importer.cc
new file mode 100644
index 0000000..2824b40
--- /dev/null
+++ b/chrome/browser/importer/ie_importer.cc
@@ -0,0 +1,578 @@
+// Copyright (c) 2010 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 <ole2.h>
+#include <intshcut.h>
+#include <pstore.h>
+#include <shlobj.h>
+#include <urlhist.h>
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "app/l10n_util.h"
+#include "app/win_util.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/registry.h"
+#include "base/scoped_comptr_win.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/importer/importer_bridge.h"
+#include "chrome/browser/importer/importer_data_types.h"
+#include "chrome/browser/password_manager/ie7_password.h"
+#include "chrome/browser/search_engines/template_url_model.h"
+#include "chrome/common/time_format.h"
+#include "chrome/common/url_constants.h"
+#include "googleurl/src/gurl.h"
+#include "grit/generated_resources.h"
+#include "webkit/glue/password_form.h"
+
+using base::Time;
+using webkit_glue::PasswordForm;
+
+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,
+ ImporterBridge* bridge) {
+ bridge_ = bridge;
+ source_path_ = profile_info.source_path.ToWStringHack();
+
+ bridge_->NotifyStarted();
+
+ // Some IE settings (such as Protected Storage) are obtained via COM APIs.
+ win_util::ScopedCOMInitializer com_initializer;
+
+ if ((items & importer::HOME_PAGE) && !cancelled())
+ ImportHomepage(); // Doesn't have a UI item.
+ // The order here is important!
+ if ((items & importer::HISTORY) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::HISTORY);
+ ImportHistory();
+ bridge_->NotifyItemEnded(importer::HISTORY);
+ }
+ if ((items & importer::FAVORITES) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::FAVORITES);
+ ImportFavorites();
+ bridge_->NotifyItemEnded(importer::FAVORITES);
+ }
+ if ((items & importer::SEARCH_ENGINES) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::SEARCH_ENGINES);
+ ImportSearchEngines();
+ bridge_->NotifyItemEnded(importer::SEARCH_ENGINES);
+ }
+ if ((items & importer::PASSWORDS) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::PASSWORDS);
+ // Always import IE6 passwords.
+ ImportPasswordsIE6();
+
+ if (CurrentIEVersion() >= 7)
+ ImportPasswordsIE7();
+ bridge_->NotifyItemEnded(importer::PASSWORDS);
+ }
+ bridge_->NotifyEnded();
+}
+
+void IEImporter::ImportFavorites() {
+ std::wstring path;
+
+ FavoritesInfo info;
+ if (!GetFavoritesInfo(&info))
+ return;
+
+ BookmarkVector bookmarks;
+ ParseFavoritesFolder(info, &bookmarks);
+
+ if (!bookmarks.empty() && !cancelled()) {
+ const std::wstring& first_folder_name =
+ l10n_util::GetString(IDS_BOOKMARK_GROUP_FROM_IE);
+ int options = 0;
+ if (import_to_bookmark_bar())
+ options = ProfileWriter::IMPORT_TO_BOOKMARK_BAR;
+ bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options);
+ }
+}
+
+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");
+ if (!pstorec_dll)
+ return;
+ PStoreCreateFunc PStoreCreateInstance =
+ (PStoreCreateFunc)GetProcAddress(pstorec_dll, "PStoreCreateInstance");
+ if (!PStoreCreateInstance) {
+ FreeLibrary(pstorec_dll);
+ return;
+ }
+
+ ScopedComPtr<IPStore, &IID_IPStore> pstore;
+ HRESULT result = PStoreCreateInstance(pstore.Receive(), 0, 0, 0);
+ if (result != S_OK) {
+ FreeLibrary(pstorec_dll);
+ return;
+ }
+
+ std::vector<AutoCompleteInfo> ac_list;
+
+ // Enumerates AutoComplete items in the protected database.
+ ScopedComPtr<IEnumPStoreItems, &IID_IEnumPStoreItems> item;
+ result = pstore->EnumItems(0, &AutocompleteGUID,
+ &AutocompleteGUID, 0, item.Receive());
+ 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(), chrome::kHttpScheme) ||
+ LowerCaseEqualsASCII(url.scheme(), chrome::kHttpsScheme))) {
+ 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;
+ }
+ }
+
+ bridge_->SetPasswordForm(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();
+
+ bridge_->AddIE7PasswordInfo(password_info);
+ }
+ }
+
+ ++reg_iterator;
+ }
+}
+
+// Reads history information from COM interface.
+void IEImporter::ImportHistory() {
+ const std::string kSchemes[] = {chrome::kHttpScheme,
+ chrome::kHttpsScheme,
+ chrome::kFtpScheme,
+ chrome::kFileScheme};
+ int total_schemes = arraysize(kSchemes);
+
+ ScopedComPtr<IUrlHistoryStg2> url_history_stg2;
+ HRESULT result;
+ result = url_history_stg2.CreateInstance(CLSID_CUrlHistory, NULL,
+ CLSCTX_INPROC_SERVER);
+ if (FAILED(result))
+ return;
+ ScopedComPtr<IEnumSTATURL> enum_url;
+ if (SUCCEEDED(result = url_history_stg2->EnumUrls(enum_url.Receive()))) {
+ 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()) {
+ bridge_->SetHistoryItems(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::string, 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 wide_url;
+ if (!sub_key.ReadValue(L"URL", &wide_url) || wide_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::string url(WideToUTF8(wide_url));
+ std::map<std::string, 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::string, 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;
+ }
+ }
+ bridge_->SetKeywords(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;
+ }
+
+ bridge_->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);
+ RegKey reg_key(HKEY_CURRENT_USER,
+ L"Software\\Microsoft\\Internet Explorer\\Toolbar");
+ if (!reg_key.ReadValue(L"LinksFolderName", buffer, &buffer_length, NULL))
+ return false;
+ info->links_folder = buffer;
+ } else {
+ info->links_folder = L"Links";
+ }
+
+ 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;
+ FilePath file;
+ std::vector<FilePath::StringType> file_list;
+ FilePath favorites_path(info.path);
+ // Favorites path length. Make sure it doesn't include the trailing \.
+ size_t favorites_path_len =
+ favorites_path.StripTrailingSeparators().value().size();
+ file_util::FileEnumerator file_enumerator(
+ favorites_path, true, file_util::FileEnumerator::FILES);
+ while (!(file = file_enumerator.Next()).value().empty() && !cancelled())
+ file_list.push_back(file.value());
+
+ // Keep the bookmarks in alphabetical order.
+ std::sort(file_list.begin(), file_list.end());
+
+ for (std::vector<FilePath::StringType>::iterator it = file_list.begin();
+ it != file_list.end(); ++it) {
+ FilePath shortcut(*it);
+ if (!LowerCaseEqualsASCII(shortcut.Extension(), ".url"))
+ continue;
+
+ // Skip the bookmark with invalid URL.
+ GURL url = GURL(ResolveInternetShortcut(*it));
+ if (!url.is_valid())
+ continue;
+
+ // Make the relative path from the Favorites folder, without the basename.
+ // ex. Suppose that the Favorites folder is C:\Users\Foo\Favorites.
+ // C:\Users\Foo\Favorites\Foo.url -> ""
+ // C:\Users\Foo\Favorites\Links\Bar\Baz.url -> "Links\Bar"
+ FilePath::StringType relative_string =
+ shortcut.DirName().value().substr(favorites_path_len);
+ if (relative_string.size() > 0 && FilePath::IsSeparator(relative_string[0]))
+ relative_string = relative_string.substr(1);
+ FilePath relative_path(relative_string);
+
+ ProfileWriter::BookmarkEntry entry;
+ // Remove the dot, the file extension, and the directory path.
+ entry.title = shortcut.RemoveExtension().BaseName().value();
+ entry.url = url;
+ entry.creation_time = GetFileCreationTime(*it);
+ if (!relative_path.empty())
+ relative_path.GetComponents(&entry.path);
+
+ // Flatten the bookmarks in Link folder onto bookmark toolbar. Otherwise,
+ // put it into "Other bookmarks".
+ if (import_to_bookmark_bar() &&
+ (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 {
+ // We put the bookmarks in a "Imported From IE"
+ // folder, so that we don't mess up the "Other bookmarks".
+ if (!import_to_bookmark_bar())
+ 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;
+ ScopedComPtr<IUniformResourceLocator> url_locator;
+ HRESULT result = url_locator.CreateInstance(CLSID_InternetShortcut, NULL,
+ CLSCTX_INPROC_SERVER);
+ if (FAILED(result))
+ return std::wstring();
+
+ ScopedComPtr<IPersistFile> persist_file;
+ result = persist_file.QueryFrom(url_locator);
+ 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);
+ RegKey reg_key(HKEY_LOCAL_MACHINE,
+ L"Software\\Microsoft\\Internet Explorer");
+ bool result = reg_key.ReadValue(L"Version", buffer, &buffer_length, NULL);
+ 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..d93405c
--- /dev/null
+++ b/chrome/browser/importer/ie_importer.h
@@ -0,0 +1,84 @@
+// 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 "base/gtest_prod_util.h"
+#include "chrome/browser/importer/importer.h"
+
+class IEImporter : public Importer {
+ public:
+ IEImporter() {}
+
+ // Importer methods.
+ virtual void StartImport(ProfileInfo browser_info,
+ uint16 items,
+ ImporterBridge* bridge);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ImporterTest, IEImporter);
+
+ virtual ~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;
+ };
+ 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;
+
+ // 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_COPY_AND_ASSIGN(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..62376c3
--- /dev/null
+++ b/chrome/browser/importer/importer.cc
@@ -0,0 +1,588 @@
+// Copyright (c) 2010 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 "app/l10n_util.h"
+#include "base/thread.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/browsing_instance.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/importer/firefox_profile_lock.h"
+#include "chrome/browser/importer/importer_bridge.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/search_engines/template_url_model.h"
+#include "chrome/browser/webdata/web_data_service.h"
+#include "chrome/common/notification_service.h"
+#include "gfx/codec/png_codec.h"
+#include "gfx/favicon_size.h"
+#include "grit/generated_resources.h"
+#include "skia/ext/image_operations.h"
+#include "webkit/glue/image_decoder.h"
+
+// TODO(port): Port these files.
+#if defined(OS_WIN)
+#include "app/win_util.h"
+#include "chrome/browser/views/importer_lock_view.h"
+#include "views/window/window.h"
+#elif defined(OS_MACOSX)
+#include "chrome/browser/cocoa/importer_lock_dialog.h"
+#elif defined(TOOLKIT_USES_GTK)
+#include "chrome/browser/gtk/import_lock_dialog_gtk.h"
+#endif
+
+using webkit_glue::PasswordForm;
+
+// Importer.
+
+Importer::Importer()
+ : cancelled_(false),
+ import_to_bookmark_bar_(false),
+ bookmark_bar_disabled_(false) {
+}
+
+Importer::~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 = skia::ImageOperations::Resize(
+ decoded, skia::ImageOperations::RESIZE_LANCZOS3, new_width, new_height);
+ }
+
+ // Encode our bitmap as a PNG.
+ gfx::PNGCodec::EncodeBGRASkBitmap(decoded, false, png_data);
+ return true;
+}
+
+// ImporterHost.
+
+ImporterHost::ImporterHost()
+ : profile_(NULL),
+ observer_(NULL),
+ task_(NULL),
+ importer_(NULL),
+ waiting_for_bookmarkbar_model_(false),
+ installed_bookmark_observer_(false),
+ is_source_readable_(true),
+ headless_(false),
+ parent_window_(NULL) {
+ importer_list_.DetectSourceProfiles();
+}
+
+ImporterHost::~ImporterHost() {
+ if (NULL != importer_)
+ importer_->Release();
+ if (installed_bookmark_observer_) {
+ DCHECK(profile_); // Only way for waiting_for_bookmarkbar_model_ to be true
+ // is if we have a profile.
+ profile_->GetBookmarkModel()->RemoveObserver(this);
+ }
+}
+
+void ImporterHost::Loaded(BookmarkModel* model) {
+ DCHECK(model->IsLoaded());
+ model->RemoveObserver(this);
+ waiting_for_bookmarkbar_model_ = false;
+ installed_bookmark_observer_ = false;
+
+ std::vector<GURL> starred_urls;
+ model->GetBookmarks(&starred_urls);
+ importer_->set_import_to_bookmark_bar(starred_urls.size() == 0);
+ InvokeTaskIfDone();
+}
+
+void ImporterHost::BookmarkModelBeingDeleted(BookmarkModel* model) {
+ installed_bookmark_observer_ = false;
+}
+
+void ImporterHost::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NotificationType::TEMPLATE_URL_MODEL_LOADED);
+ registrar_.RemoveAll();
+ InvokeTaskIfDone();
+}
+
+void ImporterHost::ShowWarningDialog() {
+ if (headless_) {
+ OnLockViewEnd(false);
+ } else {
+#if defined(OS_WIN)
+ views::Window::CreateChromeWindow(GetActiveWindow(), gfx::Rect(),
+ new ImporterLockView(this))->Show();
+#elif defined(TOOLKIT_USES_GTK)
+ ImportLockDialogGtk::Show(parent_window_, this);
+#else
+ ImportLockDialogCocoa::ShowWarning(this);
+#endif
+ }
+}
+
+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 importer::ProfileInfo& profile_info,
+ Profile* target_profile,
+ uint16 items,
+ ProfileWriter* writer,
+ bool first_run) {
+ DCHECK(!profile_); // We really only support importing from one host at a
+ // time.
+ profile_ = target_profile;
+ // 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_ = importer_list_.CreateImporterByType(profile_info.browser_type);
+ // If we fail to create Importer, exit as we cannot do anything.
+ if (!importer_) {
+ ImportEnded();
+ return;
+ }
+
+ importer_->AddRef();
+
+ importer_->set_import_to_bookmark_bar(ShouldImportToBookmarkBar(first_run));
+ importer_->set_bookmark_bar_disabled(first_run);
+ scoped_refptr<ImporterBridge> bridge(
+ new InProcessImporterBridge(writer_.get(), this));
+ task_ = NewRunnableMethod(importer_, &Importer::StartImport,
+ profile_info, items, bridge);
+
+ CheckForFirefoxLock(profile_info, items, first_run);
+
+#if defined(OS_WIN)
+ // For google toolbar import, we need the user to log in and store their GAIA
+ // credentials.
+ if (profile_info.browser_type == importer::GOOGLE_TOOLBAR5) {
+ if (!toolbar_importer_utils::IsGoogleGAIACookieInstalled()) {
+ win_util::MessageBox(
+ NULL,
+ l10n_util::GetString(IDS_IMPORTER_GOOGLE_LOGIN_TEXT).c_str(),
+ L"",
+ MB_OK | MB_TOPMOST);
+
+ GURL url("https://www.google.com/accounts/ServiceLogin");
+ BrowsingInstance* instance = new BrowsingInstance(writer_->profile());
+ SiteInstance* site = instance->GetSiteInstanceForURL(url);
+ Browser* browser = BrowserList::GetLastActive();
+ browser->AddTabWithURL(url, GURL(), PageTransition::TYPED, -1,
+ TabStripModel::ADD_SELECTED, site, std::string());
+
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &ImporterHost::OnLockViewEnd, false));
+
+ is_source_readable_ = false;
+ }
+ }
+#endif
+
+ CheckForLoadedModels(items);
+ AddRef();
+ InvokeTaskIfDone();
+}
+
+void ImporterHost::Cancel() {
+ if (importer_)
+ importer_->Cancel();
+}
+
+void ImporterHost::SetObserver(Observer* observer) {
+ observer_ = observer;
+}
+
+void ImporterHost::InvokeTaskIfDone() {
+ if (waiting_for_bookmarkbar_model_ || !registrar_.IsEmpty() ||
+ !is_source_readable_)
+ return;
+ ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, task_);
+}
+
+void ImporterHost::ImportItemStarted(importer::ImportItem item) {
+ if (observer_)
+ observer_->ImportItemStarted(item);
+}
+
+void ImporterHost::ImportItemEnded(importer::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();
+}
+
+bool ImporterHost::ShouldImportToBookmarkBar(bool first_run) {
+ bool import_to_bookmark_bar = first_run;
+ if (profile_ && profile_->GetBookmarkModel()->IsLoaded()) {
+ std::vector<GURL> starred_urls;
+ profile_->GetBookmarkModel()->GetBookmarks(&starred_urls);
+ import_to_bookmark_bar = (starred_urls.size() == 0);
+ }
+ return import_to_bookmark_bar;
+}
+
+void ImporterHost::CheckForFirefoxLock(
+ const importer::ProfileInfo& profile_info, uint16 items, bool first_run) {
+ if (profile_info.browser_type == importer::FIREFOX2 ||
+ profile_info.browser_type == importer::FIREFOX3) {
+ DCHECK(!firefox_lock_.get());
+ 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, unless running without UI.
+ is_source_readable_ = false;
+ if (!this->headless_)
+ ShowWarningDialog();
+ }
+ }
+}
+
+void ImporterHost::CheckForLoadedModels(uint16 items) {
+ // 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 & importer::FAVORITES) && !writer_->BookmarkModelIsLoaded()) {
+ profile_->GetBookmarkModel()->AddObserver(this);
+ waiting_for_bookmarkbar_model_ = true;
+ installed_bookmark_observer_ = 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 & importer::SEARCH_ENGINES) || (items & importer::FAVORITES)) {
+ if (!writer_->TemplateURLModelIsLoaded()) {
+ TemplateURLModel* model = profile_->GetTemplateURLModel();
+ registrar_.Add(this, NotificationType::TEMPLATE_URL_MODEL_LOADED,
+ Source<TemplateURLModel>(model));
+ model->Load();
+ }
+ }
+}
+
+ExternalProcessImporterHost::ExternalProcessImporterHost()
+ : cancelled_(false),
+ import_process_launched_(false) {
+}
+
+void ExternalProcessImporterHost::Loaded(BookmarkModel* model) {
+ DCHECK(model->IsLoaded());
+ model->RemoveObserver(this);
+ waiting_for_bookmarkbar_model_ = false;
+ installed_bookmark_observer_ = false;
+
+ std::vector<GURL> starred_urls;
+ model->GetBookmarks(&starred_urls);
+ // Because the import process is running externally, the decision whether
+ // to import to the bookmark bar must be stored here so that it can be
+ // passed to the importer when the import task is invoked.
+ import_to_bookmark_bar_ = (starred_urls.size() == 0);
+ InvokeTaskIfDone();
+}
+
+void ExternalProcessImporterHost::Cancel() {
+ cancelled_ = true;
+ if (import_process_launched_)
+ client_->Cancel();
+ ImportEnded(); // Tells the observer that we're done, and releases us.
+}
+
+void ExternalProcessImporterHost::StartImportSettings(
+ const importer::ProfileInfo& profile_info,
+ Profile* target_profile,
+ uint16 items,
+ ProfileWriter* writer,
+ bool first_run) {
+ DCHECK(!profile_);
+ profile_ = target_profile;
+ writer_ = writer;
+ profile_info_ = &profile_info;
+ items_ = items;
+
+ ImporterHost::AddRef(); // Balanced in ImporterHost::ImportEnded.
+
+ import_to_bookmark_bar_ = ShouldImportToBookmarkBar(first_run);
+ CheckForFirefoxLock(profile_info, items, first_run);
+ CheckForLoadedModels(items);
+
+ InvokeTaskIfDone();
+}
+
+void ExternalProcessImporterHost::InvokeTaskIfDone() {
+ if (waiting_for_bookmarkbar_model_ || !registrar_.IsEmpty() ||
+ !is_source_readable_ || cancelled_)
+ return;
+
+ // The in-process half of the bridge which catches data from the IPC pipe
+ // and feeds it to the ProfileWriter. The external process half of the
+ // bridge lives in the external process -- see ProfileImportThread.
+ // The ExternalProcessImporterClient created in the next line owns this
+ // bridge, and will delete it.
+ InProcessImporterBridge* bridge =
+ new InProcessImporterBridge(writer_.get(), this);
+ client_ = new ExternalProcessImporterClient(this, *profile_info_, items_,
+ bridge, import_to_bookmark_bar_);
+ import_process_launched_ = true;
+ client_->Start();
+}
+
+ExternalProcessImporterClient::ExternalProcessImporterClient(
+ ExternalProcessImporterHost* importer_host,
+ const importer::ProfileInfo& profile_info,
+ int items,
+ InProcessImporterBridge* bridge,
+ bool import_to_bookmark_bar)
+ : process_importer_host_(importer_host),
+ profile_info_(profile_info),
+ items_(items),
+ import_to_bookmark_bar_(import_to_bookmark_bar),
+ bridge_(bridge),
+ cancelled_(FALSE) {
+ bridge_->AddRef();
+ process_importer_host_->ImportStarted();
+}
+
+ExternalProcessImporterClient::~ExternalProcessImporterClient() {
+ bridge_->Release();
+}
+
+void ExternalProcessImporterClient::Start() {
+ AddRef(); // balanced in Cleanup.
+ ChromeThread::ID thread_id;
+ CHECK(ChromeThread::GetCurrentThreadIdentifier(&thread_id));
+ ChromeThread::PostTask(
+ ChromeThread::IO, FROM_HERE,
+ NewRunnableMethod(this,
+ &ExternalProcessImporterClient::StartProcessOnIOThread,
+ g_browser_process->resource_dispatcher_host(), thread_id));
+}
+
+void ExternalProcessImporterClient::StartProcessOnIOThread(
+ ResourceDispatcherHost* rdh,
+ ChromeThread::ID thread_id) {
+ profile_import_process_host_ =
+ new ProfileImportProcessHost(rdh, this, thread_id);
+ profile_import_process_host_->StartProfileImportProcess(profile_info_,
+ items_, import_to_bookmark_bar_);
+}
+
+void ExternalProcessImporterClient::Cancel() {
+ if (cancelled_)
+ return;
+
+ cancelled_ = true;
+ if (profile_import_process_host_) {
+ ChromeThread::PostTask(
+ ChromeThread::IO, FROM_HERE,
+ NewRunnableMethod(this,
+ &ExternalProcessImporterClient::CancelImportProcessOnIOThread));
+ }
+ Release();
+}
+
+void ExternalProcessImporterClient::CancelImportProcessOnIOThread() {
+ profile_import_process_host_->CancelProfileImportProcess();
+}
+
+void ExternalProcessImporterClient::NotifyItemFinishedOnIOThread(
+ importer::ImportItem import_item) {
+ profile_import_process_host_->ReportImportItemFinished(import_item);
+}
+
+void ExternalProcessImporterClient::OnProcessCrashed() {
+ if (cancelled_)
+ return;
+
+ process_importer_host_->Cancel();
+}
+
+void ExternalProcessImporterClient::Cleanup() {
+ if (cancelled_)
+ return;
+
+ if (process_importer_host_)
+ process_importer_host_->ImportEnded();
+ Release();
+}
+
+void ExternalProcessImporterClient::OnImportStart() {
+ if (cancelled_)
+ return;
+
+ bridge_->NotifyStarted();
+}
+
+void ExternalProcessImporterClient::OnImportFinished(bool succeeded,
+ std::string error_msg) {
+ if (cancelled_)
+ return;
+
+ if (!succeeded)
+ LOG(WARNING) << "Import failed. Error: " << error_msg;
+ Cleanup();
+}
+
+void ExternalProcessImporterClient::OnImportItemStart(int item_data) {
+ if (cancelled_)
+ return;
+
+ bridge_->NotifyItemStarted(static_cast<importer::ImportItem>(item_data));
+}
+
+void ExternalProcessImporterClient::OnImportItemFinished(int item_data) {
+ if (cancelled_)
+ return;
+
+ importer::ImportItem import_item =
+ static_cast<importer::ImportItem>(item_data);
+ bridge_->NotifyItemEnded(import_item);
+ ChromeThread::PostTask(
+ ChromeThread::IO, FROM_HERE,
+ NewRunnableMethod(this,
+ &ExternalProcessImporterClient::NotifyItemFinishedOnIOThread,
+ import_item));
+}
+
+void ExternalProcessImporterClient::OnHistoryImportStart(
+ size_t total_history_rows_count) {
+ if (cancelled_)
+ return;
+
+ total_history_rows_count_ = total_history_rows_count;
+ history_rows_.reserve(total_history_rows_count);
+}
+
+void ExternalProcessImporterClient::OnHistoryImportGroup(
+ const std::vector<history::URLRow> &history_rows_group) {
+ if (cancelled_)
+ return;
+
+ history_rows_.insert(history_rows_.end(), history_rows_group.begin(),
+ history_rows_group.end());
+ if (history_rows_.size() == total_history_rows_count_)
+ bridge_->SetHistoryItems(history_rows_);
+}
+
+void ExternalProcessImporterClient::OnHomePageImportReady(
+ const GURL& home_page) {
+ if (cancelled_)
+ return;
+
+ bridge_->AddHomePage(home_page);
+}
+
+void ExternalProcessImporterClient::OnBookmarksImportStart(
+ const std::wstring first_folder_name,
+ int options, size_t total_bookmarks_count) {
+ if (cancelled_)
+ return;
+
+ bookmarks_first_folder_name_ = first_folder_name;
+ bookmarks_options_ = options;
+ total_bookmarks_count_ = total_bookmarks_count;
+ bookmarks_.reserve(total_bookmarks_count);
+}
+
+void ExternalProcessImporterClient::OnBookmarksImportGroup(
+ const std::vector<ProfileWriter::BookmarkEntry>& bookmarks_group) {
+ if (cancelled_)
+ return;
+
+ // Collect sets of bookmarks from importer process until we have reached
+ // total_bookmarks_count_:
+ bookmarks_.insert(bookmarks_.end(), bookmarks_group.begin(),
+ bookmarks_group.end());
+ if (bookmarks_.size() == total_bookmarks_count_) {
+ bridge_->AddBookmarkEntries(bookmarks_, bookmarks_first_folder_name_,
+ bookmarks_options_);
+ }
+}
+
+void ExternalProcessImporterClient::OnFavIconsImportStart(
+ size_t total_fav_icons_count) {
+ if (cancelled_)
+ return;
+
+ total_fav_icons_count_ = total_fav_icons_count;
+ fav_icons_.reserve(total_fav_icons_count);
+}
+
+void ExternalProcessImporterClient::OnFavIconsImportGroup(
+ const std::vector<history::ImportedFavIconUsage>& fav_icons_group) {
+ if (cancelled_)
+ return;
+
+ fav_icons_.insert(fav_icons_.end(), fav_icons_group.begin(),
+ fav_icons_group.end());
+ if (fav_icons_.size() == total_fav_icons_count_)
+ bridge_->SetFavIcons(fav_icons_);
+}
+
+void ExternalProcessImporterClient::OnPasswordFormImportReady(
+ const webkit_glue::PasswordForm& form) {
+ if (cancelled_)
+ return;
+
+ bridge_->SetPasswordForm(form);
+}
+
+void ExternalProcessImporterClient::OnKeywordsImportReady(
+ const std::vector<TemplateURL>& template_urls,
+ int default_keyword_index, bool unique_on_host_and_path) {
+ if (cancelled_)
+ return;
+
+ std::vector<TemplateURL*> template_url_vec;
+ template_url_vec.reserve(template_urls.size());
+ std::vector<TemplateURL>::const_iterator iter;
+ for (iter = template_urls.begin();
+ iter != template_urls.end();
+ ++iter) {
+ template_url_vec.push_back(new TemplateURL(*iter));
+ }
+ bridge_->SetKeywords(template_url_vec, default_keyword_index,
+ unique_on_host_and_path);
+}
diff --git a/chrome/browser/importer/importer.h b/chrome/browser/importer/importer.h
new file mode 100644
index 0000000..eec498b
--- /dev/null
+++ b/chrome/browser/importer/importer.h
@@ -0,0 +1,522 @@
+// Copyright (c) 2010 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 <string>
+#include <vector>
+
+#include "build/build_config.h"
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+#include "chrome/browser/bookmarks/bookmark_model_observer.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/importer/importer_data_types.h"
+#include "chrome/browser/importer/importer_list.h"
+#include "chrome/browser/importer/profile_writer.h"
+#include "chrome/browser/profile_import_process_host.h"
+#include "chrome/common/notification_registrar.h"
+#include "gfx/native_widget_types.h"
+#include "googleurl/src/gurl.h"
+
+using importer::ImportItem;
+using importer::ProfileInfo;
+
+class ExternalProcessImporterClient;
+class ImporterBridge;
+class InProcessImporterBridge;
+class Profile;
+class Task;
+class TemplateURL;
+
+struct IE7PasswordInfo;
+
+namespace history {
+struct ImportedFavIconUsage;
+class URLRow;
+}
+
+namespace webkit_glue {
+struct PasswordForm;
+}
+
+class FirefoxProfileLock;
+class Importer;
+
+// 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::RefCountedThreadSafe<ImporterHost>,
+ public BookmarkModelObserver,
+ public NotificationObserver {
+ public:
+ ImporterHost();
+
+ // BookmarkModelObserver methods.
+ virtual void Loaded(BookmarkModel* model);
+ virtual void BookmarkNodeMoved(BookmarkModel* model,
+ const BookmarkNode* old_parent,
+ int old_index,
+ const BookmarkNode* new_parent,
+ int new_index) {}
+ virtual void BookmarkNodeAdded(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index) {}
+ virtual void BookmarkNodeRemoved(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int old_index,
+ const BookmarkNode* node) {}
+ virtual void BookmarkNodeChanged(BookmarkModel* model,
+ const BookmarkNode* node) {}
+ virtual void BookmarkNodeChildrenReordered(BookmarkModel* model,
+ const BookmarkNode* node) {}
+ virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model,
+ const BookmarkNode* node) {}
+ virtual void BookmarkModelBeingDeleted(BookmarkModel* model);
+
+ // 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.
+ // |profile_info| -- browser profile to import.
+ // |target_profile| -- profile to import into.
+ // |items| -- specifies which data to import (mask of ImportItems).
+ // |writer| -- called to actually write data back to the profile.
+ // |first_run| -- true if this method is being called during first run.
+ virtual void StartImportSettings(const importer::ProfileInfo& profile_info,
+ Profile* target_profile,
+ uint16 items,
+ ProfileWriter* writer,
+ bool first_run);
+
+ // Cancel import.
+ virtual void Cancel();
+
+ // When in headless mode, the importer will not show the warning dialog and
+ // the outcome is as if the user had canceled the import operation.
+ void set_headless() {
+ headless_ = true;
+ }
+
+ bool is_headless() const {
+ return headless_;
+ }
+
+ void set_parent_window(gfx::NativeWindow parent_window) {
+ parent_window_ = parent_window;
+ }
+
+ // 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(importer::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(importer::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.
+ virtual void ImportStarted();
+ virtual void ImportItemStarted(importer::ImportItem item);
+ virtual void ImportItemEnded(importer::ImportItem item);
+ virtual void ImportEnded();
+
+ int GetAvailableProfileCount() const {
+ return importer_list_.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 {
+ return importer_list_.GetSourceProfileNameAt(index);
+ }
+
+ // Returns the ProfileInfo at the specified index. The ProfileInfo should be
+ // passed to StartImportSettings().
+ const importer::ProfileInfo& GetSourceProfileInfoAt(int index) const {
+ return importer_list_.GetSourceProfileInfoAt(index);
+ }
+
+ // Returns the ProfileInfo with the given browser type.
+ const importer::ProfileInfo& GetSourceProfileInfoForBrowserType(
+ int browser_type) const {
+ return importer_list_.GetSourceProfileInfoForBrowserType(browser_type);
+ }
+
+ protected:
+ friend class base::RefCountedThreadSafe<ImporterHost>;
+
+ ~ImporterHost();
+
+ // Returns true if importer should import to bookmark bar.
+ bool ShouldImportToBookmarkBar(bool first_run);
+
+ // Make sure that Firefox isn't running, if import browser is Firefox. Show
+ // the user a dialog to notify that they need to close FF to continue.
+ // |profile_info| holds the browser type and source path.
+ // |items| is a mask of all ImportItems that are to be imported.
+ // |first_run| is true if this method is being called during first run.
+ void CheckForFirefoxLock(const importer::ProfileInfo& profile_info,
+ uint16 items, bool first_run);
+
+ // Make sure BookmarkModel and TemplateURLModel are loaded before import
+ // process starts, if bookmarks and / or search engines are among the items
+ // which are to be imported.
+ void CheckForLoadedModels(uint16 items);
+
+ // Profile we're importing from.
+ Profile* profile_;
+
+ Observer* observer_;
+
+ // TODO(mirandac): task_ and importer_ should be private. Can't just put
+ // them there without changing the order of construct/destruct, so do this
+ // after main CL has been committed.
+ // The task is the process of importing settings from other browsers.
+ Task* task_;
+
+ // The importer used in the task;
+ Importer* importer_;
+
+ // Writes data from the importer back to the profile.
+ scoped_refptr<ProfileWriter> writer_;
+
+ // True if we're waiting for the model to finish loading.
+ bool waiting_for_bookmarkbar_model_;
+
+ // Have we installed a listener on the bookmark model?
+ bool installed_bookmark_observer_;
+
+ // True if source profile is readable.
+ bool is_source_readable_;
+
+ // True if UI is not to be shown.
+ bool headless_;
+
+ // Receives notification when the TemplateURLModel has loaded.
+ NotificationRegistrar registrar_;
+
+ // Parent Window to use when showing any modal dialog boxes.
+ gfx::NativeWindow parent_window_;
+
+ // Firefox profile lock.
+ scoped_ptr<FirefoxProfileLock> firefox_lock_;
+
+ private:
+ // Launches the thread that starts the import task, unless bookmark or
+ // template model are not yet loaded. If load is not detected, this method
+ // will be called when the loading observer sees that model loading is
+ // complete.
+ virtual void InvokeTaskIfDone();
+
+ // Used to create an importer of the appropriate type.
+ ImporterList importer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImporterHost);
+};
+
+// This class manages the import process. It creates the in-process half of
+// the importer bridge and the external process importer client.
+class ExternalProcessImporterHost : public ImporterHost {
+ public:
+ ExternalProcessImporterHost();
+
+ // Called when the BookmarkModel has finished loading. Calls InvokeTaskIfDone
+ // to start importing.
+ virtual void Loaded(BookmarkModel* model);
+
+ // Methods inherited from ImporterHost.
+ virtual void StartImportSettings(const importer::ProfileInfo& profile_info,
+ Profile* target_profile,
+ uint16 items,
+ ProfileWriter* writer,
+ bool first_run);
+
+ virtual void Cancel();
+
+ protected:
+ // Launches the ExternalProcessImporterClient unless bookmark or template
+ // model are not yet loaded. If load is not detected, this method will be
+ // called when the loading observer sees that model loading is complete.
+ virtual void InvokeTaskIfDone();
+
+ private:
+ // Used to pass notifications from the browser side to the external process.
+ ExternalProcessImporterClient* client_;
+
+ // Data for the external importer: ------------------------------------------
+ // Information about a profile needed for importing.
+ const importer::ProfileInfo* profile_info_;
+
+ // Mask of items to be imported (see importer::ImportItem).
+ uint16 items_;
+
+ // Whether to import bookmarks to the bookmark bar.
+ bool import_to_bookmark_bar_;
+
+ // True if the import process has been cancelled.
+ bool cancelled_;
+
+ // True if the import process has been launched. This prevents race
+ // conditions on import cancel.
+ bool import_process_launched_;
+
+ // End of external importer data --------------------------------------------
+
+ DISALLOW_COPY_AND_ASSIGN(ExternalProcessImporterHost);
+};
+
+// This class is the client for the ProfileImportProcessHost. It collects
+// notifications from this process host and feeds data back to the importer
+// host, who actually does the writing.
+class ExternalProcessImporterClient
+ : public ProfileImportProcessHost::ImportProcessClient {
+ public:
+ ExternalProcessImporterClient(ExternalProcessImporterHost* importer_host,
+ const importer::ProfileInfo& profile_info,
+ int items,
+ InProcessImporterBridge* bridge,
+ bool import_to_bookmark_bar);
+
+ ~ExternalProcessImporterClient();
+
+ // Launches the task to start the external process.
+ virtual void Start();
+
+ // Creates a new ProfileImportProcessHost, which launches the import process.
+ virtual void StartProcessOnIOThread(ResourceDispatcherHost* rdh,
+ ChromeThread::ID thread_id);
+
+ // Called by the ExternalProcessImporterHost on import cancel.
+ virtual void Cancel();
+
+ // Cancel import process on IO thread.
+ void CancelImportProcessOnIOThread();
+
+ // Report item completely downloaded on IO thread.
+ void NotifyItemFinishedOnIOThread(importer::ImportItem import_item);
+
+ // Cancel import on process crash.
+ virtual void OnProcessCrashed();
+
+ // Notifies the importerhost that import has finished, and calls Release().
+ void Cleanup();
+
+ // ProfileImportProcessHost messages ----------------------------------------
+ // The following methods are called by ProfileImportProcessHost when the
+ // corresponding message has been received from the import process.
+ virtual void OnImportStart();
+ virtual void OnImportFinished(bool succeeded, std::string error_msg);
+ virtual void OnImportItemStart(int item_data);
+ virtual void OnImportItemFinished(int item_data);
+
+ // Called on first message received when importing history; gives total
+ // number of rows to be imported.
+ virtual void OnHistoryImportStart(size_t total_history_rows_count);
+
+ // Called when a group of URLRows has been received.
+ virtual void OnHistoryImportGroup(
+ const std::vector<history::URLRow> &history_rows_group);
+
+ // Called when the home page has been received.
+ virtual void OnHomePageImportReady(const GURL& home_page);
+
+ // First message received when importing bookmarks.
+ // |first_folder_name| can be NULL.
+ // |options| is described in ProfileWriter::BookmarkOptions.
+ // |total_bookmarks_count| is the total number of bookmarks to be imported.
+ virtual void OnBookmarksImportStart(
+ const std::wstring first_folder_name,
+ int options, size_t total_bookmarks_count);
+
+ // Called when a group of bookmarks has been received.
+ virtual void OnBookmarksImportGroup(
+ const std::vector<ProfileWriter::BookmarkEntry>& bookmarks_group);
+
+ // First message received when importing favicons. |total_fav_icons_size|
+ // gives the total number of fav icons to be imported.
+ virtual void OnFavIconsImportStart(size_t total_fav_icons_count);
+
+ // Called when a group of favicons has been received.
+ virtual void OnFavIconsImportGroup(
+ const std::vector<history::ImportedFavIconUsage>& fav_icons_group);
+
+ // Called when the passwordform has been received.
+ virtual void OnPasswordFormImportReady(
+ const webkit_glue::PasswordForm& form);
+
+ // Called when search engines have been received.
+ virtual void OnKeywordsImportReady(
+ const std::vector<TemplateURL>& template_urls,
+ int default_keyword_index, bool unique_on_host_and_path);
+
+ // End ProfileImportProcessHost messages ------------------------------------
+
+ private:
+ // These variables store data being collected from the importer until the
+ // entire group has been collected and is ready to be written to the profile.
+ std::vector<history::URLRow> history_rows_;
+ std::vector<ProfileWriter::BookmarkEntry> bookmarks_;
+ std::vector<history::ImportedFavIconUsage> fav_icons_;
+
+ // Usually some variation on IDS_BOOKMARK_GROUP_...; the name of the folder
+ // under which imported bookmarks will be placed.
+ std::wstring bookmarks_first_folder_name_;
+
+ // Determines how bookmarks should be added (ProfileWriter::BookmarkOptions).
+ int bookmarks_options_;
+
+ // Total number of bookmarks to import.
+ size_t total_bookmarks_count_;
+
+ // Total number of history items to import.
+ size_t total_history_rows_count_;
+
+ // Total number of fav icons to import.
+ size_t total_fav_icons_count_;
+
+ // Notifications received from the ProfileImportProcessHost are passed back
+ // to process_importer_host_, which calls the ProfileWriter to record the
+ // import data. When the import process is done, process_importer_host_
+ // deletes itself.
+ ExternalProcessImporterHost* process_importer_host_;
+
+ // Handles sending messages to the external process. Deletes itself when
+ // the external process dies (see ChildProcessHost::OnChildDied).
+ ProfileImportProcessHost* profile_import_process_host_;
+
+ // Data to be passed from the importer host to the external importer.
+ const importer::ProfileInfo& profile_info_;
+ int items_;
+ bool import_to_bookmark_bar_;
+
+ // Takes import data coming over IPC and delivers it to be written by the
+ // ProfileWriter. Released by ExternalProcessImporterClient in its
+ // destructor.
+ InProcessImporterBridge* bridge_;
+
+ // True if import process has been cancelled.
+ bool cancelled_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExternalProcessImporterClient);
+};
+
+// The base class of all importers.
+class Importer : public base::RefCountedThreadSafe<Importer> {
+ public:
+ // 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(importer::ProfileInfo profile_info,
+ uint16 items,
+ ImporterBridge* bridge) = 0;
+
+ // Cancels the import process.
+ virtual void Cancel() { cancelled_ = true; }
+
+ void set_import_to_bookmark_bar(bool import_to_bookmark_bar) {
+ import_to_bookmark_bar_ = import_to_bookmark_bar;
+ }
+
+ void set_bookmark_bar_disabled(bool bookmark_bar_disabled) {
+ bookmark_bar_disabled_ = bookmark_bar_disabled;
+ }
+
+ bool bookmark_bar_disabled() {
+ return bookmark_bar_disabled_;
+ }
+
+ bool cancelled() const { return cancelled_; }
+
+ protected:
+ friend class base::RefCountedThreadSafe<Importer>;
+
+ Importer();
+ virtual ~Importer();
+
+ // 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 import_to_bookmark_bar() const { return import_to_bookmark_bar_; }
+
+ scoped_refptr<ImporterBridge> bridge_;
+
+ private:
+ // True if the caller cancels the import process.
+ bool cancelled_;
+
+ // True if the importer is created in the first run UI.
+ bool import_to_bookmark_bar_;
+
+ // Whether bookmark bar is disabled (not shown) for importer. This is set
+ // true during first run to prevent out of process bookmark importer from
+ // updating bookmark bar settings.
+ bool bookmark_bar_disabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(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.
+ // TODO (4164): this is never invoked, either rip it out or invoke it.
+ 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(gfx::NativeWindow parent_window,
+ int16 items,
+ ImporterHost* coordinator,
+ const importer::ProfileInfo& source_profile,
+ Profile* target_profile,
+ ImportObserver* observer,
+ bool first_run);
+
+#endif // CHROME_BROWSER_IMPORTER_IMPORTER_H_
diff --git a/chrome/browser/importer/importer_bridge.cc b/chrome/browser/importer/importer_bridge.cc
new file mode 100644
index 0000000..72a73a6
--- /dev/null
+++ b/chrome/browser/importer/importer_bridge.cc
@@ -0,0 +1,190 @@
+// Copyright (c) 2010 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_bridge.h"
+
+#include "app/l10n_util.h"
+#include "base/scoped_ptr.h"
+#include "base/values.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/importer/importer.h"
+#if defined(OS_WIN)
+#include "chrome/browser/password_manager/ie7_password.h"
+#endif
+#include "chrome/common/child_thread.h"
+#include "chrome/browser/importer/importer_messages.h"
+#include "chrome/profile_import/profile_import_thread.h"
+#include "webkit/glue/password_form.h"
+
+InProcessImporterBridge::InProcessImporterBridge(ProfileWriter* writer,
+ ImporterHost* host)
+ : writer_(writer), host_(host) {
+}
+
+void InProcessImporterBridge::AddBookmarkEntries(
+ const std::vector<ProfileWriter::BookmarkEntry>& bookmarks,
+ const std::wstring& first_folder_name,
+ int options) {
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(
+ writer_, &ProfileWriter::AddBookmarkEntry, bookmarks,
+ first_folder_name, options));
+}
+
+void InProcessImporterBridge::AddHomePage(const GURL &home_page) {
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(writer_, &ProfileWriter::AddHomepage, home_page));
+}
+
+#if defined(OS_WIN)
+void InProcessImporterBridge::AddIE7PasswordInfo(
+ const IE7PasswordInfo password_info) {
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(writer_, &ProfileWriter::AddIE7PasswordInfo,
+ password_info));
+}
+#endif // OS_WIN
+
+void InProcessImporterBridge::SetFavIcons(
+ const std::vector<history::ImportedFavIconUsage>& fav_icons) {
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(writer_, &ProfileWriter::AddFavicons, fav_icons));
+}
+
+void InProcessImporterBridge::SetHistoryItems(
+ const std::vector<history::URLRow> &rows) {
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(writer_, &ProfileWriter::AddHistoryPage, rows));
+}
+
+void InProcessImporterBridge::SetKeywords(
+ const std::vector<TemplateURL*>& template_urls,
+ int default_keyword_index,
+ bool unique_on_host_and_path) {
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(
+ writer_, &ProfileWriter::AddKeywords, template_urls,
+ default_keyword_index, unique_on_host_and_path));
+}
+
+void InProcessImporterBridge::SetPasswordForm(
+ const webkit_glue::PasswordForm& form) {
+ LOG(ERROR) << "IPImporterBridge::SetPasswordForm";
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(writer_, &ProfileWriter::AddPasswordForm, form));
+}
+
+void InProcessImporterBridge::NotifyItemStarted(importer::ImportItem item) {
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(host_, &ImporterHost::ImportItemStarted, item));
+}
+
+void InProcessImporterBridge::NotifyItemEnded(importer::ImportItem item) {
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(host_, &ImporterHost::ImportItemEnded, item));
+}
+
+void InProcessImporterBridge::NotifyStarted() {
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(host_, &ImporterHost::ImportStarted));
+}
+
+void InProcessImporterBridge::NotifyEnded() {
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(host_, &ImporterHost::ImportEnded));
+}
+
+std::wstring InProcessImporterBridge::GetLocalizedString(int message_id) {
+ return l10n_util::GetString(message_id);
+}
+
+ExternalProcessImporterBridge::ExternalProcessImporterBridge(
+ ProfileImportThread* profile_import_thread,
+ const DictionaryValue& localized_strings)
+ : profile_import_thread_(profile_import_thread) {
+ // Bridge needs to make its own copy because OS 10.6 autoreleases the
+ // localized_strings value that is passed in (see http://crbug.com/46003 ).
+ localized_strings_.reset(
+ static_cast<DictionaryValue*>(localized_strings.DeepCopy()));
+}
+
+void ExternalProcessImporterBridge::AddBookmarkEntries(
+ const std::vector<ProfileWriter::BookmarkEntry>& bookmarks,
+ const std::wstring& first_folder_name, int options) {
+ profile_import_thread_->NotifyBookmarksImportReady(bookmarks,
+ first_folder_name, options);
+}
+
+void ExternalProcessImporterBridge::AddHomePage(const GURL &home_page) {
+ // TODO(mirandac): remove home page import from code base.
+ // http://crbug.com/45678 :-)
+ NOTIMPLEMENTED();
+}
+
+#if defined(OS_WIN)
+void ExternalProcessImporterBridge::AddIE7PasswordInfo(
+ const IE7PasswordInfo password_info) {
+ NOTIMPLEMENTED();
+}
+#endif
+
+void ExternalProcessImporterBridge::SetFavIcons(
+ const std::vector<history::ImportedFavIconUsage>& fav_icons) {
+ profile_import_thread_->NotifyFavIconsImportReady(fav_icons);
+}
+
+void ExternalProcessImporterBridge::SetHistoryItems(
+ const std::vector<history::URLRow> &rows) {
+ profile_import_thread_->NotifyHistoryImportReady(rows);
+}
+
+void ExternalProcessImporterBridge::SetKeywords(
+ const std::vector<TemplateURL*>& template_urls,
+ int default_keyword_index,
+ bool unique_on_host_and_path) {
+ profile_import_thread_->NotifyKeywordsReady(template_urls,
+ default_keyword_index, unique_on_host_and_path);
+}
+
+void ExternalProcessImporterBridge::SetPasswordForm(
+ const webkit_glue::PasswordForm& form) {
+ profile_import_thread_->NotifyPasswordFormReady(form);
+}
+
+void ExternalProcessImporterBridge::NotifyItemStarted(
+ importer::ImportItem item) {
+ profile_import_thread_->NotifyItemStarted(item);
+}
+
+void ExternalProcessImporterBridge::NotifyItemEnded(importer::ImportItem item) {
+ profile_import_thread_->NotifyItemEnded(item);
+}
+
+void ExternalProcessImporterBridge::NotifyStarted() {
+ profile_import_thread_->NotifyStarted();
+}
+
+void ExternalProcessImporterBridge::NotifyEnded() {
+ // The internal process detects import end when all items have been received.
+}
+
+std::wstring ExternalProcessImporterBridge::GetLocalizedString(
+ int message_id) {
+ std::wstring message;
+ localized_strings_->GetString(IntToWString(message_id), &message);
+ return message;
+}
+
diff --git a/chrome/browser/importer/importer_bridge.h b/chrome/browser/importer/importer_bridge.h
new file mode 100644
index 0000000..993ec54
--- /dev/null
+++ b/chrome/browser/importer/importer_bridge.h
@@ -0,0 +1,168 @@
+// Copyright (c) 2010 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_BRIDGE_H_
+#define CHROME_BROWSER_IMPORTER_IMPORTER_BRIDGE_H_
+
+#include "build/build_config.h"
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/string16.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/importer/importer_data_types.h"
+// TODO: remove this, see friend declaration in ImporterBridge.
+#include "chrome/browser/importer/toolbar_importer.h"
+
+class ProfileImportThread;
+class DictionaryValue;
+class ImporterHost;
+
+class ImporterBridge : public base::RefCountedThreadSafe<ImporterBridge> {
+ public:
+ ImporterBridge() { }
+
+ virtual void AddBookmarkEntries(
+ const std::vector<ProfileWriter::BookmarkEntry>& bookmarks,
+ const std::wstring& first_folder_name,
+ int options) = 0;
+ virtual void AddHomePage(const GURL &home_page) = 0;
+
+#if defined(OS_WIN)
+ virtual void AddIE7PasswordInfo(const IE7PasswordInfo password_info) = 0;
+#endif
+
+ virtual void SetFavIcons(
+ const std::vector<history::ImportedFavIconUsage>& fav_icons) = 0;
+ virtual void SetHistoryItems(const std::vector<history::URLRow> &rows) = 0;
+ virtual void SetKeywords(const std::vector<TemplateURL*> &template_urls,
+ int default_keyword_index,
+ bool unique_on_host_and_path) = 0;
+ virtual void SetPasswordForm(const webkit_glue::PasswordForm& form) = 0;
+
+ // Notifies the coordinator that the collection of data for the specified
+ // item has begun.
+ virtual void NotifyItemStarted(importer::ImportItem item) = 0;
+
+ // Notifies the coordinator that the collection of data for the specified
+ // item has completed.
+ virtual void NotifyItemEnded(importer::ImportItem item) = 0;
+
+ // Notifies the coordinator that the import operation has begun.
+ virtual void NotifyStarted() = 0;
+
+ // Notifies the coordinator that the entire import operation has completed.
+ virtual void NotifyEnded() = 0;
+
+ // For InProcessImporters this calls l10n_util. For ExternalProcessImporters
+ // this calls the set of strings we've ported over to the external process.
+ // It's good to avoid having to create a separate ResourceBundle for the
+ // external import process, since the importer only needs a few strings.
+ virtual std::wstring GetLocalizedString(int message_id) = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<ImporterBridge>;
+ // TODO: In order to run Toolbar5Importer OOP we need to cut this
+ // connection, but as an interim step we allow Toolbar5Import to break
+ // the abstraction here and assume import is in-process.
+ friend class Toolbar5Importer;
+
+ virtual ~ImporterBridge() {}
+
+ DISALLOW_COPY_AND_ASSIGN(ImporterBridge);
+};
+
+class InProcessImporterBridge : public ImporterBridge {
+ public:
+ InProcessImporterBridge(ProfileWriter* writer,
+ ImporterHost* host);
+
+ // Methods inherited from ImporterBridge. On the internal side, these
+ // methods launch tasks to write the data to the profile with the |writer_|.
+ virtual void AddBookmarkEntries(
+ const std::vector<ProfileWriter::BookmarkEntry>& bookmarks,
+ const std::wstring& first_folder_name,
+ int options);
+ virtual void AddHomePage(const GURL &home_page);
+
+#if defined(OS_WIN)
+ virtual void AddIE7PasswordInfo(const IE7PasswordInfo password_info);
+#endif
+
+ virtual void SetFavIcons(
+ const std::vector<history::ImportedFavIconUsage>& fav_icons);
+ virtual void SetHistoryItems(const std::vector<history::URLRow> &rows);
+ virtual void SetKeywords(const std::vector<TemplateURL*>& template_urls,
+ int default_keyword_index,
+ bool unique_on_host_and_path);
+ virtual void SetPasswordForm(const webkit_glue::PasswordForm& form);
+
+ virtual void NotifyItemStarted(importer::ImportItem item);
+ virtual void NotifyItemEnded(importer::ImportItem item);
+ virtual void NotifyStarted();
+ virtual void NotifyEnded();
+ virtual std::wstring GetLocalizedString(int message_id);
+
+ private:
+ ~InProcessImporterBridge() {}
+
+ ProfileWriter* const writer_; // weak
+ ImporterHost* const host_; // weak
+
+ DISALLOW_COPY_AND_ASSIGN(InProcessImporterBridge);
+};
+
+// When the importer is run in an external process, the bridge is effectively
+// split in half by the IPC infrastructure. The external bridge receives data
+// and notifications from the importer, and sends it across IPC. The
+// internal bridge gathers the data from the IPC host and writes it to the
+// profile.
+class ExternalProcessImporterBridge : public ImporterBridge {
+ public:
+ ExternalProcessImporterBridge(ProfileImportThread* profile_import_thread,
+ const DictionaryValue& localized_strings);
+
+ // Methods inherited from ImporterBridge. On the external side, these
+ // methods gather data and give it to a ProfileImportThread to pass back
+ // to the browser process.
+ virtual void AddBookmarkEntries(
+ const std::vector<ProfileWriter::BookmarkEntry>& bookmarks,
+ const std::wstring& first_folder_name, int options);
+ virtual void AddHomePage(const GURL &home_page);
+
+#if defined(OS_WIN)
+ virtual void AddIE7PasswordInfo(const IE7PasswordInfo password_info);
+#endif
+
+ virtual void SetFavIcons(
+ const std::vector<history::ImportedFavIconUsage>& fav_icons);
+ virtual void SetHistoryItems(const std::vector<history::URLRow> &rows);
+ virtual void SetKeywords(const std::vector<TemplateURL*>& template_urls,
+ int default_keyword_index,
+ bool unique_on_host_and_path);
+ virtual void SetPasswordForm(const webkit_glue::PasswordForm& form);
+
+ virtual void NotifyItemStarted(importer::ImportItem item);
+ virtual void NotifyItemEnded(importer::ImportItem item);
+ virtual void NotifyStarted();
+ virtual void NotifyEnded();
+ virtual std::wstring GetLocalizedString(int message_id);
+
+ private:
+ ~ExternalProcessImporterBridge() {}
+
+ // Call back to send data and messages across IPC.
+ ProfileImportThread* const profile_import_thread_;
+
+ // Holds strings needed by the external importer because the resource
+ // bundle isn't available to the external process.
+ scoped_ptr<DictionaryValue> localized_strings_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExternalProcessImporterBridge);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_IMPORTER_BRIDGE_H_
diff --git a/chrome/browser/importer/importer_data_types.h b/chrome/browser/importer/importer_data_types.h
new file mode 100644
index 0000000..444cc45
--- /dev/null
+++ b/chrome/browser/importer/importer_data_types.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2010 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_DATA_TYPES_H_
+#define CHROME_BROWSER_IMPORTER_IMPORTER_DATA_TYPES_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+
+// Types needed for importing data from other browsers and the Google
+// Toolbar.
+namespace importer {
+
+// An enumeration of the type of data that can be imported.
+enum ImportItem {
+ NONE = 0,
+ HISTORY = 1 << 0,
+ FAVORITES = 1 << 1,
+ COOKIES = 1 << 2, // Not supported yet.
+ PASSWORDS = 1 << 3,
+ SEARCH_ENGINES = 1 << 4,
+ HOME_PAGE = 1 << 5,
+ ALL = (1 << 6) - 1 // All the bits should be 1, hence the -1.
+};
+
+// An enumeration of the type of browsers that we support to import
+// settings and data from them. Numbers added so that data can be
+// reliably cast to ints and passed across IPC.
+enum ProfileType {
+#if defined(OS_WIN)
+ MS_IE = 0,
+#endif
+ FIREFOX2 = 1,
+ FIREFOX3 = 2,
+#if defined(OS_MACOSX)
+ SAFARI = 3,
+#endif
+ GOOGLE_TOOLBAR5 = 4,
+ // Identifies a 'bookmarks.html' file.
+ BOOKMARKS_HTML = 5
+};
+
+// Information about a profile needed by an importer to do import work.
+struct ProfileInfo {
+ std::wstring description;
+ importer::ProfileType browser_type;
+ FilePath source_path;
+ FilePath app_path;
+ uint16 services_supported; // Bitmask of ImportItem.
+};
+
+} // namespace importer
+
+#endif // CHROME_BROWSER_IMPORTER_IMPORTER_DATA_TYPES_H_
+
diff --git a/chrome/browser/importer/importer_list.cc b/chrome/browser/importer/importer_list.cc
new file mode 100644
index 0000000..0919489
--- /dev/null
+++ b/chrome/browser/importer/importer_list.cc
@@ -0,0 +1,186 @@
+// Copyright (c) 2010 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_list.h"
+
+#include "app/l10n_util.h"
+#include "base/file_util.h"
+#include "base/stl_util-inl.h"
+#include "base/values.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/first_run.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/importer_bridge.h"
+#include "chrome/browser/importer/toolbar_importer.h"
+#include "chrome/browser/shell_integration.h"
+#include "grit/generated_resources.h"
+
+#if defined(OS_WIN)
+#include "app/win_util.h"
+#include "chrome/browser/importer/ie_importer.h"
+#include "chrome/browser/password_manager/ie7_password.h"
+#endif
+#if defined(OS_MACOSX)
+#include "base/mac_util.h"
+#include "chrome/browser/importer/safari_importer.h"
+#endif
+
+ImporterList::ImporterList() {
+}
+
+ImporterList::~ImporterList() {
+ STLDeleteContainerPointers(source_profiles_.begin(), source_profiles_.end());
+}
+
+void ImporterList::DetectSourceProfiles() {
+#if defined(OS_WIN)
+ // The order in which detect is called determines the order
+ // in which the options appear in the dropdown combo-box
+ if (ShellIntegration::IsFirefoxDefaultBrowser()) {
+ DetectFirefoxProfiles();
+ DetectIEProfiles();
+ } else {
+ DetectIEProfiles();
+ DetectFirefoxProfiles();
+ }
+ // TODO(brg) : Current UI requires win_util.
+ DetectGoogleToolbarProfiles();
+#else
+#if defined(OS_MACOSX)
+ DetectSafariProfiles();
+#endif
+ DetectFirefoxProfiles();
+#endif
+}
+
+Importer* ImporterList::CreateImporterByType(importer::ProfileType type) {
+ switch (type) {
+#if defined(OS_WIN)
+ case importer::MS_IE:
+ return new IEImporter();
+#endif
+ case importer::BOOKMARKS_HTML:
+ case importer::FIREFOX2:
+ return new Firefox2Importer();
+ case importer::FIREFOX3:
+ return new Firefox3Importer();
+ case importer::GOOGLE_TOOLBAR5:
+ return new Toolbar5Importer();
+#if defined(OS_MACOSX)
+ case importer::SAFARI:
+ return new SafariImporter(mac_util::GetUserLibraryPath());
+#endif // OS_MACOSX
+ }
+ NOTREACHED();
+ return NULL;
+}
+
+int ImporterList::GetAvailableProfileCount() const {
+ return static_cast<int>(source_profiles_.size());
+}
+
+std::wstring ImporterList::GetSourceProfileNameAt(int index) const {
+ DCHECK(index >=0 && index < GetAvailableProfileCount());
+ return source_profiles_[index]->description;
+}
+
+const importer::ProfileInfo& ImporterList::GetSourceProfileInfoAt(
+ int index) const {
+ DCHECK(index >=0 && index < GetAvailableProfileCount());
+ return *source_profiles_[index];
+}
+
+const importer::ProfileInfo& ImporterList::GetSourceProfileInfoForBrowserType(
+ int browser_type) const {
+ int count = GetAvailableProfileCount();
+ for (int i = 0; i < count; ++i) {
+ if (source_profiles_[i]->browser_type == browser_type)
+ return *source_profiles_[i];
+ }
+ NOTREACHED();
+ return *(new importer::ProfileInfo());
+}
+
+#if defined(OS_WIN)
+void ImporterList::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 = importer::MS_IE;
+ ie->source_path.clear();
+ ie->app_path.clear();
+ ie->services_supported = importer::HISTORY | importer::FAVORITES |
+ importer::COOKIES | importer::PASSWORDS | importer::SEARCH_ENGINES;
+ source_profiles_.push_back(ie);
+}
+#endif
+
+void ImporterList::DetectFirefoxProfiles() {
+ FilePath profile_path = GetFirefoxProfilePath();
+ if (profile_path.empty())
+ return;
+
+ // Detects which version of Firefox is installed.
+ importer::ProfileType firefox_type;
+ FilePath app_path;
+ int version = 0;
+#if defined(OS_WIN)
+ version = GetCurrentFirefoxMajorVersionFromRegistry();
+#endif
+ if (version != 2 && version != 3)
+ GetFirefoxVersionAndPathFromProfile(profile_path, &version, &app_path);
+
+ if (version == 2) {
+ firefox_type = importer::FIREFOX2;
+ } else if (version == 3) {
+ firefox_type = importer::FIREFOX3;
+ } else {
+ // Ignores other versions of firefox.
+ return;
+ }
+
+ importer::ProfileInfo* firefox = new importer::ProfileInfo();
+ firefox->description = l10n_util::GetString(IDS_IMPORT_FROM_FIREFOX);
+ firefox->browser_type = firefox_type;
+ firefox->source_path = profile_path;
+#if defined(OS_WIN)
+ firefox->app_path = FilePath::FromWStringHack(
+ GetFirefoxInstallPathFromRegistry());
+#endif
+ if (firefox->app_path.empty())
+ firefox->app_path = app_path;
+ firefox->services_supported = importer::HISTORY | importer::FAVORITES |
+ importer::PASSWORDS | importer::SEARCH_ENGINES;
+ source_profiles_.push_back(firefox);
+}
+
+void ImporterList::DetectGoogleToolbarProfiles() {
+ if (!FirstRun::IsChromeFirstRun()) {
+ importer::ProfileInfo* google_toolbar = new importer::ProfileInfo();
+ google_toolbar->browser_type = importer::GOOGLE_TOOLBAR5;
+ google_toolbar->description = l10n_util::GetString(
+ IDS_IMPORT_FROM_GOOGLE_TOOLBAR);
+ google_toolbar->source_path.clear();
+ google_toolbar->app_path.clear();
+ google_toolbar->services_supported = importer::FAVORITES;
+ source_profiles_.push_back(google_toolbar);
+ }
+}
+
+#if defined(OS_MACOSX)
+void ImporterList::DetectSafariProfiles() {
+ uint16 items = importer::NONE;
+ if (SafariImporter::CanImport(mac_util::GetUserLibraryPath(), &items)) {
+ importer::ProfileInfo* safari = new importer::ProfileInfo();
+ safari->browser_type = importer::SAFARI;
+ safari->description = l10n_util::GetString(IDS_IMPORT_FROM_SAFARI);
+ safari->source_path.clear();
+ safari->app_path.clear();
+ safari->services_supported = items;
+ source_profiles_.push_back(safari);
+ }
+}
+#endif // OS_MACOSX
diff --git a/chrome/browser/importer/importer_list.h b/chrome/browser/importer/importer_list.h
new file mode 100644
index 0000000..c76aab3
--- /dev/null
+++ b/chrome/browser/importer/importer_list.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2010 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_LIST_H_
+#define CHROME_BROWSER_IMPORTER_IMPORTER_LIST_H_
+
+#include <string>
+#include <vector>
+
+#include "build/build_config.h"
+#include "base/basictypes.h"
+#include "chrome/browser/importer/importer_data_types.h"
+
+class Importer;
+
+class ImporterList {
+ public:
+ ImporterList();
+ ~ImporterList();
+
+ // 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();
+
+ Importer* CreateImporterByType(importer::ProfileType type);
+
+ // Returns the number of different browser profiles you can import from.
+ int GetAvailableProfileCount() const;
+
+ // 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 importer::ProfileInfo& GetSourceProfileInfoAt(int index) const;
+
+ // Returns the ProfileInfo with the given browser type.
+ const importer::ProfileInfo& GetSourceProfileInfoForBrowserType(
+ int browser_type) const;
+
+ // Helper methods for detecting available profiles.
+#if defined(OS_WIN)
+ void DetectIEProfiles();
+#endif
+ void DetectFirefoxProfiles();
+ void DetectGoogleToolbarProfiles();
+#if defined(OS_MACOSX)
+ void DetectSafariProfiles();
+#endif
+
+ private:
+ // The list of profiles with the default one first.
+ std::vector<importer::ProfileInfo*> source_profiles_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImporterList);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_IMPORTER_LIST_H_
diff --git a/chrome/browser/importer/importer_messages.h b/chrome/browser/importer/importer_messages.h
new file mode 100644
index 0000000..4bbba62
--- /dev/null
+++ b/chrome/browser/importer/importer_messages.h
@@ -0,0 +1,369 @@
+// Copyright (c) 2010 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_MESSAGES_H_
+#define CHROME_BROWSER_IMPORTER_IMPORTER_MESSAGES_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/importer/importer_data_types.h"
+#include "chrome/browser/importer/profile_writer.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/common/common_param_traits.h"
+#include "ipc/ipc_message_utils.h"
+
+namespace IPC {
+
+// Traits for importer::ProfileInfo struct to pack/unpack.
+template <>
+struct ParamTraits<importer::ProfileInfo> {
+ typedef importer::ProfileInfo param_type;
+ static void Write(Message* m, const param_type& p) {
+ WriteParam(m, p.description);
+ WriteParam(m, static_cast<int>(p.browser_type));
+ WriteParam(m, p.source_path);
+ WriteParam(m, p.app_path);
+ WriteParam(m, static_cast<int>(p.services_supported));
+ }
+ static bool Read(const Message* m, void** iter, param_type* p) {
+ if (!ReadParam(m, iter, &p->description))
+ return false;
+
+ int browser_type = 0;
+ if (!ReadParam(m, iter, &browser_type))
+ return false;
+ p->browser_type = static_cast<importer::ProfileType>(browser_type);
+
+ if (!ReadParam(m, iter, &p->source_path) ||
+ !ReadParam(m, iter, &p->app_path))
+ return false;
+
+ int services_supported = 0;
+ if (!ReadParam(m, iter, &services_supported))
+ return false;
+ p->services_supported = static_cast<uint16>(services_supported);
+
+ return true;
+ }
+ static void Log(const param_type& p, std::wstring* l) {
+ l->append(L"(");
+ LogParam(p.description, l);
+ l->append(L", ");
+ LogParam(static_cast<int>(p.browser_type), l);
+ l->append(L", ");
+ LogParam(p.source_path, l);
+ l->append(L", ");
+ LogParam(p.app_path, l);
+ l->append(L", ");
+ LogParam(static_cast<int>(p.services_supported), l);
+ l->append(L")");
+ }
+}; // ParamTraits<importer::ProfileInfo>
+
+// Traits for history::URLRow to pack/unpack.
+template <>
+struct ParamTraits<history::URLRow> {
+ typedef history::URLRow param_type;
+ static void Write(Message* m, const param_type& p) {
+ WriteParam(m, p.id());
+ WriteParam(m, p.url());
+ WriteParam(m, p.title());
+ WriteParam(m, p.visit_count());
+ WriteParam(m, p.typed_count());
+ WriteParam(m, p.last_visit());
+ WriteParam(m, p.hidden());
+ WriteParam(m, p.favicon_id());
+ }
+ static bool Read(const Message* m, void** iter, param_type* p) {
+ history::URLID id;
+ GURL url;
+ string16 title;
+ int visit_count, typed_count;
+ base::Time last_visit;
+ bool hidden;
+ history::FavIconID favicon_id;
+ if (!ReadParam(m, iter, &id) ||
+ !ReadParam(m, iter, &url) ||
+ !ReadParam(m, iter, &title) ||
+ !ReadParam(m, iter, &visit_count) ||
+ !ReadParam(m, iter, &typed_count) ||
+ !ReadParam(m, iter, &last_visit) ||
+ !ReadParam(m, iter, &hidden) ||
+ !ReadParam(m, iter, &favicon_id))
+ return false;
+ *p = history::URLRow(url, id);
+ p->set_title(title);
+ p->set_visit_count(visit_count);
+ p->set_typed_count(typed_count);
+ p->set_last_visit(last_visit);
+ p->set_hidden(hidden);
+ p->set_favicon_id(favicon_id);
+ return true;
+ }
+ static void Log(const param_type& p, std::wstring* l) {
+ l->append(L"(");
+ LogParam(p.id(), l);
+ l->append(L", ");
+ LogParam(p.url(), l);
+ l->append(L", ");
+ LogParam(p.title(), l);
+ l->append(L", ");
+ LogParam(p.visit_count(), l);
+ l->append(L", ");
+ LogParam(p.typed_count(), l);
+ l->append(L", ");
+ LogParam(p.last_visit(), l);
+ l->append(L", ");
+ LogParam(p.hidden(), l);
+ l->append(L", ");
+ LogParam(p.favicon_id(), l);
+ l->append(L")");
+ }
+}; // ParamTraits<history::URLRow>
+
+// Traits for ProfileWriter::BookmarkEntry to pack/unpack.
+template <>
+struct ParamTraits<ProfileWriter::BookmarkEntry> {
+ typedef ProfileWriter::BookmarkEntry param_type;
+ static void Write(Message* m, const param_type& p) {
+ WriteParam(m, p.in_toolbar);
+ WriteParam(m, p.url);
+ WriteParam(m, p.path);
+ WriteParam(m, p.title);
+ WriteParam(m, p.creation_time);
+ }
+ static bool Read(const Message* m, void** iter, param_type* p) {
+ return
+ (ReadParam(m, iter, &p->in_toolbar)) &&
+ (ReadParam(m, iter, &p->url)) &&
+ (ReadParam(m, iter, &p->path)) &&
+ (ReadParam(m, iter, &p->title)) &&
+ (ReadParam(m, iter, &p->creation_time));
+ }
+ static void Log(const param_type& p, std::wstring* l) {
+ l->append(L"(");
+ LogParam(p.in_toolbar, l);
+ l->append(L", ");
+ LogParam(p.url, l);
+ l->append(L", ");
+ LogParam(p.path, l);
+ l->append(L", ");
+ LogParam(p.title, l);
+ l->append(L", ");
+ LogParam(p.creation_time, l);
+ l->append(L")");
+ }
+}; // ParamTraits<ProfileWriter::BookmarkEntry>
+
+// Traits for history::ImportedFavIconUsage.
+template <>
+struct ParamTraits<history::ImportedFavIconUsage> {
+ typedef history::ImportedFavIconUsage param_type;
+ static void Write(Message* m, const param_type& p) {
+ WriteParam(m, p.favicon_url);
+ WriteParam(m, p.png_data);
+ WriteParam(m, p.urls);
+ }
+ static bool Read(const Message* m, void** iter, param_type* p) {
+ return
+ ReadParam(m, iter, &p->favicon_url) &&
+ ReadParam(m, iter, &p->png_data) &&
+ ReadParam(m, iter, &p->urls);
+ }
+ static void Log(const param_type& p, std::wstring* l) {
+ l->append(L"(");
+ LogParam(p.favicon_url, l);
+ l->append(L", ");
+ LogParam(p.png_data, l);
+ l->append(L", ");
+ LogParam(p.urls, l);
+ l->append(L")");
+ }
+}; // ParamTraits<history::ImportedFavIconUsage
+
+// Traits for TemplateURLRef
+template <>
+struct ParamTraits<TemplateURLRef> {
+ typedef TemplateURLRef param_type;
+ static void Write(Message* m, const param_type& p) {
+ WriteParam(m, p.url());
+ WriteParam(m, p.index_offset());
+ WriteParam(m, p.page_offset());
+ }
+ static bool Read(const Message* m, void** iter, param_type* p) {
+ std::string url;
+ int index_offset;
+ int page_offset;
+ if (!ReadParam(m, iter, &url) ||
+ !ReadParam(m, iter, &index_offset) ||
+ !ReadParam(m, iter, &page_offset))
+ return false;
+ *p = TemplateURLRef(url, index_offset, page_offset);
+ return true;
+ }
+ static void Log(const param_type& p, std::wstring* l) {
+ l->append(L"<TemplateURLRef>");
+ }
+};
+
+// Traits for TemplateURL::ImageRef
+template <>
+struct ParamTraits<TemplateURL::ImageRef> {
+ typedef TemplateURL::ImageRef param_type;
+ static void Write(Message* m, const param_type& p) {
+ WriteParam(m, p.type);
+ WriteParam(m, p.width);
+ WriteParam(m, p.height);
+ WriteParam(m, p.url);
+ }
+ static bool Read(const Message* m, void** iter, param_type* p) {
+ std::wstring type;
+ int width;
+ int height;
+ GURL url;
+ if (!ReadParam(m, iter, &type) ||
+ !ReadParam(m, iter, &width) ||
+ !ReadParam(m, iter, &height) ||
+ !ReadParam(m, iter, &url))
+ return false;
+ *p = TemplateURL::ImageRef(type, width, height, url); // here in
+ return true;
+ }
+ static void Log(const param_type& p, std::wstring* l) {
+ l->append(L"<TemplateURL::ImageRef>");
+ }
+};
+
+// Traits for TemplateURL
+template <>
+struct ParamTraits<TemplateURL> {
+ typedef TemplateURL param_type;
+ static void Write(Message* m, const param_type& p) {
+ WriteParam(m, p.short_name());
+ WriteParam(m, p.description());
+ if (p.suggestions_url()) {
+ WriteParam(m, true);
+ WriteParam(m, *p.suggestions_url());
+ } else {
+ WriteParam(m, false);
+ }
+ WriteParam(m, *p.url());
+ WriteParam(m, p.originating_url());
+ WriteParam(m, p.keyword());
+ WriteParam(m, p.autogenerate_keyword());
+ WriteParam(m, p.show_in_default_list());
+ WriteParam(m, p.safe_for_autoreplace());
+ WriteParam(m, p.image_refs().size());
+
+ std::vector<TemplateURL::ImageRef>::const_iterator iter;
+ for (iter = p.image_refs().begin(); iter != p.image_refs().end(); ++iter) {
+ WriteParam(m, iter->type);
+ WriteParam(m, iter->width);
+ WriteParam(m, iter->height);
+ WriteParam(m, iter->url);
+ }
+
+ WriteParam(m, p.languages());
+ WriteParam(m, p.input_encodings());
+ WriteParam(m, p.date_created());
+ WriteParam(m, p.usage_count());
+ WriteParam(m, p.prepopulate_id());
+ }
+ static bool Read(const Message* m, void** iter, param_type* p) {
+ std::wstring short_name;
+ std::wstring description;
+ bool includes_suggestions_url;
+ TemplateURLRef suggestions_url;
+ TemplateURLRef url;
+ GURL originating_url;
+ std::wstring keyword;
+ bool autogenerate_keyword;
+ bool show_in_default_list;
+ bool safe_for_autoreplace;
+ std::vector<std::wstring> languages;
+ std::vector<std::string> input_encodings;
+ base::Time date_created;
+ int usage_count;
+ int prepopulate_id;
+
+ if (!ReadParam(m, iter, &short_name) ||
+ !ReadParam(m, iter, &description))
+ return false;
+
+ if (!ReadParam(m, iter, &includes_suggestions_url))
+ return false;
+ if (includes_suggestions_url) {
+ if (!ReadParam(m, iter, &suggestions_url))
+ return false;
+ }
+
+ size_t image_refs_size = 0;
+ if (!ReadParam(m, iter, &url) ||
+ !ReadParam(m, iter, &originating_url) ||
+ !ReadParam(m, iter, &keyword) ||
+ !ReadParam(m, iter, &autogenerate_keyword) ||
+ !ReadParam(m, iter, &show_in_default_list) ||
+ !ReadParam(m, iter, &safe_for_autoreplace) ||
+ !ReadParam(m, iter, &image_refs_size))
+ return false;
+
+ *p = TemplateURL();
+ for (size_t i = 0; i < image_refs_size; ++i) {
+ std::wstring type;
+ int width;
+ int height;
+ GURL url;
+ if (!ReadParam(m, iter, &type) ||
+ !ReadParam(m, iter, &width) ||
+ !ReadParam(m, iter, &height) ||
+ !ReadParam(m, iter, &url))
+ return false;
+ p->add_image_ref(TemplateURL::ImageRef(type, width, height, url));
+ }
+
+ if (!ReadParam(m, iter, &languages) ||
+ !ReadParam(m, iter, &input_encodings) ||
+ !ReadParam(m, iter, &date_created) ||
+ !ReadParam(m, iter, &usage_count) ||
+ !ReadParam(m, iter, &prepopulate_id))
+ return false;
+
+ p->set_short_name(short_name);
+ p->set_description(description);
+ p->SetSuggestionsURL(suggestions_url.url(), suggestions_url.index_offset(),
+ suggestions_url.page_offset());
+ p->SetURL(url.url(), url.index_offset(), url.page_offset());
+ p->set_originating_url(originating_url);
+ p->set_keyword(keyword);
+ p->set_autogenerate_keyword(autogenerate_keyword);
+ p->set_show_in_default_list(show_in_default_list);
+ p->set_safe_for_autoreplace(safe_for_autoreplace);
+
+ std::vector<std::wstring>::const_iterator lang_iter;
+ for (lang_iter = languages.begin();
+ lang_iter != languages.end();
+ ++lang_iter) {
+ p->add_language(*lang_iter);
+ }
+ p->set_input_encodings(input_encodings);
+ p->set_date_created(date_created);
+ p->set_usage_count(usage_count);
+ p->set_prepopulate_id(prepopulate_id);
+ return true;
+ }
+ static void Log(const param_type& p, std::wstring* l) {
+ l->append(L"<TemplateURL>");
+ }
+};
+
+} // namespace IPC
+
+#define MESSAGES_INTERNAL_FILE \
+ "chrome/browser/importer/importer_messages_internal.h"
+#include "ipc/ipc_message_macros.h"
+
+#endif // CHROME_BROWSER_IMPORTER_IMPORTER_MESSAGES_H_
diff --git a/chrome/browser/importer/importer_messages_internal.h b/chrome/browser/importer/importer_messages_internal.h
new file mode 100644
index 0000000..b247237
--- /dev/null
+++ b/chrome/browser/importer/importer_messages_internal.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2010 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 <string>
+#include <vector>
+
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/importer/importer_data_types.h"
+#include "ipc/ipc_message_macros.h"
+#include "webkit/glue/password_form.h"
+
+//-----------------------------------------------------------------------------
+// ProfileImportProcess messages
+// These are messages sent from the browser to the profile import process.
+IPC_BEGIN_MESSAGES(ProfileImportProcess)
+ IPC_MESSAGE_CONTROL4(ProfileImportProcessMsg_StartImport,
+ importer::ProfileInfo /* ProfileInfo struct */,
+ int /* bitmask of items to import */,
+ DictionaryValue /* localized strings */,
+ bool /* import to bookmark bar */)
+
+ IPC_MESSAGE_CONTROL0(ProfileImportProcessMsg_CancelImport)
+
+ IPC_MESSAGE_CONTROL1(ProfileImportProcessMsg_ReportImportItemFinished,
+ int /* ImportItem */)
+IPC_END_MESSAGES(ProfileImportProcess)
+
+//---------------------------------------------------------------------------
+// ProfileImportProcessHost messages
+// These are messages sent from the profile import process to the browser.
+IPC_BEGIN_MESSAGES(ProfileImportProcessHost)
+ // These messages send information about the status of the import and
+ // individual import tasks.
+ IPC_MESSAGE_CONTROL0(ProfileImportProcessHostMsg_Import_Started)
+
+ IPC_MESSAGE_CONTROL2(ProfileImportProcessHostMsg_Import_Finished,
+ bool /* was import successful? */,
+ std::string /* error message, if any */)
+
+ IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_ImportItem_Started,
+ int /* ImportItem */)
+
+ IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_ImportItem_Finished,
+ int /* ImportItem */)
+
+ // These messages send data from the external importer process back to
+ // the process host so it can be written to the profile.
+ IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_NotifyHistoryImportStart,
+ int /* total number of history::URLRow items */)
+
+ IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_NotifyHistoryImportGroup,
+ std::vector<history::URLRow>)
+
+ IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_NotifyHomePageImportReady,
+ GURL /* GURL of home page */)
+
+ IPC_MESSAGE_CONTROL3(ProfileImportProcessHostMsg_NotifyBookmarksImportStart,
+ std::wstring /* first folder name */,
+ int /* options */,
+ int /* total number of bookmarks */)
+
+ IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_NotifyBookmarksImportGroup,
+ std::vector<ProfileWriter::BookmarkEntry>)
+
+ IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_NotifyFavIconsImportStart,
+ int /* total number of FavIcons */)
+
+ IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_NotifyFavIconsImportGroup,
+ std::vector<history::ImportedFavIconUsage> )
+
+ IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_NotifyPasswordFormReady,
+ webkit_glue::PasswordForm )
+
+ IPC_MESSAGE_CONTROL3(ProfileImportProcessHostMsg_NotifyKeywordsReady,
+ std::vector<TemplateURL>,
+ int, /* default keyword index */
+ bool /* unique on host and path */)
+IPC_END_MESSAGES(ProfileImportProcessHost)
+
diff --git a/chrome/browser/importer/importer_unittest.cc b/chrome/browser/importer/importer_unittest.cc
new file mode 100644
index 0000000..1dce41e
--- /dev/null
+++ b/chrome/browser/importer/importer_unittest.cc
@@ -0,0 +1,885 @@
+// Copyright (c) 2010 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 "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <unknwn.h>
+#include <intshcut.h>
+#include <pstore.h>
+#include <urlhist.h>
+#include <shlguid.h>
+#endif
+
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/stl_util-inl.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/importer/importer.h"
+#include "chrome/browser/importer/importer_bridge.h"
+#include "chrome/browser/importer/importer_data_types.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/common/chrome_paths.h"
+#include "webkit/glue/password_form.h"
+
+#if defined(OS_WIN)
+#include "base/scoped_comptr_win.h"
+#include "app/win_util.h"
+#include "chrome/browser/importer/ie_importer.h"
+#include "chrome/browser/password_manager/ie7_password.h"
+#endif
+
+using importer::FAVORITES;
+using importer::FIREFOX2;
+using importer::FIREFOX3;
+using importer::HISTORY;
+using importer::ImportItem;
+#if defined(OS_WIN)
+using importer::MS_IE;
+#endif
+using importer::PASSWORDS;
+using importer::SEARCH_ENGINES;
+using webkit_glue::PasswordForm;
+
+// TODO(estade): some of these are disabled on mac. http://crbug.com/48007
+#if defined(OS_MACOSX)
+#define MAYBE(x) DISABLED_##x
+#else
+#define MAYBE(x) x
+#endif
+
+class ImporterTest : public testing::Test {
+ public:
+ ImporterTest()
+ : ui_thread_(ChromeThread::UI, &message_loop_),
+ file_thread_(ChromeThread::FILE, &message_loop_) {}
+ 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_));
+ test_path_ = test_path_.AppendASCII("ImporterTest");
+ file_util::Delete(test_path_, true);
+ file_util::CreateDirectory(test_path_);
+ profile_path_ = test_path_.AppendASCII("profile");
+ app_path_ = test_path_.AppendASCII("app");
+ file_util::CreateDirectory(app_path_);
+ }
+
+ 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_));
+ }
+
+ void Firefox3xImporterTest(std::string profile_dir,
+ ImporterHost::Observer* observer,
+ ProfileWriter* writer,
+ bool import_search_plugins) {
+ FilePath data_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
+ data_path = data_path.AppendASCII(profile_dir);
+ ASSERT_TRUE(file_util::CopyDirectory(data_path, profile_path_, true));
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
+ data_path = data_path.AppendASCII("firefox3_nss");
+ ASSERT_TRUE(file_util::CopyDirectory(data_path, profile_path_, false));
+
+ FilePath search_engine_path = app_path_;
+ search_engine_path = search_engine_path.AppendASCII("searchplugins");
+ file_util::CreateDirectory(search_engine_path);
+ if (import_search_plugins) {
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
+ data_path = data_path.AppendASCII("firefox3_searchplugins");
+ if (!file_util::PathExists(data_path)) {
+ // TODO(maruel): Create search test data that we can open source!
+ LOG(ERROR) << L"Missing internal test data";
+ return;
+ }
+ ASSERT_TRUE(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();
+ host->SetObserver(observer);
+ int items = HISTORY | PASSWORDS | FAVORITES;
+ if (import_search_plugins)
+ items = items | SEARCH_ENGINES;
+ loop->PostTask(FROM_HERE, NewRunnableMethod(host.get(),
+ &ImporterHost::StartImportSettings, profile_info,
+ static_cast<Profile*>(NULL), items, writer, true));
+ loop->Run();
+ }
+
+ MessageLoopForUI message_loop_;
+ ChromeThread ui_thread_;
+ ChromeThread file_thread_;
+ FilePath test_path_;
+ FilePath profile_path_;
+ FilePath app_path_;
+};
+
+const int kMaxPathSize = 5;
+
+typedef struct {
+ const bool in_toolbar;
+ const size_t 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;
+
+// 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 (size_t 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;
+}
+
+#if defined(OS_WIN)
+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);
+}
+
+class TestObserver : public ProfileWriter,
+ public ImporterHost::Observer {
+ public:
+ TestObserver() : ProfileWriter(NULL) {
+ bookmark_count_ = 0;
+ history_count_ = 0;
+ password_count_ = 0;
+ }
+
+ virtual void ImportItemStarted(importer::ImportItem item) {}
+ virtual void ImportItemEnded(importer::ImportItem item) {}
+ virtual void ImportStarted() {}
+ virtual void ImportEnded() {
+ MessageLoop::current()->Quit();
+ EXPECT_EQ(arraysize(kIEBookmarks), bookmark_count_);
+ EXPECT_EQ(1, history_count_);
+#if 0 // This part of the test is disabled. See bug #2466
+ if (IsWindowsVista())
+ EXPECT_EQ(0, password_count_);
+ else
+ EXPECT_EQ(1, password_count_);
+#endif
+ }
+
+ virtual bool BookmarkModelIsLoaded() const {
+ // Profile is ready for writing.
+ return true;
+ }
+
+ virtual bool TemplateURLModelIsLoaded() const {
+ return true;
+ }
+
+ 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,
+ const std::wstring& first_folder_name,
+ int options) {
+ // 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:
+ ~TestObserver() {}
+
+ size_t bookmark_count_;
+ size_t history_count_;
+ size_t password_count_;
+};
+
+bool CreateUrlFile(std::wstring file, std::wstring url) {
+ ScopedComPtr<IUniformResourceLocator> locator;
+ HRESULT result = locator.CreateInstance(CLSID_InternetShortcut, NULL,
+ CLSCTX_INPROC_SERVER);
+ if (FAILED(result))
+ return false;
+ ScopedComPtr<IPersistFile> persist_file;
+ result = persist_file.QueryFrom(locator);
+ 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) {
+ ScopedComPtr<IEnumPStoreItems, NULL> item;
+ HRESULT result = pstore->EnumItems(0, type, subtype, 0, item.Receive());
+ 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) {
+ // Sets up a favorites folder.
+ win_util::ScopedCOMInitializer com_init;
+ std::wstring path = test_path_.ToWStringHack();
+ 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;
+#if 0 // This part of the test is disabled. See bug #2466
+ ScopedComPtr<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.Receive(), 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);
+ }
+#endif
+
+ // Sets up a special history link.
+ ScopedComPtr<IUrlHistoryStg2> url_history_stg2;
+ res = url_history_stg2.CreateInstance(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();
+
+ 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,
+ static_cast<Profile*>(NULL), HISTORY | PASSWORDS | FAVORITES, observer,
+ true));
+ loop->Run();
+
+ // Cleans up.
+ url_history_stg2->DeleteUrl(kIEIdentifyUrl, 0);
+ url_history_stg2.Release();
+#if 0 // This part of the test is disabled. See bug #2466
+ if (!IsWindowsVista()) {
+ ClearPStoreType(pstore, &type, &subtype);
+ // Releases it befor unload the dll.
+ pstore.Release();
+ FreeLibrary(pstorec_dll);
+ }
+#endif
+}
+
+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);
+}
+#endif // defined(OS_WIN)
+
+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=%22er%22&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 char* url;
+} KeywordList;
+
+static const KeywordList kFirefox2Keywords[] = {
+ // Searh plugins
+ { L"amazon.com",
+ "http://www.amazon.com/exec/obidos/external-search/?field-keywords="
+ "{searchTerms}&mode=blended" },
+ { L"answers.com",
+ "http://www.answers.com/main/ntquery?s={searchTerms}&gwp=13" },
+ { L"search.creativecommons.org",
+ "http://search.creativecommons.org/?q={searchTerms}" },
+ { L"search.ebay.com",
+ "http://search.ebay.com/search/search.dll?query={searchTerms}&"
+ "MfcISAPICommand=GetResult&ht=1&ebaytag1=ebayreg&srchdesc=n&"
+ "maxRecordsReturned=300&maxRecordsPerPage=50&SortProperty=MetaEndSort" },
+ { L"google.com",
+ "http://www.google.com/search?q={searchTerms}&ie=utf-8&oe=utf-8&aq=t" },
+ { L"search.yahoo.com",
+ "http://search.yahoo.com/search?p={searchTerms}&ei=UTF-8" },
+ { L"flickr.com",
+ "http://www.flickr.com/photos/tags/?q={searchTerms}" },
+ { L"imdb.com",
+ "http://www.imdb.com/find?q={searchTerms}" },
+ { L"webster.com",
+ "http://www.webster.com/cgi-bin/dictionary?va={searchTerms}" },
+ // Search keywords.
+ { L"google", "http://www.google.com/" },
+ { 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(importer::ImportItem item) {}
+ virtual void ImportItemEnded(importer::ImportItem item) {}
+ virtual void ImportStarted() {}
+ virtual void ImportEnded() {
+ MessageLoop::current()->Quit();
+ EXPECT_EQ(arraysize(kFirefox2Bookmarks), bookmark_count_);
+ EXPECT_EQ(1U, 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 bool TemplateURLModelIsLoaded() const {
+ return true;
+ }
+
+ 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(WideToUTF16(p.username_element), form.username_element);
+ EXPECT_EQ(WideToUTF16(p.username), form.username_value);
+ EXPECT_EQ(WideToUTF16(p.password_element), form.password_element);
+ EXPECT_EQ(WideToUTF16(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(1U, page.size());
+ EXPECT_EQ("http://en-us.www.mozilla.com/", page[0].url().spec());
+ EXPECT_EQ(ASCIIToUTF16("Firefox Updated"), page[0].title());
+ ++history_count_;
+ }
+
+ virtual void AddBookmarkEntry(const std::vector<BookmarkEntry>& bookmark,
+ const std::wstring& first_folder_name,
+ int options) {
+ 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 (size_t j = 0; j < arraysize(kFirefox2Keywords); ++j) {
+ 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:
+ ~FirefoxObserver() {}
+
+ size_t bookmark_count_;
+ size_t history_count_;
+ size_t password_count_;
+ size_t keyword_count_;
+ std::wstring default_keyword_;
+ std::string default_keyword_url_;
+};
+
+TEST_F(ImporterTest, MAYBE(Firefox2Importer)) {
+ FilePath data_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
+ data_path = data_path.AppendASCII("firefox2_profile");
+ ASSERT_TRUE(file_util::CopyDirectory(data_path, profile_path_, true));
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
+ data_path = data_path.AppendASCII("firefox2_nss");
+ ASSERT_TRUE(file_util::CopyDirectory(data_path, profile_path_, false));
+
+ FilePath search_engine_path = app_path_;
+ search_engine_path = search_engine_path.AppendASCII("searchplugins");
+ file_util::CreateDirectory(search_engine_path);
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path));
+ data_path = data_path.AppendASCII("firefox2_searchplugins");
+ if (!file_util::PathExists(data_path)) {
+ // TODO(maruel): Create test data that we can open source!
+ LOG(ERROR) << L"Missing internal test data";
+ return;
+ }
+ ASSERT_TRUE(file_util::CopyDirectory(data_path, search_engine_path, false));
+
+ MessageLoop* loop = MessageLoop::current();
+ scoped_refptr<ImporterHost> host = new ImporterHost();
+ 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,
+ static_cast<Profile*>(NULL),
+ 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",
+ "http://www.amazon.com/exec/obidos/external-search/?field-keywords="
+ "{searchTerms}&mode=blended" },
+ { L"answers.com",
+ "http://www.answers.com/main/ntquery?s={searchTerms}&gwp=13" },
+ { L"search.creativecommons.org",
+ "http://search.creativecommons.org/?q={searchTerms}" },
+ { L"search.ebay.com",
+ "http://search.ebay.com/search/search.dll?query={searchTerms}&"
+ "MfcISAPICommand=GetResult&ht=1&ebaytag1=ebayreg&srchdesc=n&"
+ "maxRecordsReturned=300&maxRecordsPerPage=50&SortProperty=MetaEndSort" },
+ { L"google.com",
+ "http://www.google.com/search?q={searchTerms}&ie=utf-8&oe=utf-8&aq=t" },
+ { L"en.wikipedia.org",
+ "http://en.wikipedia.org/wiki/Special:Search?search={searchTerms}" },
+ { L"search.yahoo.com",
+ "http://search.yahoo.com/search?p={searchTerms}&ei=UTF-8" },
+ { L"flickr.com",
+ "http://www.flickr.com/photos/tags/?q={searchTerms}" },
+ { L"imdb.com",
+ "http://www.imdb.com/find?q={searchTerms}" },
+ { L"webster.com",
+ "http://www.webster.com/cgi-bin/dictionary?va={searchTerms}" },
+ // Search keywords.
+ { L"\x4E2D\x6587", "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), import_search_engines_(true) {
+ }
+
+ explicit Firefox3Observer(bool import_search_engines)
+ : ProfileWriter(NULL), bookmark_count_(0), history_count_(0),
+ password_count_(0), keyword_count_(0),
+ import_search_engines_(import_search_engines) {
+ }
+
+ virtual void ImportItemStarted(importer::ImportItem item) {}
+ virtual void ImportItemEnded(importer::ImportItem item) {}
+ virtual void ImportStarted() {}
+ virtual void ImportEnded() {
+ MessageLoop::current()->Quit();
+ EXPECT_EQ(arraysize(kFirefox3Bookmarks), bookmark_count_);
+ EXPECT_EQ(1U, history_count_);
+ EXPECT_EQ(arraysize(kFirefox3Passwords), password_count_);
+ if (import_search_engines_) {
+ 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 bool TemplateURLModelIsLoaded() const {
+ return true;
+ }
+
+ 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(WideToUTF16(p.username_element), form.username_element);
+ EXPECT_EQ(WideToUTF16(p.username), form.username_value);
+ EXPECT_EQ(WideToUTF16(p.password_element), form.password_element);
+ EXPECT_EQ(WideToUTF16(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(3U, page.size());
+ EXPECT_EQ("http://www.google.com/", page[0].url().spec());
+ EXPECT_EQ(ASCIIToUTF16("Google"), page[0].title());
+ EXPECT_EQ("http://www.google.com/", page[1].url().spec());
+ EXPECT_EQ(ASCIIToUTF16("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(ASCIIToUTF16("example form (POST)"), page[2].title());
+ ++history_count_;
+ }
+
+ virtual void AddBookmarkEntry(const std::vector<BookmarkEntry>& bookmark,
+ const std::wstring& first_folder_name,
+ int options) {
+ 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 (size_t j = 0; j < arraysize(kFirefox3Keywords); ++j) {
+ 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:
+ ~Firefox3Observer() {}
+
+ size_t bookmark_count_;
+ size_t history_count_;
+ size_t password_count_;
+ size_t keyword_count_;
+ bool import_search_engines_;
+ std::wstring default_keyword_;
+ std::string default_keyword_url_;
+};
+
+TEST_F(ImporterTest, MAYBE(Firefox30Importer)) {
+ scoped_refptr<Firefox3Observer> observer = new Firefox3Observer();
+ Firefox3xImporterTest("firefox3_profile", observer.get(), observer.get(),
+ true);
+}
+
+TEST_F(ImporterTest, MAYBE(Firefox35Importer)) {
+ bool import_search_engines = false;
+ scoped_refptr<Firefox3Observer> observer =
+ new Firefox3Observer(import_search_engines);
+ Firefox3xImporterTest("firefox35_profile", observer.get(), observer.get(),
+ import_search_engines);
+}
diff --git a/chrome/browser/importer/mork_reader.cc b/chrome/browser/importer/mork_reader.cc
new file mode 100644
index 0000000..49dadb0
--- /dev/null
+++ b/chrome/browser/importer/mork_reader.cc
@@ -0,0 +1,585 @@
+/* -*- 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/file_path.h"
+#include "base/i18n/icu_string_conversions.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/importer/firefox_importer_utils.h"
+#include "chrome/browser/importer/importer.h"
+#include "chrome/browser/importer/importer_bridge.h"
+
+using base::Time;
+
+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 FilePath& path) {
+ stream_.open(path.value().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);
+
+ string16 title;
+ if (data.swap_bytes) {
+ base::CodepageToUTF16(values[kNameColumn], base::kCodepageUTF16BE,
+ base::OnStringConversionError::SKIP, &title);
+ } else {
+ base::CodepageToUTF16(values[kNameColumn], base::kCodepageUTF16LE,
+ base::OnStringConversionError::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(const FilePath& file, ImporterBridge* bridge) {
+ 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())
+ bridge->SetHistoryItems(rows);
+}
diff --git a/chrome/browser/importer/mork_reader.h b/chrome/browser/importer/mork_reader.h
new file mode 100644
index 0000000..cb61be5
--- /dev/null
+++ b/chrome/browser/importer/mork_reader.h
@@ -0,0 +1,167 @@
+/* -*- 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 <iosfwd>
+#include <fstream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+
+class FilePath;
+class ImporterBridge;
+class MessageLoop;
+class ProfileWriter;
+
+// 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 FilePath& 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(const FilePath& file, ImporterBridge* bridge);
+
+#endif // CHROME_BROWSER_IMPORTER_MORK_READER_H__
diff --git a/chrome/browser/importer/nss_decryptor.cc b/chrome/browser/importer/nss_decryptor.cc
new file mode 100644
index 0000000..91149e2
--- /dev/null
+++ b/chrome/browser/importer/nss_decryptor.cc
@@ -0,0 +1,301 @@
+// Copyright (c) 2010 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/nss_decryptor.h"
+
+#include <string>
+#include <vector>
+
+#include "base/scoped_ptr.h"
+#include "build/build_config.h"
+#include "chrome/common/sqlite_utils.h"
+
+#if defined(USE_NSS)
+#include <pk11pub.h>
+#include <pk11sdr.h>
+#endif // defined(USE_NSS)
+
+#include "base/base64.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "webkit/glue/password_form.h"
+
+using webkit_glue::PasswordForm;
+
+// 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 ***** */
+
+string16 NSSDecryptor::Decrypt(const std::string& crypt) const {
+ // Do nothing if NSS is not loaded.
+ if (!is_nss_initialized_)
+ return string16();
+
+ // The old style password is encoded in base64. They are identified
+ // by a leading '~'. Otherwise, we should decrypt the text.
+ std::string plain;
+ if (crypt[0] != '~') {
+ std::string decoded_data;
+ base::Base64Decode(crypt, &decoded_data);
+ PK11SlotInfo* slot = GetKeySlotForDB();
+ SECStatus result = PK11_Authenticate(slot, PR_TRUE, NULL);
+ if (result != SECSuccess) {
+ FreeSlot(slot);
+ return string16();
+ }
+
+ 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;
+#if defined(USE_NSS)
+ result = PK11SDR_DecryptWithSlot(slot, &request, &reply, NULL);
+#else
+ result = PK11SDR_Decrypt(&request, &reply, NULL);
+#endif // defined(USE_NSS)
+ if (result == SECSuccess)
+ plain.assign(reinterpret_cast<char*>(reply.data), reply.len);
+
+ SECITEM_FreeItem(&reply, PR_FALSE);
+ FreeSlot(slot);
+ } else {
+ // Deletes the leading '~' before decoding.
+ base::Base64Decode(crypt.substr(1), &plain);
+ }
+
+ return UTF8ToUTF16(plain);
+}
+
+// There are three versions of password files. 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 = UTF8ToUTF16(lines[begin++]);
+ form.username_value = Decrypt(lines[begin++]);
+ // The element name has a leading '*'.
+ if (lines[begin].at(0) == '*') {
+ form.password_element = UTF8ToUTF16(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);
+ }
+ }
+}
+
+bool NSSDecryptor::ReadAndParseSignons(const FilePath& sqlite_file,
+ std::vector<webkit_glue::PasswordForm>* forms) {
+ sqlite3* sqlite;
+ if (sqlite_utils::OpenSqliteDb(sqlite_file, &sqlite) != SQLITE_OK)
+ return false;
+ sqlite_utils::scoped_sqlite_db_ptr db(sqlite);
+
+ SQLStatement s;
+ const char* stmt = "SELECT hostname FROM moz_disabledHosts";
+ if (s.prepare(db.get(), stmt) != SQLITE_OK)
+ return false;
+
+ GURL::Replacements rep;
+ rep.ClearQuery();
+ rep.ClearRef();
+ rep.ClearUsername();
+ rep.ClearPassword();
+ // Read domains for which passwords are never saved.
+ while (s.step() == SQLITE_ROW) {
+ PasswordForm form;
+ form.origin = GURL(s.column_string(0)).ReplaceComponents(rep);
+ form.signon_realm = form.origin.GetOrigin().spec();
+ form.blacklisted_by_user = true;
+ forms->push_back(form);
+ }
+
+ SQLStatement s2;
+ const char* stmt2 = "SELECT hostname, httpRealm, formSubmitURL, "
+ "usernameField, passwordField, encryptedUsername, "
+ "encryptedPassword FROM moz_logins";
+
+ if (s2.prepare(db.get(), stmt2) != SQLITE_OK)
+ return false;
+
+ while (s2.step() == SQLITE_ROW) {
+ GURL url;
+ std::string realm(s2.column_string(1));
+ if (!realm.empty()) {
+ // In this case, the scheme may not exsit. Assume HTTP.
+ std::string host(s2.column_string(0));
+ if (host.find("://") == std::string::npos)
+ host = "http://" + host;
+ url = GURL(host);
+ } else {
+ url = GURL(s2.column_string(0));
+ }
+ // Skip this row if the URL is not valid.
+ if (!url.is_valid())
+ continue;
+
+ PasswordForm form;
+ 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();
+ // The user name, password and action.
+ form.username_element = UTF8ToUTF16(s2.column_string(3));
+ form.username_value = Decrypt(s2.column_string(5));
+ form.password_element = UTF8ToUTF16(s2.column_string(4));
+ form.password_value = Decrypt(s2.column_string(6));
+ form.action = GURL(s2.column_string(2)).ReplaceComponents(rep);
+ forms->push_back(form);
+ }
+ return true;
+}
diff --git a/chrome/browser/importer/nss_decryptor.h b/chrome/browser/importer/nss_decryptor.h
new file mode 100644
index 0000000..c133665
--- /dev/null
+++ b/chrome/browser/importer/nss_decryptor.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2009 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_NSS_DECRYPTOR_H_
+#define CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_MACOSX)
+#include "chrome/browser/importer/nss_decryptor_mac.h"
+#elif defined(OS_WIN)
+#include "chrome/browser/importer/nss_decryptor_win.h"
+#elif defined(USE_NSS)
+#include "chrome/browser/importer/nss_decryptor_system_nss.h"
+#endif
+
+#endif // CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_H_
diff --git a/chrome/browser/importer/nss_decryptor_mac.h b/chrome/browser/importer/nss_decryptor_mac.h
new file mode 100644
index 0000000..af1b004
--- /dev/null
+++ b/chrome/browser/importer/nss_decryptor_mac.h
@@ -0,0 +1,162 @@
+// Copyright (c) 2009 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_NSS_DECRYPTOR_MAC_H_
+#define CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_MAC_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/logging.h"
+
+// 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);
+namespace webkit_glue {
+struct PasswordForm;
+}
+
+// A wrapper for Firefox NSS decrypt component.
+class NSSDecryptor {
+ public:
+ 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),
+ is_nss_initialized_(false) {}
+ ~NSSDecryptor();
+
+ // Initializes NSS if it hasn't already been initialized.
+ bool Init(const std::wstring& /* dll_path */,
+ const std::wstring& /* db_path */);
+
+ // Decrypts Firefox stored passwords. Before using this method,
+ // make sure Init() returns true.
+ string16 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<webkit_glue::PasswordForm>* forms);
+
+ // Reads and parses the Firefox password sqlite db, decrypts the
+ // username/password and reads other related information.
+ // The result will be stored in |forms|.
+ bool ReadAndParseSignons(const FilePath& sqlite_file,
+ std::vector<webkit_glue::PasswordForm>* forms);
+ private:
+ PK11SlotInfo* GetKeySlotForDB() const { return PK11_GetInternalKeySlot(); }
+ void FreeSlot(PK11SlotInfo* slot) const { PK11_FreeSlot(slot); }
+
+ // 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;
+
+ // Libraries necessary for decrypting the passwords.
+ static const wchar_t kNSS3Library[];
+
+ // True if NSS_Init() has been called
+ bool is_nss_initialized_;
+
+ DISALLOW_COPY_AND_ASSIGN(NSSDecryptor);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_MAC_H_
diff --git a/chrome/browser/importer/nss_decryptor_mac.mm b/chrome/browser/importer/nss_decryptor_mac.mm
new file mode 100644
index 0000000..e319c74
--- /dev/null
+++ b/chrome/browser/importer/nss_decryptor_mac.mm
@@ -0,0 +1,75 @@
+// Copyright (c) 2009 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 <Cocoa/Cocoa.h>
+
+#include <dlfcn.h>
+
+#include "base/sys_string_conversions.h"
+
+#include "chrome/browser/importer/nss_decryptor_mac.h"
+#include "chrome/browser/importer/firefox_importer_utils.h"
+
+#include "base/logging.h"
+
+// static
+const wchar_t NSSDecryptor::kNSS3Library[] = L"libnss3.dylib";
+
+// Important!! : On OS X the nss3 libraries are compiled with depedencies
+// on one another, referenced using dyld's @executable_path directive.
+// To make a long story short in order to get the libraries to load, dyld's
+// fallback path needs to be set to the directory containing the libraries.
+// To do so, the process this function runs in must have the
+// DYLD_FALLBACK_LIBRARY_PATH set on startup to said directory.
+bool NSSDecryptor::Init(const std::wstring& dll_path,
+ const std::wstring& db_path) {
+ if (getenv("DYLD_FALLBACK_LIBRARY_PATH") == NULL) {
+ LOG(ERROR) << "DYLD_FALLBACK_LIBRARY_PATH variable not set";
+ return false;
+ }
+ FilePath dylib_file_path = FilePath::FromWStringHack(dll_path);
+ FilePath nss3_path = dylib_file_path.Append("libnss3.dylib");
+
+ void *nss_3_lib = dlopen(nss3_path.value().c_str(), RTLD_LAZY);
+ if (!nss_3_lib) {
+ LOG(ERROR) << "Failed to load nss3 lib" << dlerror();
+ return false;
+ }
+
+ NSS_Init = (NSSInitFunc)dlsym(nss_3_lib, "NSS_Init");
+ NSS_Shutdown = (NSSShutdownFunc)dlsym(nss_3_lib, "NSS_Shutdown");
+ PK11_GetInternalKeySlot =
+ (PK11GetInternalKeySlotFunc)dlsym(nss_3_lib, "PK11_GetInternalKeySlot");
+ PK11_CheckUserPassword =
+ (PK11CheckUserPasswordFunc)dlsym(nss_3_lib, "PK11_CheckUserPassword");
+ PK11_FreeSlot = (PK11FreeSlotFunc)dlsym(nss_3_lib, "PK11_FreeSlot");
+ PK11_Authenticate =
+ (PK11AuthenticateFunc)dlsym(nss_3_lib, "PK11_Authenticate");
+ PK11SDR_Decrypt = (PK11SDRDecryptFunc)dlsym(nss_3_lib, "PK11SDR_Decrypt");
+ SECITEM_FreeItem = (SECITEMFreeItemFunc)dlsym(nss_3_lib, "SECITEM_FreeItem");
+
+ if (!NSS_Init || !NSS_Shutdown || !PK11_GetInternalKeySlot ||
+ !PK11_CheckUserPassword || !PK11_FreeSlot || !PK11_Authenticate ||
+ !PK11SDR_Decrypt || !SECITEM_FreeItem) {
+ LOG(ERROR) << "NSS3 importer couldn't find entry points";
+ return false;
+ }
+
+ SECStatus result = NSS_Init(base::SysWideToNativeMB(db_path).c_str());
+
+ if (result != SECSuccess) {
+ LOG(ERROR) << "NSS_Init Failed returned: " << result;
+ return false;
+ }
+
+ is_nss_initialized_ = true;
+ return true;
+}
+
+NSSDecryptor::~NSSDecryptor() {
+ if (NSS_Shutdown && is_nss_initialized_) {
+ NSS_Shutdown();
+ is_nss_initialized_ = false;
+ }
+}
diff --git a/chrome/browser/importer/nss_decryptor_system_nss.cc b/chrome/browser/importer/nss_decryptor_system_nss.cc
new file mode 100644
index 0000000..06be5da
--- /dev/null
+++ b/chrome/browser/importer/nss_decryptor_system_nss.cc
@@ -0,0 +1,269 @@
+// Copyright (c) 2009 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/nss_decryptor_system_nss.h"
+
+#include <pk11pub.h>
+#include <pk11sdr.h>
+
+#include "base/basictypes.h"
+#include "base/nss_util.h"
+#include "base/string_util.h"
+#include "base/sys_string_conversions.h"
+
+NSSDecryptor::NSSDecryptor() : is_nss_initialized_(false), db_slot_(NULL) {}
+NSSDecryptor::~NSSDecryptor() {
+ if (db_slot_) {
+ // Deliberately leave the user db open, just in case we need to open more
+ // than one, because there's an NSS bug with reopening user dbs.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=506140
+ // SECMOD_CloseUserDB(db_slot_);
+ PK11_FreeSlot(db_slot_);
+ }
+}
+
+bool NSSDecryptor::Init(const std::wstring& /* dll_path */,
+ const std::wstring& db_path) {
+ base::EnsureNSSInit();
+ is_nss_initialized_ = true;
+ const std::string modspec =
+ StringPrintf("configDir='%s' tokenDescription='Firefox NSS database' "
+ "flags=readOnly", base::SysWideToNativeMB(db_path).c_str());
+ db_slot_ = SECMOD_OpenUserDB(modspec.c_str());
+ return db_slot_ != NULL;
+}
+
+// This method is based on some NSS code in
+// security/nss/lib/pk11wrap/pk11sdr.c, CVS revision 1.22
+// This code is copied because the implementation assumes the use of the
+// internal key slot for decryption, but we need to use another slot.
+// 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):
+ * thayes@netscape.com
+ *
+ * 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 ***** */
+
+/*
+ * Data structure and template for encoding the result of an SDR operation
+ * This is temporary. It should include the algorithm ID of the encryption
+ * mechanism
+ */
+struct SDRResult
+{
+ SECItem keyid;
+ SECAlgorithmID alg;
+ SECItem data;
+};
+typedef struct SDRResult SDRResult;
+
+static SEC_ASN1Template g_template[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof (SDRResult) },
+ { SEC_ASN1_OCTET_STRING, offsetof(SDRResult, keyid) },
+ { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(SDRResult, alg),
+ SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
+ { SEC_ASN1_OCTET_STRING, offsetof(SDRResult, data) },
+ { 0 }
+};
+
+static SECStatus
+unpadBlock(SECItem *data, int blockSize, SECItem *result)
+{
+ SECStatus rv = SECSuccess;
+ int padLength;
+ int i;
+
+ result->data = 0;
+ result->len = 0;
+
+ /* Remove the padding from the end if the input data */
+ if (data->len == 0 || data->len % blockSize != 0) {
+ rv = SECFailure;
+ goto loser;
+ }
+
+ padLength = data->data[data->len-1];
+ if (padLength > blockSize) { rv = SECFailure; goto loser; }
+
+ /* verify padding */
+ for (i=data->len - padLength; static_cast<uint32>(i) < data->len; i++) {
+ if (data->data[i] != padLength) {
+ rv = SECFailure;
+ goto loser;
+ }
+ }
+
+ result->len = data->len - padLength;
+ result->data = (unsigned char *)PORT_Alloc(result->len);
+ if (!result->data) { rv = SECFailure; goto loser; }
+
+ PORT_Memcpy(result->data, data->data, result->len);
+
+ if (padLength < 2) {
+ return SECWouldBlock;
+ }
+
+loser:
+ return rv;
+}
+
+/* decrypt a block */
+static SECStatus
+pk11Decrypt(PK11SlotInfo *slot, PLArenaPool *arena,
+ CK_MECHANISM_TYPE type, PK11SymKey *key,
+ SECItem *params, SECItem *in, SECItem *result)
+{
+ PK11Context *ctx = 0;
+ SECItem paddedResult;
+ SECStatus rv;
+
+ paddedResult.len = 0;
+ paddedResult.data = 0;
+
+ ctx = PK11_CreateContextBySymKey(type, CKA_DECRYPT, key, params);
+ if (!ctx) { rv = SECFailure; goto loser; }
+
+ paddedResult.len = in->len;
+ paddedResult.data = static_cast<unsigned char*>(
+ PORT_ArenaAlloc(arena, paddedResult.len));
+
+ rv = PK11_CipherOp(ctx, paddedResult.data,
+ (int*)&paddedResult.len, paddedResult.len,
+ in->data, in->len);
+ if (rv != SECSuccess) goto loser;
+
+ PK11_Finalize(ctx);
+
+ /* Remove the padding */
+ rv = unpadBlock(&paddedResult, PK11_GetBlockSize(type, 0), result);
+ if (rv) goto loser;
+
+loser:
+ if (ctx) PK11_DestroyContext(ctx, PR_TRUE);
+ return rv;
+}
+
+SECStatus NSSDecryptor::PK11SDR_DecryptWithSlot(
+ PK11SlotInfo* slot, SECItem* data, SECItem* result, void* cx) const {
+ SECStatus rv = SECSuccess;
+ PK11SymKey *key = 0;
+ CK_MECHANISM_TYPE type;
+ SDRResult sdrResult;
+ SECItem *params = 0;
+ SECItem possibleResult = { siBuffer, NULL, 0 };
+ PLArenaPool *arena = 0;
+
+ arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
+ if (!arena) { rv = SECFailure; goto loser; }
+
+ /* Decode the incoming data */
+ memset(&sdrResult, 0, sizeof sdrResult);
+ rv = SEC_QuickDERDecodeItem(arena, &sdrResult, g_template, data);
+ if (rv != SECSuccess) goto loser; /* Invalid format */
+
+ /* Get the parameter values from the data */
+ params = PK11_ParamFromAlgid(&sdrResult.alg);
+ if (!params) { rv = SECFailure; goto loser; }
+
+ /* Use triple-DES (Should look up the algorithm) */
+ type = CKM_DES3_CBC;
+ key = PK11_FindFixedKey(slot, type, &sdrResult.keyid, cx);
+ if (!key) {
+ rv = SECFailure;
+ } else {
+ rv = pk11Decrypt(slot, arena, type, key, params,
+ &sdrResult.data, result);
+ }
+
+ /*
+ * if the pad value was too small (1 or 2), then it's statistically
+ * 'likely' that (1 in 256) that we may not have the correct key.
+ * Check the other keys for a better match. If we find none, use
+ * this result.
+ */
+ if (rv == SECWouldBlock)
+ possibleResult = *result;
+
+ /*
+ * handle the case where your key indicies may have been broken
+ */
+ if (rv != SECSuccess) {
+ PK11SymKey *keyList = PK11_ListFixedKeysInSlot(slot, NULL, cx);
+ PK11SymKey *testKey = NULL;
+ PK11SymKey *nextKey = NULL;
+
+ for (testKey = keyList; testKey;
+ testKey = PK11_GetNextSymKey(testKey)) {
+ rv = pk11Decrypt(slot, arena, type, testKey, params,
+ &sdrResult.data, result);
+ if (rv == SECSuccess)
+ break;
+
+ /* found a close match. If it's our first remember it */
+ if (rv == SECWouldBlock) {
+ if (possibleResult.data) {
+ /* this is unlikely but possible. If we hit this condition,
+ * we have no way of knowing which possibility to prefer.
+ * in this case we just match the key the application
+ * thought was the right one */
+ SECITEM_ZfreeItem(result, PR_FALSE);
+ } else {
+ possibleResult = *result;
+ }
+ }
+ }
+
+ /* free the list */
+ for (testKey = keyList; testKey; testKey = nextKey) {
+ nextKey = PK11_GetNextSymKey(testKey);
+ PK11_FreeSymKey(testKey);
+ }
+ }
+
+ /* we didn't find a better key, use the one with a small pad value */
+ if ((rv != SECSuccess) && (possibleResult.data)) {
+ *result = possibleResult;
+ possibleResult.data = NULL;
+ rv = SECSuccess;
+ }
+
+ loser:
+ if (arena) PORT_FreeArena(arena, PR_TRUE);
+ if (key) PK11_FreeSymKey(key);
+ if (params) SECITEM_ZfreeItem(params, PR_TRUE);
+ if (possibleResult.data) SECITEM_ZfreeItem(&possibleResult, PR_FALSE);
+
+ return rv;
+}
diff --git a/chrome/browser/importer/nss_decryptor_system_nss.h b/chrome/browser/importer/nss_decryptor_system_nss.h
new file mode 100644
index 0000000..7071428
--- /dev/null
+++ b/chrome/browser/importer/nss_decryptor_system_nss.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2009 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_NSS_DECRYPTOR_SYSTEM_NSS_H_
+#define CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_SYSTEM_NSS_H_
+
+#include <secmodt.h>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+
+namespace webkit_glue {
+struct PasswordForm;
+}
+
+// A wrapper for Firefox NSS decrypt component.
+class NSSDecryptor {
+ public:
+ NSSDecryptor();
+ ~NSSDecryptor();
+
+ // Initializes NSS if it hasn't already been initialized.
+ bool Init(const std::wstring& /* dll_path */,
+ const std::wstring& db_path);
+
+ // Decrypts Firefox stored passwords. Before using this method,
+ // make sure Init() returns true.
+ string16 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<webkit_glue::PasswordForm>* forms);
+
+ // Reads and parses the Firefox password sqlite db, decrypts the
+ // username/password and reads other related information.
+ // The result will be stored in |forms|.
+ bool ReadAndParseSignons(const FilePath& sqlite_file,
+ std::vector<webkit_glue::PasswordForm>* forms);
+ private:
+ // Does not actually free the slot, since we'll free it when NSSDecryptor is
+ // destroyed.
+ void FreeSlot(PK11SlotInfo* slot) const {}
+ PK11SlotInfo* GetKeySlotForDB() const { return db_slot_; }
+
+ SECStatus PK11SDR_DecryptWithSlot(
+ PK11SlotInfo* slot, SECItem* data, SECItem* result, void* cx) const;
+
+ bool is_nss_initialized_;
+ PK11SlotInfo* db_slot_;
+
+ DISALLOW_COPY_AND_ASSIGN(NSSDecryptor);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_SYSTEM_NSS_H_
diff --git a/chrome/browser/importer/nss_decryptor_win.cc b/chrome/browser/importer/nss_decryptor_win.cc
new file mode 100644
index 0000000..a662abb
--- /dev/null
+++ b/chrome/browser/importer/nss_decryptor_win.cc
@@ -0,0 +1,178 @@
+// Copyright (c) 2009 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/nss_decryptor_win.h"
+#include "base/file_util.h"
+#include "base/sys_string_conversions.h"
+
+namespace {
+
+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
+
+// 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";
+
+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;
+ }
+ }
+ HMODULE plds4_dll = GetModuleHandle(kPLDS4Library);
+ HMODULE nspr4_dll = GetModuleHandle(kNSPR4Library);
+
+ return InitNSS(db_path, plds4_dll, 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::InitNSS(const std::wstring& db_path,
+ base::NativeLibrary plds4_dll,
+ base::NativeLibrary nspr4_dll) {
+ // NSPR DLLs are already loaded now.
+ if (plds4_dll == NULL || nspr4_dll == NULL) {
+ Free();
+ return false;
+ }
+
+ // Gets the function address.
+ NSS_Init = (NSSInitFunc)
+ base::GetFunctionPointerFromNativeLibrary(nss3_dll_, "NSS_Init");
+ NSS_Shutdown = (NSSShutdownFunc)
+ base::GetFunctionPointerFromNativeLibrary(nss3_dll_, "NSS_Shutdown");
+ PK11_GetInternalKeySlot = (PK11GetInternalKeySlotFunc)
+ base::GetFunctionPointerFromNativeLibrary(nss3_dll_,
+ "PK11_GetInternalKeySlot");
+ PK11_FreeSlot = (PK11FreeSlotFunc)
+ base::GetFunctionPointerFromNativeLibrary(nss3_dll_, "PK11_FreeSlot");
+ PK11_Authenticate = (PK11AuthenticateFunc)
+ base::GetFunctionPointerFromNativeLibrary(nss3_dll_, "PK11_Authenticate");
+ PK11SDR_Decrypt = (PK11SDRDecryptFunc)
+ base::GetFunctionPointerFromNativeLibrary(nss3_dll_, "PK11SDR_Decrypt");
+ SECITEM_FreeItem = (SECITEMFreeItemFunc)
+ base::GetFunctionPointerFromNativeLibrary(nss3_dll_, "SECITEM_FreeItem");
+ PL_ArenaFinish = (PLArenaFinishFunc)
+ base::GetFunctionPointerFromNativeLibrary(plds4_dll, "PL_ArenaFinish");
+ PR_Cleanup = (PRCleanupFunc)
+ base::GetFunctionPointerFromNativeLibrary(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)
+ base::UnloadNativeLibrary(softokn3_dll_);
+ if (nss3_dll_ != NULL)
+ base::UnloadNativeLibrary(nss3_dll_);
+ 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;
+ nss3_dll_ = NULL;
+ softokn3_dll_ = NULL;
+}
diff --git a/chrome/browser/importer/nss_decryptor_win.h b/chrome/browser/importer/nss_decryptor_win.h
new file mode 100644
index 0000000..f0b2345
--- /dev/null
+++ b/chrome/browser/importer/nss_decryptor_win.h
@@ -0,0 +1,177 @@
+// Copyright (c) 2009 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_NSS_DECRYPTOR_WIN_H_
+#define CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_WIN_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/native_library.h"
+
+// 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);
+
+namespace webkit_glue {
+struct PasswordForm;
+}
+
+// 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<webkit_glue::PasswordForm>* forms);
+
+ // Reads and parses the Firefox password sqlite db, decrypts the
+ // username/password and reads other related information.
+ // The result will be stored in |forms|.
+ bool ReadAndParseSignons(const FilePath& sqlite_file,
+ std::vector<webkit_glue::PasswordForm>* forms);
+
+ private:
+ // Performs tasks common across all platforms to initialize NSS.
+ bool InitNSS(const std::wstring& db_path,
+ base::NativeLibrary plds4_dll,
+ base::NativeLibrary nspr4_dll);
+
+ PK11SlotInfo* GetKeySlotForDB() const { return PK11_GetInternalKeySlot(); }
+ void FreeSlot(PK11SlotInfo* slot) const { PK11_FreeSlot(slot); }
+
+ // 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.
+ base::NativeLibrary nss3_dll_;
+ base::NativeLibrary softokn3_dll_;
+
+ // True if NSS_Init() has been called
+ bool is_nss_initialized_;
+
+ DISALLOW_COPY_AND_ASSIGN(NSSDecryptor);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_WIN_H_
diff --git a/chrome/browser/importer/profile_writer.cc b/chrome/browser/importer/profile_writer.cc
new file mode 100644
index 0000000..0061efc
--- /dev/null
+++ b/chrome/browser/importer/profile_writer.cc
@@ -0,0 +1,348 @@
+// Copyright (c) 2010 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/profile_writer.h"
+
+#include "base/string_util.h"
+#include "base/thread.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#include "chrome/browser/importer/importer.h"
+#include "chrome/browser/password_manager/password_store.h"
+#include "chrome/browser/pref_service.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/search_engines/template_url_model.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/pref_names.h"
+
+using webkit_glue::PasswordForm;
+
+bool ProfileWriter::BookmarkModelIsLoaded() const {
+ return profile_->GetBookmarkModel()->IsLoaded();
+}
+
+bool ProfileWriter::TemplateURLModelIsLoaded() const {
+ return profile_->GetTemplateURLModel()->loaded();
+}
+
+void ProfileWriter::AddPasswordForm(const PasswordForm& form) {
+ profile_->GetPasswordStore(Profile::EXPLICIT_ACCESS)->AddLogin(form);
+}
+
+#if defined(OS_WIN)
+void ProfileWriter::AddIE7PasswordInfo(const IE7PasswordInfo& info) {
+ profile_->GetWebDataService(Profile::EXPLICIT_ACCESS)->AddIE7Login(info);
+}
+#endif
+
+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.
+ const PrefService::Preference* pref = prefs->FindPreference(prefs::kHomePage);
+ if (pref && !pref->IsManaged()) {
+ prefs->SetString(prefs::kHomePage, home_page.spec());
+ prefs->ScheduleSavePersistentPrefs();
+ }
+}
+
+void ProfileWriter::AddBookmarkEntry(
+ const std::vector<BookmarkEntry>& bookmark,
+ const std::wstring& first_folder_name,
+ int options) {
+ BookmarkModel* model = profile_->GetBookmarkModel();
+ DCHECK(model->IsLoaded());
+
+ bool import_to_bookmark_bar = ((options & IMPORT_TO_BOOKMARK_BAR) != 0);
+ std::wstring real_first_folder = import_to_bookmark_bar ? first_folder_name :
+ GenerateUniqueFolderName(model, first_folder_name);
+
+ bool show_bookmark_toolbar = false;
+ std::set<const BookmarkNode*> groups_added_to;
+ bool import_mode = false;
+ if (bookmark.size() > 1) {
+ model->BeginImportMode();
+ import_mode = true;
+ }
+ for (std::vector<BookmarkEntry>::const_iterator it = bookmark.begin();
+ it != bookmark.end(); ++it) {
+ // Don't insert this url if it isn't valid.
+ if (!it->url.is_valid())
+ continue;
+
+ // We suppose that bookmarks are unique by Title, URL, and Folder. Since
+ // checking for uniqueness may not be always the user's intention we have
+ // this as an option.
+ if (options & ADD_IF_UNIQUE && DoesBookmarkExist(model, *it,
+ real_first_folder, import_to_bookmark_bar))
+ 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]
+ const 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) {
+ const BookmarkNode* child = NULL;
+ const std::wstring& folder_name = (!import_to_bookmark_bar &&
+ !it->in_toolbar && (i == it->path.begin())) ? real_first_folder : *i;
+
+ for (int index = 0; index < parent->GetChildCount(); ++index) {
+ const BookmarkNode* node = parent->GetChild(index);
+ if ((node->type() == BookmarkNode::BOOKMARK_BAR ||
+ node->type() == BookmarkNode::FOLDER) &&
+ node->GetTitle() == folder_name) {
+ child = node;
+ break;
+ }
+ }
+ if (child == NULL)
+ child = model->AddGroup(parent, parent->GetChildCount(), folder_name);
+ 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<const BookmarkNode*>::const_iterator i =
+ groups_added_to.begin();
+ i != groups_added_to.end(); ++i) {
+ model->ResetDateGroupModified(*i);
+ }
+
+ if (import_mode) {
+ model->EndImportMode();
+ }
+
+ if (show_bookmark_toolbar && !(options & BOOKMARK_BAR_DISABLED))
+ ShowBookmarkBar();
+}
+
+void ProfileWriter::AddFavicons(
+ const std::vector<history::ImportedFavIconUsage>& favicons) {
+ profile_->GetFaviconService(Profile::EXPLICIT_ACCESS)->
+ SetImportedFavicons(favicons);
+}
+
+typedef std::map<std::string, const TemplateURL*> HostPathMap;
+
+// Returns the key for the map built by BuildHostPathMap. If url_string is not
+// a valid URL, an empty string is returned, otherwise host+path is returned.
+static std::string HostPathKeyForURL(const GURL& url) {
+ return url.is_valid() ? url.host() + url.path() : std::string();
+}
+
+// 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, the TemplateURL should not be added to
+// HostPathMap.
+//
+// If |try_url_if_invalid| is true, and |t_url| isn't valid, a string is built
+// from the raw TemplateURL string. Use a value of true for |try_url_if_invalid|
+// when checking imported URLs as the imported URL may not be valid yet may
+// match the host+path of one of the default URLs. This is used to catch the
+// case of IE using an invalid OSDD URL for Live Search, yet the host+path
+// matches our prepopulate data. IE's URL for Live Search is something like
+// 'http://...{Language}...'. As {Language} is not a valid OSDD parameter value
+// the TemplateURL is invalid.
+static std::string BuildHostPathKey(const TemplateURL* t_url,
+ bool try_url_if_invalid) {
+ if (t_url->url()) {
+ if (try_url_if_invalid && !t_url->url()->IsValid())
+ return HostPathKeyForURL(GURL(t_url->url()->url()));
+
+ if (t_url->url()->SupportsReplacement()) {
+ return HostPathKeyForURL(GURL(
+ t_url->url()->ReplaceSearchTerms(
+ *t_url, L"random string",
+ TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, std::wstring())));
+ }
+ }
+ 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], false);
+ 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, true)) != host_path_map.end()) {
+ if (default_keyword) {
+ const TemplateURL* turl_with_host_path =
+ host_path_map[BuildHostPathKey(t_url, true)];
+ if (turl_with_host_path)
+ model->SetDefaultSearchProvider(turl_with_host_path);
+ else
+ NOTREACHED(); // BuildHostPathMap should only insert non-null values.
+ }
+ delete t_url;
+ continue;
+ }
+ if (t_url->url() && t_url->url()->IsValid()) {
+ model->Add(t_url);
+ if (default_keyword && TemplateURL::SupportsReplacement(t_url))
+ model->SetDefaultSearchProvider(t_url);
+ } else {
+ // Don't add invalid TemplateURLs to the model.
+ delete 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();
+ Source<Profile> source(profile_);
+ NotificationService::current()->Notify(
+ NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED, source,
+ NotificationService::NoDetails());
+ }
+}
+
+std::wstring ProfileWriter::GenerateUniqueFolderName(
+ BookmarkModel* model,
+ const std::wstring& folder_name) {
+ // Build a set containing the folder names of the other folder.
+ std::set<std::wstring> other_folder_names;
+ const BookmarkNode* other = model->other_node();
+
+ for (int i = 0, child_count = other->GetChildCount(); i < child_count; ++i) {
+ const BookmarkNode* node = other->GetChild(i);
+ if (node->is_folder())
+ other_folder_names.insert(node->GetTitle());
+ }
+
+ if (other_folder_names.find(folder_name) == other_folder_names.end())
+ return folder_name; // Name is unique, use it.
+
+ // Otherwise iterate until we find a unique name.
+ for (int i = 1; i < 100; ++i) {
+ std::wstring name = folder_name + StringPrintf(L" (%d)", i);
+ if (other_folder_names.find(name) == other_folder_names.end())
+ return name;
+ }
+
+ return folder_name;
+}
+
+bool ProfileWriter::DoesBookmarkExist(
+ BookmarkModel* model,
+ const BookmarkEntry& entry,
+ const std::wstring& first_folder_name,
+ bool import_to_bookmark_bar) {
+ std::vector<const BookmarkNode*> nodes_with_same_url;
+ model->GetNodesByURL(entry.url, &nodes_with_same_url);
+ if (nodes_with_same_url.empty())
+ return false;
+
+ for (size_t i = 0; i < nodes_with_same_url.size(); ++i) {
+ const BookmarkNode* node = nodes_with_same_url[i];
+ if (entry.title != node->GetTitle())
+ continue;
+
+ // Does the path match?
+ bool found_match = true;
+ const BookmarkNode* parent = node->GetParent();
+ for (std::vector<std::wstring>::const_reverse_iterator path_it =
+ entry.path.rbegin();
+ (path_it != entry.path.rend()) && found_match; ++path_it) {
+ const std::wstring& folder_name =
+ (!import_to_bookmark_bar && path_it + 1 == entry.path.rend()) ?
+ first_folder_name : *path_it;
+ if (NULL == parent || *path_it != folder_name)
+ found_match = false;
+ else
+ parent = parent->GetParent();
+ }
+
+ // We need a post test to differentiate checks such as
+ // /home/hello and /hello. The parent should either by the other folder
+ // node, or the bookmarks bar, depending upon import_to_bookmark_bar and
+ // entry.in_toolbar.
+ if (found_match &&
+ ((import_to_bookmark_bar && entry.in_toolbar && parent !=
+ model->GetBookmarkBarNode()) ||
+ ((!import_to_bookmark_bar || !entry.in_toolbar) &&
+ parent != model->other_node()))) {
+ found_match = false;
+ }
+
+ if (found_match)
+ return true; // Found a match with the same url path and title.
+ }
+ return false;
+}
diff --git a/chrome/browser/importer/profile_writer.h b/chrome/browser/importer/profile_writer.h
new file mode 100644
index 0000000..38e04d8
--- /dev/null
+++ b/chrome/browser/importer/profile_writer.h
@@ -0,0 +1,136 @@
+// Copyright (c) 2010 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_PROFILE_WRITER_H_
+#define CHROME_BROWSER_IMPORTER_PROFILE_WRITER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/ref_counted.h"
+#include "base/time.h"
+#include "chrome/browser/bookmarks/bookmark_model_observer.h"
+#include "googleurl/src/gurl.h"
+
+class Profile;
+class TemplateURL;
+
+struct IE7PasswordInfo;
+
+namespace history {
+struct ImportedFavIconUsage;
+class URLRow;
+}
+
+namespace webkit_glue {
+struct PasswordForm;
+}
+
+// ProfileWriter encapsulates profile for writing entries into it.
+// This object must be invoked on UI thread.
+class ProfileWriter : public base::RefCountedThreadSafe<ProfileWriter> {
+ public:
+ // Used to identify how the bookmarks are added.
+ enum BookmarkOptions {
+ // Indicates the bookmark should only be added if unique. Uniqueness
+ // is done by title, url and path. That is, if this is passed to
+ // AddBookmarkEntry the bookmark is added only if there is no other
+ // URL with the same url, path and title.
+ ADD_IF_UNIQUE = 1 << 0,
+
+ // Indicates the bookmarks should be added to the bookmark bar.
+ IMPORT_TO_BOOKMARK_BAR = 1 << 1,
+
+ // Indicates the bookmark bar is not shown.
+ BOOKMARK_BAR_DISABLED = 1 << 2
+ };
+
+ explicit ProfileWriter(Profile* profile) : profile_(profile) {}
+
+ // These functions return true if the corresponding model has been loaded.
+ // If the models haven't been loaded, the importer waits to run until they've
+ // completed.
+ virtual bool BookmarkModelIsLoaded() const;
+ virtual bool TemplateURLModelIsLoaded() const;
+
+ // A bookmark entry.
+ // TODO(mirandac): remove instances of wstring from ProfileWriter
+ // (http://crbug.com/43460).
+ struct BookmarkEntry {
+ BookmarkEntry() : in_toolbar(false) {}
+ bool in_toolbar;
+ GURL url;
+ std::vector<std::wstring> path;
+ std::wstring title;
+ base::Time creation_time;
+ };
+
+ // Helper methods for adding data to local stores.
+ virtual void AddPasswordForm(const webkit_glue::PasswordForm& form);
+#if defined(OS_WIN)
+ virtual void AddIE7PasswordInfo(const IE7PasswordInfo& info);
+#endif
+ virtual void AddHistoryPage(const std::vector<history::URLRow>& page);
+ virtual void AddHomepage(const GURL& homepage);
+ // Adds the bookmarks to the BookmarkModel.
+ // |options| is a bitmask of BookmarkOptions and dictates how and
+ // which bookmarks are added. If the bitmask contains IMPORT_TO_BOOKMARK_BAR,
+ // then any entries with a value of true for in_toolbar are added to
+ // the bookmark bar. If the bitmask does not contain IMPORT_TO_BOOKMARK_BAR
+ // then the folder name the bookmarks are added to is uniqued based on
+ // |first_folder_name|. For example, if |first_folder_name| is 'foo'
+ // and a folder with the name 'foo' already exists in the other
+ // bookmarks folder, then the folder name 'foo 2' is used.
+ // If |options| contains ADD_IF_UNIQUE, then the bookmark is added only
+ // if another bookmarks does not exist with the same title, path and
+ // url.
+ virtual void AddBookmarkEntry(const std::vector<BookmarkEntry>& bookmark,
+ const std::wstring& first_folder_name,
+ int options);
+ 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();
+
+ Profile* profile() const { return profile_; }
+
+ protected:
+ friend class base::RefCountedThreadSafe<ProfileWriter>;
+
+ virtual ~ProfileWriter() {}
+
+ private:
+ // Generates a unique folder name. If folder_name is not unique, then this
+ // repeatedly tests for '|folder_name| + (i)' until a unique name is found.
+ std::wstring GenerateUniqueFolderName(BookmarkModel* model,
+ const std::wstring& folder_name);
+
+ // Returns true if a bookmark exists with the same url, title and path
+ // as |entry|. |first_folder_name| is the name to use for the first
+ // path entry if |import_to_bookmark_bar| is true.
+ bool DoesBookmarkExist(BookmarkModel* model,
+ const BookmarkEntry& entry,
+ const std::wstring& first_folder_name,
+ bool import_to_bookmark_bar);
+
+ Profile* const profile_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProfileWriter);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_PROFILE_WRITER_H_
diff --git a/chrome/browser/importer/safari_importer.h b/chrome/browser/importer/safari_importer.h
new file mode 100644
index 0000000..6b686e9
--- /dev/null
+++ b/chrome/browser/importer/safari_importer.h
@@ -0,0 +1,100 @@
+// Copyright (c) 2010 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_SAFARI_IMPORTER_H_
+#define CHROME_BROWSER_IMPORTER_SAFARI_IMPORTER_H_
+
+#include "chrome/browser/importer/importer.h"
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "chrome/common/sqlite_utils.h"
+#include "chrome/browser/importer/importer_data_types.h"
+
+#if __OBJC__
+@class NSDictionary;
+@class NSString;
+#else
+class NSDictionary;
+class NSString;
+#endif
+
+// Importer for Safari on OS X.
+class SafariImporter : public Importer {
+ public:
+ // |library_dir| is the full path to the ~/Library directory,
+ // We pass it in as a parameter for testing purposes.
+ explicit SafariImporter(const FilePath& library_dir);
+
+ // Importer methods.
+ virtual void StartImport(importer::ProfileInfo profile_info,
+ uint16 items,
+ ImporterBridge* bridge);
+
+
+ // Does this user account have a Safari Profile and if so, what items
+ // are supported?
+ // in: library_dir - ~/Library or a standin for testing purposes.
+ // out: services_supported - the service supported for import.
+ // returns true if we can import the Safari profile.
+ static bool CanImport(const FilePath& library_dir, uint16 *services_supported);
+ private:
+ FRIEND_TEST_ALL_PREFIXES(SafariImporterTest, BookmarkImport);
+ FRIEND_TEST_ALL_PREFIXES(SafariImporterTest, FavIconImport);
+ FRIEND_TEST_ALL_PREFIXES(SafariImporterTest, HistoryImport);
+
+ virtual ~SafariImporter();
+
+ // Multiple URLs can share the same FavIcon, this is a map
+ // of URLs -> IconIDs that we load as a temporary step before
+ // actually loading the icons.
+ typedef std::map<int64, std::set<GURL> > FaviconMap;
+
+ void ImportBookmarks();
+ void ImportPasswords();
+ void ImportHistory();
+
+ // Parse Safari's stored bookmarks.
+ void ParseBookmarks(std::vector<ProfileWriter::BookmarkEntry>* bookmarks);
+
+ // Function to recursively read Bookmarks out of Safari plist.
+ // |bookmark_folder| The dictionary containing a folder to parse.
+ // |parent_path_elements| Path elements up to this point.
+ // |is_in_toolbar| Is this folder in the toolbar.
+ // |out_bookmarks| BookMark element array to write into.
+ void RecursiveReadBookmarksFolder(
+ NSDictionary* bookmark_folder,
+ const std::vector<std::wstring>& parent_path_elements,
+ bool is_in_toolbar,
+ std::vector<ProfileWriter::BookmarkEntry>* out_bookmarks);
+
+
+ // Converts history time stored by Safari as a double serialized as a string,
+ // to seconds-since-UNIX-Ephoch-format used by Chrome.
+ double HistoryTimeToEpochTime(NSString* history_time);
+
+ // Parses Safari's history and loads it into the input array.
+ void ParseHistoryItems(std::vector<history::URLRow>* history_items);
+
+ // Loads the favicon Database file, returns NULL on failure.
+ sqlite3* OpenFavIconDB();
+
+ // Loads the urls associated with the favicons into favicon_map;
+ void ImportFavIconURLs(sqlite3* db, FaviconMap* favicon_map);
+
+ // Loads and reencodes the individual favicons.
+ void LoadFaviconData(sqlite3* db,
+ const FaviconMap& favicon_map,
+ std::vector<history::ImportedFavIconUsage>* favicons);
+
+ FilePath library_dir_;
+
+ DISALLOW_COPY_AND_ASSIGN(SafariImporter);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_SAFARI_IMPORTER_H_
diff --git a/chrome/browser/importer/safari_importer.mm b/chrome/browser/importer/safari_importer.mm
new file mode 100644
index 0000000..9a30073
--- /dev/null
+++ b/chrome/browser/importer/safari_importer.mm
@@ -0,0 +1,395 @@
+// Copyright (c) 2010 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 <Cocoa/Cocoa.h>
+
+#include "chrome/browser/importer/safari_importer.h"
+
+#include <map>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/message_loop.h"
+#include "base/scoped_nsobject.h"
+#include "base/string16.h"
+#include "base/sys_string_conversions.h"
+#include "base/time.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/importer/importer_bridge.h"
+#include "chrome/browser/importer/importer_data_types.h"
+#include "chrome/common/sqlite_utils.h"
+#include "chrome/common/url_constants.h"
+#include "googleurl/src/gurl.h"
+#include "grit/generated_resources.h"
+#include "net/base/data_url.h"
+
+using importer::FAVORITES;
+using importer::HISTORY;
+using importer::NONE;
+using importer::PASSWORDS;
+using importer::ProfileInfo;
+
+namespace {
+
+// A function like this is used by other importers in order to filter out
+// URLS we don't want to import.
+// For now it's pretty basic, but I've split it out so it's easy to slot
+// in necessary logic for filtering URLS, should we need it.
+bool CanImportSafariURL(const GURL& url) {
+ // The URL is not valid.
+ if (!url.is_valid())
+ return false;
+
+ return true;
+}
+
+} // namespace
+
+SafariImporter::SafariImporter(const FilePath& library_dir)
+ : library_dir_(library_dir) {
+}
+
+SafariImporter::~SafariImporter() {
+}
+
+// static
+bool SafariImporter::CanImport(const FilePath& library_dir,
+ uint16 *services_supported) {
+ DCHECK(services_supported);
+ *services_supported = importer::NONE;
+
+ // Import features are toggled by the following:
+ // bookmarks import: existence of ~/Library/Safari/Bookmarks.plist file.
+ // history import: existence of ~/Library/Safari/History.plist file.
+ FilePath safari_dir = library_dir.Append("Safari");
+ FilePath bookmarks_path = safari_dir.Append("Bookmarks.plist");
+ FilePath history_path = safari_dir.Append("History.plist");
+
+ using file_util::PathExists;
+ if (PathExists(bookmarks_path))
+ *services_supported |= importer::FAVORITES;
+ if (PathExists(history_path))
+ *services_supported |= importer::HISTORY;
+
+ return *services_supported != importer::NONE;
+}
+
+void SafariImporter::StartImport(importer::ProfileInfo profile_info,
+ uint16 services_supported,
+ ImporterBridge* bridge) {
+ bridge_ = bridge;
+ // The order here is important!
+ bridge_->NotifyStarted();
+ // In keeping with import on other platforms (and for other browsers), we
+ // don't import the home page (since it may lead to a useless homepage); see
+ // crbug.com/25603.
+ if ((services_supported & importer::HISTORY) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::HISTORY);
+ ImportHistory();
+ bridge_->NotifyItemEnded(importer::HISTORY);
+ }
+ if ((services_supported & importer::FAVORITES) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::FAVORITES);
+ ImportBookmarks();
+ bridge_->NotifyItemEnded(importer::FAVORITES);
+ }
+ if ((services_supported & importer::PASSWORDS) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::PASSWORDS);
+ ImportPasswords();
+ bridge_->NotifyItemEnded(importer::PASSWORDS);
+ }
+ bridge_->NotifyEnded();
+}
+
+void SafariImporter::ImportBookmarks() {
+ std::vector<ProfileWriter::BookmarkEntry> bookmarks;
+ ParseBookmarks(&bookmarks);
+
+ // Write bookmarks into profile.
+ if (!bookmarks.empty() && !cancelled()) {
+ const std::wstring& first_folder_name =
+ bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP_FROM_SAFARI);
+ int options = 0;
+ if (import_to_bookmark_bar())
+ options = ProfileWriter::IMPORT_TO_BOOKMARK_BAR;
+ bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options);
+ }
+
+ // Import favicons.
+ sqlite_utils::scoped_sqlite_db_ptr db(OpenFavIconDB());
+ FaviconMap favicon_map;
+ ImportFavIconURLs(db.get(), &favicon_map);
+ // Write favicons into profile.
+ if (!favicon_map.empty() && !cancelled()) {
+ std::vector<history::ImportedFavIconUsage> favicons;
+ LoadFaviconData(db.get(), favicon_map, &favicons);
+ bridge_->SetFavIcons(favicons);
+ }
+}
+
+sqlite3* SafariImporter::OpenFavIconDB() {
+ // Construct ~/Library/Safari/WebIcons.db path
+ NSString* library_dir = [NSString
+ stringWithUTF8String:library_dir_.value().c_str()];
+ NSString* safari_dir = [library_dir
+ stringByAppendingPathComponent:@"Safari"];
+ NSString* favicons_db_path = [safari_dir
+ stringByAppendingPathComponent:@"WebpageIcons.db"];
+
+ sqlite3* favicons_db;
+ const char* safariicons_dbname = [favicons_db_path fileSystemRepresentation];
+ if (sqlite3_open(safariicons_dbname, &favicons_db) != SQLITE_OK)
+ return NULL;
+
+ return favicons_db;
+}
+
+void SafariImporter::ImportFavIconURLs(sqlite3* db, FaviconMap* favicon_map) {
+ SQLStatement s;
+ const char* stmt = "SELECT iconID, url FROM PageURL;";
+ if (s.prepare(db, stmt) != SQLITE_OK)
+ return;
+
+ while (s.step() == SQLITE_ROW && !cancelled()) {
+ int64 icon_id = s.column_int(0);
+ GURL url = GURL(s.column_string(1));
+ (*favicon_map)[icon_id].insert(url);
+ }
+}
+
+void SafariImporter::LoadFaviconData(sqlite3* db,
+ const FaviconMap& favicon_map,
+ std::vector<history::ImportedFavIconUsage>* favicons) {
+ SQLStatement s;
+ const char* stmt = "SELECT i.url, d.data "
+ "FROM IconInfo i JOIN IconData d "
+ "ON i.iconID = d.iconID "
+ "WHERE i.iconID = ?;";
+ 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();
+ }
+}
+
+void SafariImporter::RecursiveReadBookmarksFolder(
+ NSDictionary* bookmark_folder,
+ const std::vector<std::wstring>& parent_path_elements,
+ bool is_in_toolbar,
+ std::vector<ProfileWriter::BookmarkEntry>* out_bookmarks) {
+ DCHECK(bookmark_folder);
+
+ NSString* type = [bookmark_folder objectForKey:@"WebBookmarkType"];
+ NSString* title = [bookmark_folder objectForKey:@"Title"];
+
+ // Are we the dictionary that contains all other bookmarks?
+ // We need to know this so we don't add it to the path.
+ bool is_top_level_bookmarks_container = [bookmark_folder
+ objectForKey:@"WebBookmarkFileVersion"] != nil;
+
+ // We're expecting a list of bookmarks here, if that isn't what we got, fail.
+ if (!is_top_level_bookmarks_container) {
+ // Top level containers sometimes don't have title attributes.
+ if (![type isEqualToString:@"WebBookmarkTypeList"] || !title) {
+ DCHECK(false) << "Type =("
+ << (type ? base::SysNSStringToUTF8(type) : "Null Type")
+ << ") Title=(" << (title ? base::SysNSStringToUTF8(title) : "Null title")
+ << ")";
+ return;
+ }
+ }
+
+ std::vector<std::wstring> path_elements(parent_path_elements);
+ // Is this the toolbar folder?
+ if ([title isEqualToString:@"BookmarksBar"]) {
+ // Be defensive, the toolbar items shouldn't have a prepended path.
+ path_elements.clear();
+ is_in_toolbar = true;
+ } else if ([title isEqualToString:@"BookmarksMenu"]) {
+ // top level container for normal bookmarks.
+ path_elements.clear();
+ } else if (!is_top_level_bookmarks_container) {
+ if (title)
+ path_elements.push_back(base::SysNSStringToWide(title));
+ }
+
+ NSArray* elements = [bookmark_folder objectForKey:@"Children"];
+ // TODO(jeremy) Does Chrome support importing empty folders?
+ if (!elements)
+ return;
+
+ // Iterate over individual bookmarks.
+ for (NSDictionary* bookmark in elements) {
+ NSString* type = [bookmark objectForKey:@"WebBookmarkType"];
+ if (!type)
+ continue;
+
+ // If this is a folder, recurse.
+ if ([type isEqualToString:@"WebBookmarkTypeList"]) {
+ RecursiveReadBookmarksFolder(bookmark,
+ path_elements,
+ is_in_toolbar,
+ out_bookmarks);
+ }
+
+ // If we didn't see a bookmark folder, then we're expecting a bookmark
+ // item, if that's not what we got then ignore it.
+ if (![type isEqualToString:@"WebBookmarkTypeLeaf"])
+ continue;
+
+ NSString* url = [bookmark objectForKey:@"URLString"];
+ NSString* title = [[bookmark objectForKey:@"URIDictionary"]
+ objectForKey:@"title"];
+
+ if (!url || !title)
+ continue;
+
+ // Output Bookmark.
+ ProfileWriter::BookmarkEntry entry;
+ // Safari doesn't specify a creation time for the bookmark.
+ entry.creation_time = base::Time::Now();
+ entry.title = base::SysNSStringToWide(title);
+ entry.url = GURL(base::SysNSStringToUTF8(url));
+ entry.path = path_elements;
+ entry.in_toolbar = is_in_toolbar;
+
+ out_bookmarks->push_back(entry);
+ }
+}
+
+void SafariImporter::ParseBookmarks(
+ std::vector<ProfileWriter::BookmarkEntry>* bookmarks) {
+ DCHECK(bookmarks);
+
+ // Construct ~/Library/Safari/Bookmarks.plist path
+ NSString* library_dir = [NSString
+ stringWithUTF8String:library_dir_.value().c_str()];
+ NSString* safari_dir = [library_dir
+ stringByAppendingPathComponent:@"Safari"];
+ NSString* bookmarks_plist = [safari_dir
+ stringByAppendingPathComponent:@"Bookmarks.plist"];
+
+ // Load the plist file.
+ NSDictionary* bookmarks_dict = [NSDictionary
+ dictionaryWithContentsOfFile:bookmarks_plist];
+ if (!bookmarks_dict)
+ return;
+
+ // Recursively read in bookmarks.
+ std::vector<std::wstring> parent_path_elements;
+ RecursiveReadBookmarksFolder(bookmarks_dict, parent_path_elements, false,
+ bookmarks);
+}
+
+void SafariImporter::ImportPasswords() {
+ // Safari stores it's passwords in the Keychain, same as us so we don't need
+ // to import them.
+ // Note: that we don't automatically pick them up, there is some logic around
+ // the user needing to explicitly input his username in a page and blurring
+ // the field before we pick it up, but the details of that are beyond the
+ // scope of this comment.
+}
+
+void SafariImporter::ImportHistory() {
+ std::vector<history::URLRow> rows;
+ ParseHistoryItems(&rows);
+
+ if (!rows.empty() && !cancelled()) {
+ bridge_->SetHistoryItems(rows);
+ }
+}
+
+double SafariImporter::HistoryTimeToEpochTime(NSString* history_time) {
+ DCHECK(history_time);
+ // Add Difference between Unix epoch and CFAbsoluteTime epoch in seconds.
+ // Unix epoch is 1970-01-01 00:00:00.0 UTC,
+ // CF epoch is 2001-01-01 00:00:00.0 UTC.
+ return CFStringGetDoubleValue(reinterpret_cast<CFStringRef>(history_time)) +
+ kCFAbsoluteTimeIntervalSince1970;
+}
+
+void SafariImporter::ParseHistoryItems(
+ std::vector<history::URLRow>* history_items) {
+ DCHECK(history_items);
+
+ // Construct ~/Library/Safari/History.plist path
+ NSString* library_dir = [NSString
+ stringWithUTF8String:library_dir_.value().c_str()];
+ NSString* safari_dir = [library_dir
+ stringByAppendingPathComponent:@"Safari"];
+ NSString* history_plist = [safari_dir
+ stringByAppendingPathComponent:@"History.plist"];
+
+ // Load the plist file.
+ NSDictionary* history_dict = [NSDictionary
+ dictionaryWithContentsOfFile:history_plist];
+ if (!history_dict)
+ return;
+
+ NSArray* safari_history_items = [history_dict
+ objectForKey:@"WebHistoryDates"];
+
+ for (NSDictionary* history_item in safari_history_items) {
+ using base::SysNSStringToUTF8;
+ using base::SysNSStringToUTF16;
+ NSString* url_ns = [history_item objectForKey:@""];
+ if (!url_ns)
+ continue;
+
+ GURL url(SysNSStringToUTF8(url_ns));
+
+ if (!CanImportSafariURL(url))
+ continue;
+
+ history::URLRow row(url);
+ NSString* title_ns = [history_item objectForKey:@"title"];
+
+ // Sometimes items don't have a title, in which case we just substitue
+ // the url.
+ if (!title_ns)
+ title_ns = url_ns;
+
+ row.set_title(SysNSStringToUTF16(title_ns));
+ int visit_count = [[history_item objectForKey:@"visitCount"]
+ intValue];
+ row.set_visit_count(visit_count);
+ // Include imported URLs in autocompletion - don't hide them.
+ row.set_hidden(0);
+ // Item was never typed before in the omnibox.
+ row.set_typed_count(0);
+
+ NSString* last_visit_str = [history_item objectForKey:@"lastVisitedDate"];
+ // The last visit time should always be in the history item, but if not
+ /// just continue without this item.
+ DCHECK(last_visit_str);
+ if (!last_visit_str)
+ continue;
+
+ // Convert Safari's last visit time to Unix Epoch time.
+ double seconds_since_unix_epoch = HistoryTimeToEpochTime(last_visit_str);
+ row.set_last_visit(base::Time::FromDoubleT(seconds_since_unix_epoch));
+
+ history_items->push_back(row);
+ }
+}
diff --git a/chrome/browser/importer/safari_importer_unittest.mm b/chrome/browser/importer/safari_importer_unittest.mm
new file mode 100644
index 0000000..081c26e
--- /dev/null
+++ b/chrome/browser/importer/safari_importer_unittest.mm
@@ -0,0 +1,171 @@
+// Copyright (c) 2010 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/safari_importer.h"
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/sys_string_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/test/file_test_utils.h"
+#include "chrome/browser/importer/importer_bridge.h"
+#include "testing/platform_test.h"
+
+using importer::COOKIES;
+using importer::FAVORITES;
+using importer::HISTORY;
+using importer::HOME_PAGE;
+using importer::NONE;
+using importer::PASSWORDS;
+using importer::SEARCH_ENGINES;
+
+// In order to test the Safari import functionality effectively, we store a
+// simulated Library directory containing dummy data files in the same
+// structure as ~/Library in the Chrome test data directory.
+// This function returns the path to that directory.
+FilePath GetTestSafariLibraryPath() {
+ FilePath test_dir;
+ PathService::Get(chrome::DIR_TEST_DATA, &test_dir);
+
+ // Our simulated ~/Library directory
+ test_dir = test_dir.AppendASCII("safari_import");
+ return test_dir;
+}
+
+class SafariImporterTest : public PlatformTest {
+ public:
+ SafariImporter* GetSafariImporter() {
+ FilePath test_library_dir = GetTestSafariLibraryPath();
+ CHECK(file_util::PathExists(test_library_dir)) <<
+ "Missing test data directory";
+
+ return new SafariImporter(test_library_dir);
+ }
+};
+
+TEST_F(SafariImporterTest, HistoryImport) {
+ scoped_refptr<SafariImporter> importer(GetSafariImporter());
+
+ std::vector<history::URLRow> history_items;
+ importer->ParseHistoryItems(&history_items);
+
+ // Should be 2 history items.
+ ASSERT_EQ(history_items.size(), 2U);
+
+ history::URLRow& it1 = history_items[0];
+ EXPECT_EQ(it1.url(), GURL("http://www.firsthistoryitem.com/"));
+ EXPECT_EQ(it1.title(), UTF8ToUTF16("First History Item Title"));
+ EXPECT_EQ(it1.visit_count(), 1);
+ EXPECT_EQ(it1.hidden(), 0);
+ EXPECT_EQ(it1.typed_count(), 0);
+ EXPECT_EQ(it1.last_visit().ToDoubleT(),
+ importer->HistoryTimeToEpochTime(@"270598264.4"));
+
+ history::URLRow& it2 = history_items[1];
+ std::string second_item_title("http://www.secondhistoryitem.com/");
+ EXPECT_EQ(it2.url(), GURL(second_item_title));
+ // The second item lacks a title so we expect the URL to be substituted.
+ EXPECT_EQ(UTF16ToUTF8(it2.title()), second_item_title.c_str());
+ EXPECT_EQ(it2.visit_count(), 55);
+ EXPECT_EQ(it2.hidden(), 0);
+ EXPECT_EQ(it2.typed_count(), 0);
+ EXPECT_EQ(it2.last_visit().ToDoubleT(),
+ importer->HistoryTimeToEpochTime(@"270598231.4"));
+}
+
+TEST_F(SafariImporterTest, BookmarkImport) {
+ // Expected results
+ const struct {
+ bool in_toolbar;
+ GURL url;
+ // If the path array for an element is entry set this to true.
+ bool path_is_empty;
+ // We ony support one level of nesting in paths, this makes testing a little
+ // easier.
+ std::wstring path;
+ std::wstring title;
+ } kImportedBookmarksData[] = {
+ {true, GURL("http://www.apple.com/"), true, L"", L"Apple"},
+ {true, GURL("http://www.yahoo.com/"), true, L"", L"Yahoo!"},
+ {true, GURL("http://www.cnn.com/"), false, L"News", L"CNN"},
+ {true, GURL("http://www.nytimes.com/"), false, L"News",
+ L"The New York Times"},
+ {false, GURL("http://www.reddit.com/"), true, L"",
+ L"reddit.com: what's new online!"},
+ };
+
+ scoped_refptr<SafariImporter> importer(GetSafariImporter());
+ std::vector<ProfileWriter::BookmarkEntry> bookmarks;
+ importer->ParseBookmarks(&bookmarks);
+ size_t num_bookmarks = bookmarks.size();
+ EXPECT_EQ(num_bookmarks, ARRAYSIZE_UNSAFE(kImportedBookmarksData));
+
+ for (size_t i = 0; i < num_bookmarks; ++i) {
+ ProfileWriter::BookmarkEntry& entry = bookmarks[i];
+ EXPECT_EQ(entry.in_toolbar, kImportedBookmarksData[i].in_toolbar);
+ EXPECT_EQ(entry.url, kImportedBookmarksData[i].url);
+ if (kImportedBookmarksData[i].path_is_empty) {
+ EXPECT_EQ(entry.path.size(), 0U);
+ } else {
+ EXPECT_EQ(entry.path.size(), 1U);
+ EXPECT_EQ(entry.path[0], kImportedBookmarksData[i].path);
+ EXPECT_EQ(entry.title, kImportedBookmarksData[i].title);
+ }
+ }
+}
+
+TEST_F(SafariImporterTest, FavIconImport) {
+ scoped_refptr<SafariImporter> importer(GetSafariImporter());
+ sqlite_utils::scoped_sqlite_db_ptr db(importer->OpenFavIconDB());
+ ASSERT_TRUE(db.get() != NULL);
+
+ SafariImporter::FaviconMap favicon_map;
+ importer->ImportFavIconURLs(db.get(), &favicon_map);
+
+ std::vector<history::ImportedFavIconUsage> favicons;
+ importer->LoadFaviconData(db.get(), favicon_map, &favicons);
+
+ size_t num_favicons = favicons.size();
+ ASSERT_EQ(num_favicons, 2U);
+
+ history::ImportedFavIconUsage &fav0 = favicons[0];
+ EXPECT_EQ("http://s.ytimg.com/yt/favicon-vfl86270.ico",
+ fav0.favicon_url.spec());
+ EXPECT_GT(fav0.png_data.size(), 0U);
+ EXPECT_EQ(fav0.urls.size(), 1U);
+ EXPECT_TRUE(fav0.urls.find(GURL("http://www.youtube.com/"))
+ != fav0.urls.end());
+
+ history::ImportedFavIconUsage &fav1 = favicons[1];
+ EXPECT_EQ("http://www.opensearch.org/favicon.ico",
+ fav1.favicon_url.spec());
+ EXPECT_GT(fav1.png_data.size(), 0U);
+ EXPECT_EQ(fav1.urls.size(), 2U);
+ EXPECT_TRUE(fav1.urls.find(GURL("http://www.opensearch.org/Home"))
+ != fav1.urls.end());
+
+ EXPECT_TRUE(fav1.urls.find(
+ GURL("http://www.opensearch.org/Special:Search?search=lalala&go=Search"))
+ != fav1.urls.end());
+}
+
+TEST_F(SafariImporterTest, CanImport) {
+ uint16 items = NONE;
+ EXPECT_TRUE(SafariImporter::CanImport(GetTestSafariLibraryPath(), &items));
+ EXPECT_EQ(items, HISTORY | FAVORITES);
+ EXPECT_EQ(items & COOKIES, NONE);
+ EXPECT_EQ(items & PASSWORDS, NONE);
+ EXPECT_EQ(items & SEARCH_ENGINES, NONE);
+ EXPECT_EQ(items & HOME_PAGE, NONE);
+
+ // Check that we don't import anything from a bogus library directory.
+ FilePath fake_library_dir;
+ file_util::CreateNewTempDirectory("FakeSafariLibrary", &fake_library_dir);
+ FileAutoDeleter deleter(fake_library_dir);
+ EXPECT_FALSE(SafariImporter::CanImport(fake_library_dir, &items));
+}
diff --git a/chrome/browser/importer/toolbar_importer.cc b/chrome/browser/importer/toolbar_importer.cc
new file mode 100644
index 0000000..5b0a93c
--- /dev/null
+++ b/chrome/browser/importer/toolbar_importer.cc
@@ -0,0 +1,597 @@
+// Copyright (c) 2010 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/toolbar_importer.h"
+
+#include <limits>
+
+#include "base/rand_util.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/first_run.h"
+#include "chrome/browser/importer/importer_bridge.h"
+#include "chrome/browser/importer/importer_data_types.h"
+#include "chrome/browser/profile.h"
+#include "chrome/common/libxml_utils.h"
+#include "chrome/common/net/url_request_context_getter.h"
+#include "grit/generated_resources.h"
+#include "net/base/cookie_monster.h"
+#include "net/base/data_url.h"
+#include "net/url_request/url_request_context.h"
+
+using importer::FAVORITES;
+using importer::NONE;
+using importer::ProfileInfo;
+
+//
+// ToolbarImporterUtils
+//
+static const char* kGoogleDomainUrl = "http://.google.com/";
+static const wchar_t kSplitStringToken = L';';
+static const char* kGoogleDomainSecureCookieId = "SID=";
+
+bool toolbar_importer_utils::IsGoogleGAIACookieInstalled() {
+ net::CookieStore* store =
+ Profile::GetDefaultRequestContext()->GetCookieStore();
+ GURL url(kGoogleDomainUrl);
+ net::CookieOptions options;
+ options.set_include_httponly(); // The SID cookie might be httponly.
+ std::string cookies = store->GetCookiesWithOptions(url, options);
+ std::vector<std::string> cookie_list;
+ SplitString(cookies, kSplitStringToken, &cookie_list);
+ for (std::vector<std::string>::iterator current = cookie_list.begin();
+ current != cookie_list.end();
+ ++current) {
+ size_t position = (*current).find(kGoogleDomainSecureCookieId);
+ if (0 == position)
+ return true;
+ }
+ return false;
+}
+
+//
+// Toolbar5Importer
+//
+const char Toolbar5Importer::kXmlApiReplyXmlTag[] = "xml_api_reply";
+const char Toolbar5Importer::kBookmarksXmlTag[] = "bookmarks";
+const char Toolbar5Importer::kBookmarkXmlTag[] = "bookmark";
+const char Toolbar5Importer::kTitleXmlTag[] = "title";
+const char Toolbar5Importer::kUrlXmlTag[] = "url";
+const char Toolbar5Importer::kTimestampXmlTag[] = "timestamp";
+const char Toolbar5Importer::kLabelsXmlTag[] = "labels";
+const char Toolbar5Importer::kLabelsXmlCloseTag[] = "/labels";
+const char Toolbar5Importer::kLabelXmlTag[] = "label";
+const char Toolbar5Importer::kAttributesXmlTag[] = "attributes";
+
+const char Toolbar5Importer::kRandomNumberToken[] = "{random_number}";
+const char Toolbar5Importer::kAuthorizationToken[] = "{auth_token}";
+const char Toolbar5Importer::kAuthorizationTokenPrefix[] = "/*";
+const char Toolbar5Importer::kAuthorizationTokenSuffix[] = "*/";
+const char Toolbar5Importer::kMaxNumToken[] = "{max_num}";
+const char Toolbar5Importer::kMaxTimestampToken[] = "{max_timestamp}";
+
+const char Toolbar5Importer::kT5AuthorizationTokenUrl[] =
+ "http://www.google.com/notebook/token?zx={random_number}";
+const char Toolbar5Importer::kT5FrontEndUrlTemplate[] =
+ "http://www.google.com/notebook/toolbar?cmd=list&tok={auth_token}&"
+ "num={max_num}&min={max_timestamp}&all=0&zx={random_number}";
+
+// Importer methods.
+
+// The constructor should set the initial state to NOT_USED.
+Toolbar5Importer::Toolbar5Importer()
+ : state_(NOT_USED),
+ items_to_import_(importer::NONE),
+ token_fetcher_(NULL),
+ data_fetcher_(NULL) {
+}
+
+// The destructor insures that the fetchers are currently not being used, as
+// their thread-safe implementation requires that they are cancelled from the
+// thread in which they were constructed.
+Toolbar5Importer::~Toolbar5Importer() {
+ DCHECK(!token_fetcher_);
+ DCHECK(!data_fetcher_);
+}
+
+void Toolbar5Importer::StartImport(importer::ProfileInfo profile_info,
+ uint16 items,
+ ImporterBridge* bridge) {
+ DCHECK(bridge);
+
+ bridge_ = bridge;
+ items_to_import_ = items;
+ state_ = INITIALIZED;
+
+ bridge_->NotifyStarted();
+ ContinueImport();
+}
+
+// The public cancel method serves two functions, as a callback from the UI
+// as well as an internal callback in case of cancel. An internal callback
+// is required since the URLFetcher must be destroyed from the thread it was
+// created.
+void Toolbar5Importer::Cancel() {
+ // In the case when the thread is not importing messages we are to
+ // cancel as soon as possible.
+ Importer::Cancel();
+
+ // If we are conducting network operations, post a message to the importer
+ // thread for synchronization.
+ if (ChromeThread::CurrentlyOn(ChromeThread::UI)) {
+ EndImport();
+ } else {
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(this, &Toolbar5Importer::Cancel));
+ }
+}
+
+void Toolbar5Importer::OnURLFetchComplete(
+ const URLFetcher* source,
+ const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const ResponseCookies& cookies,
+ const std::string& data) {
+ if (cancelled()) {
+ EndImport();
+ return;
+ }
+
+ if (200 != response_code) { // HTTP/Ok
+ // Cancelling here will update the UI and bypass the rest of bookmark
+ // import.
+ EndImportBookmarks();
+ return;
+ }
+
+ switch (state_) {
+ case GET_AUTHORIZATION_TOKEN:
+ GetBookmarkDataFromServer(data);
+ break;
+ case GET_BOOKMARKS:
+ GetBookmarksFromServerDataResponse(data);
+ break;
+ default:
+ NOTREACHED() << "Invalid state.";
+ EndImportBookmarks();
+ break;
+ }
+}
+
+void Toolbar5Importer::ContinueImport() {
+ DCHECK((items_to_import_ == importer::FAVORITES) ||
+ (items_to_import_ == importer::NONE)) <<
+ "The items requested are not supported";
+
+ // The order here is important. Each Begin... will clear the flag
+ // of its item before its task finishes and re-enters this method.
+ if (importer::NONE == items_to_import_) {
+ EndImport();
+ return;
+ }
+ if ((items_to_import_ & importer::FAVORITES) && !cancelled()) {
+ items_to_import_ &= ~importer::FAVORITES;
+ BeginImportBookmarks();
+ return;
+ }
+ // TODO(brg): Import history, autocomplete, other toolbar information
+ // in a future release.
+
+ // This code should not be reached, but gracefully handles the possibility
+ // that StartImport was called with unsupported items_to_import.
+ if (!cancelled())
+ EndImport();
+}
+
+void Toolbar5Importer::EndImport() {
+ if (state_ != DONE) {
+ state_ = DONE;
+ // By spec the fetchers must be destroyed within the same
+ // thread they are created. The importer is destroyed in the ui_thread
+ // so when we complete in the file_thread we destroy them first.
+ if (NULL != token_fetcher_) {
+ delete token_fetcher_;
+ token_fetcher_ = NULL;
+ }
+
+ if (NULL != data_fetcher_) {
+ delete data_fetcher_;
+ data_fetcher_ = NULL;
+ }
+
+ bridge_->NotifyEnded();
+ }
+}
+
+void Toolbar5Importer::BeginImportBookmarks() {
+ bridge_->NotifyItemStarted(importer::FAVORITES);
+ GetAuthenticationFromServer();
+}
+
+void Toolbar5Importer::EndImportBookmarks() {
+ bridge_->NotifyItemEnded(importer::FAVORITES);
+ ContinueImport();
+}
+
+
+// Notebook front-end connection manager implementation follows.
+void Toolbar5Importer::GetAuthenticationFromServer() {
+ if (cancelled()) {
+ EndImport();
+ return;
+ }
+
+ // Authentication is a token string retrieved from the authentication server
+ // To access it we call the url below with a random number replacing the
+ // value in the string.
+ state_ = GET_AUTHORIZATION_TOKEN;
+
+ // Random number construction.
+ int random = base::RandInt(0, std::numeric_limits<int>::max());
+ std::string random_string = UintToString(random);
+
+ // Retrieve authorization token from the network.
+ std::string url_string(kT5AuthorizationTokenUrl);
+ url_string.replace(url_string.find(kRandomNumberToken),
+ arraysize(kRandomNumberToken) - 1,
+ random_string);
+ GURL url(url_string);
+
+ token_fetcher_ = new URLFetcher(url, URLFetcher::GET, this);
+ token_fetcher_->set_request_context(Profile::GetDefaultRequestContext());
+ token_fetcher_->Start();
+}
+
+void Toolbar5Importer::GetBookmarkDataFromServer(const std::string& response) {
+ if (cancelled()) {
+ EndImport();
+ return;
+ }
+
+ state_ = GET_BOOKMARKS;
+
+ // Parse and verify the authorization token from the response.
+ std::string token;
+ if (!ParseAuthenticationTokenResponse(response, &token)) {
+ EndImportBookmarks();
+ return;
+ }
+
+ // Build the Toolbar FE connection string, and call the server for
+ // the xml blob. We must tag the connection string with a random number.
+ std::string conn_string = kT5FrontEndUrlTemplate;
+ int random = base::RandInt(0, std::numeric_limits<int>::max());
+ std::string random_string = UintToString(random);
+ conn_string.replace(conn_string.find(kRandomNumberToken),
+ arraysize(kRandomNumberToken) - 1,
+ random_string);
+ conn_string.replace(conn_string.find(kAuthorizationToken),
+ arraysize(kAuthorizationToken) - 1,
+ token);
+ GURL url(conn_string);
+
+ data_fetcher_ = new URLFetcher(url, URLFetcher::GET, this);
+ data_fetcher_->set_request_context(Profile::GetDefaultRequestContext());
+ data_fetcher_->Start();
+}
+
+void Toolbar5Importer::GetBookmarksFromServerDataResponse(
+ const std::string& response) {
+ if (cancelled()) {
+ EndImport();
+ return;
+ }
+
+ state_ = PARSE_BOOKMARKS;
+
+ XmlReader reader;
+ if (reader.Load(response) && !cancelled()) {
+ // Construct Bookmarks
+ std::vector<ProfileWriter::BookmarkEntry> bookmarks;
+ if (ParseBookmarksFromReader(&reader, &bookmarks,
+ WideToUTF16(bridge_->GetLocalizedString(
+ IDS_BOOKMARK_GROUP_FROM_GOOGLE_TOOLBAR))))
+ AddBookmarksToChrome(bookmarks);
+ }
+ EndImportBookmarks();
+}
+
+bool Toolbar5Importer::ParseAuthenticationTokenResponse(
+ const std::string& response,
+ std::string* token) {
+ DCHECK(token);
+
+ *token = response;
+ size_t position = token->find(kAuthorizationTokenPrefix);
+ if (0 != position)
+ return false;
+ token->replace(position, arraysize(kAuthorizationTokenPrefix) - 1, "");
+
+ position = token->find(kAuthorizationTokenSuffix);
+ if (token->size() != (position + (arraysize(kAuthorizationTokenSuffix) - 1)))
+ return false;
+ token->replace(position, arraysize(kAuthorizationTokenSuffix) - 1, "");
+
+ return true;
+}
+
+// Parsing
+bool Toolbar5Importer::ParseBookmarksFromReader(
+ XmlReader* reader,
+ std::vector<ProfileWriter::BookmarkEntry>* bookmarks,
+ const string16& bookmark_group_string) {
+ DCHECK(reader);
+ DCHECK(bookmarks);
+
+ // The XML blob returned from the server is described in the
+ // Toolbar-Notebook/Bookmarks Protocol document located at
+ // https://docs.google.com/a/google.com/Doc?docid=cgt3m7dr_24djt62m&hl=en
+ // We are searching for the section with structure
+ // <bookmarks><bookmark>...</bookmark><bookmark>...</bookmark></bookmarks>
+
+ // Locate the |bookmarks| blob.
+ if (!reader->SkipToElement())
+ return false;
+
+ if (!LocateNextTagByName(reader, kBookmarksXmlTag))
+ return false;
+
+ // Parse each |bookmark| blob
+ while (LocateNextTagWithStopByName(reader, kBookmarkXmlTag,
+ kBookmarksXmlTag)) {
+ ProfileWriter::BookmarkEntry bookmark_entry;
+ std::vector<BookmarkFolderType> folders;
+ if (ExtractBookmarkInformation(reader, &bookmark_entry, &folders,
+ bookmark_group_string)) {
+ // For each folder we create a new bookmark entry. Duplicates will
+ // be detected when we attempt to create the bookmark in the profile.
+ for (std::vector<BookmarkFolderType>::iterator folder = folders.begin();
+ folder != folders.end();
+ ++folder) {
+ bookmark_entry.path = *folder;
+ bookmarks->push_back(bookmark_entry);
+ }
+ }
+ }
+
+ if (0 == bookmarks->size())
+ return false;
+
+ return true;
+}
+
+bool Toolbar5Importer::LocateNextOpenTag(XmlReader* reader) {
+ DCHECK(reader);
+
+ while (!reader->SkipToElement()) {
+ if (!reader->Read())
+ return false;
+ }
+ return true;
+}
+
+bool Toolbar5Importer::LocateNextTagByName(XmlReader* reader,
+ const std::string& tag) {
+ DCHECK(reader);
+
+ // Locate the |tag| blob.
+ while (tag != reader->NodeName()) {
+ if (!reader->Read() || !LocateNextOpenTag(reader))
+ return false;
+ }
+ return true;
+}
+
+bool Toolbar5Importer::LocateNextTagWithStopByName(XmlReader* reader,
+ const std::string& tag,
+ const std::string& stop) {
+ DCHECK(reader);
+
+ DCHECK_NE(tag, stop);
+ // Locate the |tag| blob.
+ while (tag != reader->NodeName()) {
+ // Move to the next open tag.
+ if (!reader->Read() || !LocateNextOpenTag(reader))
+ return false;
+ // If we encounter the stop word return false.
+ if (stop == reader->NodeName())
+ return false;
+ }
+ return true;
+}
+
+bool Toolbar5Importer::ExtractBookmarkInformation(
+ XmlReader* reader,
+ ProfileWriter::BookmarkEntry* bookmark_entry,
+ std::vector<BookmarkFolderType>* bookmark_folders,
+ const string16& bookmark_group_string) {
+ DCHECK(reader);
+ DCHECK(bookmark_entry);
+ DCHECK(bookmark_folders);
+
+ // The following is a typical bookmark entry.
+ // The reader should be pointing to the <title> tag at the moment.
+ //
+ // <bookmark>
+ // <title>MyTitle</title>
+ // <url>http://www.sohu.com/</url>
+ // <timestamp>1153328691085181</timestamp>
+ // <id>N123nasdf239</id>
+ // <notebook_id>Bxxxxxxx</notebook_id> (for bookmarks, a special id is used)
+ // <section_id>Sxxxxxx</section_id>
+ // <has_highlight>0</has_highlight>
+ // <labels>
+ // <label>China</label>
+ // <label>^k</label> (if this special label is present, the note is deleted)
+ // </labels>
+ // <attributes>
+ // <attribute>
+ // <name>favicon_url</name>
+ // <value>http://www.sohu.com/favicon.ico</value>
+ // </attribute>
+ // <attribute>
+ // <name>favicon_timestamp</name>
+ // <value>1153328653</value>
+ // </attribute>
+ // <attribute>
+ // <name>notebook_name</name>
+ // <value>My notebook 0</value>
+ // </attribute>
+ // <attribute>
+ // <name>section_name</name>
+ // <value>My section 0</value>
+ // </attribute>
+ // </attributes>
+ // </bookmark>
+ //
+ // We parse the blob in order, title->url->timestamp etc. Any failure
+ // causes us to skip this bookmark.
+
+ if (!ExtractTitleFromXmlReader(reader, bookmark_entry))
+ return false;
+ if (!ExtractUrlFromXmlReader(reader, bookmark_entry))
+ return false;
+ if (!ExtractTimeFromXmlReader(reader, bookmark_entry))
+ return false;
+ if (!ExtractFoldersFromXmlReader(reader, bookmark_folders,
+ bookmark_group_string))
+ return false;
+
+ return true;
+}
+
+bool Toolbar5Importer::ExtractNamedValueFromXmlReader(XmlReader* reader,
+ const std::string& name,
+ std::string* buffer) {
+ DCHECK(reader);
+ DCHECK(buffer);
+
+ if (name != reader->NodeName())
+ return false;
+ if (!reader->ReadElementContent(buffer))
+ return false;
+ return true;
+}
+
+bool Toolbar5Importer::ExtractTitleFromXmlReader(
+ XmlReader* reader,
+ ProfileWriter::BookmarkEntry* entry) {
+ DCHECK(reader);
+ DCHECK(entry);
+
+ if (!LocateNextTagWithStopByName(reader, kTitleXmlTag, kUrlXmlTag))
+ return false;
+ std::string buffer;
+ if (!ExtractNamedValueFromXmlReader(reader, kTitleXmlTag, &buffer)) {
+ return false;
+ }
+ entry->title = UTF8ToWide(buffer);
+ return true;
+}
+
+bool Toolbar5Importer::ExtractUrlFromXmlReader(
+ XmlReader* reader,
+ ProfileWriter::BookmarkEntry* entry) {
+ DCHECK(reader);
+ DCHECK(entry);
+
+ if (!LocateNextTagWithStopByName(reader, kUrlXmlTag, kTimestampXmlTag))
+ return false;
+ std::string buffer;
+ if (!ExtractNamedValueFromXmlReader(reader, kUrlXmlTag, &buffer)) {
+ return false;
+ }
+ entry->url = GURL(buffer);
+ return true;
+}
+
+bool Toolbar5Importer::ExtractTimeFromXmlReader(
+ XmlReader* reader,
+ ProfileWriter::BookmarkEntry* entry) {
+ DCHECK(reader);
+ DCHECK(entry);
+ if (!LocateNextTagWithStopByName(reader, kTimestampXmlTag, kLabelsXmlTag))
+ return false;
+ std::string buffer;
+ if (!ExtractNamedValueFromXmlReader(reader, kTimestampXmlTag, &buffer)) {
+ return false;
+ }
+ int64 timestamp;
+ if (!StringToInt64(buffer, &timestamp)) {
+ return false;
+ }
+ entry->creation_time = base::Time::FromTimeT(timestamp);
+ return true;
+}
+
+bool Toolbar5Importer::ExtractFoldersFromXmlReader(
+ XmlReader* reader,
+ std::vector<BookmarkFolderType>* bookmark_folders,
+ const string16& bookmark_group_string) {
+ DCHECK(reader);
+ DCHECK(bookmark_folders);
+
+ // Read in the labels for this bookmark from the xml. There may be many
+ // labels for any one bookmark.
+ if (!LocateNextTagWithStopByName(reader, kLabelsXmlTag, kAttributesXmlTag))
+ return false;
+
+ // It is within scope to have an empty labels section, so we do not
+ // return false if the labels are empty.
+ if (!reader->Read() || !LocateNextOpenTag(reader))
+ return false;
+
+ std::vector<std::wstring> label_vector;
+ while (kLabelXmlTag == reader->NodeName()) {
+ std::string label_buffer;
+ if (!reader->ReadElementContent(&label_buffer)) {
+ label_buffer = "";
+ }
+ label_vector.push_back(UTF8ToWide(label_buffer));
+ LocateNextOpenTag(reader);
+ }
+
+ if (0 == label_vector.size()) {
+ if (!FirstRun::IsChromeFirstRun()) {
+ bookmark_folders->resize(1);
+ (*bookmark_folders)[0].push_back(UTF16ToWide(bookmark_group_string));
+ }
+ return true;
+ }
+
+ // We will be making one bookmark folder for each label.
+ bookmark_folders->resize(label_vector.size());
+
+ for (size_t index = 0; index < label_vector.size(); ++index) {
+ // If this is the first run then we place favorites with no labels
+ // in the title bar. Else they are placed in the "Google Toolbar" folder.
+ if (!FirstRun::IsChromeFirstRun() || !label_vector[index].empty()) {
+ (*bookmark_folders)[index].push_back(UTF16ToWide(bookmark_group_string));
+ }
+
+ // If the label and is in the form "xxx:yyy:zzz" this was created from an
+ // IE or Firefox folder. We undo the label creation and recreate the
+ // correct folder.
+ std::vector<std::wstring> folder_names;
+ SplitString(label_vector[index], L':', &folder_names);
+ (*bookmark_folders)[index].insert((*bookmark_folders)[index].end(),
+ folder_names.begin(), folder_names.end());
+ }
+
+ return true;
+}
+
+// Bookmark creation
+void Toolbar5Importer::AddBookmarksToChrome(
+ const std::vector<ProfileWriter::BookmarkEntry>& bookmarks) {
+ if (!bookmarks.empty() && !cancelled()) {
+ const std::wstring& first_folder_name =
+ bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP_FROM_GOOGLE_TOOLBAR);
+ int options = ProfileWriter::ADD_IF_UNIQUE |
+ (import_to_bookmark_bar() ? ProfileWriter::IMPORT_TO_BOOKMARK_BAR : 0);
+ bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options);
+ }
+}
diff --git a/chrome/browser/importer/toolbar_importer.h b/chrome/browser/importer/toolbar_importer.h
new file mode 100644
index 0000000..0f31832
--- /dev/null
+++ b/chrome/browser/importer/toolbar_importer.h
@@ -0,0 +1,172 @@
+// Copyright (c) 2010 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.
+
+// The functionality provided here allows the user to import their bookmarks
+// (favorites) from Google Toolbar.
+
+#ifndef CHROME_BROWSER_IMPORTER_TOOLBAR_IMPORTER_H_
+#define CHROME_BROWSER_IMPORTER_TOOLBAR_IMPORTER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/string16.h"
+#include "chrome/browser/importer/importer.h"
+#include "chrome/browser/importer/importer_data_types.h"
+#include "chrome/common/net/url_fetcher.h"
+
+class ImporterBridge;
+class XmlReader;
+
+// Currently the only configuration information we need is to check whether or
+// not the user currently has their GAIA cookie. This is done by the function
+// exposed through the ToolbarImportUtils namespace.
+namespace toolbar_importer_utils {
+bool IsGoogleGAIACookieInstalled();
+} // namespace toolbar_importer_utils
+
+// Toolbar5Importer is a class which exposes the functionality needed to
+// communicate with the Google Toolbar v5 front-end, negotiate the download of
+// Toolbar bookmarks, parse them, and install them on the client.
+// Toolbar5Importer should not have StartImport called more than once. Futher
+// if StartImport is called, then the class must not be destroyed until it
+// has either completed or Toolbar5Importer->Cancel() has been called.
+class Toolbar5Importer : public URLFetcher::Delegate, public Importer {
+ public:
+ Toolbar5Importer();
+
+ // Importer view calls this method to begin the process. The items parameter
+ // should only either be NONE or FAVORITES, since as of right now these are
+ // the only items this importer supports. This method provides implementation
+ // of Importer::StartImport.
+ virtual void StartImport(importer::ProfileInfo profile_info,
+ uint16 items,
+ ImporterBridge* bridge);
+
+ // Importer view call this method when the user clicks the cancel button
+ // in the ImporterView UI. We need to post a message to our loop
+ // to cancel network retrieval.
+ virtual void Cancel();
+
+ // URLFetcher::Delegate method called back from the URLFetcher object.
+ virtual void OnURLFetchComplete(const URLFetcher* source,
+ const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const ResponseCookies& cookies,
+ const std::string& data);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(Toolbar5ImporterTest, BookmarkParse);
+
+ virtual ~Toolbar5Importer();
+
+ // Internal states of the toolbar importer.
+ enum InternalStateEnum {
+ NOT_USED = -1,
+ INITIALIZED,
+ GET_AUTHORIZATION_TOKEN,
+ GET_BOOKMARKS,
+ PARSE_BOOKMARKS,
+ DONE
+ };
+
+ typedef std::vector<std::wstring> BookmarkFolderType;
+
+ // URLs for connecting to the toolbar front end are defined below.
+ static const char kT5AuthorizationTokenUrl[];
+ static const char kT5FrontEndUrlTemplate[];
+
+ // Token replacement tags are defined below.
+ static const char kRandomNumberToken[];
+ static const char kAuthorizationToken[];
+ static const char kAuthorizationTokenPrefix[];
+ static const char kAuthorizationTokenSuffix[];
+ static const char kMaxNumToken[];
+ static const char kMaxTimestampToken[];
+
+ // XML tag names are defined below.
+ static const char kXmlApiReplyXmlTag[];
+ static const char kBookmarksXmlTag[];
+ static const char kBookmarkXmlTag[];
+ static const char kTitleXmlTag[];
+ static const char kUrlXmlTag[];
+ static const char kTimestampXmlTag[];
+ static const char kLabelsXmlTag[];
+ static const char kLabelsXmlCloseTag[];
+ static const char kLabelXmlTag[];
+ static const char kAttributesXmlTag[];
+
+ // Flow control for asynchronous import is controlled by the methods below.
+ // ContinueImport is called back by each import action taken. BeginXXX
+ // and EndXXX are responsible for updating the state of the asynchronous
+ // import. EndImport is responsible for state cleanup and notifying the
+ // caller that import has completed.
+ void ContinueImport();
+ void EndImport();
+ void BeginImportBookmarks();
+ void EndImportBookmarks();
+
+ // Network I/O is done by the methods below. These three methods are called
+ // in the order provided. The last two are called back with the HTML
+ // response provided by the Toolbar server.
+ void GetAuthenticationFromServer();
+ void GetBookmarkDataFromServer(const std::string& response);
+ void GetBookmarksFromServerDataResponse(const std::string& response);
+
+ // XML Parsing is implemented with the methods below.
+ bool ParseAuthenticationTokenResponse(const std::string& response,
+ std::string* token);
+
+ static bool ParseBookmarksFromReader(
+ XmlReader* reader,
+ std::vector<ProfileWriter::BookmarkEntry>* bookmarks,
+ const string16& bookmark_group_string);
+
+ static bool LocateNextOpenTag(XmlReader* reader);
+ static bool LocateNextTagByName(XmlReader* reader, const std::string& tag);
+ static bool LocateNextTagWithStopByName(
+ XmlReader* reader,
+ const std::string& tag,
+ const std::string& stop);
+
+ static bool ExtractBookmarkInformation(
+ XmlReader* reader,
+ ProfileWriter::BookmarkEntry* bookmark_entry,
+ std::vector<BookmarkFolderType>* bookmark_folders,
+ const string16& bookmark_group_string);
+ static bool ExtractNamedValueFromXmlReader(XmlReader* reader,
+ const std::string& name,
+ std::string* buffer);
+ static bool ExtractTitleFromXmlReader(XmlReader* reader,
+ ProfileWriter::BookmarkEntry* entry);
+ static bool ExtractUrlFromXmlReader(XmlReader* reader,
+ ProfileWriter::BookmarkEntry* entry);
+ static bool ExtractTimeFromXmlReader(XmlReader* reader,
+ ProfileWriter::BookmarkEntry* entry);
+ static bool ExtractFoldersFromXmlReader(
+ XmlReader* reader,
+ std::vector<BookmarkFolderType>* bookmark_folders,
+ const string16& bookmark_group_string);
+
+ // Bookmark creation is done by the method below.
+ void AddBookmarksToChrome(
+ const std::vector<ProfileWriter::BookmarkEntry>& bookmarks);
+
+ // Internal state is stored in state_.
+ InternalStateEnum state_;
+
+ // Bitmask of Importer::ImportItem is stored in items_to_import_.
+ uint16 items_to_import_;
+
+ // The fetchers need to be available to cancel the network call on user cancel
+ // hence they are stored as member variables.
+ URLFetcher* token_fetcher_;
+ URLFetcher* data_fetcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(Toolbar5Importer);
+};
+
+#endif // CHROME_BROWSER_IMPORTER_TOOLBAR_IMPORTER_H_
diff --git a/chrome/browser/importer/toolbar_importer_unittest.cc b/chrome/browser/importer/toolbar_importer_unittest.cc
new file mode 100644
index 0000000..0bc6ab5
--- /dev/null
+++ b/chrome/browser/importer/toolbar_importer_unittest.cc
@@ -0,0 +1,482 @@
+// 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 <string>
+#include <vector>
+
+#include "base/string16.h"
+#include "chrome/browser/first_run.h"
+#include "chrome/browser/importer/importer.h"
+#include "chrome/browser/importer/toolbar_importer.h"
+#include "chrome/common/libxml_utils.h"
+#include "googleurl/src/gurl.h"
+
+// See http://crbug.com/11838
+TEST(Toolbar5ImporterTest, BookmarkParse) {
+static const wchar_t* kTitle = L"MyTitle";
+static const char* kUrl = "http://www.google.com/";
+static const wchar_t* kFolder = L"Google";
+static const wchar_t* kFolder2 = L"Homepage";
+static const wchar_t* kFolderArray[3] = {L"Google", L"Search", L"Page"};
+
+static const wchar_t* kOtherTitle = L"MyOtherTitle";
+static const char* kOtherUrl = "http://www.google.com/mail";
+static const wchar_t* kOtherFolder = L"Mail";
+
+static const string16 kBookmarkGroupTitle = ASCIIToUTF16("BookmarkGroupTitle");
+
+// Since the following is very dense to read I enumerate the test cases here.
+// 1. Correct bookmark structure with one label.
+// 2. Correct bookmark structure with no labels.
+// 3. Correct bookmark structure with two labels.
+// 4. Correct bookmark structure with a folder->label translation by toolbar.
+// 5. Correct bookmark structure with no favicon.
+// 6. Two correct bookmarks.
+// The following are error cases by removing sections from the xml:
+// 7. Empty string passed as xml.
+// 8. No <bookmarks> section in the xml.
+// 9. No <bookmark> section below the <bookmarks> section.
+// 10. No <title> in a <bookmark> section.
+// 11. No <url> in a <bookmark> section.
+// 12. No <timestamp> in a <bookmark> section.
+// 13. No <labels> in a <bookmark> section.
+static const char* kGoodBookmark =
+ "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>"
+ " <bookmark> "
+ "<title>MyTitle</title> "
+ "<url>http://www.google.com/</url> "
+ "<timestamp>1153328691085181</timestamp> "
+ "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> "
+ "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>"
+ "<labels> <label>Google</label> </labels> "
+ "<attributes> "
+ "<attribute> "
+ "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> "
+ "</attribute> "
+ "<attribute> "
+ "<name>favicon_timestamp</name> <value>1153328653</value> "
+ "</attribute> "
+ "<attribute> <name>notebook_name</name> <value>My notebook 0</value> "
+ "</attribute> "
+ "<attribute> <name>section_name</name> <value>My section 0 "
+ "</value> </attribute> </attributes> "
+ "</bookmark> </bookmarks>";
+static const char* kGoodBookmarkNoLabel =
+ "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>"
+ " <bookmark> "
+ "<title>MyTitle</title> "
+ "<url>http://www.google.com/</url> "
+ "<timestamp>1153328691085181</timestamp> "
+ "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> "
+ "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>"
+ "<labels> </labels> "
+ "<attributes> "
+ "<attribute> "
+ "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> "
+ "</attribute> "
+ "<attribute> "
+ "<name>favicon_timestamp</name> <value>1153328653</value> "
+ "</attribute> "
+ "<attribute> <name>notebook_name</name> <value>My notebook 0</value> "
+ "</attribute> "
+ "<attribute> <name>section_name</name> <value>My section 0 "
+ "</value> </attribute> </attributes> "
+ "</bookmark> </bookmarks>";
+static const char* kGoodBookmarkTwoLabels =
+ "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>"
+ " <bookmark> "
+ "<title>MyTitle</title> "
+ "<url>http://www.google.com/</url> "
+ "<timestamp>1153328691085181</timestamp> "
+ "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> "
+ "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>"
+ "<labels> <label>Google</label> <label>Homepage</label> </labels> "
+ "<attributes> "
+ "<attribute> "
+ "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> "
+ "</attribute> "
+ "<attribute> "
+ "<name>favicon_timestamp</name> <value>1153328653</value> "
+ "</attribute> "
+ "<attribute> <name>notebook_name</name> <value>My notebook 0</value> "
+ "</attribute> "
+ "<attribute> <name>section_name</name> <value>My section 0 "
+ "</value> </attribute> </attributes> "
+ "</bookmark> </bookmarks>";
+static const char* kGoodBookmarkFolderLabel =
+ "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>"
+ " <bookmark> "
+ "<title>MyTitle</title> "
+ "<url>http://www.google.com/</url> "
+ "<timestamp>1153328691085181</timestamp> "
+ "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> "
+ "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>"
+ "<labels> <label>Google:Search:Page</label> </labels> "
+ "<attributes> "
+ "<attribute> "
+ "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> "
+ "</attribute> "
+ "<attribute> "
+ "<name>favicon_timestamp</name> <value>1153328653</value> "
+ "</attribute> "
+ "<attribute> <name>notebook_name</name> <value>My notebook 0</value> "
+ "</attribute> "
+ "<attribute> <name>section_name</name> <value>My section 0 "
+ "</value> </attribute> </attributes> "
+ "</bookmark> </bookmarks>";
+static const char* kGoodBookmarkNoFavicon =
+ "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>"
+ " <bookmark> "
+ "<title>MyTitle</title> "
+ "<url>http://www.google.com/</url> "
+ "<timestamp>1153328691085181</timestamp> "
+ "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> "
+ "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>"
+ "<labels> <label>Google</label> </labels> "
+ "<attributes> "
+ "<attribute> "
+ "<name>favicon_timestamp</name> <value>1153328653</value> "
+ "</attribute> "
+ "<attribute> <name>notebook_name</name> <value>My notebook 0</value> "
+ "</attribute> "
+ "<attribute> <name>section_name</name> <value>My section 0 "
+ "</value> </attribute> </attributes> "
+ "</bookmark> </bookmarks>";
+static const char* kGoodBookmark2Items =
+ "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>"
+ " <bookmark> "
+ "<title>MyTitle</title> "
+ "<url>http://www.google.com/</url> "
+ "<timestamp>1153328691085181</timestamp> "
+ "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> "
+ "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>"
+ "<labels> <label>Google</label> </labels> "
+ "<attributes> "
+ "<attribute> "
+ "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> "
+ "</attribute> "
+ "<attribute> "
+ "<name>favicon_timestamp</name> <value>1153328653</value> "
+ "</attribute> "
+ "<attribute> <name>notebook_name</name> <value>My notebook 0</value> "
+ "</attribute> "
+ "<attribute> <name>section_name</name> <value>My section 0 "
+ "</value> </attribute> </attributes> "
+ "</bookmark>"
+ " <bookmark> "
+ "<title>MyOtherTitle</title> "
+ "<url>http://www.google.com/mail</url> "
+ "<timestamp>1153328691085181</timestamp> "
+ "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> "
+ "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>"
+ "<labels> <label>Mail</label> </labels> "
+ "<attributes> "
+ "<attribute> "
+ "<name>favicon_url</name>"
+ "<value>http://www.google.com/mail/favicon.ico</value> "
+ "</attribute> "
+ "<attribute> "
+ "<name>favicon_timestamp</name> <value>1253328653</value> "
+ "</attribute> "
+ "<attribute> <name>notebook_name</name> <value>My notebook 0</value> "
+ "</attribute> "
+ "<attribute> <name>section_name</name> <value>My section 0 "
+ "</value> </attribute> </attributes> "
+ "</bookmark>"
+ "</bookmarks>";
+static const char* kEmptyString = "";
+static const char* kBadBookmarkNoBookmarks =
+ " <bookmark> "
+ "<title>MyTitle</title> "
+ "<url>http://www.google.com/</url> "
+ "<timestamp>1153328691085181</timestamp> "
+ "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> "
+ "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>"
+ "<labels> <label>Google</label> </labels> "
+ "<attributes> "
+ "<attribute> "
+ "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> "
+ "</attribute> "
+ "<attribute> "
+ "<name>favicon_timestamp</name> <value>1153328653</value> "
+ "</attribute> "
+ "<attribute> <name>notebook_name</name> <value>My notebook 0</value> "
+ "</attribute> "
+ "<attribute> <name>section_name</name> <value>My section 0 "
+ "</value> </attribute> </attributes> "
+ "</bookmark> </bookmarks>";
+static const char* kBadBookmarkNoBookmark =
+ "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>"
+ "<title>MyTitle</title> "
+ "<url>http://www.google.com/</url> "
+ "<timestamp>1153328691085181</timestamp> "
+ "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> "
+ "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>"
+ "<labels> <label>Google</label> </labels> "
+ "<attributes> "
+ "<attribute> "
+ "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> "
+ "</attribute> "
+ "<attribute> "
+ "<name>favicon_timestamp</name> <value>1153328653</value> "
+ "</attribute> "
+ "<attribute> <name>notebook_name</name> <value>My notebook 0</value> "
+ "</attribute> "
+ "<attribute> <name>section_name</name> <value>My section 0 "
+ "</value> </attribute> </attributes> "
+ "</bookmark> </bookmarks>";
+static const char* kBadBookmarkNoTitle =
+ "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>"
+ " <bookmark> "
+ "<url>http://www.google.com/</url> "
+ "<timestamp>1153328691085181</timestamp> "
+ "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> "
+ "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>"
+ "<labels> <label>Google</label> </labels> "
+ "<attributes> "
+ "<attribute> "
+ "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> "
+ "</attribute> "
+ "<attribute> "
+ "<name>favicon_timestamp</name> <value>1153328653</value> "
+ "</attribute> "
+ "<attribute> <name>notebook_name</name> <value>My notebook 0</value> "
+ "</attribute> "
+ "<attribute> <name>section_name</name> <value>My section 0 "
+ "</value> </attribute> </attributes> "
+ "</bookmark> </bookmarks>";
+static const char* kBadBookmarkNoUrl =
+ "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>"
+ " <bookmark> "
+ "<title>MyTitle</title> "
+ "<timestamp>1153328691085181</timestamp> "
+ "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> "
+ "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>"
+ "<labels> <label>Google</label> </labels> "
+ "<attributes> "
+ "<attribute> "
+ "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> "
+ "</attribute> "
+ "<attribute> "
+ "<name>favicon_timestamp</name> <value>1153328653</value> "
+ "</attribute> "
+ "<attribute> <name>notebook_name</name> <value>My notebook 0</value> "
+ "</attribute> "
+ "<attribute> <name>section_name</name> <value>My section 0 "
+ "</value> </attribute> </attributes> "
+ "</bookmark> </bookmarks>";
+static const char* kBadBookmarkNoTimestamp =
+ "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>"
+ " <bookmark> "
+ "<title>MyTitle</title> "
+ "<url>http://www.google.com/</url> "
+ "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> "
+ "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>"
+ "<labels> <label>Google</label> </labels> "
+ "<attributes> "
+ "<attribute> "
+ "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> "
+ "</attribute> "
+ "<attribute> "
+ "<name>favicon_timestamp</name> <value>1153328653</value> "
+ "</attribute> "
+ "<attribute> <name>notebook_name</name> <value>My notebook 0</value> "
+ "</attribute> "
+ "<attribute> <name>section_name</name> <value>My section 0 "
+ "</value> </attribute> </attributes> "
+ "</bookmark> </bookmarks>";
+static const char* kBadBookmarkNoLabels =
+ "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>"
+ " <bookmark> "
+ "<title>MyTitle</title> "
+ "<url>http://www.google.com/</url> "
+ "<timestamp>1153328691085181</timestamp> "
+ "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> "
+ "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>"
+ "<attributes> "
+ "<attribute> "
+ "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> "
+ "</attribute> "
+ "<attribute> "
+ "<name>favicon_timestamp</name> <value>1153328653</value> "
+ "</attribute> "
+ "<attribute> <name>notebook_name</name> <value>My notebook 0</value> "
+ "</attribute> "
+ "<attribute> <name>section_name</name> <value>My section 0 "
+ "</value> </attribute> </attributes> "
+ "</bookmark> </bookmarks>";
+
+ XmlReader reader;
+ std::string bookmark_xml;
+ std::vector<ProfileWriter::BookmarkEntry> bookmarks;
+
+ const GURL url(kUrl);
+ const GURL other_url(kOtherUrl);
+
+ // Test doesn't work if the importer thinks this is the first run of Chromium.
+ // Mark this as a subsequent run of the browser.
+ FirstRun::CreateSentinel();
+
+ // Test case 1 is parsing a basic bookmark with a single label.
+ bookmark_xml = kGoodBookmark;
+ bookmarks.clear();
+ XmlReader reader1;
+ EXPECT_TRUE(reader1.Load(bookmark_xml));
+ EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader1, &bookmarks,
+ kBookmarkGroupTitle));
+
+ ASSERT_EQ(1U, bookmarks.size());
+ EXPECT_FALSE(bookmarks[0].in_toolbar);
+ EXPECT_EQ(kTitle, bookmarks[0].title);
+ EXPECT_EQ(url, bookmarks[0].url);
+ ASSERT_EQ(2U, bookmarks[0].path.size());
+ EXPECT_EQ(kFolder, bookmarks[0].path[1]);
+
+ // Test case 2 is parsing a single bookmark with no label.
+ bookmark_xml = kGoodBookmarkNoLabel;
+ bookmarks.clear();
+ XmlReader reader2;
+ EXPECT_TRUE(reader2.Load(bookmark_xml));
+ EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader2, &bookmarks,
+ kBookmarkGroupTitle));
+
+ ASSERT_EQ(1U, bookmarks.size());
+ EXPECT_FALSE(bookmarks[0].in_toolbar);
+ EXPECT_EQ(kTitle, bookmarks[0].title);
+ EXPECT_EQ(url, bookmarks[0].url);
+ EXPECT_EQ(1U, bookmarks[0].path.size());
+
+ // Test case 3 is parsing a single bookmark with two labels.
+ bookmark_xml = kGoodBookmarkTwoLabels;
+ bookmarks.clear();
+ XmlReader reader3;
+ EXPECT_TRUE(reader3.Load(bookmark_xml));
+ EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader3, &bookmarks,
+ kBookmarkGroupTitle));
+
+ ASSERT_EQ(2U, bookmarks.size());
+ EXPECT_FALSE(bookmarks[0].in_toolbar);
+ EXPECT_FALSE(bookmarks[1].in_toolbar);
+ EXPECT_EQ(kTitle, bookmarks[0].title);
+ EXPECT_EQ(kTitle, bookmarks[1].title);
+ EXPECT_EQ(url, bookmarks[0].url);
+ EXPECT_EQ(url, bookmarks[1].url);
+ ASSERT_EQ(2U, bookmarks[0].path.size());
+ EXPECT_EQ(kFolder, bookmarks[0].path[1]);
+ ASSERT_EQ(2U, bookmarks[1].path.size());
+ EXPECT_EQ(kFolder2, bookmarks[1].path[1]);
+
+ // Test case 4 is parsing a single bookmark which has a label with a colon,
+ // this test file name translation between Toolbar and Chrome.
+ bookmark_xml = kGoodBookmarkFolderLabel;
+ bookmarks.clear();
+ XmlReader reader4;
+ EXPECT_TRUE(reader4.Load(bookmark_xml));
+ EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader4, &bookmarks,
+ kBookmarkGroupTitle));
+
+ ASSERT_EQ(1U, bookmarks.size());
+ EXPECT_FALSE(bookmarks[0].in_toolbar);
+ EXPECT_EQ(kTitle, bookmarks[0].title);
+ EXPECT_EQ(url, bookmarks[0].url);
+ ASSERT_EQ(4U, bookmarks[0].path.size());
+ EXPECT_EQ(std::wstring(kFolderArray[0]),
+ bookmarks[0].path[1]);
+ EXPECT_EQ(std::wstring(kFolderArray[1]),
+ bookmarks[0].path[2]);
+ EXPECT_EQ(std::wstring(kFolderArray[2]),
+ bookmarks[0].path[3]);
+
+ // Test case 5 is parsing a single bookmark without a favicon URL.
+ bookmark_xml = kGoodBookmarkNoFavicon;
+ bookmarks.clear();
+ XmlReader reader5;
+ EXPECT_TRUE(reader5.Load(bookmark_xml));
+ EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader5, &bookmarks,
+ kBookmarkGroupTitle));
+
+ ASSERT_EQ(1U, bookmarks.size());
+ EXPECT_FALSE(bookmarks[0].in_toolbar);
+ EXPECT_EQ(kTitle, bookmarks[0].title);
+ EXPECT_EQ(url, bookmarks[0].url);
+ ASSERT_EQ(2U, bookmarks[0].path.size());
+ EXPECT_EQ(kFolder, bookmarks[0].path[1]);
+
+ // Test case 6 is parsing two bookmarks.
+ bookmark_xml = kGoodBookmark2Items;
+ bookmarks.clear();
+ XmlReader reader6;
+ EXPECT_TRUE(reader6.Load(bookmark_xml));
+ EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader6, &bookmarks,
+ kBookmarkGroupTitle));
+
+ ASSERT_EQ(2U, bookmarks.size());
+ EXPECT_FALSE(bookmarks[0].in_toolbar);
+ EXPECT_FALSE(bookmarks[1].in_toolbar);
+ EXPECT_EQ(kTitle, bookmarks[0].title);
+ EXPECT_EQ(kOtherTitle, bookmarks[1].title);
+ EXPECT_EQ(url, bookmarks[0].url);
+ EXPECT_EQ(other_url, bookmarks[1].url);
+ ASSERT_EQ(2U, bookmarks[0].path.size());
+ EXPECT_EQ(kFolder, bookmarks[0].path[1]);
+ ASSERT_EQ(2U, bookmarks[1].path.size());
+ EXPECT_EQ(kOtherFolder, bookmarks[1].path[1]);
+
+ // Test case 7 is parsing an empty string for bookmarks.
+ bookmark_xml = kEmptyString;
+ bookmarks.clear();
+ XmlReader reader7;
+ EXPECT_FALSE(reader7.Load(bookmark_xml));
+
+ // Test case 8 is testing the error when no <bookmarks> section is present.
+ bookmark_xml = kBadBookmarkNoBookmarks;
+ bookmarks.clear();
+ XmlReader reader8;
+ EXPECT_TRUE(reader8.Load(bookmark_xml));
+ EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader8,
+ &bookmarks, kBookmarkGroupTitle));
+
+ // Test case 9 tests when no <bookmark> section is present.
+ bookmark_xml = kBadBookmarkNoBookmark;
+ bookmarks.clear();
+ XmlReader reader9;
+ EXPECT_TRUE(reader9.Load(bookmark_xml));
+ EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader9,
+ &bookmarks, kBookmarkGroupTitle));
+
+
+ // Test case 10 tests when a bookmark has no <title> section.
+ bookmark_xml = kBadBookmarkNoTitle;
+ bookmarks.clear();
+ XmlReader reader10;
+ EXPECT_TRUE(reader10.Load(bookmark_xml));
+ EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader10,
+ &bookmarks, kBookmarkGroupTitle));
+
+ // Test case 11 tests when a bookmark has no <url> section.
+ bookmark_xml = kBadBookmarkNoUrl;
+ bookmarks.clear();
+ XmlReader reader11;
+ EXPECT_TRUE(reader11.Load(bookmark_xml));
+ EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader11,
+ &bookmarks, kBookmarkGroupTitle));
+
+ // Test case 12 tests when a bookmark has no <timestamp> section.
+ bookmark_xml = kBadBookmarkNoTimestamp;
+ bookmarks.clear();
+ XmlReader reader12;
+ EXPECT_TRUE(reader12.Load(bookmark_xml));
+ EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader12,
+ &bookmarks, kBookmarkGroupTitle));
+
+ // Test case 13 tests when a bookmark has no <labels> section.
+ bookmark_xml = kBadBookmarkNoLabels;
+ bookmarks.clear();
+ XmlReader reader13;
+ EXPECT_TRUE(reader13.Load(bookmark_xml));
+ EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader13,
+ &bookmarks, kBookmarkGroupTitle));
+}