From 6c9851a40d3f6280dc322c2392d9cfcf8fba1b2d Mon Sep 17 00:00:00 2001 From: "beng@google.com" Date: Sat, 13 Sep 2008 00:56:27 +0000 Subject: Move importer files into an importer subdirectory. Also delete title chomper no one uses it. B=2205 Review URL: http://codereview.chromium.org/3035 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@2154 0039d316-1c4b-4281-b951-d872f2087c98 --- chrome/browser/importer/firefox2_importer.cc | 515 +++++++++++++ chrome/browser/importer/firefox2_importer.h | 112 +++ chrome/browser/importer/firefox3_importer.cc | 502 +++++++++++++ chrome/browser/importer/firefox3_importer.h | 85 +++ .../browser/importer/firefox_importer_unittest.cc | 216 ++++++ chrome/browser/importer/firefox_importer_utils.cc | 727 ++++++++++++++++++ chrome/browser/importer/firefox_importer_utils.h | 211 ++++++ chrome/browser/importer/firefox_profile_lock.cc | 87 +++ chrome/browser/importer/firefox_profile_lock.h | 94 +++ chrome/browser/importer/ie_importer.cc | 567 ++++++++++++++ chrome/browser/importer/ie_importer.h | 89 +++ chrome/browser/importer/importer.cc | 541 ++++++++++++++ chrome/browser/importer/importer.h | 357 +++++++++ chrome/browser/importer/importer_unittest.cc | 819 +++++++++++++++++++++ chrome/browser/importer/mork_reader.cc | 581 +++++++++++++++ chrome/browser/importer/mork_reader.h | 165 +++++ 16 files changed, 5668 insertions(+) create mode 100644 chrome/browser/importer/firefox2_importer.cc create mode 100644 chrome/browser/importer/firefox2_importer.h create mode 100644 chrome/browser/importer/firefox3_importer.cc create mode 100644 chrome/browser/importer/firefox3_importer.h create mode 100644 chrome/browser/importer/firefox_importer_unittest.cc create mode 100644 chrome/browser/importer/firefox_importer_utils.cc create mode 100644 chrome/browser/importer/firefox_importer_utils.h create mode 100644 chrome/browser/importer/firefox_profile_lock.cc create mode 100644 chrome/browser/importer/firefox_profile_lock.h create mode 100644 chrome/browser/importer/ie_importer.cc create mode 100644 chrome/browser/importer/ie_importer.h create mode 100644 chrome/browser/importer/importer.cc create mode 100644 chrome/browser/importer/importer.h create mode 100644 chrome/browser/importer/importer_unittest.cc create mode 100644 chrome/browser/importer/mork_reader.cc create mode 100644 chrome/browser/importer/mork_reader.h (limited to 'chrome/browser/importer') diff --git a/chrome/browser/importer/firefox2_importer.cc b/chrome/browser/importer/firefox2_importer.cc new file mode 100644 index 0000000..59f1778 --- /dev/null +++ b/chrome/browser/importer/firefox2_importer.cc @@ -0,0 +1,515 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/firefox2_importer.h" + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/registry.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/importer/firefox_importer_utils.h" +#include "chrome/browser/importer/mork_reader.h" +#include "chrome/browser/template_url.h" +#include "chrome/browser/template_url_parser.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/time_format.h" +#include "generated_resources.h" +#include "net/base/data_url.h" + +// Firefox2Importer. + +Firefox2Importer::Firefox2Importer() { +} + +Firefox2Importer::~Firefox2Importer() { +} + +void Firefox2Importer::StartImport(ProfileInfo profile_info, + uint16 items, ProfileWriter* writer, + ImporterHost* host) { + writer_ = writer; + source_path_ = profile_info.source_path; + app_path_ = profile_info.app_path; + importer_host_ = host; + + // The order here is important! + NotifyStarted(); + if ((items & HOME_PAGE) && !cancelled()) + ImportHomepage(); // Doesn't have a UI item. + if ((items & FAVORITES) && !cancelled()) { + NotifyItemStarted(FAVORITES); + ImportBookmarks(); + NotifyItemEnded(FAVORITES); + } + if ((items & SEARCH_ENGINES) && !cancelled()) { + NotifyItemStarted(SEARCH_ENGINES); + ImportSearchEngines(); + NotifyItemEnded(SEARCH_ENGINES); + } + if ((items & PASSWORDS) && !cancelled()) { + NotifyItemStarted(PASSWORDS); + ImportPasswords(); + NotifyItemEnded(PASSWORDS); + } + if ((items & HISTORY) && !cancelled()) { + NotifyItemStarted(HISTORY); + ImportHistory(); + NotifyItemEnded(HISTORY); + } + NotifyEnded(); +} + +// static +void Firefox2Importer::LoadDefaultBookmarks(const std::wstring& app_path, + std::set *urls) { + // Firefox keeps its default bookmarks in a bookmarks.html file that + // lives at: \defaults\profile\bookmarks.html + std::wstring file = app_path; + file_util::AppendToPath(&file, L"defaults\\profile\\bookmarks.html"); + + urls->clear(); + + // Read the whole file. + std::string content; + file_util::ReadFileToString(file, &content); + std::vector lines; + SplitString(content, '\n', &lines); + + std::string charset; + for (size_t i = 0; i < lines.size(); ++i) { + std::string line; + TrimString(lines[i], " ", &line); + + // Get the encoding of the bookmark file. + if (ParseCharsetFromLine(line, &charset)) + continue; + + // Get the bookmark. + std::wstring title; + GURL url, favicon; + std::wstring shortcut; + Time add_date; + std::wstring post_data; + if (ParseBookmarkFromLine(line, charset, &title, &url, + &favicon, &shortcut, &add_date, + &post_data)) + urls->insert(url); + } +} + +// static +TemplateURL* Firefox2Importer::CreateTemplateURL(const std::wstring& title, + const std::wstring& keyword, + const GURL& url) { + // Skip if the keyword or url is invalid. + if (keyword.empty() && url.is_valid()) + return NULL; + + TemplateURL* t_url = new TemplateURL(); + // We set short name by using the title if it exists. + // Otherwise, we use the shortcut. + t_url->set_short_name(!title.empty() ? title : keyword); + t_url->set_keyword(keyword); + t_url->SetURL(TemplateURLRef::DisplayURLToURLRef(UTF8ToWide(url.spec())), + 0, 0); + return t_url; +} + +void Firefox2Importer::ImportBookmarks() { + // Read the whole file. + std::wstring file = source_path_; + file_util::AppendToPath(&file, L"bookmarks.html"); + std::string content; + file_util::ReadFileToString(file, &content); + std::vector lines; + SplitString(content, '\n', &lines); + + // Load the default bookmarks. + std::set default_urls; + LoadDefaultBookmarks(app_path_, &default_urls); + + // Parse the bookmarks.html file. + std::vector bookmarks, toolbar_bookmarks; + std::vector template_urls; + std::vector favicons; + std::wstring last_folder + = l10n_util::GetString(IDS_BOOKMARK_GROUP_FROM_FIREFOX); + bool last_folder_on_toolbar = false; + std::vector path; + size_t toolbar_folder = 0; + std::string charset; + for (size_t i = 0; i < lines.size() && !cancelled(); ++i) { + std::string line; + TrimString(lines[i], " ", &line); + + // Get the encoding of the bookmark file. + if (ParseCharsetFromLine(line, &charset)) + continue; + + // Get the folder name. + if (ParseFolderNameFromLine(line, charset, &last_folder, + &last_folder_on_toolbar)) + continue; + + // Get the bookmark entry. + std::wstring title, shortcut; + GURL url, favicon; + Time add_date; + std::wstring post_data; + // TODO(jcampan): http://b/issue?id=1196285 we do not support POST based + // keywords yet. + if (ParseBookmarkFromLine(line, charset, &title, + &url, &favicon, &shortcut, &add_date, + &post_data) && + post_data.empty() && + CanImportURL(GURL(url)) && + default_urls.find(url) == default_urls.end()) { + if (toolbar_folder > path.size() && path.size() > 0) { + NOTREACHED(); // error in parsing. + break; + } + + ProfileWriter::BookmarkEntry entry; + entry.creation_time = add_date; + entry.url = url; + entry.title = title; + + if (first_run() && toolbar_folder) { + // Flatten the items in toolbar. + entry.in_toolbar = true; + entry.path.assign(path.begin() + toolbar_folder, path.end()); + toolbar_bookmarks.push_back(entry); + } else { + // Insert the item into the "Imported from Firefox" folder after + // the first run. + entry.path.assign(path.begin(), path.end()); + if (first_run()) + entry.path.erase(entry.path.begin()); + bookmarks.push_back(entry); + } + + // Save the favicon. DataURLToFaviconUsage will handle the case where + // there is no favicon. + DataURLToFaviconUsage(url, favicon, &favicons); + + // If there is a SHORTCUT attribute for this bookmark, we + // add it as our keywords. + TemplateURL* t_url = CreateTemplateURL(title, shortcut, url); + if (t_url) + template_urls.push_back(t_url); + + continue; + } + + // Bookmarks in sub-folder are encapsulated with
tag. + if (StartsWithASCII(line, "
", true)) { + path.push_back(last_folder); + last_folder.clear(); + if (last_folder_on_toolbar && !toolbar_folder) + toolbar_folder = path.size(); + } else if (StartsWithASCII(line, "
", true)) { + if (path.empty()) + break; // Mismatch
. + path.pop_back(); + if (toolbar_folder > path.size()) + toolbar_folder = 0; + } + } + + // Write data into profile. + bookmarks.insert(bookmarks.begin(), toolbar_bookmarks.begin(), + toolbar_bookmarks.end()); + if (!bookmarks.empty() && !cancelled()) { + main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, + &ProfileWriter::AddBookmarkEntry, bookmarks)); + } + if (!template_urls.empty() && !cancelled()) { + main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, + &ProfileWriter::AddKeywords, template_urls, -1, false)); + } else { + STLDeleteContainerPointers(template_urls.begin(), template_urls.end()); + } + if (!favicons.empty()) { + main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, + &ProfileWriter::AddFavicons, favicons)); + } +} + +void Firefox2Importer::ImportPasswords() { + // Initializes NSS3. + NSSDecryptor decryptor; + if (!decryptor.Init(source_path_, source_path_) && + !decryptor.Init(app_path_, source_path_)) + return; + + // Firefox 2 uses signons2.txt to store the pssswords. If it doesn't + // exist, we try to find its older version. + std::wstring file = source_path_; + file_util::AppendToPath(&file, L"signons2.txt"); + if (!file_util::PathExists(file)) { + file = source_path_; + file_util::AppendToPath(&file, L"signons.txt"); + } + + std::string content; + file_util::ReadFileToString(file, &content); + std::vector forms; + decryptor.ParseSignons(content, &forms); + + if (!cancelled()) { + for (size_t i = 0; i < forms.size(); ++i) { + main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, + &ProfileWriter::AddPasswordForm, forms[i])); + } + } +} + +void Firefox2Importer::ImportHistory() { + std::wstring file = source_path_; + file_util::AppendToPath(&file, L"history.dat"); + ImportHistoryFromFirefox2(file, main_loop_, writer_); +} + +void Firefox2Importer::ImportSearchEngines() { + std::vector files; + GetSearchEnginesXMLFiles(&files); + + std::vector search_engines; + ParseSearchEnginesFromXMLFiles(files, &search_engines); + main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, + &ProfileWriter::AddKeywords, search_engines, + GetFirefoxDefaultSearchEngineIndex(search_engines, source_path_), + true)); +} + +void Firefox2Importer::ImportHomepage() { + GURL homepage = GetHomepage(source_path_); + if (homepage.is_valid() && !IsDefaultHomepage(homepage, app_path_)) { + main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, + &ProfileWriter::AddHomepage, homepage)); + } +} + +void Firefox2Importer::GetSearchEnginesXMLFiles( + std::vector* files) { + // Search engines are contained in XML files in a searchplugins directory that + // can be found in 2 locations: + // - Firefox install dir (default search engines) + // - the profile dir (user added search engines) + std::wstring dir(app_path_); + file_util::AppendToPath(&dir, L"searchplugins"); + FindXMLFilesInDir(dir, files); + + std::wstring profile_dir = source_path_; + file_util::AppendToPath(&profile_dir, L"searchplugins"); + FindXMLFilesInDir(profile_dir, files); +} + +// static +bool Firefox2Importer::ParseCharsetFromLine(const std::string& line, + std::string* charset) { + const char kCharset[] = "charset="; + if (StartsWithASCII(line, "', end) + 1; + // If no end tag or start tag is broken, we skip to find the folder name. + if (end == std::string::npos || tag_end < arraysize(kFolderOpen)) + return false; + + CodepageToWide(line.substr(tag_end, end - tag_end), charset.c_str(), + OnStringUtilConversionError::SKIP, folder_name); + HTMLUnescape(folder_name); + + std::string attribute_list = line.substr(arraysize(kFolderOpen), + tag_end - arraysize(kFolderOpen) - 1); + std::string value; + if (GetAttribute(attribute_list, kToolbarFolderAttribute, &value) && + LowerCaseEqualsASCII(value, "true")) + *is_toolbar_folder = true; + else + *is_toolbar_folder = false; + + return true; +} + +// static +bool Firefox2Importer::ParseBookmarkFromLine(const std::string& line, + const std::string& charset, + std::wstring* title, + GURL* url, + GURL* favicon, + std::wstring* shortcut, + Time* add_date, + std::wstring* post_data) { + const char kItemOpen[] = "
clear(); + *url = GURL(); + *favicon = GURL(); + shortcut->clear(); + post_data->clear(); + *add_date = Time(); + + if (!StartsWithASCII(line, kItemOpen, true)) + return false; + + size_t end = line.find(kItemClose); + size_t tag_end = line.rfind('>', end) + 1; + if (end == std::string::npos || tag_end < arraysize(kItemOpen)) + return false; // No end tag or start tag is broken. + + std::string attribute_list = line.substr(arraysize(kItemOpen), + tag_end - arraysize(kItemOpen) - 1); + + // We don't import Live Bookmark folders, which is Firefox's RSS reading + // feature, since the user never necessarily bookmarked them and we don't + // have this feature to update their contents. + std::string value; + if (GetAttribute(attribute_list, kFeedURLAttribute, &value)) + return false; + + // Title + CodepageToWide(line.substr(tag_end, end - tag_end), charset.c_str(), + OnStringUtilConversionError::SKIP, title); + HTMLUnescape(title); + + // URL + if (GetAttribute(attribute_list, kHrefAttribute, &value)) { + ReplaceSubstringsAfterOffset(&value, 0, "%22", "\""); + *url = GURL(value); + } + + // Favicon + if (GetAttribute(attribute_list, kIconAttribute, &value)) + *favicon = GURL(value); + + // Keyword + if (GetAttribute(attribute_list, kShortcutURLAttribute, &value)) { + CodepageToWide(value, charset.c_str(), OnStringUtilConversionError::SKIP, + shortcut); + HTMLUnescape(shortcut); + } + + // Add date + if (GetAttribute(attribute_list, kAddDateAttribute, &value)) { + int64 time = StringToInt64(value); + // Upper bound it at 32 bits. + if (0 < time && time < (1LL << 32)) + *add_date = Time::FromTimeT(time); + } + + // Post data. + if (GetAttribute(attribute_list, kPostDataAttribute, &value)) { + CodepageToWide(value, charset.c_str(), + OnStringUtilConversionError::SKIP, post_data); + HTMLUnescape(post_data); + } + + return true; +} + +// static +bool Firefox2Importer::GetAttribute(const std::string& attribute_list, + const std::string& attribute, + std::string* value) { + const char kQuote[] = "\""; + + size_t begin = attribute_list.find(attribute + "=" + kQuote); + if (begin == std::string::npos) + return false; // Can't find the attribute. + + begin = attribute_list.find(kQuote, begin) + 1; + size_t end = attribute_list.find(kQuote, begin); + if (end == std::string::npos) + return false; // The value is not quoted. + + *value = attribute_list.substr(begin, end - begin); + return true; +} + +// static +void Firefox2Importer::HTMLUnescape(std::wstring *text) { + ReplaceSubstringsAfterOffset(text, 0, L"<", L"<"); + ReplaceSubstringsAfterOffset(text, 0, L">", L">"); + ReplaceSubstringsAfterOffset(text, 0, L"&", L"&"); + ReplaceSubstringsAfterOffset(text, 0, L""", L"\""); + ReplaceSubstringsAfterOffset(text, 0, L"'", L"\'"); +} + +// static +void Firefox2Importer::FindXMLFilesInDir( + const std::wstring& dir, + std::vector* xml_files) { + file_util::FileEnumerator file_enum(dir, false, + file_util::FileEnumerator::FILES, + L"*.xml"); + std::wstring file(file_enum.Next()); + while (!file.empty()) { + xml_files->push_back(file); + file = file_enum.Next(); + } +} + +// static +void Firefox2Importer::DataURLToFaviconUsage( + const GURL& link_url, + const GURL& favicon_data, + std::vector* favicons) { + if (!link_url.is_valid() || !favicon_data.is_valid() || + !favicon_data.SchemeIs("data")) + return; + + // Parse the data URL. + std::string mime_type, char_set, data; + if (!net::DataURL::Parse(favicon_data, &mime_type, &char_set, &data) || + data.empty()) + return; + + history::ImportedFavIconUsage usage; + if (!ReencodeFavicon(reinterpret_cast(&data[0]), + data.size(), &usage.png_data)) + return; // Unable to decode. + + // We need to make up a URL for the favicon. We use a version of the page's + // URL so that we can be sure it will not collide. + usage.favicon_url = GURL(std::string("made-up-favicon:") + link_url.spec()); + + // We only have one URL per favicon for Firefox 2 bookmarks. + usage.urls.insert(link_url); + + favicons->push_back(usage); +} + diff --git a/chrome/browser/importer/firefox2_importer.h b/chrome/browser/importer/firefox2_importer.h new file mode 100644 index 0000000..2c44993 --- /dev/null +++ b/chrome/browser/importer/firefox2_importer.h @@ -0,0 +1,112 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_FIREFOX2_IMPORTER_H_ +#define CHROME_BROWSER_IMPORTER_FIREFOX2_IMPORTER_H_ + +#include "chrome/browser/importer/importer.h" + +class TemplateURL; + +// Importer for Mozilla Firefox 2. +class Firefox2Importer : public Importer { + public: + Firefox2Importer(); + virtual ~Firefox2Importer(); + + // Importer methods. + virtual void StartImport(ProfileInfo profile_info, + uint16 items, + ProfileWriter* writer, + ImporterHost* host); + + // Loads the default bookmarks in the Firefox installed at |firefox_app_path|, + // and stores their locations in |urls|. + static void LoadDefaultBookmarks(const std::wstring& firefox_app_path, + std::set *urls); + + // Creates a TemplateURL with the |keyword| and |url|. |title| may be empty. + // This function transfers ownership of the created TemplateURL to the caller. + static TemplateURL* CreateTemplateURL(const std::wstring& title, + const std::wstring& keyword, + const GURL& url); + + private: + FRIEND_TEST(FirefoxImporterTest, Firefox2BookmarkParse); + FRIEND_TEST(FirefoxImporterTest, Firefox2CookesParse); + + void ImportBookmarks(); + void ImportPasswords(); + void ImportHistory(); + void ImportSearchEngines(); + // Import the user's home page, unless it is set to default home page as + // defined in browserconfig.properties. + void ImportHomepage(); + + // Fills |files| with the paths to the files containing the search engine + // descriptions. + void GetSearchEnginesXMLFiles(std::vector* 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
tag: + //

title

+ //

+ // ... container ... + //

+ // And a bookmark is presented by a tag: + //

name + // Reference: http://kb.mozillazine.org/Bookmarks.html + static bool ParseCharsetFromLine(const std::string& line, + std::string* charset); + static bool ParseFolderNameFromLine(const std::string& line, + const std::string& charset, + std::wstring* folder_name, + bool* is_toolbar_folder); + // See above, this will also put the data: URL of the favicon into *favicon + // if there is a favicon given. |post_data| is set for POST base keywords to + // the contents of the actual POST (with %s for the search term). + static bool ParseBookmarkFromLine(const std::string& line, + const std::string& charset, + std::wstring* title, + GURL* url, + GURL* favicon, + std::wstring* shortcut, + Time* add_date, + std::wstring* post_data); + + // Fetches the given attribute value from the |tag|. Returns true if + // successful, and |value| will contain the value. + static bool GetAttribute(const std::string& tag, + const std::string& attribute, + std::string* value); + + // There are some characters in html file will be escaped: + // '<', '>', '"', '\', '&' + // Un-escapes them if the bookmark name has those characters. + static void HTMLUnescape(std::wstring* text); + + // Fills |xml_files| with the file with an xml extension found under |dir|. + static void FindXMLFilesInDir(const std::wstring& dir, + std::vector* 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* favicons); + + ProfileWriter* writer_; + std::wstring source_path_; + std::wstring app_path_; + + DISALLOW_EVIL_CONSTRUCTORS(Firefox2Importer); +}; + +#endif // CHROME_BROWSER_IMPORTER_FIREFOX2_IMPORTER_H_ + diff --git a/chrome/browser/importer/firefox3_importer.cc b/chrome/browser/importer/firefox3_importer.cc new file mode 100644 index 0000000..3279130 --- /dev/null +++ b/chrome/browser/importer/firefox3_importer.cc @@ -0,0 +1,502 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/firefox3_importer.h" + +#include + +#include "base/file_util.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "chrome/browser/importer/firefox2_importer.h" +#include "chrome/browser/importer/firefox_importer_utils.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/time_format.h" +#include "generated_resources.h" + +// Wraps the function sqlite3_close() in a class that is +// used in scoped_ptr_malloc. + +namespace { + +class DBClose { + public: + inline void operator()(sqlite3* x) const { + sqlite3_close(x); + } +}; + +} // namespace + +void Firefox3Importer::StartImport(ProfileInfo profile_info, + uint16 items, ProfileWriter* writer, + ImporterHost* host) { + writer_ = writer; + source_path_ = profile_info.source_path; + app_path_ = profile_info.app_path; + importer_host_ = host; + + + // The order here is important! + NotifyStarted(); + if ((items & HOME_PAGE) && !cancelled()) + ImportHomepage(); // Doesn't have a UI item. + if ((items & FAVORITES) && !cancelled()) { + NotifyItemStarted(FAVORITES); + ImportBookmarks(); + NotifyItemEnded(FAVORITES); + } + if ((items & SEARCH_ENGINES) && !cancelled()) { + NotifyItemStarted(SEARCH_ENGINES); + ImportSearchEngines(); + NotifyItemEnded(SEARCH_ENGINES); + } + if ((items & PASSWORDS) && !cancelled()) { + NotifyItemStarted(PASSWORDS); + ImportPasswords(); + NotifyItemEnded(PASSWORDS); + } + if ((items & HISTORY) && !cancelled()) { + NotifyItemStarted(HISTORY); + ImportHistory(); + NotifyItemEnded(HISTORY); + } + NotifyEnded(); +} + +void Firefox3Importer::ImportHistory() { + std::wstring file = source_path_; + file_util::AppendToPath(&file, L"places.sqlite"); + if (!file_util::PathExists(file)) + return; + + sqlite3* sqlite; + if (sqlite3_open(WideToUTF8(file).c_str(), &sqlite) != SQLITE_OK) + return; + scoped_ptr_malloc 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 rows; + while (s.step() == SQLITE_ROW && !cancelled()) { + GURL url(s.column_string(0)); + + // Filter out unwanted URLs. + if (!CanImportURL(url)) + continue; + + history::URLRow row(url); + row.set_title(s.column_string16(1)); + row.set_visit_count(s.column_int(2)); + row.set_hidden(s.column_int(3) == 1); + row.set_typed_count(s.column_int(4)); + row.set_last_visit(Time::FromTimeT(s.column_int64(5)/1000000)); + + rows.push_back(row); + } + if (!rows.empty() && !cancelled()) { + main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, + &ProfileWriter::AddHistoryPage, rows)); + } +} + +void Firefox3Importer::ImportBookmarks() { + std::wstring file = source_path_; + file_util::AppendToPath(&file, L"places.sqlite"); + if (!file_util::PathExists(file)) + return; + + sqlite3* sqlite; + if (sqlite3_open(WideToUTF8(file).c_str(), &sqlite) != SQLITE_OK) + return; + scoped_ptr_malloc 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 livemark_id; + LoadLivemarkIDs(db.get(), &livemark_id); + + // Load the default bookmarks. Its storage is the same as Firefox 2. + std::set 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 bookmarks; + std::vector 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 post_keyword_ids; + SQLStatement s; + const char* stmt = "SELECT b.id FROM moz_bookmarks b " + "INNER JOIN moz_items_annos ia ON ia.item_id = b.id " + "INNER JOIN moz_anno_attributes aa ON ia.anno_attribute_id = aa.id " + "WHERE aa.name = 'bookmarkProperties/POSTData'"; + if (s.prepare(db.get(), stmt) == SQLITE_OK) { + while (s.step() == SQLITE_ROW && !cancelled()) + post_keyword_ids.insert(s.column_int(0)); + } else { + NOTREACHED(); + } + + std::wstring firefox_folder = + l10n_util::GetString(IDS_BOOKMARK_GROUP_FROM_FIREFOX); + for (size_t i = 0; i < list.size(); ++i) { + BookmarkItem* item = list[i]; + + // The type of bookmark items is 1. + if (item->type != 1) + continue; + + // Skip the default bookmarks and unwanted URLs. + if (!CanImportURL(item->url) || + default_urls.find(item->url) != default_urls.end() || + post_keyword_ids.find(item->id) != post_keyword_ids.end()) + continue; + + // Find the bookmark path by tracing their links to parent folders. + std::vector path; + BookmarkItem* child = item; + bool found_path = false; + bool is_in_toolbar = false; + while (child->parent >= 0) { + BookmarkItem* parent = list[child->parent]; + if (parent->id == toolbar_folder_id) { + // This bookmark entry should be put in the bookmark bar. + // But we put it in the Firefox group after first run, so + // that do not mess up the bookmark bar. + if (first_run()) { + is_in_toolbar = true; + } else { + path.insert(path.begin(), parent->title); + path.insert(path.begin(), firefox_folder); + } + found_path = true; + break; + } else if (parent->id == menu_folder_id || + parent->id == unsorted_folder_id) { + // After the first run, the item will be placed in a folder in + // the "Other bookmarks". + if (!first_run()) + path.insert(path.begin(), firefox_folder); + found_path = true; + break; + } else if (livemark_id.find(parent->id) != livemark_id.end()) { + // If the entry is under a livemark folder, we don't import it. + break; + } + path.insert(path.begin(), parent->title); + child = parent; + } + + if (!found_path) + continue; + + ProfileWriter::BookmarkEntry entry; + entry.creation_time = item->date_added; + entry.title = item->title; + entry.url = item->url; + entry.path = path; + entry.in_toolbar = is_in_toolbar; + + bookmarks.push_back(entry); + + if (item->favicon) + favicon_map[item->favicon].insert(item->url); + + // This bookmark has a keyword, we import it to our TemplateURL model. + TemplateURL* t_url = Firefox2Importer::CreateTemplateURL( + item->title, UTF8ToWide(item->keyword), item->url); + if (t_url) + template_urls.push_back(t_url); + } + + STLDeleteContainerPointers(list.begin(), list.end()); + + // Write into profile. + if (!bookmarks.empty() && !cancelled()) { + main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, + &ProfileWriter::AddBookmarkEntry, bookmarks)); + } + if (!template_urls.empty() && !cancelled()) { + main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, + &ProfileWriter::AddKeywords, template_urls, -1, false)); + } else { + STLDeleteContainerPointers(template_urls.begin(), template_urls.end()); + } + if (!favicon_map.empty() && !cancelled()) { + std::vector favicons; + LoadFavicons(db.get(), favicon_map, &favicons); + main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, + &ProfileWriter::AddFavicons, favicons)); + } +} + +void Firefox3Importer::ImportPasswords() { + // Initializes NSS3. + NSSDecryptor decryptor; + if (!decryptor.Init(source_path_, source_path_) && + !decryptor.Init(app_path_, source_path_)) + return; + + // Firefox 3 uses signons3.txt to store the passwords. + std::wstring file = source_path_; + file_util::AppendToPath(&file, L"signons3.txt"); + if (!file_util::PathExists(file)) { + file = source_path_; + file_util::AppendToPath(&file, L"signons2.txt"); + } + + std::string content; + file_util::ReadFileToString(file, &content); + std::vector forms; + decryptor.ParseSignons(content, &forms); + + if (!cancelled()) { + for (size_t i = 0; i < forms.size(); ++i) { + main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, + &ProfileWriter::AddPasswordForm, forms[i])); + } + } +} + +void Firefox3Importer::ImportSearchEngines() { + std::vector files; + GetSearchEnginesXMLFiles(&files); + + std::vector search_engines; + ParseSearchEnginesFromXMLFiles(files, &search_engines); + main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, + &ProfileWriter::AddKeywords, search_engines, + GetFirefoxDefaultSearchEngineIndex(search_engines, source_path_), true)); +} + +void Firefox3Importer::ImportHomepage() { + GURL homepage = GetHomepage(source_path_); + if (homepage.is_valid() && !IsDefaultHomepage(homepage, app_path_)) { + main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, + &ProfileWriter::AddHomepage, homepage)); + } +} + +void Firefox3Importer::GetSearchEnginesXMLFiles( + std::vector* files) { + std::wstring file = source_path_; + file_util::AppendToPath(&file, L"search.sqlite"); + if (!file_util::PathExists(file)) + return; + + sqlite3* sqlite; + if (sqlite3_open(WideToUTF8(file).c_str(), &sqlite) != SQLITE_OK) + return; + scoped_ptr_malloc db(sqlite); + + SQLStatement s; + const char* stmt = "SELECT engineid FROM engine_data ORDER BY value ASC"; + + if (s.prepare(db.get(), stmt) != SQLITE_OK) + return; + + std::wstring app_path = app_path_; + file_util::AppendToPath(&app_path, L"searchplugins"); + std::wstring profile_path = source_path_; + file_util::AppendToPath(&profile_path, L"searchplugins"); + + const std::wstring kAppPrefix = L"[app]/"; + const std::wstring kProfilePrefix = L"[profile]/"; + while (s.step() == SQLITE_ROW && !cancelled()) { + std::wstring file; + std::wstring engine = UTF8ToWide(s.column_string(0)); + // The string contains [app]/.xml or [profile]/.xml where the + // [app] and [profile] need to be replaced with the actual app or profile + // path. + size_t index = engine.find(kAppPrefix); + if (index != std::wstring::npos) { + file = app_path; + file_util::AppendToPath( + &file, + engine.substr(index + kAppPrefix.length())); // Remove '[app]/'. + } else if ((index = engine.find(kProfilePrefix)) != std::wstring::npos) { + file = profile_path; + file_util::AppendToPath( + &file, + engine.substr(index + kProfilePrefix.length())); // Remove + // '[profile]/'. + } else { + NOTREACHED() << "Unexpected Firefox 3 search engine id"; + continue; + } + files->push_back(file); + } +} + +void Firefox3Importer::LoadRootNodeID(sqlite3* db, + int* toolbar_folder_id, + int* menu_folder_id, + int* unsorted_folder_id) { + const char kToolbarFolderName[] = "toolbar"; + const char kMenuFolderName[] = "menu"; + const char kUnsortedFolderName[] = "unfiled"; + + SQLStatement s; + const char* stmt = "SELECT root_name, folder_id FROM moz_bookmarks_roots"; + if (s.prepare(db, stmt) != SQLITE_OK) + return; + + while (s.step() == SQLITE_ROW) { + std::string folder = s.column_string(0); + int id = s.column_int(1); + if (folder == kToolbarFolderName) + *toolbar_folder_id = id; + else if (folder == kMenuFolderName) + *menu_folder_id = id; + else if (folder == kUnsortedFolderName) + *unsorted_folder_id = id; + } +} + +void Firefox3Importer::LoadLivemarkIDs(sqlite3* db, + std::set* livemark) { + const char kFeedAnnotation[] = "livemark/feedURI"; + livemark->clear(); + + SQLStatement s; + const char* stmt = "SELECT b.item_id " + "FROM moz_anno_attributes a " + "JOIN moz_items_annos b ON a.id = b.anno_attribute_id " + "WHERE a.name = ? "; + if (s.prepare(db, stmt) != SQLITE_OK) + return; + + s.bind_string(0, kFeedAnnotation); + while (s.step() == SQLITE_ROW && !cancelled()) + livemark->insert(s.column_int(0)); +} + +void Firefox3Importer::GetTopBookmarkFolder(sqlite3* db, int folder_id, + BookmarkList* list) { + SQLStatement s; + const char* stmt = "SELECT b.title " + "FROM moz_bookmarks b " + "WHERE b.type = 2 AND b.id = ? " + "ORDER BY b.position"; + if (s.prepare(db, stmt) != SQLITE_OK) + return; + + s.bind_int(0, folder_id); + if (s.step() == SQLITE_ROW) { + BookmarkItem* item = new BookmarkItem; + item->parent = -1; // The top level folder has no parent. + item->id = folder_id; + item->title = s.column_string16(0); + item->type = 2; + item->favicon = 0; + list->push_back(item); + } +} + +void Firefox3Importer::GetWholeBookmarkFolder(sqlite3* db, BookmarkList* list, + size_t position) { + if (position >= list->size()) { + NOTREACHED(); + return; + } + + SQLStatement s; + const char* stmt = "SELECT b.id, h.url, COALESCE(b.title, h.title), " + "b.type, k.keyword, b.dateAdded, h.favicon_id " + "FROM moz_bookmarks b " + "LEFT JOIN moz_places h ON b.fk = h.id " + "LEFT JOIN moz_keywords k ON k.id = b.keyword_id " + "WHERE b.type IN (1,2) AND b.parent = ? " + "ORDER BY b.position"; + if (s.prepare(db, stmt) != SQLITE_OK) + return; + + s.bind_int(0, (*list)[position]->id); + BookmarkList temp_list; + while (s.step() == SQLITE_ROW) { + BookmarkItem* item = new BookmarkItem; + item->parent = static_cast(position); + item->id = s.column_int(0); + item->url = GURL(s.column_string(1)); + item->title = s.column_string16(2); + item->type = s.column_int(3); + item->keyword = s.column_string(4); + item->date_added = Time::FromTimeT(s.column_int64(5)/1000000); + item->favicon = s.column_int64(6); + + temp_list.push_back(item); + } + + // Appends all items to the list. + for (BookmarkList::iterator i = temp_list.begin(); + i != temp_list.end(); ++i) { + list->push_back(*i); + // Recursive add bookmarks in sub-folders. + if ((*i)->type == 2) + GetWholeBookmarkFolder(db, list, list->size() - 1); + } +} + +void Firefox3Importer::LoadFavicons( + sqlite3* db, + const FaviconMap& favicon_map, + std::vector* 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 data; + if (!s.column_blob_as_vector(1, &data) || data.empty()) + continue; // Data definitely invalid. + + if (!ReencodeFavicon(&data[0], data.size(), &usage.png_data)) + continue; // Unable to decode. + + usage.urls = i->second; + favicons->push_back(usage); + } + s.reset(); + } +} + diff --git a/chrome/browser/importer/firefox3_importer.h b/chrome/browser/importer/firefox3_importer.h new file mode 100644 index 0000000..d4cc0bc --- /dev/null +++ b/chrome/browser/importer/firefox3_importer.h @@ -0,0 +1,85 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_FIREFOX3_IMPORTER_H_ +#define CHROME_BROWSER_IMPORTER_FIREFOX3_IMPORTER_H_ + +#include +#include +#include + +#include "base/basictypes.h" +#include "chrome/browser/importer/importer.h" +#include "chrome/common/sqlite_utils.h" +#include "googleurl/src/gurl.h" + +// Importer for Mozilla Firefox 3. +// Firefox 3 stores its persistent information in a new system called places. +// http://wiki.mozilla.org/Places +class Firefox3Importer : public Importer { + public: + Firefox3Importer() { } + virtual ~Firefox3Importer() { } + + // Importer methods. + virtual void StartImport(ProfileInfo profile_info, + uint16 items, + ProfileWriter* writer, + ImporterHost* host); + + private: + typedef std::map > FaviconMap; + + void ImportBookmarks(); + void ImportPasswords(); + void ImportHistory(); + void ImportSearchEngines(); + // Import the user's home page, unless it is set to default home page as + // defined in browserconfig.properties. + void ImportHomepage(); + void GetSearchEnginesXMLFiles(std::vector* files); + + // The struct stores the information about a bookmark item. + struct BookmarkItem { + int parent; + int id; + GURL url; + std::wstring title; + int type; + std::string keyword; + Time date_added; + int64 favicon; + }; + typedef std::vector 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* 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* favicons); + + ProfileWriter* writer_; + std::wstring source_path_; + std::wstring app_path_; + + DISALLOW_EVIL_CONSTRUCTORS(Firefox3Importer); +}; + +#endif // CHROME_BROWSER_IMPORTER_FIREFOX3_IMPORTER_H_ + diff --git a/chrome/browser/importer/firefox_importer_unittest.cc b/chrome/browser/importer/firefox_importer_unittest.cc new file mode 100644 index 0000000..d47b133 --- /dev/null +++ b/chrome/browser/importer/firefox_importer_unittest.cc @@ -0,0 +1,216 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/file_util.h" +#include "base/path_service.h" +#include "chrome/browser/importer/firefox2_importer.h" +#include "chrome/browser/importer/firefox_importer_utils.h" +#include "chrome/browser/importer/firefox_profile_lock.h" +#include "chrome/common/chrome_paths.h" + +TEST(FirefoxImporterTest, Firefox2NSS3Decryptor) { + std::wstring nss_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &nss_path)); + file_util::AppendToPath(&nss_path, L"firefox2_nss"); + std::wstring db_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &db_path)); + file_util::AppendToPath(&db_path, L"firefox2_profile"); + NSSDecryptor decryptor; + EXPECT_TRUE(decryptor.Init(nss_path, db_path)); + EXPECT_EQ(L"hello", decryptor.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAE" + "wFAYIKoZIhvcNAwcECBJM63MpT9rtBAjMCm7qo/EhlA==")); + // Test UTF-16 encoding. + EXPECT_EQ(L"\x4E2D", decryptor.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAE" + "wFAYIKoZIhvcNAwcECN9OQ5ZFmhb8BAiFo1Z+fUvaIQ==")); +} + +TEST(FirefoxImporterTest, Firefox3NSS3Decryptor) { + std::wstring nss_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &nss_path)); + file_util::AppendToPath(&nss_path, L"firefox3_nss"); + std::wstring db_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &db_path)); + file_util::AppendToPath(&db_path, L"firefox3_profile"); + NSSDecryptor decryptor; + EXPECT_TRUE(decryptor.Init(nss_path, db_path)); + EXPECT_EQ(L"hello", decryptor.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAE" + "wFAYIKoZIhvcNAwcECKajtRg4qFSHBAhv9luFkXgDJA==")); + // Test UTF-16 encoding. + EXPECT_EQ(L"\x4E2D", decryptor.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAE" + "wFAYIKoZIhvcNAwcECLWqqiccfQHWBAie74hxnULxlw==")); +} + +TEST(FirefoxImporterTest, Firefox2BookmarkParse) { + bool result; + + // Tests charset. + std::string charset; + result = Firefox2Importer::ParseCharsetFromLine( + "", + &charset); + EXPECT_TRUE(result); + EXPECT_EQ("UTF-8", charset); + + // Escaped characters in name. + std::wstring folder_name; + bool is_toolbar_folder; + result = Firefox2Importer::ParseFolderNameFromLine( + "

< >" + " & " ' \\ /

", + 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( + "

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