diff options
author | Ben Murdoch <benm@google.com> | 2010-07-29 17:14:53 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2010-08-04 14:29:45 +0100 |
commit | c407dc5cd9bdc5668497f21b26b09d988ab439de (patch) | |
tree | 7eaf8707c0309516bdb042ad976feedaf72b0bb1 /chrome/browser/importer | |
parent | 0998b1cdac5733f299c12d88bc31ef9c8035b8fa (diff) | |
download | external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.zip external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.gz external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.bz2 |
Merge Chromium src@r53293
Change-Id: Ia79acf8670f385cee48c45b0a75371d8e950af34
Diffstat (limited to 'chrome/browser/importer')
51 files changed, 11725 insertions, 0 deletions
diff --git a/chrome/browser/importer/firefox2_importer.cc b/chrome/browser/importer/firefox2_importer.cc new file mode 100644 index 0000000..d551105 --- /dev/null +++ b/chrome/browser/importer/firefox2_importer.cc @@ -0,0 +1,587 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/firefox2_importer.h" + +#include <string> +#include <vector> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/i18n/icu_string_conversions.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "base/stl_util-inl.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/browser/importer/firefox_importer_utils.h" +#include "chrome/browser/importer/importer_bridge.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "chrome/browser/importer/mork_reader.h" +#include "chrome/browser/importer/nss_decryptor.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/browser/search_engines/template_url_parser.h" +#include "chrome/common/time_format.h" +#include "chrome/common/url_constants.h" +#include "grit/generated_resources.h" +#include "net/base/data_url.h" +#include "webkit/glue/password_form.h" + +using base::Time; +using importer::BOOKMARKS_HTML; +using importer::FAVORITES; +using importer::HISTORY; +using importer::HOME_PAGE; +using importer::PASSWORDS; +using importer::ProfileInfo; +using importer::SEARCH_ENGINES; +using webkit_glue::PasswordForm; + +// Firefox2Importer. + +Firefox2Importer::Firefox2Importer() : parsing_bookmarks_html_file_(false) { +} + +Firefox2Importer::~Firefox2Importer() { +} + +void Firefox2Importer::StartImport(importer::ProfileInfo profile_info, + uint16 items, + ImporterBridge* bridge) { + bridge_ = bridge; + source_path_ = profile_info.source_path; + app_path_ = profile_info.app_path; + + parsing_bookmarks_html_file_ = + (profile_info.browser_type == importer::BOOKMARKS_HTML); + + // The order here is important! + bridge_->NotifyStarted(); + if ((items & importer::HOME_PAGE) && !cancelled()) + ImportHomepage(); // Doesn't have a UI item. + + // Note history should be imported before bookmarks because bookmark import + // will also import favicons and we store favicon for a URL only if the URL + // exist in history or bookmarks. + if ((items & importer::HISTORY) && !cancelled()) { + bridge_->NotifyItemStarted(importer::HISTORY); + ImportHistory(); + bridge_->NotifyItemEnded(importer::HISTORY); + } + + if ((items & importer::FAVORITES) && !cancelled()) { + bridge_->NotifyItemStarted(importer::FAVORITES); + ImportBookmarks(); + bridge_->NotifyItemEnded(importer::FAVORITES); + } + if ((items & importer::SEARCH_ENGINES) && !cancelled()) { + bridge_->NotifyItemStarted(importer::SEARCH_ENGINES); + ImportSearchEngines(); + bridge_->NotifyItemEnded(importer::SEARCH_ENGINES); + } + if ((items & importer::PASSWORDS) && !cancelled()) { + bridge_->NotifyItemStarted(importer::PASSWORDS); + ImportPasswords(); + bridge_->NotifyItemEnded(importer::PASSWORDS); + } + bridge_->NotifyEnded(); +} + +// static +void Firefox2Importer::LoadDefaultBookmarks(const FilePath& app_path, + std::set<GURL> *urls) { + FilePath file = app_path.AppendASCII("defaults") + .AppendASCII("profile") + .AppendASCII("bookmarks.html"); + + urls->clear(); + + // Read the whole file. + std::string content; + file_util::ReadFileToString(file, &content); + std::vector<std::string> lines; + SplitString(content, '\n', &lines); + + std::string charset; + for (size_t i = 0; i < lines.size(); ++i) { + std::string line; + TrimString(lines[i], " ", &line); + + // Get the encoding of the bookmark file. + if (ParseCharsetFromLine(line, &charset)) + continue; + + // Get the bookmark. + std::wstring title; + GURL url, favicon; + std::wstring shortcut; + Time add_date; + std::wstring post_data; + if (ParseBookmarkFromLine(line, charset, &title, &url, + &favicon, &shortcut, &add_date, + &post_data)) + urls->insert(url); + } +} + +// static +TemplateURL* Firefox2Importer::CreateTemplateURL(const std::wstring& title, + const std::wstring& keyword, + const GURL& url) { + // Skip if the keyword or url is invalid. + if (keyword.empty() && url.is_valid()) + return NULL; + + TemplateURL* t_url = new TemplateURL(); + // We set short name by using the title if it exists. + // Otherwise, we use the shortcut. + t_url->set_short_name(!title.empty() ? title : keyword); + t_url->set_keyword(keyword); + t_url->SetURL(TemplateURLRef::DisplayURLToURLRef(UTF8ToWide(url.spec())), + 0, 0); + return t_url; +} + +// static +void Firefox2Importer::ImportBookmarksFile( + const FilePath& file_path, + const std::set<GURL>& default_urls, + bool import_to_bookmark_bar, + const std::wstring& first_folder_name, + Importer* importer, + std::vector<ProfileWriter::BookmarkEntry>* bookmarks, + std::vector<TemplateURL*>* template_urls, + std::vector<history::ImportedFavIconUsage>* favicons) { + std::string content; + file_util::ReadFileToString(file_path, &content); + std::vector<std::string> lines; + SplitString(content, '\n', &lines); + + std::vector<ProfileWriter::BookmarkEntry> toolbar_bookmarks; + std::wstring last_folder = first_folder_name; + bool last_folder_on_toolbar = false; + std::vector<std::wstring> path; + size_t toolbar_folder = 0; + std::string charset; + for (size_t i = 0; i < lines.size() && (!importer || !importer->cancelled()); + ++i) { + std::string line; + TrimString(lines[i], " ", &line); + + // Get the encoding of the bookmark file. + if (ParseCharsetFromLine(line, &charset)) + continue; + + // Get the folder name. + if (ParseFolderNameFromLine(line, charset, &last_folder, + &last_folder_on_toolbar)) + continue; + + // Get the bookmark entry. + std::wstring title, shortcut; + GURL url, favicon; + Time add_date; + std::wstring post_data; + // TODO(jcampan): http://b/issue?id=1196285 we do not support POST based + // keywords yet. + if (ParseBookmarkFromLine(line, charset, &title, + &url, &favicon, &shortcut, &add_date, + &post_data) && + post_data.empty() && + CanImportURL(GURL(url)) && + default_urls.find(url) == default_urls.end()) { + if (toolbar_folder > path.size() && path.size() > 0) { + NOTREACHED(); // error in parsing. + break; + } + + ProfileWriter::BookmarkEntry entry; + entry.creation_time = add_date; + entry.url = url; + entry.title = title; + + if (import_to_bookmark_bar && toolbar_folder) { + // Flatten the items in toolbar. + entry.in_toolbar = true; + entry.path.assign(path.begin() + toolbar_folder, path.end()); + toolbar_bookmarks.push_back(entry); + } else { + // Insert the item into the "Imported from Firefox" folder. + entry.path.assign(path.begin(), path.end()); + if (import_to_bookmark_bar) + entry.path.erase(entry.path.begin()); + bookmarks->push_back(entry); + } + + // Save the favicon. DataURLToFaviconUsage will handle the case where + // there is no favicon. + if (favicons) + DataURLToFaviconUsage(url, favicon, favicons); + + if (template_urls) { + // If there is a SHORTCUT attribute for this bookmark, we + // add it as our keywords. + TemplateURL* t_url = CreateTemplateURL(title, shortcut, url); + if (t_url) + template_urls->push_back(t_url); + } + + continue; + } + + // Bookmarks in sub-folder are encapsulated with <DL> tag. + if (StartsWithASCII(line, "<DL>", true)) { + path.push_back(last_folder); + last_folder.clear(); + if (last_folder_on_toolbar && !toolbar_folder) + toolbar_folder = path.size(); + } else if (StartsWithASCII(line, "</DL>", true)) { + if (path.empty()) + break; // Mismatch <DL>. + path.pop_back(); + if (toolbar_folder > path.size()) + toolbar_folder = 0; + } + } + + bookmarks->insert(bookmarks->begin(), toolbar_bookmarks.begin(), + toolbar_bookmarks.end()); +} + +void Firefox2Importer::ImportBookmarks() { + // Load the default bookmarks. + std::set<GURL> default_urls; + if (!parsing_bookmarks_html_file_) + LoadDefaultBookmarks(app_path_, &default_urls); + + // Parse the bookmarks.html file. + std::vector<ProfileWriter::BookmarkEntry> bookmarks, toolbar_bookmarks; + std::vector<TemplateURL*> template_urls; + std::vector<history::ImportedFavIconUsage> favicons; + FilePath file = source_path_; + if (!parsing_bookmarks_html_file_) + file = file.AppendASCII("bookmarks.html"); + std::wstring first_folder_name; + first_folder_name = bridge_->GetLocalizedString( + parsing_bookmarks_html_file_ ? IDS_BOOKMARK_GROUP : + IDS_BOOKMARK_GROUP_FROM_FIREFOX); + + ImportBookmarksFile(file, default_urls, import_to_bookmark_bar(), + first_folder_name, this, &bookmarks, &template_urls, + &favicons); + + // Write data into profile. + if (!bookmarks.empty() && !cancelled()) { + int options = 0; + if (import_to_bookmark_bar()) + options |= ProfileWriter::IMPORT_TO_BOOKMARK_BAR; + if (bookmark_bar_disabled()) + options |= ProfileWriter::BOOKMARK_BAR_DISABLED; + bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options); + } + if (!parsing_bookmarks_html_file_ && !template_urls.empty() && + !cancelled()) { + bridge_->SetKeywords(template_urls, -1, false); + } else { + STLDeleteContainerPointers(template_urls.begin(), template_urls.end()); + } + if (!favicons.empty()) { + bridge_->SetFavIcons(favicons); + } +} + +void Firefox2Importer::ImportPasswords() { + // Initializes NSS3. + NSSDecryptor decryptor; + if (!decryptor.Init(source_path_.ToWStringHack(), + source_path_.ToWStringHack()) && + !decryptor.Init(app_path_.ToWStringHack(), + source_path_.ToWStringHack())) { + return; + } + + // Firefox 2 uses signons2.txt to store the pssswords. If it doesn't + // exist, we try to find its older version. + FilePath file = source_path_.AppendASCII("signons2.txt"); + if (!file_util::PathExists(file)) { + file = source_path_.AppendASCII("signons.txt"); + } + + std::string content; + file_util::ReadFileToString(file, &content); + std::vector<PasswordForm> forms; + decryptor.ParseSignons(content, &forms); + + if (!cancelled()) { + for (size_t i = 0; i < forms.size(); ++i) { + bridge_->SetPasswordForm(forms[i]); + } + } +} + +void Firefox2Importer::ImportHistory() { + FilePath file = source_path_.AppendASCII("history.dat"); + ImportHistoryFromFirefox2(file, bridge_); +} + +void Firefox2Importer::ImportSearchEngines() { + std::vector<FilePath> files; + GetSearchEnginesXMLFiles(&files); + + std::vector<TemplateURL*> search_engines; + ParseSearchEnginesFromXMLFiles(files, &search_engines); + + int default_index = + GetFirefoxDefaultSearchEngineIndex(search_engines, source_path_); + bridge_->SetKeywords(search_engines, default_index, true); +} + +void Firefox2Importer::ImportHomepage() { + GURL home_page = GetHomepage(source_path_); + if (home_page.is_valid() && !IsDefaultHomepage(home_page, app_path_)) { + bridge_->AddHomePage(home_page); + } +} + +void Firefox2Importer::GetSearchEnginesXMLFiles( + std::vector<FilePath>* files) { + // Search engines are contained in XML files in a searchplugins directory that + // can be found in 2 locations: + // - Firefox install dir (default search engines) + // - the profile dir (user added search engines) + FilePath dir = app_path_.AppendASCII("searchplugins"); + FindXMLFilesInDir(dir, files); + + FilePath profile_dir = source_path_.AppendASCII("searchplugins"); + FindXMLFilesInDir(profile_dir, files); +} + +// static +bool Firefox2Importer::ParseCharsetFromLine(const std::string& line, + std::string* charset) { + const char kCharset[] = "charset="; + if (StartsWithASCII(line, "<META", true) && + line.find("CONTENT=\"") != std::string::npos) { + size_t begin = line.find(kCharset); + if (begin == std::string::npos) + return false; + begin += std::string(kCharset).size(); + size_t end = line.find_first_of('\"', begin); + *charset = line.substr(begin, end - begin); + return true; + } + return false; +} + +// static +bool Firefox2Importer::ParseFolderNameFromLine(const std::string& line, + const std::string& charset, + std::wstring* folder_name, + bool* is_toolbar_folder) { + const char kFolderOpen[] = "<DT><H3"; + const char kFolderClose[] = "</H3>"; + const char kToolbarFolderAttribute[] = "PERSONAL_TOOLBAR_FOLDER"; + + if (!StartsWithASCII(line, kFolderOpen, true)) + return false; + + size_t end = line.find(kFolderClose); + size_t tag_end = line.rfind('>', end) + 1; + // If no end tag or start tag is broken, we skip to find the folder name. + if (end == std::string::npos || tag_end < arraysize(kFolderOpen)) + return false; + + base::CodepageToWide(line.substr(tag_end, end - tag_end), charset.c_str(), + base::OnStringConversionError::SKIP, folder_name); + HTMLUnescape(folder_name); + + std::string attribute_list = line.substr(arraysize(kFolderOpen), + tag_end - arraysize(kFolderOpen) - 1); + std::string value; + if (GetAttribute(attribute_list, kToolbarFolderAttribute, &value) && + LowerCaseEqualsASCII(value, "true")) + *is_toolbar_folder = true; + else + *is_toolbar_folder = false; + + return true; +} + +// static +bool Firefox2Importer::ParseBookmarkFromLine(const std::string& line, + const std::string& charset, + std::wstring* title, + GURL* url, + GURL* favicon, + std::wstring* shortcut, + Time* add_date, + std::wstring* post_data) { + const char kItemOpen[] = "<DT><A"; + const char kItemClose[] = "</A>"; + const char kFeedURLAttribute[] = "FEEDURL"; + const char kHrefAttribute[] = "HREF"; + const char kIconAttribute[] = "ICON"; + const char kShortcutURLAttribute[] = "SHORTCUTURL"; + const char kAddDateAttribute[] = "ADD_DATE"; + const char kPostDataAttribute[] = "POST_DATA"; + + title->clear(); + *url = GURL(); + *favicon = GURL(); + shortcut->clear(); + post_data->clear(); + *add_date = Time(); + + if (!StartsWithASCII(line, kItemOpen, true)) + return false; + + size_t end = line.find(kItemClose); + size_t tag_end = line.rfind('>', end) + 1; + if (end == std::string::npos || tag_end < arraysize(kItemOpen)) + return false; // No end tag or start tag is broken. + + std::string attribute_list = line.substr(arraysize(kItemOpen), + tag_end - arraysize(kItemOpen) - 1); + + // We don't import Live Bookmark folders, which is Firefox's RSS reading + // feature, since the user never necessarily bookmarked them and we don't + // have this feature to update their contents. + std::string value; + if (GetAttribute(attribute_list, kFeedURLAttribute, &value)) + return false; + + // Title + base::CodepageToWide(line.substr(tag_end, end - tag_end), charset.c_str(), + base::OnStringConversionError::SKIP, title); + HTMLUnescape(title); + + // URL + if (GetAttribute(attribute_list, kHrefAttribute, &value)) { + std::wstring w_url; + base::CodepageToWide(value, charset.c_str(), + base::OnStringConversionError::SKIP, &w_url); + HTMLUnescape(&w_url); + + string16 url16 = WideToUTF16Hack(w_url); + + *url = GURL(url16); + } + + // Favicon + if (GetAttribute(attribute_list, kIconAttribute, &value)) + *favicon = GURL(value); + + // Keyword + if (GetAttribute(attribute_list, kShortcutURLAttribute, &value)) { + base::CodepageToWide(value, charset.c_str(), + base::OnStringConversionError::SKIP, shortcut); + HTMLUnescape(shortcut); + } + + // Add date + if (GetAttribute(attribute_list, kAddDateAttribute, &value)) { + int64 time = StringToInt64(value); + // Upper bound it at 32 bits. + if (0 < time && time < (1LL << 32)) + *add_date = Time::FromTimeT(time); + } + + // Post data. + if (GetAttribute(attribute_list, kPostDataAttribute, &value)) { + base::CodepageToWide(value, charset.c_str(), + base::OnStringConversionError::SKIP, post_data); + HTMLUnescape(post_data); + } + + return true; +} + +// static +bool Firefox2Importer::GetAttribute(const std::string& attribute_list, + const std::string& attribute, + std::string* value) { + const char kQuote[] = "\""; + + size_t begin = attribute_list.find(attribute + "=" + kQuote); + if (begin == std::string::npos) + return false; // Can't find the attribute. + + begin = attribute_list.find(kQuote, begin) + 1; + + size_t end = begin + 1; + while (end < attribute_list.size()) { + if (attribute_list[end] == '"' && + attribute_list[end - 1] != '\\') { + break; + } + end++; + } + + if (end == attribute_list.size()) + return false; // The value is not quoted. + + *value = attribute_list.substr(begin, end - begin); + return true; +} + +// static +void Firefox2Importer::HTMLUnescape(std::wstring *text) { + string16 text16 = WideToUTF16Hack(*text); + ReplaceSubstringsAfterOffset( + &text16, 0, ASCIIToUTF16("<"), ASCIIToUTF16("<")); + ReplaceSubstringsAfterOffset( + &text16, 0, ASCIIToUTF16(">"), ASCIIToUTF16(">")); + ReplaceSubstringsAfterOffset( + &text16, 0, ASCIIToUTF16("&"), ASCIIToUTF16("&")); + ReplaceSubstringsAfterOffset( + &text16, 0, ASCIIToUTF16("""), ASCIIToUTF16("\"")); + ReplaceSubstringsAfterOffset( + &text16, 0, ASCIIToUTF16("'"), ASCIIToUTF16("\'")); + text->assign(UTF16ToWideHack(text16)); +} + +// static +void Firefox2Importer::FindXMLFilesInDir( + const FilePath& dir, + std::vector<FilePath>* xml_files) { + file_util::FileEnumerator file_enum(dir, false, + file_util::FileEnumerator::FILES, + FILE_PATH_LITERAL("*.xml")); + FilePath file(file_enum.Next()); + while (!file.empty()) { + xml_files->push_back(file); + file = file_enum.Next(); + } +} + +// static +void Firefox2Importer::DataURLToFaviconUsage( + const GURL& link_url, + const GURL& favicon_data, + std::vector<history::ImportedFavIconUsage>* favicons) { + if (!link_url.is_valid() || !favicon_data.is_valid() || + !favicon_data.SchemeIs(chrome::kDataScheme)) + return; + + // Parse the data URL. + std::string mime_type, char_set, data; + if (!net::DataURL::Parse(favicon_data, &mime_type, &char_set, &data) || + data.empty()) + return; + + history::ImportedFavIconUsage usage; + if (!ReencodeFavicon(reinterpret_cast<const unsigned char*>(&data[0]), + data.size(), &usage.png_data)) + return; // Unable to decode. + + // We need to make up a URL for the favicon. We use a version of the page's + // URL so that we can be sure it will not collide. + usage.favicon_url = GURL(std::string("made-up-favicon:") + link_url.spec()); + + // We only have one URL per favicon for Firefox 2 bookmarks. + usage.urls.insert(link_url); + + favicons->push_back(usage); +} diff --git a/chrome/browser/importer/firefox2_importer.h b/chrome/browser/importer/firefox2_importer.h new file mode 100644 index 0000000..c320e38 --- /dev/null +++ b/chrome/browser/importer/firefox2_importer.h @@ -0,0 +1,130 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_FIREFOX2_IMPORTER_H_ +#define CHROME_BROWSER_IMPORTER_FIREFOX2_IMPORTER_H_ + +#include <set> + +#include "base/file_path.h" +#include "base/gtest_prod_util.h" +#include "chrome/browser/importer/importer.h" +#include "chrome/browser/importer/importer_data_types.h" + +class TemplateURL; + +// Importer for Mozilla Firefox 2. +class Firefox2Importer : public Importer { + public: + Firefox2Importer(); + + // Importer methods. + virtual void StartImport(importer::ProfileInfo profile_info, + uint16 items, + ImporterBridge* bridge); + + // Loads the default bookmarks in the Firefox installed at |firefox_app_path|, + // and stores their locations in |urls|. + static void LoadDefaultBookmarks(const FilePath& firefox_app_path, + std::set<GURL> *urls); + + // Creates a TemplateURL with the |keyword| and |url|. |title| may be empty. + // This function transfers ownership of the created TemplateURL to the caller. + static TemplateURL* CreateTemplateURL(const std::wstring& title, + const std::wstring& keyword, + const GURL& url); + + // Imports the bookmarks from the specified file. |template_urls| and + // |favicons| may be null, in which case TemplateURLs and favicons are + // not parsed. Any bookmarks in |default_urls| are ignored. + static void ImportBookmarksFile( + const FilePath& file_path, + const std::set<GURL>& default_urls, + bool import_to_bookmark_bar, + const std::wstring& first_folder_name, + Importer* importer, + std::vector<ProfileWriter::BookmarkEntry>* bookmarks, + std::vector<TemplateURL*>* template_urls, + std::vector<history::ImportedFavIconUsage>* favicons); + + private: + FRIEND_TEST_ALL_PREFIXES(FirefoxImporterTest, Firefox2BookmarkParse); + FRIEND_TEST_ALL_PREFIXES(FirefoxImporterTest, Firefox2CookesParse); + + virtual ~Firefox2Importer(); + + void ImportBookmarks(); + void ImportPasswords(); + void ImportHistory(); + void ImportSearchEngines(); + // Import the user's home page, unless it is set to default home page as + // defined in browserconfig.properties. + void ImportHomepage(); + + // Fills |files| with the paths to the files containing the search engine + // descriptions. + void GetSearchEnginesXMLFiles(std::vector<FilePath>* files); + + // Helper methods for parsing bookmark file. + // Firefox 2 saves its bookmarks in a html file. We are interested in the + // bookmarks and folders, and their hierarchy. A folder starts with a + // heading tag, which contains it title. All bookmarks and sub-folders is + // following, and bracketed by a <DL> tag: + // <DT><H3 PERSONAL_TOOLBAR_FOLDER="true" ...>title</H3> + // <DL><p> + // ... container ... + // </DL><p> + // And a bookmark is presented by a <A> tag: + // <DT><A HREF="url" SHORTCUTURL="shortcut" ADD_DATE="11213014"...>name</A> + // Reference: http://kb.mozillazine.org/Bookmarks.html + static bool ParseCharsetFromLine(const std::string& line, + std::string* charset); + static bool ParseFolderNameFromLine(const std::string& line, + const std::string& charset, + std::wstring* folder_name, + bool* is_toolbar_folder); + // See above, this will also put the data: URL of the favicon into *favicon + // if there is a favicon given. |post_data| is set for POST base keywords to + // the contents of the actual POST (with %s for the search term). + static bool ParseBookmarkFromLine(const std::string& line, + const std::string& charset, + std::wstring* title, + GURL* url, + GURL* favicon, + std::wstring* shortcut, + base::Time* add_date, + std::wstring* post_data); + + // Fetches the given attribute value from the |tag|. Returns true if + // successful, and |value| will contain the value. + static bool GetAttribute(const std::string& tag, + const std::string& attribute, + std::string* value); + + // There are some characters in html file will be escaped: + // '<', '>', '"', '\', '&' + // Un-escapes them if the bookmark name has those characters. + static void HTMLUnescape(std::wstring* text); + + // Fills |xml_files| with the file with an xml extension found under |dir|. + static void FindXMLFilesInDir(const FilePath& dir, + std::vector<FilePath>* xml_files); + + // Given the URL of a page and a favicon data URL, adds an appropriate record + // to the given favicon usage vector. Will do nothing if the favicon is not + // valid. + static void DataURLToFaviconUsage( + const GURL& link_url, + const GURL& favicon_data, + std::vector<history::ImportedFavIconUsage>* favicons); + + FilePath source_path_; + FilePath app_path_; + // If true, we only parse the bookmarks.html file specified as source_path_. + bool parsing_bookmarks_html_file_; + + DISALLOW_COPY_AND_ASSIGN(Firefox2Importer); +}; + +#endif // CHROME_BROWSER_IMPORTER_FIREFOX2_IMPORTER_H_ diff --git a/chrome/browser/importer/firefox3_importer.cc b/chrome/browser/importer/firefox3_importer.cc new file mode 100644 index 0000000..2b95cc3 --- /dev/null +++ b/chrome/browser/importer/firefox3_importer.cc @@ -0,0 +1,532 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/firefox3_importer.h" + +#include <set> + +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/scoped_ptr.h" +#include "base/stl_util-inl.h" +#include "base/string_util.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/browser/importer/firefox2_importer.h" +#include "chrome/browser/importer/firefox_importer_utils.h" +#include "chrome/browser/importer/importer_bridge.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "chrome/browser/importer/nss_decryptor.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/common/time_format.h" +#include "chrome/common/sqlite_utils.h" +#include "grit/generated_resources.h" +#include "webkit/glue/password_form.h" + +using base::Time; +using importer::BOOKMARKS_HTML; +using importer::FAVORITES; +using importer::HISTORY; +using importer::HOME_PAGE; +using importer::PASSWORDS; +using importer::ProfileInfo; +using importer::SEARCH_ENGINES; +using webkit_glue::PasswordForm; + +void Firefox3Importer::StartImport(importer::ProfileInfo profile_info, + uint16 items, + ImporterBridge* bridge) { + bridge_ = bridge; + source_path_ = profile_info.source_path; + app_path_ = profile_info.app_path; + + + // The order here is important! + bridge_->NotifyStarted(); + if ((items & importer::HOME_PAGE) && !cancelled()) + ImportHomepage(); // Doesn't have a UI item. + + // Note history should be imported before bookmarks because bookmark import + // will also import favicons and we store favicon for a URL only if the URL + // exist in history or bookmarks. + if ((items & importer::HISTORY) && !cancelled()) { + bridge_->NotifyItemStarted(importer::HISTORY); + ImportHistory(); + bridge_->NotifyItemEnded(importer::HISTORY); + } + + if ((items & importer::FAVORITES) && !cancelled()) { + bridge_->NotifyItemStarted(importer::FAVORITES); + ImportBookmarks(); + bridge_->NotifyItemEnded(importer::FAVORITES); + } + if ((items & importer::SEARCH_ENGINES) && !cancelled()) { + bridge_->NotifyItemStarted(importer::SEARCH_ENGINES); + ImportSearchEngines(); + bridge_->NotifyItemEnded(importer::SEARCH_ENGINES); + } + if ((items & importer::PASSWORDS) && !cancelled()) { + bridge_->NotifyItemStarted(importer::PASSWORDS); + ImportPasswords(); + bridge_->NotifyItemEnded(importer::PASSWORDS); + } + bridge_->NotifyEnded(); +} + +void Firefox3Importer::ImportHistory() { + FilePath file = source_path_.AppendASCII("places.sqlite"); + if (!file_util::PathExists(file)) + return; + + sqlite3* sqlite; + if (sqlite3_open(WideToUTF8(file.ToWStringHack()).c_str(), + &sqlite) != SQLITE_OK) { + return; + } + sqlite_utils::scoped_sqlite_db_ptr db(sqlite); + + SQLStatement s; + // |visit_type| represent the transition type of URLs (typed, click, + // redirect, bookmark, etc.) We eliminate some URLs like sub-frames and + // redirects, since we don't want them to appear in history. + // Firefox transition types are defined in: + // toolkit/components/places/public/nsINavHistoryService.idl + const char* stmt = "SELECT h.url, h.title, h.visit_count, " + "h.hidden, h.typed, v.visit_date " + "FROM moz_places h JOIN moz_historyvisits v " + "ON h.id = v.place_id " + "WHERE v.visit_type <= 3"; + + if (s.prepare(db.get(), stmt) != SQLITE_OK) + return; + + std::vector<history::URLRow> rows; + while (s.step() == SQLITE_ROW && !cancelled()) { + GURL url(s.column_string(0)); + + // Filter out unwanted URLs. + if (!CanImportURL(url)) + continue; + + history::URLRow row(url); + row.set_title(UTF8ToUTF16(s.column_string(1))); + row.set_visit_count(s.column_int(2)); + row.set_hidden(s.column_int(3) == 1); + row.set_typed_count(s.column_int(4)); + row.set_last_visit(Time::FromTimeT(s.column_int64(5)/1000000)); + + rows.push_back(row); + } + if (!rows.empty() && !cancelled()) { + bridge_->SetHistoryItems(rows); + } +} + +void Firefox3Importer::ImportBookmarks() { + FilePath file = source_path_.AppendASCII("places.sqlite"); + if (!file_util::PathExists(file)) + return; + + sqlite3* sqlite; + if (sqlite3_open(WideToUTF8(file.ToWStringHack()).c_str(), + &sqlite) != SQLITE_OK) { + return; + } + sqlite_utils::scoped_sqlite_db_ptr db(sqlite); + + // Get the bookmark folders that we are interested in. + int toolbar_folder_id = -1; + int menu_folder_id = -1; + int unsorted_folder_id = -1; + LoadRootNodeID(db.get(), &toolbar_folder_id, &menu_folder_id, + &unsorted_folder_id); + + // Load livemark IDs. + std::set<int> livemark_id; + LoadLivemarkIDs(db.get(), &livemark_id); + + // Load the default bookmarks. Its storage is the same as Firefox 2. + std::set<GURL> default_urls; + Firefox2Importer::LoadDefaultBookmarks(app_path_, &default_urls); + + BookmarkList list; + GetTopBookmarkFolder(db.get(), toolbar_folder_id, &list); + GetTopBookmarkFolder(db.get(), menu_folder_id, &list); + GetTopBookmarkFolder(db.get(), unsorted_folder_id, &list); + size_t count = list.size(); + for (size_t i = 0; i < count; ++i) + GetWholeBookmarkFolder(db.get(), &list, i); + + std::vector<ProfileWriter::BookmarkEntry> bookmarks; + std::vector<TemplateURL*> template_urls; + FaviconMap favicon_map; + + // TODO(jcampan): http://b/issue?id=1196285 we do not support POST based + // keywords yet. We won't include them in the list. + std::set<int> post_keyword_ids; + SQLStatement s; + const char* stmt = "SELECT b.id FROM moz_bookmarks b " + "INNER JOIN moz_items_annos ia ON ia.item_id = b.id " + "INNER JOIN moz_anno_attributes aa ON ia.anno_attribute_id = aa.id " + "WHERE aa.name = 'bookmarkProperties/POSTData'"; + if (s.prepare(db.get(), stmt) == SQLITE_OK) { + while (s.step() == SQLITE_ROW && !cancelled()) + post_keyword_ids.insert(s.column_int(0)); + } else { + NOTREACHED(); + } + + std::wstring firefox_folder = + bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP_FROM_FIREFOX); + for (size_t i = 0; i < list.size(); ++i) { + BookmarkItem* item = list[i]; + + // The type of bookmark items is 1. + if (item->type != 1) + continue; + + // Skip the default bookmarks and unwanted URLs. + if (!CanImportURL(item->url) || + default_urls.find(item->url) != default_urls.end() || + post_keyword_ids.find(item->id) != post_keyword_ids.end()) + continue; + + // Find the bookmark path by tracing their links to parent folders. + std::vector<std::wstring> path; + BookmarkItem* child = item; + bool found_path = false; + bool is_in_toolbar = false; + while (child->parent >= 0) { + BookmarkItem* parent = list[child->parent]; + if (parent->id == toolbar_folder_id) { + // This bookmark entry should be put in the bookmark bar. + // But we put it in the Firefox group after first run, so + // that do not mess up the bookmark bar. + if (import_to_bookmark_bar()) { + is_in_toolbar = true; + } else { + path.insert(path.begin(), parent->title); + path.insert(path.begin(), firefox_folder); + } + found_path = true; + break; + } else if (parent->id == menu_folder_id || + parent->id == unsorted_folder_id) { + // After the first run, the item will be placed in a folder in + // the "Other bookmarks". + if (!import_to_bookmark_bar()) + path.insert(path.begin(), firefox_folder); + found_path = true; + break; + } else if (livemark_id.find(parent->id) != livemark_id.end()) { + // If the entry is under a livemark folder, we don't import it. + break; + } + path.insert(path.begin(), parent->title); + child = parent; + } + + if (!found_path) + continue; + + ProfileWriter::BookmarkEntry entry; + entry.creation_time = item->date_added; + entry.title = item->title; + entry.url = item->url; + entry.path = path; + entry.in_toolbar = is_in_toolbar; + + bookmarks.push_back(entry); + + if (item->favicon) + favicon_map[item->favicon].insert(item->url); + + // This bookmark has a keyword, we import it to our TemplateURL model. + TemplateURL* t_url = Firefox2Importer::CreateTemplateURL( + item->title, UTF8ToWide(item->keyword), item->url); + if (t_url) + template_urls.push_back(t_url); + } + + STLDeleteContainerPointers(list.begin(), list.end()); + + // Write into profile. + if (!bookmarks.empty() && !cancelled()) { + const std::wstring& first_folder_name = + bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP_FROM_FIREFOX); + int options = 0; + if (import_to_bookmark_bar()) + options = ProfileWriter::IMPORT_TO_BOOKMARK_BAR; + bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options); + } + if (!template_urls.empty() && !cancelled()) { + bridge_->SetKeywords(template_urls, -1, false); + } else { + STLDeleteContainerPointers(template_urls.begin(), template_urls.end()); + } + if (!favicon_map.empty() && !cancelled()) { + std::vector<history::ImportedFavIconUsage> favicons; + LoadFavicons(db.get(), favicon_map, &favicons); + bridge_->SetFavIcons(favicons); + } +} + +void Firefox3Importer::ImportPasswords() { + // Initializes NSS3. + NSSDecryptor decryptor; + if (!decryptor.Init(source_path_.ToWStringHack(), + source_path_.ToWStringHack()) && + !decryptor.Init(app_path_.ToWStringHack(), + source_path_.ToWStringHack())) { + return; + } + + std::vector<PasswordForm> forms; + FilePath source_path = source_path_; + FilePath file = source_path.AppendASCII("signons.sqlite"); + if (file_util::PathExists(file)) { + // Since Firefox 3.1, passwords are in signons.sqlite db. + decryptor.ReadAndParseSignons(file, &forms); + } else { + // Firefox 3.0 uses signons3.txt to store the passwords. + file = source_path.AppendASCII("signons3.txt"); + if (!file_util::PathExists(file)) + file = source_path.AppendASCII("signons2.txt"); + + std::string content; + file_util::ReadFileToString(file, &content); + decryptor.ParseSignons(content, &forms); + } + + if (!cancelled()) { + for (size_t i = 0; i < forms.size(); ++i) { + bridge_->SetPasswordForm(forms[i]); + } + } +} + +void Firefox3Importer::ImportSearchEngines() { + std::vector<FilePath> files; + GetSearchEnginesXMLFiles(&files); + + std::vector<TemplateURL*> search_engines; + ParseSearchEnginesFromXMLFiles(files, &search_engines); + int default_index = + GetFirefoxDefaultSearchEngineIndex(search_engines, source_path_); + bridge_->SetKeywords(search_engines, default_index, true); +} + +void Firefox3Importer::ImportHomepage() { + GURL home_page = GetHomepage(source_path_); + if (home_page.is_valid() && !IsDefaultHomepage(home_page, app_path_)) { + bridge_->AddHomePage(home_page); + } +} + +void Firefox3Importer::GetSearchEnginesXMLFiles( + std::vector<FilePath>* files) { + FilePath file = source_path_.AppendASCII("search.sqlite"); + if (!file_util::PathExists(file)) + return; + + sqlite3* sqlite; + if (sqlite3_open(WideToUTF8(file.ToWStringHack()).c_str(), + &sqlite) != SQLITE_OK) { + return; + } + sqlite_utils::scoped_sqlite_db_ptr db(sqlite); + + SQLStatement s; + const char* stmt = "SELECT engineid FROM engine_data " + "WHERE engineid NOT IN " + "(SELECT engineid FROM engine_data " + "WHERE name='hidden') " + "ORDER BY value ASC"; + + if (s.prepare(db.get(), stmt) != SQLITE_OK) + return; + + FilePath app_path = app_path_.AppendASCII("searchplugins"); + FilePath profile_path = source_path_.AppendASCII("searchplugins"); + + // Firefox doesn't store a search engine in its sqlite database unless + // the user has changed the default definition of engine. So we get search + // engines from sqlite db as well as from file system. + if (s.step() == SQLITE_ROW) { + const std::wstring kAppPrefix = L"[app]/"; + const std::wstring kProfilePrefix = L"[profile]/"; + do { + FilePath file; + std::wstring engine = UTF8ToWide(s.column_string(0)); + + // The string contains [app]/<name>.xml or [profile]/<name>.xml where + // the [app] and [profile] need to be replaced with the actual app or + // profile path. + size_t index = engine.find(kAppPrefix); + if (index != std::wstring::npos) { + // Remove '[app]/'. + file = app_path.Append(FilePath::FromWStringHack( + engine.substr(index + kAppPrefix.length()))); + } else if ((index = engine.find(kProfilePrefix)) != std::wstring::npos) { + // Remove '[profile]/'. + file = profile_path.Append( + FilePath::FromWStringHack( + engine.substr(index + kProfilePrefix.length()))); + } else { + // Looks like absolute path to the file. + file = FilePath::FromWStringHack(engine); + } + files->push_back(file); + } while (s.step() == SQLITE_ROW && !cancelled()); + } + + // Get search engine definition from file system. + file_util::FileEnumerator engines(app_path, false, + file_util::FileEnumerator::FILES); + for (FilePath engine_path = engines.Next(); !engine_path.value().empty(); + engine_path = engines.Next()) { + files->push_back(engine_path); + } +} + +void Firefox3Importer::LoadRootNodeID(sqlite3* db, + int* toolbar_folder_id, + int* menu_folder_id, + int* unsorted_folder_id) { + const char kToolbarFolderName[] = "toolbar"; + const char kMenuFolderName[] = "menu"; + const char kUnsortedFolderName[] = "unfiled"; + + SQLStatement s; + const char* stmt = "SELECT root_name, folder_id FROM moz_bookmarks_roots"; + if (s.prepare(db, stmt) != SQLITE_OK) + return; + + while (s.step() == SQLITE_ROW) { + std::string folder = s.column_string(0); + int id = s.column_int(1); + if (folder == kToolbarFolderName) + *toolbar_folder_id = id; + else if (folder == kMenuFolderName) + *menu_folder_id = id; + else if (folder == kUnsortedFolderName) + *unsorted_folder_id = id; + } +} + +void Firefox3Importer::LoadLivemarkIDs(sqlite3* db, + std::set<int>* livemark) { + const char kFeedAnnotation[] = "livemark/feedURI"; + livemark->clear(); + + SQLStatement s; + const char* stmt = "SELECT b.item_id " + "FROM moz_anno_attributes a " + "JOIN moz_items_annos b ON a.id = b.anno_attribute_id " + "WHERE a.name = ? "; + if (s.prepare(db, stmt) != SQLITE_OK) + return; + + s.bind_string(0, kFeedAnnotation); + while (s.step() == SQLITE_ROW && !cancelled()) + livemark->insert(s.column_int(0)); +} + +void Firefox3Importer::GetTopBookmarkFolder(sqlite3* db, int folder_id, + BookmarkList* list) { + SQLStatement s; + const char* stmt = "SELECT b.title " + "FROM moz_bookmarks b " + "WHERE b.type = 2 AND b.id = ? " + "ORDER BY b.position"; + if (s.prepare(db, stmt) != SQLITE_OK) + return; + + s.bind_int(0, folder_id); + if (s.step() == SQLITE_ROW) { + BookmarkItem* item = new BookmarkItem; + item->parent = -1; // The top level folder has no parent. + item->id = folder_id; + item->title = s.column_wstring(0); + item->type = 2; + item->favicon = 0; + list->push_back(item); + } +} + +void Firefox3Importer::GetWholeBookmarkFolder(sqlite3* db, BookmarkList* list, + size_t position) { + if (position >= list->size()) { + NOTREACHED(); + return; + } + + SQLStatement s; + const char* stmt = "SELECT b.id, h.url, COALESCE(b.title, h.title), " + "b.type, k.keyword, b.dateAdded, h.favicon_id " + "FROM moz_bookmarks b " + "LEFT JOIN moz_places h ON b.fk = h.id " + "LEFT JOIN moz_keywords k ON k.id = b.keyword_id " + "WHERE b.type IN (1,2) AND b.parent = ? " + "ORDER BY b.position"; + if (s.prepare(db, stmt) != SQLITE_OK) + return; + + s.bind_int(0, (*list)[position]->id); + BookmarkList temp_list; + while (s.step() == SQLITE_ROW) { + BookmarkItem* item = new BookmarkItem; + item->parent = static_cast<int>(position); + item->id = s.column_int(0); + item->url = GURL(s.column_string(1)); + item->title = s.column_wstring(2); + item->type = s.column_int(3); + item->keyword = s.column_string(4); + item->date_added = Time::FromTimeT(s.column_int64(5)/1000000); + item->favicon = s.column_int64(6); + + temp_list.push_back(item); + } + + // Appends all items to the list. + for (BookmarkList::iterator i = temp_list.begin(); + i != temp_list.end(); ++i) { + list->push_back(*i); + // Recursive add bookmarks in sub-folders. + if ((*i)->type == 2) + GetWholeBookmarkFolder(db, list, list->size() - 1); + } +} + +void Firefox3Importer::LoadFavicons( + sqlite3* db, + const FaviconMap& favicon_map, + std::vector<history::ImportedFavIconUsage>* favicons) { + SQLStatement s; + const char* stmt = "SELECT url, data FROM moz_favicons WHERE id=?"; + if (s.prepare(db, stmt) != SQLITE_OK) + return; + + for (FaviconMap::const_iterator i = favicon_map.begin(); + i != favicon_map.end(); ++i) { + s.bind_int64(0, i->first); + if (s.step() == SQLITE_ROW) { + history::ImportedFavIconUsage usage; + + usage.favicon_url = GURL(s.column_string(0)); + if (!usage.favicon_url.is_valid()) + continue; // Don't bother importing favicons with invalid URLs. + + std::vector<unsigned char> data; + if (!s.column_blob_as_vector(1, &data) || data.empty()) + continue; // Data definitely invalid. + + if (!ReencodeFavicon(&data[0], data.size(), &usage.png_data)) + continue; // Unable to decode. + + usage.urls = i->second; + favicons->push_back(usage); + } + s.reset(); + } +} diff --git a/chrome/browser/importer/firefox3_importer.h b/chrome/browser/importer/firefox3_importer.h new file mode 100644 index 0000000..f8bfa2f --- /dev/null +++ b/chrome/browser/importer/firefox3_importer.h @@ -0,0 +1,87 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_FIREFOX3_IMPORTER_H_ +#define CHROME_BROWSER_IMPORTER_FIREFOX3_IMPORTER_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "chrome/browser/importer/importer.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "googleurl/src/gurl.h" + +struct sqlite3; + +// Importer for Mozilla Firefox 3. +// Firefox 3 stores its persistent information in a new system called places. +// http://wiki.mozilla.org/Places +class Firefox3Importer : public Importer { + public: + Firefox3Importer() { } + + // Importer methods. + virtual void StartImport(importer::ProfileInfo profile_info, + uint16 items, + ImporterBridge* bridge); + + private: + typedef std::map<int64, std::set<GURL> > FaviconMap; + + virtual ~Firefox3Importer() { } + + void ImportBookmarks(); + void ImportPasswords(); + void ImportHistory(); + void ImportSearchEngines(); + // Import the user's home page, unless it is set to default home page as + // defined in browserconfig.properties. + void ImportHomepage(); + void GetSearchEnginesXMLFiles(std::vector<FilePath>* files); + + // The struct stores the information about a bookmark item. + struct BookmarkItem { + int parent; + int id; + GURL url; + std::wstring title; + int type; + std::string keyword; + base::Time date_added; + int64 favicon; + }; + typedef std::vector<BookmarkItem*> BookmarkList; + + // Gets the specific IDs of bookmark root node from |db|. + void LoadRootNodeID(sqlite3* db, int* toolbar_folder_id, + int* menu_folder_id, int* unsorted_folder_id); + + // Loads all livemark IDs from database |db|. + void LoadLivemarkIDs(sqlite3* db, std::set<int>* livemark); + + // Gets the bookmark folder with given ID, and adds the entry in |list| + // if successful. + void GetTopBookmarkFolder(sqlite3* db, int folder_id, BookmarkList* list); + + // Loads all children of the given folder, and appends them to the |list|. + void GetWholeBookmarkFolder(sqlite3* db, BookmarkList* list, + size_t position); + + // Loads the favicons given in the map from the database, loads the data, + // and converts it into FaviconUsage structures. + void LoadFavicons(sqlite3* db, + const FaviconMap& favicon_map, + std::vector<history::ImportedFavIconUsage>* favicons); + + FilePath source_path_; + FilePath app_path_; + + DISALLOW_COPY_AND_ASSIGN(Firefox3Importer); +}; + +#endif // CHROME_BROWSER_IMPORTER_FIREFOX3_IMPORTER_H_ diff --git a/chrome/browser/importer/firefox_importer_unittest.cc b/chrome/browser/importer/firefox_importer_unittest.cc new file mode 100644 index 0000000..42c23ac --- /dev/null +++ b/chrome/browser/importer/firefox_importer_unittest.cc @@ -0,0 +1,178 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/importer/firefox2_importer.h" +#include "chrome/browser/importer/firefox_importer_unittest_utils.h" +#include "chrome/browser/importer/firefox_importer_utils.h" +#include "chrome/browser/importer/nss_decryptor.h" +#include "chrome/common/chrome_paths.h" + +using base::Time; + +// The following 2 tests require the use of the NSSDecryptor, on OSX this needs +// to run in a separate process, so we use a proxy object so we can share the +// same test between platforms. +TEST(FirefoxImporterTest, Firefox2NSS3Decryptor) { + FilePath nss_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &nss_path)); +#ifdef OS_MACOSX + nss_path = nss_path.AppendASCII("firefox2_nss_mac"); +#else + nss_path = nss_path.AppendASCII("firefox2_nss"); +#endif // !OS_MACOSX + FilePath db_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &db_path)); + db_path = db_path.AppendASCII("firefox2_profile"); + + FFUnitTestDecryptorProxy decryptor_proxy; + ASSERT_TRUE(decryptor_proxy.Setup(nss_path.ToWStringHack())); + + EXPECT_TRUE(decryptor_proxy.DecryptorInit(nss_path.ToWStringHack(), + db_path.ToWStringHack())); + EXPECT_EQ(ASCIIToUTF16("hello"), + decryptor_proxy.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECBJ" + "M63MpT9rtBAjMCm7qo/EhlA==")); + // Test UTF-16 encoding. + EXPECT_EQ(WideToUTF16(L"\x4E2D"), + decryptor_proxy.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECN9" + "OQ5ZFmhb8BAiFo1Z+fUvaIQ==")); +} + +TEST(FirefoxImporterTest, Firefox3NSS3Decryptor) { + FilePath nss_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &nss_path)); +#ifdef OS_MACOSX + nss_path = nss_path.AppendASCII("firefox3_nss_mac"); +#else + nss_path = nss_path.AppendASCII("firefox3_nss"); +#endif // !OS_MACOSX + FilePath db_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &db_path)); + db_path = db_path.AppendASCII("firefox3_profile"); + + FFUnitTestDecryptorProxy decryptor_proxy; + ASSERT_TRUE(decryptor_proxy.Setup(nss_path.ToWStringHack())); + + EXPECT_TRUE(decryptor_proxy.DecryptorInit(nss_path.ToWStringHack(), + db_path.ToWStringHack())); + EXPECT_EQ(ASCIIToUTF16("hello"), + decryptor_proxy.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECKa" + "jtRg4qFSHBAhv9luFkXgDJA==")); + // Test UTF-16 encoding. + EXPECT_EQ(WideToUTF16(L"\x4E2D"), + decryptor_proxy.Decrypt("MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECLW" + "qqiccfQHWBAie74hxnULxlw==")); +} + +TEST(FirefoxImporterTest, Firefox2BookmarkParse) { + bool result; + + // Tests charset. + std::string charset; + result = Firefox2Importer::ParseCharsetFromLine( + "<META HTTP-EQUIV=\"Content-Type\" " + "CONTENT=\"text/html; charset=UTF-8\">", + &charset); + EXPECT_TRUE(result); + EXPECT_EQ("UTF-8", charset); + + // Escaped characters in name. + std::wstring folder_name; + bool is_toolbar_folder; + result = Firefox2Importer::ParseFolderNameFromLine( + "<DT><H3 ADD_DATE=\"1207558707\" >< >" + " & " ' \\ /</H3>", + charset, &folder_name, &is_toolbar_folder); + EXPECT_TRUE(result); + EXPECT_EQ(L"< > & \" ' \\ /", folder_name); + EXPECT_EQ(false, is_toolbar_folder); + + // Empty name and toolbar folder attribute. + result = Firefox2Importer::ParseFolderNameFromLine( + "<DT><H3 PERSONAL_TOOLBAR_FOLDER=\"true\"></H3>", + charset, &folder_name, &is_toolbar_folder); + EXPECT_TRUE(result); + EXPECT_EQ(L"", folder_name); + EXPECT_EQ(true, is_toolbar_folder); + + // Unicode characters in title and shortcut. + std::wstring title; + GURL url, favicon; + std::wstring shortcut; + std::wstring post_data; + Time add_date; + result = Firefox2Importer::ParseBookmarkFromLine( + "<DT><A HREF=\"http://chinese.site.cn/path?query=1#ref\" " + "SHORTCUTURL=\"\xE4\xB8\xAD\">\xE4\xB8\xAD\xE6\x96\x87</A>", + charset, &title, &url, &favicon, &shortcut, &add_date, &post_data); + EXPECT_TRUE(result); + EXPECT_EQ(L"\x4E2D\x6587", title); + EXPECT_EQ("http://chinese.site.cn/path?query=1#ref", url.spec()); + EXPECT_EQ(L"\x4E2D", shortcut); + EXPECT_EQ(L"", post_data); + EXPECT_TRUE(Time() == add_date); + + // No shortcut, and url contains %22 ('"' character). + result = Firefox2Importer::ParseBookmarkFromLine( + "<DT><A HREF=\"http://domain.com/?q=%22<>%22\">name</A>", + charset, &title, &url, &favicon, &shortcut, &add_date, &post_data); + EXPECT_TRUE(result); + EXPECT_EQ(L"name", title); + EXPECT_EQ("http://domain.com/?q=%22%3C%3E%22", url.spec()); + EXPECT_EQ(L"", shortcut); + EXPECT_EQ(L"", post_data); + EXPECT_TRUE(Time() == add_date); + + result = Firefox2Importer::ParseBookmarkFromLine( + "<DT><A HREF=\"http://domain.com/?g="\"\">name</A>", + charset, &title, &url, &favicon, &shortcut, &add_date, &post_data); + EXPECT_TRUE(result); + EXPECT_EQ(L"name", title); + EXPECT_EQ("http://domain.com/?g=%22", url.spec()); + EXPECT_EQ(L"", shortcut); + EXPECT_EQ(L"", post_data); + EXPECT_TRUE(Time() == add_date); + + // Creation date. + result = Firefox2Importer::ParseBookmarkFromLine( + "<DT><A HREF=\"http://site/\" ADD_DATE=\"1121301154\">name</A>", + charset, &title, &url, &favicon, &shortcut, &add_date, &post_data); + EXPECT_TRUE(result); + EXPECT_EQ(L"name", title); + EXPECT_EQ(GURL("http://site/"), url); + EXPECT_EQ(L"", shortcut); + EXPECT_EQ(L"", post_data); + EXPECT_TRUE(Time::FromTimeT(1121301154) == add_date); + + // Post-data + result = Firefox2Importer::ParseBookmarkFromLine( + "<DT><A HREF=\"http://localhost:8080/test/hello.html\" ADD_DATE=\"" + "1212447159\" LAST_VISIT=\"1212447251\" LAST_MODIFIED=\"1212447248\"" + "SHORTCUTURL=\"post\" ICON=\"data:\" POST_DATA=\"lname%3D%25s\"" + "LAST_CHARSET=\"UTF-8\" ID=\"rdf:#$weKaR3\">Test Post keyword</A>", + charset, &title, &url, &favicon, &shortcut, &add_date, &post_data); + EXPECT_TRUE(result); + EXPECT_EQ(L"Test Post keyword", title); + EXPECT_EQ("http://localhost:8080/test/hello.html", url.spec()); + EXPECT_EQ(L"post", shortcut); + EXPECT_EQ(L"lname%3D%25s", post_data); + EXPECT_TRUE(Time::FromTimeT(1212447159) == add_date); + + // Invalid case. + result = Firefox2Importer::ParseBookmarkFromLine( + "<DT><A HREF=\"http://domain.com/?q=%22", + charset, &title, &url, &favicon, &shortcut, &add_date, &post_data); + EXPECT_FALSE(result); + EXPECT_EQ(L"", title); + EXPECT_EQ("", url.spec()); + EXPECT_EQ(L"", shortcut); + EXPECT_EQ(L"", post_data); + EXPECT_TRUE(Time() == add_date); +} diff --git a/chrome/browser/importer/firefox_importer_unittest_messages_internal.h b/chrome/browser/importer/firefox_importer_unittest_messages_internal.h new file mode 100644 index 0000000..68b2582 --- /dev/null +++ b/chrome/browser/importer/firefox_importer_unittest_messages_internal.h @@ -0,0 +1,33 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This header is meant to be included in multiple passes, hence no traditional +// header guard. +// See ipc_message_macros.h for explanation of the macros and passes. + +#include "ipc/ipc_message_macros.h" + +// Messages definitions for messages sent between the unit test binary and +// a child process by FFUnitTestDecryptorProxy. +IPC_BEGIN_MESSAGES(Test) + +// Server->Child: Initialize the decrytor with the following paramters. +IPC_MESSAGE_CONTROL2(Msg_Decryptor_Init, + std::wstring /* dll_path */, + std::wstring /* db_path */) +// Child->Server: Return paramter from init call. +IPC_MESSAGE_CONTROL1(Msg_Decryptor_InitReturnCode, + bool /* ret */) + +// Server->Child: Decrypt a given string. +IPC_MESSAGE_CONTROL1(Msg_Decrypt, + std::string /* crypt */) +// Child->Server: Decrypted String. +IPC_MESSAGE_CONTROL1(Msg_Decryptor_Response, + string16 /* unencrypted_str */) + +// Server->Child: Die. +IPC_MESSAGE_CONTROL0(Msg_Decryptor_Quit) + +IPC_END_MESSAGES(Test) diff --git a/chrome/browser/importer/firefox_importer_unittest_utils.h b/chrome/browser/importer/firefox_importer_unittest_utils.h new file mode 100644 index 0000000..3eb12d6 --- /dev/null +++ b/chrome/browser/importer/firefox_importer_unittest_utils.h @@ -0,0 +1,84 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_FIREFOX_IMPORTER_UNITTEST_UTILS_H_ +#define CHROME_BROWSER_IMPORTER_FIREFOX_IMPORTER_UNITTEST_UTILS_H_ + +#include "base/basictypes.h" +#include "base/process_util.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/importer/nss_decryptor.h" + +class FFDecryptorServerChannelListener; +namespace IPC { + class Channel; +} // namespace IPC +class MessageLoopForIO; + +// On OS X NSSDecryptor needs to run in a separate process. To allow us to use +// the same unit test on all platforms we use a proxy class which spawns a +// child process to do decryption on OS X, and calls through directly +// to NSSDecryptor on other platforms. +// On OS X: +// 2 IPC messages are sent for every method of NSSDecryptor, one containing the +// input arguments sent from Server->Child and one coming back containing +// the return argument e.g. +// +// -> Msg_Decryptor_Init(dll_path, db_path) +// <- Msg_Decryptor_InitReturnCode(bool) +class FFUnitTestDecryptorProxy { + public: + FFUnitTestDecryptorProxy(); + virtual ~FFUnitTestDecryptorProxy(); + + // Initialize a decryptor, returns true if the object was + // constructed successfully. + bool Setup(const std::wstring& nss_path); + + // This match the parallel functions in NSSDecryptor. + bool DecryptorInit(const std::wstring& dll_path, const std::wstring& db_path); + string16 Decrypt(const std::string& crypt); + + private: +#if defined(OS_MACOSX) + // Blocks until either a timeout is reached, or until the client process + // responds to an IPC message. + // Returns true if a reply was received successfully and false if the + // the operation timed out. + bool WaitForClientResponse(); + + base::ProcessHandle child_process_; + scoped_ptr<IPC::Channel> channel_; + scoped_ptr<FFDecryptorServerChannelListener> listener_; + scoped_ptr<MessageLoopForIO> message_loop_; +#else + NSSDecryptor decryptor_; +#endif // !OS_MACOSX + DISALLOW_COPY_AND_ASSIGN(FFUnitTestDecryptorProxy); +}; + +// On Non-OSX platforms FFUnitTestDecryptorProxy simply calls through to +// NSSDecryptor. +#if !defined(OS_MACOSX) +FFUnitTestDecryptorProxy::FFUnitTestDecryptorProxy() { +} + +FFUnitTestDecryptorProxy::~FFUnitTestDecryptorProxy() { +} + +bool FFUnitTestDecryptorProxy::Setup(const std::wstring& /* nss_path */) { + return true; +} + +bool FFUnitTestDecryptorProxy::DecryptorInit(const std::wstring& dll_path, + const std::wstring& db_path) { + return decryptor_.Init(dll_path, db_path); +} + +string16 FFUnitTestDecryptorProxy::Decrypt(const std::string& crypt) { + return decryptor_.Decrypt(crypt); +} +#endif // !OS_MACOSX + +#endif // CHROME_BROWSER_IMPORTER_FIREFOX_IMPORTER_UNITTEST_UTILS_H_ diff --git a/chrome/browser/importer/firefox_importer_unittest_utils_mac.cc b/chrome/browser/importer/firefox_importer_unittest_utils_mac.cc new file mode 100644 index 0000000..03dddc7 --- /dev/null +++ b/chrome/browser/importer/firefox_importer_unittest_utils_mac.cc @@ -0,0 +1,265 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/firefox_importer_unittest_utils.h" + +#include "base/command_line.h" +#include "base/debug_on_start.h" +#include "base/file_path.h" +#include "base/message_loop.h" +#include "chrome/browser/importer/firefox_importer_utils.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_descriptors.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_message_utils.h" +#include "ipc/ipc_switches.h" +#include "testing/multiprocess_func_list.h" + +// Definition of IPC Messages used for this test. +#define MESSAGES_INTERNAL_FILE \ + "chrome/browser/importer/firefox_importer_unittest_messages_internal.h" +#include "ipc/ipc_message_macros.h" + +namespace { + +// Name of IPC Channel to use for Server<-> Child Communications. +const char kTestChannelID[] = "T1"; + +// Launch the child process: +// |nss_path| - path to the NSS directory holding the decryption libraries. +// |channel| - IPC Channel to use for communication. +// |handle| - On return, the process handle to use to communicate with the +// child. +bool LaunchNSSDecrypterChildProcess(const std::wstring& nss_path, + const IPC::Channel& channel, base::ProcessHandle* handle) { + CommandLine cl(*CommandLine::ForCurrentProcess()); + cl.AppendSwitchWithValue("client", "NSSDecrypterChildProcess"); + + FilePath ff_dylib_dir = FilePath::FromWStringHack(nss_path); + // Set env variable needed for FF encryption libs to load. + // See "chrome/browser/importer/nss_decryptor_mac.mm" for an explanation of + // why we need this. + base::environment_vector env; + std::pair<std::string, std::string> dyld_override; + dyld_override.first = "DYLD_FALLBACK_LIBRARY_PATH"; + dyld_override.second = ff_dylib_dir.value(); + env.push_back(dyld_override); + + base::file_handle_mapping_vector fds_to_map; + const int ipcfd = channel.GetClientFileDescriptor(); + if (ipcfd > -1) { + fds_to_map.push_back(std::pair<int,int>(ipcfd, kPrimaryIPCChannel + 3)); + } else { + return false; + } + + bool debug_on_start = CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDebugChildren); + return base::LaunchApp(cl.argv(), env, fds_to_map, debug_on_start, handle); +} + +} // namespace + +//----------------------- Server -------------------- + +// Class to communicate on the server side of the IPC Channel. +// Method calls are sent over IPC and replies are read back into class +// variables. +// This class needs to be called on a single thread. +class FFDecryptorServerChannelListener : public IPC::Channel::Listener { + public: + FFDecryptorServerChannelListener() + : got_result(false), sender_(NULL) {} + + void SetSender(IPC::Message::Sender* sender) { + sender_ = sender; + } + + void OnInitDecryptorResponse(bool result) { + DCHECK(!got_result); + result_bool = result; + got_result = true; + MessageLoop::current()->Quit(); + } + + void OnDecryptedTextResonse(const string16& decrypted_text) { + DCHECK(!got_result); + result_string = decrypted_text; + got_result = true; + MessageLoop::current()->Quit(); + } + + void QuitClient() { + if (sender_) + sender_->Send(new Msg_Decryptor_Quit()); + } + + virtual void OnMessageReceived(const IPC::Message& msg) { + IPC_BEGIN_MESSAGE_MAP(FFDecryptorServerChannelListener, msg) + IPC_MESSAGE_HANDLER(Msg_Decryptor_InitReturnCode, OnInitDecryptorResponse) + IPC_MESSAGE_HANDLER(Msg_Decryptor_Response, OnDecryptedTextResonse) + IPC_END_MESSAGE_MAP() + } + + // If an error occured, just kill the message Loop. + virtual void OnChannelError() { + got_result = false; + MessageLoop::current()->Quit(); + } + + // Results of IPC calls. + string16 result_string; + bool result_bool; + // True if IPC call succeeded and data in above variables is valid. + bool got_result; + + private: + IPC::Message::Sender* sender_; // weak +}; + +FFUnitTestDecryptorProxy::FFUnitTestDecryptorProxy() + : child_process_(0) { +} + +bool FFUnitTestDecryptorProxy::Setup(const std::wstring& nss_path) { + // Create a new message loop and spawn the child process. + message_loop_.reset(new MessageLoopForIO()); + + listener_.reset(new FFDecryptorServerChannelListener()); + channel_.reset(new IPC::Channel(kTestChannelID, + IPC::Channel::MODE_SERVER, + listener_.get())); + channel_->Connect(); + listener_->SetSender(channel_.get()); + + // Spawn child and set up sync IPC connection. + bool ret = LaunchNSSDecrypterChildProcess(nss_path, + *(channel_.get()), + &child_process_); + return ret && (child_process_ != 0); +} + +FFUnitTestDecryptorProxy::~FFUnitTestDecryptorProxy() { + listener_->QuitClient(); + channel_->Close(); + + if (child_process_) { + base::WaitForSingleProcess(child_process_, 5000); + base::CloseProcessHandle(child_process_); + } +} + +// A message_loop task that quits the message loop when invoked, setting cancel +// causes the task to do nothing when invoked. +class CancellableQuitMsgLoop : public base::RefCounted<CancellableQuitMsgLoop> { + public: + CancellableQuitMsgLoop() : cancelled_(false) {} + void QuitNow() { + if (!cancelled_) + MessageLoop::current()->Quit(); + } + bool cancelled_; +}; + +// Spin until either a client response arrives or a timeout occurs. +bool FFUnitTestDecryptorProxy::WaitForClientResponse() { + const int64 kLoopTimeoutMS = 10 * 1000; // 10 seconds. + + // What we're trying to do here is to wait for an RPC message to go over the + // wire and the client to reply. If the client does not replyy by a given + // timeout we kill the message loop. + // The way we do this is to post a CancellableQuitMsgLoop for 3 seconds in + // the future and cancel it if an RPC message comes back earlier. + // This relies on the IPC listener class to quit the message loop itself when + // a message comes in. + scoped_refptr<CancellableQuitMsgLoop> quit_task = + new CancellableQuitMsgLoop(); + MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod( + quit_task.get(), &CancellableQuitMsgLoop::QuitNow), kLoopTimeoutMS); + + message_loop_->Run(); + bool ret = !quit_task->cancelled_; + quit_task->cancelled_ = false; + return ret; +} + +bool FFUnitTestDecryptorProxy::DecryptorInit(const std::wstring& dll_path, + const std::wstring& db_path) { + channel_->Send(new Msg_Decryptor_Init(dll_path, db_path)); + bool ok = WaitForClientResponse(); + if (ok && listener_->got_result) { + listener_->got_result = false; + return listener_->result_bool; + } + return false; +} + +string16 FFUnitTestDecryptorProxy::Decrypt(const std::string& crypt) { + channel_->Send(new Msg_Decrypt(crypt)); + bool ok = WaitForClientResponse(); + if (ok && listener_->got_result) { + listener_->got_result = false; + return listener_->result_string; + } + return string16(); +} + +//---------------------------- Child Process ----------------------- + +// Class to listen on the client side of the ipc channel, it calls through +// to the NSSDecryptor and sends back a reply. +class FFDecryptorClientChannelListener : public IPC::Channel::Listener { + public: + FFDecryptorClientChannelListener() + : sender_(NULL) {} + + void SetSender(IPC::Message::Sender* sender) { + sender_ = sender; + } + + void OnDecryptor_Init(std::wstring dll_path, std::wstring db_path) { + bool ret = decryptor_.Init(dll_path, db_path); + sender_->Send(new Msg_Decryptor_InitReturnCode(ret)); + } + + void OnDecrypt(std::string crypt) { + string16 unencrypted_str = decryptor_.Decrypt(crypt); + sender_->Send(new Msg_Decryptor_Response(unencrypted_str)); + } + + void OnQuitRequest() { + MessageLoop::current()->Quit(); + } + + virtual void OnMessageReceived(const IPC::Message& msg) { + IPC_BEGIN_MESSAGE_MAP(FFDecryptorClientChannelListener, msg) + IPC_MESSAGE_HANDLER(Msg_Decryptor_Init, OnDecryptor_Init) + IPC_MESSAGE_HANDLER(Msg_Decrypt, OnDecrypt) + IPC_MESSAGE_HANDLER(Msg_Decryptor_Quit, OnQuitRequest) + IPC_END_MESSAGE_MAP() + } + + virtual void OnChannelError() { + MessageLoop::current()->Quit(); + } + + private: + NSSDecryptor decryptor_; + IPC::Message::Sender* sender_; +}; + +// Entry function in child process. +MULTIPROCESS_TEST_MAIN(NSSDecrypterChildProcess) { + MessageLoopForIO main_message_loop; + FFDecryptorClientChannelListener listener; + + IPC::Channel channel(kTestChannelID, IPC::Channel::MODE_CLIENT, &listener); + channel.Connect(); + listener.SetSender(&channel); + + // run message loop + MessageLoop::current()->Run(); + + return 0; +} diff --git a/chrome/browser/importer/firefox_importer_utils.cc b/chrome/browser/importer/firefox_importer_utils.cc new file mode 100644 index 0000000..b9a7a74 --- /dev/null +++ b/chrome/browser/importer/firefox_importer_utils.cc @@ -0,0 +1,450 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/firefox_importer_utils.h" + +#include <algorithm> +#include <map> +#include <string> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/browser/search_engines/template_url_model.h" +#include "chrome/browser/search_engines/template_url_parser.h" +#include "googleurl/src/gurl.h" + +namespace { + +// FirefoxURLParameterFilter is used to remove parameter mentioning Firefox from +// the search URL when importing search engines. +class FirefoxURLParameterFilter : public TemplateURLParser::ParameterFilter { + public: + FirefoxURLParameterFilter() {} + virtual ~FirefoxURLParameterFilter() {} + + // TemplateURLParser::ParameterFilter method. + virtual bool KeepParameter(const std::string& key, + const std::string& value) { + std::string low_value = StringToLowerASCII(value); + if (low_value.find("mozilla") != std::string::npos || + low_value.find("firefox") != std::string::npos || + low_value.find("moz:") != std::string::npos ) + return false; + return true; + } + + private: + DISALLOW_COPY_AND_ASSIGN(FirefoxURLParameterFilter); +}; +} // namespace + +FilePath GetFirefoxProfilePath() { + DictionaryValue root; + FilePath ini_file = GetProfilesINI(); + ParseProfileINI(ini_file, &root); + + FilePath source_path; + for (int i = 0; ; ++i) { + std::string current_profile = StringPrintf("Profile%d", i); + if (!root.HasKeyASCII(current_profile)) { + // Profiles are continuously numbered. So we exit when we can't + // find the i-th one. + break; + } + std::string is_relative; + string16 path16; + if (root.GetStringASCII(current_profile + ".IsRelative", &is_relative) && + root.GetString(current_profile + ".Path", &path16)) { +#if defined(OS_WIN) + ReplaceSubstringsAfterOffset( + &path16, 0, ASCIIToUTF16("/"), ASCIIToUTF16("\\")); +#endif + FilePath path = FilePath::FromWStringHack(UTF16ToWide(path16)); + + // IsRelative=1 means the folder path would be relative to the + // path of profiles.ini. IsRelative=0 refers to a custom profile + // location. + if (is_relative == "1") { + path = ini_file.DirName().Append(path); + } + + // We only import the default profile when multiple profiles exist, + // since the other profiles are used mostly by developers for testing. + // Otherwise, Profile0 will be imported. + std::string is_default; + if ((root.GetStringASCII(current_profile + ".Default", &is_default) && + is_default == "1") || i == 0) { + // We have found the default profile. + return path; + } + } + } + return FilePath(); +} + + +bool GetFirefoxVersionAndPathFromProfile(const FilePath& profile_path, + int* version, + FilePath* app_path) { + bool ret = false; + FilePath compatibility_file = profile_path.AppendASCII("compatibility.ini"); + std::string content; + file_util::ReadFileToString(compatibility_file, &content); + ReplaceSubstringsAfterOffset(&content, 0, "\r\n", "\n"); + std::vector<std::string> lines; + SplitString(content, '\n', &lines); + + for (size_t i = 0; i < lines.size(); ++i) { + const std::string& line = lines[i]; + if (line.empty() || line[0] == '#' || line[0] == ';') + continue; + size_t equal = line.find('='); + if (equal != std::string::npos) { + std::string key = line.substr(0, equal); + if (key == "LastVersion") { + *version = line.substr(equal + 1)[0] - '0'; + ret = true; + } else if (key == "LastAppDir") { + // TODO(evanm): If the path in question isn't convertible to + // UTF-8, what does Firefox do? If it puts raw bytes in the + // file, we could go straight from bytes -> filepath; + // otherwise, we're out of luck here. + *app_path = FilePath::FromWStringHack( + UTF8ToWide(line.substr(equal + 1))); + } + } + } + return ret; +} + +void ParseProfileINI(const FilePath& file, DictionaryValue* root) { + // Reads the whole INI file. + std::string content; + file_util::ReadFileToString(file, &content); + ReplaceSubstringsAfterOffset(&content, 0, "\r\n", "\n"); + std::vector<std::string> lines; + SplitString(content, '\n', &lines); + + // Parses the file. + root->Clear(); + std::wstring current_section; + for (size_t i = 0; i < lines.size(); ++i) { + std::wstring line = UTF8ToWide(lines[i]); + if (line.empty()) { + // Skips the empty line. + continue; + } + if (line[0] == L'#' || line[0] == L';') { + // This line is a comment. + continue; + } + if (line[0] == L'[') { + // It is a section header. + current_section = line.substr(1); + size_t end = current_section.rfind(L']'); + if (end != std::wstring::npos) + current_section.erase(end); + } else { + std::wstring key, value; + size_t equal = line.find(L'='); + if (equal != std::wstring::npos) { + key = line.substr(0, equal); + value = line.substr(equal + 1); + // Checks whether the section and key contain a '.' character. + // Those sections and keys break DictionaryValue's path format, + // so we discard them. + if (current_section.find(L'.') == std::wstring::npos && + key.find(L'.') == std::wstring::npos) + root->SetString(current_section + L"." + key, value); + } + } + } +} + +bool CanImportURL(const GURL& url) { + const char* kInvalidSchemes[] = {"wyciwyg", "place", "about", "chrome"}; + + // The URL is not valid. + if (!url.is_valid()) + return false; + + // Filter out the URLs with unsupported schemes. + for (size_t i = 0; i < arraysize(kInvalidSchemes); ++i) { + if (url.SchemeIs(kInvalidSchemes[i])) + return false; + } + + return true; +} + +void ParseSearchEnginesFromXMLFiles(const std::vector<FilePath>& xml_files, + std::vector<TemplateURL*>* search_engines) { + DCHECK(search_engines); + + std::map<std::string, TemplateURL*> search_engine_for_url; + std::string content; + // The first XML file represents the default search engine in Firefox 3, so we + // need to keep it on top of the list. + TemplateURL* default_turl = NULL; + for (std::vector<FilePath>::const_iterator file_iter = xml_files.begin(); + file_iter != xml_files.end(); ++file_iter) { + file_util::ReadFileToString(*file_iter, &content); + TemplateURL* template_url = new TemplateURL(); + FirefoxURLParameterFilter param_filter; + if (TemplateURLParser::Parse( + reinterpret_cast<const unsigned char*>(content.data()), + content.length(), ¶m_filter, template_url) && + template_url->url()) { + std::string url = template_url->url()->url(); + std::map<std::string, TemplateURL*>::iterator iter = + search_engine_for_url.find(url); + if (iter != search_engine_for_url.end()) { + // We have already found a search engine with the same URL. We give + // priority to the latest one found, as GetSearchEnginesXMLFiles() + // returns a vector with first Firefox default search engines and then + // the user's ones. We want to give priority to the user ones. + delete iter->second; + search_engine_for_url.erase(iter); + } + // Give this a keyword to facilitate tab-to-search, if possible. + template_url->set_keyword( + TemplateURLModel::GenerateKeyword(GURL(url), false)); + template_url->set_show_in_default_list(true); + search_engine_for_url[url] = template_url; + if (!default_turl) + default_turl = template_url; + } else { + delete template_url; + } + content.clear(); + } + + // Put the results in the |search_engines| vector. + std::map<std::string, TemplateURL*>::iterator t_iter; + for (t_iter = search_engine_for_url.begin(); + t_iter != search_engine_for_url.end(); ++t_iter) { + if (t_iter->second == default_turl) + search_engines->insert(search_engines->begin(), default_turl); + else + search_engines->push_back(t_iter->second); + } +} + +bool ReadPrefFile(const FilePath& path, std::string* content) { + if (content == NULL) + return false; + + file_util::ReadFileToString(path, content); + + if (content->empty()) { + NOTREACHED() << "Firefox preference file " << path.value() + << " is empty."; + return false; + } + + return true; +} + +std::string ReadBrowserConfigProp(const FilePath& app_path, + const std::string& pref_key) { + std::string content; + if (!ReadPrefFile(app_path.AppendASCII("browserconfig.properties"), &content)) + return ""; + + // This file has the syntax: key=value. + size_t prop_index = content.find(pref_key + "="); + if (prop_index == std::string::npos) + return ""; + + size_t start = prop_index + pref_key.length(); + size_t stop = std::string::npos; + if (start != std::string::npos) + stop = content.find("\n", start + 1); + + if (start == std::string::npos || + stop == std::string::npos || (start == stop)) { + NOTREACHED() << "Firefox property " << pref_key << " could not be parsed."; + return ""; + } + + return content.substr(start + 1, stop - start - 1); +} + +std::string ReadPrefsJsValue(const FilePath& profile_path, + const std::string& pref_key) { + std::string content; + if (!ReadPrefFile(profile_path.AppendASCII("prefs.js"), &content)) + return ""; + + // This file has the syntax: user_pref("key", value); + std::string search_for = std::string("user_pref(\"") + pref_key + + std::string("\", "); + size_t prop_index = content.find(search_for); + if (prop_index == std::string::npos) + return ""; + + size_t start = prop_index + search_for.length(); + size_t stop = std::string::npos; + if (start != std::string::npos) + stop = content.find(")", start + 1); + + if (start == std::string::npos || stop == std::string::npos) { + NOTREACHED() << "Firefox property " << pref_key << " could not be parsed."; + return ""; + } + + // String values have double quotes we don't need to return to the caller. + if (content[start] == '\"' && content[stop - 1] == '\"') { + ++start; + --stop; + } + + return content.substr(start, stop - start); +} + +int GetFirefoxDefaultSearchEngineIndex( + const std::vector<TemplateURL*>& search_engines, + const FilePath& profile_path) { + // The default search engine is contained in the file prefs.js found in the + // profile directory. + // It is the "browser.search.selectedEngine" property. + if (search_engines.empty()) + return -1; + + std::wstring default_se_name = UTF8ToWide( + ReadPrefsJsValue(profile_path, "browser.search.selectedEngine")); + + if (default_se_name.empty()) { + // browser.search.selectedEngine does not exist if the user has not changed + // from the default (or has selected the default). + // TODO: should fallback to 'browser.search.defaultengine' if selectedEngine + // is empty. + return -1; + } + + int default_se_index = -1; + for (std::vector<TemplateURL*>::const_iterator iter = search_engines.begin(); + iter != search_engines.end(); ++iter) { + if (default_se_name == (*iter)->short_name()) { + default_se_index = static_cast<int>(iter - search_engines.begin()); + break; + } + } + if (default_se_index == -1) { + NOTREACHED() << + "Firefox default search engine not found in search engine list"; + } + + return default_se_index; +} + +GURL GetHomepage(const FilePath& profile_path) { + std::string home_page_list = + ReadPrefsJsValue(profile_path, "browser.startup.homepage"); + + size_t seperator = home_page_list.find_first_of('|'); + if (seperator == std::string::npos) + return GURL(home_page_list); + + return GURL(home_page_list.substr(0, seperator)); +} + +bool IsDefaultHomepage(const GURL& homepage, const FilePath& app_path) { + if (!homepage.is_valid()) + return false; + + std::string default_homepages = + ReadBrowserConfigProp(app_path, "browser.startup.homepage"); + + size_t seperator = default_homepages.find_first_of('|'); + if (seperator == std::string::npos) + return homepage.spec() == GURL(default_homepages).spec(); + + // Crack the string into separate homepage urls. + std::vector<std::string> urls; + SplitString(default_homepages, '|', &urls); + + for (size_t i = 0; i < urls.size(); ++i) { + if (homepage.spec() == GURL(urls[i]).spec()) + return true; + } + + return false; +} + +bool ParsePrefFile(const FilePath& pref_file, DictionaryValue* prefs) { + // The string that is before a pref key. + const std::string kUserPrefString = "user_pref(\""; + std::string contents; + if (!file_util::ReadFileToString(pref_file, &contents)) + return false; + + std::vector<std::string> lines; + Tokenize(contents, "\n", &lines); + + for (std::vector<std::string>::const_iterator iter = lines.begin(); + iter != lines.end(); ++iter) { + const std::string& line = *iter; + size_t start_key = line.find(kUserPrefString); + if (start_key == std::string::npos) + continue; // Could be a comment or a blank line. + start_key += kUserPrefString.length(); + size_t stop_key = line.find('"', start_key); + if (stop_key == std::string::npos) { + LOG(ERROR) << "Invalid key found in Firefox pref file '" << + pref_file.value() << "' line is '" << line << "'."; + continue; + } + std::string key = line.substr(start_key, stop_key - start_key); + size_t start_value = line.find(',', stop_key + 1); + if (start_value == std::string::npos) { + LOG(ERROR) << "Invalid value found in Firefox pref file '" << + pref_file.value() << "' line is '" << line << "'."; + continue; + } + size_t stop_value = line.find(");", start_value + 1); + if (stop_value == std::string::npos) { + LOG(ERROR) << "Invalid value found in Firefox pref file '" << + pref_file.value() << "' line is '" << line << "'."; + continue; + } + std::string value = line.substr(start_value + 1, + stop_value - start_value - 1); + TrimWhitespace(value, TRIM_ALL, &value); + // Value could be a boolean. + bool is_value_true = LowerCaseEqualsASCII(value, "true"); + if (is_value_true || LowerCaseEqualsASCII(value, "false")) { + prefs->SetBoolean(ASCIIToWide(key), is_value_true); + continue; + } + + // Value could be a string. + if (value.size() >= 2U && + value[0] == '"' && value[value.size() - 1] == '"') { + value = value.substr(1, value.size() - 2); + // ValueString only accept valid UTF-8. Simply ignore that entry if it is + // not UTF-8. + if (IsStringUTF8(value)) + prefs->SetString(ASCIIToWide(key), value); + else + LOG(INFO) << "Non UTF8 value for key " << key << ", ignored."; + continue; + } + + // Or value could be an integer. + int int_value = 0; + if (StringToInt(value, &int_value)) { + prefs->SetInteger(ASCIIToWide(key), int_value); + continue; + } + + LOG(ERROR) << "Invalid value found in Firefox pref file '" << + pref_file.value() << "' value is '" << value << "'."; + } + return true; +} diff --git a/chrome/browser/importer/firefox_importer_utils.h b/chrome/browser/importer/firefox_importer_utils.h new file mode 100644 index 0000000..1ad7ddb --- /dev/null +++ b/chrome/browser/importer/firefox_importer_utils.h @@ -0,0 +1,97 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_FIREFOX_IMPORTER_UTILS_H_ +#define CHROME_BROWSER_IMPORTER_FIREFOX_IMPORTER_UTILS_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "build/build_config.h" + +class DictionaryValue; +class GURL; +class TemplateURL; + +#if defined(OS_WIN) +// Detects which version of Firefox is installed from registry. Returns its +// major version, and drops the minor version. Returns 0 if +// failed. If there are indicators of both FF2 and FF3 it is +// biased to return the biggest version. +int GetCurrentFirefoxMajorVersionFromRegistry(); + +// Detects where Firefox lives. Returns a empty string if Firefox +// is not installed. +std::wstring GetFirefoxInstallPathFromRegistry(); +#endif // OS_WIN + +#if defined(OS_MACOSX) + +// Get the directory in which the Firefox .dylibs live, we need to load these +// in order to decoded FF profile passwords. +// The Path is usuall FF App Bundle/Contents/Mac OS/ +// Returns empty path on failure. +FilePath GetFirefoxDylibPath(); +#endif // OS_MACOSX + +// Returns the path to the Firefox profile. +FilePath GetFirefoxProfilePath(); + +// Detects version of Firefox and installation path from given Firefox profile +bool GetFirefoxVersionAndPathFromProfile(const FilePath& profile_path, + int* version, + FilePath* app_path); + +// Gets the full path of the profiles.ini file. This file records +// the profiles that can be used by Firefox. Returns an empty +// string if failed. +FilePath GetProfilesINI(); + +// Parses the profile.ini file, and stores its information in |root|. +// This file is a plain-text file. Key/value pairs are stored one per +// line, and they are separeated in different sections. For example: +// [General] +// StartWithLastProfile=1 +// +// [Profile0] +// Name=default +// IsRelative=1 +// Path=Profiles/abcdefeg.default +// We set "[value]" in path "<Section>.<Key>". For example, the path +// "Genenral.StartWithLastProfile" has the value "1". +void ParseProfileINI(const FilePath& file, DictionaryValue* root); + +// Returns true if we want to add the URL to the history. We filter +// out the URL with a unsupported scheme. +bool CanImportURL(const GURL& url); + +// Parses the OpenSearch XML files in |xml_files| and populates |search_engines| +// with the resulting TemplateURLs. +void ParseSearchEnginesFromXMLFiles(const std::vector<FilePath>& xml_files, + std::vector<TemplateURL*>* search_engines); + +// Returns the index of the default search engine in the |search_engines| list. +// If none is found, -1 is returned. +int GetFirefoxDefaultSearchEngineIndex( + const std::vector<TemplateURL*>& search_engines, + const FilePath& profile_path); + +// Returns the home page set in Firefox in a particular profile. +GURL GetHomepage(const FilePath& profile_path); + +// Checks to see if this home page is a default home page, as specified by +// the resource file browserconfig.properties in the Firefox application +// directory. +bool IsDefaultHomepage(const GURL& homepage, const FilePath& app_path); + +// Parses the prefs found in the file |pref_file| and puts the key/value pairs +// in |prefs|. Keys are strings, and values can be strings, booleans or +// integers. Returns true if it succeeded, false otherwise (in which case +// |prefs| is not filled). +// Note: for strings, only valid UTF-8 string values are supported. If a +// key/pair is not valid UTF-8, it is ignored and will not appear in |prefs|. +bool ParsePrefFile(const FilePath& pref_file, DictionaryValue* prefs); + +#endif // CHROME_BROWSER_IMPORTER_FIREFOX_IMPORTER_UTILS_H_ diff --git a/chrome/browser/importer/firefox_importer_utils_linux.cc b/chrome/browser/importer/firefox_importer_utils_linux.cc new file mode 100644 index 0000000..eeefd38 --- /dev/null +++ b/chrome/browser/importer/firefox_importer_utils_linux.cc @@ -0,0 +1,21 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/firefox_importer_utils.h" + +#include "base/file_util.h" + +FilePath GetProfilesINI() { + FilePath ini_file; + // The default location of the profile folder containing user data is + // under user HOME directory in .mozilla/firefox folder on Linux. + FilePath home = file_util::GetHomeDir(); + if (!home.empty()) { + ini_file = home.Append(".mozilla/firefox/profiles.ini"); + } + if (file_util::PathExists(ini_file)) + return ini_file; + + return FilePath(); +} diff --git a/chrome/browser/importer/firefox_importer_utils_mac.mm b/chrome/browser/importer/firefox_importer_utils_mac.mm new file mode 100644 index 0000000..08f222d --- /dev/null +++ b/chrome/browser/importer/firefox_importer_utils_mac.mm @@ -0,0 +1,40 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <Cocoa/Cocoa.h> + +#include "chrome/browser/importer/firefox_importer_utils.h" + +#include "base/file_util.h" +#include "base/path_service.h" + +FilePath GetProfilesINI() { + FilePath app_data_path; + if (!PathService::Get(base::DIR_APP_DATA, &app_data_path)) { + return FilePath(); + } + FilePath ini_file = app_data_path.Append("Firefox").Append("profiles.ini"); + if (!file_util::PathExists(ini_file)) { + return FilePath(); + } + return ini_file; +} + +FilePath GetFirefoxDylibPath() { + CFURLRef appURL = nil; + if (LSFindApplicationForInfo(kLSUnknownCreator, + CFSTR("org.mozilla.firefox"), + NULL, + NULL, + &appURL) != noErr) { + return FilePath(); + } + NSBundle *ff_bundle = [NSBundle + bundleWithPath:[reinterpret_cast<const NSURL*>(appURL) path]]; + CFRelease(appURL); + NSString *ff_library_path = [[ff_bundle executablePath] + stringByDeletingLastPathComponent]; + + return FilePath([ff_library_path fileSystemRepresentation]); +} diff --git a/chrome/browser/importer/firefox_importer_utils_win.cc b/chrome/browser/importer/firefox_importer_utils_win.cc new file mode 100644 index 0000000..77e3fcb --- /dev/null +++ b/chrome/browser/importer/firefox_importer_utils_win.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/firefox_importer_utils.h" + +#include <shlobj.h> + +#include "base/file_util.h" +#include "base/registry.h" + +// NOTE: Keep these in order since we need test all those paths according +// to priority. For example. One machine has multiple users. One non-admin +// user installs Firefox 2, which causes there is a Firefox2 entry under HKCU. +// One admin user installs Firefox 3, which causes there is a Firefox 3 entry +// under HKLM. So when the non-admin user log in, we should deal with Firefox 2 +// related data instead of Firefox 3. +static const HKEY kFireFoxRegistryPaths[] = { + HKEY_CURRENT_USER, + HKEY_LOCAL_MACHINE +}; + +int GetCurrentFirefoxMajorVersionFromRegistry() { + TCHAR ver_buffer[128]; + DWORD ver_buffer_length = sizeof(ver_buffer); + int highest_version = 0; + // When installing Firefox with admin account, the product keys will be + // written under HKLM\Mozilla. Otherwise it the keys will be written under + // HKCU\Mozilla. + for (int i = 0; i < arraysize(kFireFoxRegistryPaths); ++i) { + RegKey reg_key(kFireFoxRegistryPaths[i], + L"Software\\Mozilla\\Mozilla Firefox"); + + bool result = reg_key.ReadValue(L"CurrentVersion", ver_buffer, + &ver_buffer_length, NULL); + if (!result) + continue; + highest_version = std::max(highest_version, _wtoi(ver_buffer)); + } + return highest_version; +} + +std::wstring GetFirefoxInstallPathFromRegistry() { + // Detects the path that Firefox is installed in. + std::wstring registry_path = L"Software\\Mozilla\\Mozilla Firefox"; + TCHAR buffer[MAX_PATH]; + DWORD buffer_length = sizeof(buffer); + RegKey reg_key(HKEY_LOCAL_MACHINE, registry_path.c_str()); + bool result = reg_key.ReadValue(L"CurrentVersion", buffer, + &buffer_length, NULL); + if (!result) + return std::wstring(); + registry_path += L"\\" + std::wstring(buffer) + L"\\Main"; + buffer_length = sizeof(buffer); + RegKey reg_key_directory = RegKey(HKEY_LOCAL_MACHINE, registry_path.c_str()); + result = reg_key_directory.ReadValue(L"Install Directory", buffer, + &buffer_length, NULL); + if (!result) + return std::wstring(); + return buffer; +} + +FilePath GetProfilesINI() { + FilePath ini_file; + // The default location of the profile folder containing user data is + // under the "Application Data" folder in Windows XP. + wchar_t buffer[MAX_PATH] = {0}; + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, + SHGFP_TYPE_CURRENT, buffer))) { + ini_file = FilePath(buffer).Append(L"Mozilla\\Firefox\\profiles.ini"); + } + if (file_util::PathExists(ini_file)) + return ini_file; + + return FilePath(); +} diff --git a/chrome/browser/importer/firefox_profile_lock.cc b/chrome/browser/importer/firefox_profile_lock.cc new file mode 100644 index 0000000..fff771d --- /dev/null +++ b/chrome/browser/importer/firefox_profile_lock.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/firefox_profile_lock.h" + +#include "base/file_path.h" + +// This class is based on Firefox code in: +// profile/dirserviceprovider/src/nsProfileLock.cpp +// The license block is: + +/* ***** BEGIN LICENSE BLOCK ***** +* Version: MPL 1.1/GPL 2.0/LGPL 2.1 +* +* The contents of this file are subject to the Mozilla Public License Version +* 1.1 (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* http://www.mozilla.org/MPL/ +* +* Software distributed under the License is distributed on an "AS IS" basis, +* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +* for the specific language governing rights and limitations under the +* License. +* +* The Original Code is mozilla.org code. +* +* The Initial Developer of the Original Code is +* Netscape Communications Corporation. +* Portions created by the Initial Developer are Copyright (C) 2002 +* the Initial Developer. All Rights Reserved. +* +* Contributor(s): +* Conrad Carlen <ccarlen@netscape.com> +* Brendan Eich <brendan@mozilla.org> +* Colin Blake <colin@theblakes.com> +* Javier Pedemonte <pedemont@us.ibm.com> +* Mats Palmgren <mats.palmgren@bredband.net> +* +* Alternatively, the contents of this file may be used under the terms of +* either the GNU General Public License Version 2 or later (the "GPL"), or +* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +* in which case the provisions of the GPL or the LGPL are applicable instead +* of those above. If you wish to allow use of your version of this file only +* under the terms of either the GPL or the LGPL, and not to allow others to +* use your version of this file under the terms of the MPL, indicate your +* decision by deleting the provisions above and replace them with the notice +* and other provisions required by the GPL or the LGPL. If you do not delete +* the provisions above, a recipient may use your version of this file under +* the terms of any one of the MPL, the GPL or the LGPL. +* +* ***** END LICENSE BLOCK ***** */ + +// static +#if defined(OS_MACOSX) +const FilePath::CharType* FirefoxProfileLock::kLockFileName = + FILE_PATH_LITERAL(".parentlock"); +const FilePath::CharType* FirefoxProfileLock::kOldLockFileName = + FILE_PATH_LITERAL("parent.lock"); +#elif defined(OS_POSIX) +// http://www.google.com/codesearch/p?hl=en#e_ObwTAVPyo/profile/dirserviceprovider/src/nsProfileLock.cpp&l=433 +const FilePath::CharType* FirefoxProfileLock::kLockFileName = + FILE_PATH_LITERAL(".parentlock"); +const FilePath::CharType* FirefoxProfileLock::kOldLockFileName = + FILE_PATH_LITERAL("lock"); +#else +const FilePath::CharType* FirefoxProfileLock::kLockFileName = + FILE_PATH_LITERAL("parent.lock"); +#endif + +FirefoxProfileLock::FirefoxProfileLock(const FilePath& path) { + Init(); + lock_file_ = path.Append(kLockFileName); + Lock(); +} + +FirefoxProfileLock::~FirefoxProfileLock() { + Unlock(); +} diff --git a/chrome/browser/importer/firefox_profile_lock.h b/chrome/browser/importer/firefox_profile_lock.h new file mode 100644 index 0000000..20b9065 --- /dev/null +++ b/chrome/browser/importer/firefox_profile_lock.h @@ -0,0 +1,114 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_FIREFOX_PROFILE_LOCK_H__ +#define CHROME_BROWSER_IMPORTER_FIREFOX_PROFILE_LOCK_H__ + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +#include <string> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/gtest_prod_util.h" + +// Firefox is designed to allow only one application to access its +// profile at the same time. +// Reference: +// http://kb.mozillazine.org/Profile_in_use +// +// This class is based on Firefox code in: +// profile/dirserviceprovider/src/nsProfileLock.cpp +// The license block is: + +/* ***** BEGIN LICENSE BLOCK ***** +* Version: MPL 1.1/GPL 2.0/LGPL 2.1 +* +* The contents of this file are subject to the Mozilla Public License Version +* 1.1 (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* http://www.mozilla.org/MPL/ +* +* Software distributed under the License is distributed on an "AS IS" basis, +* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +* for the specific language governing rights and limitations under the +* License. +* +* The Original Code is mozilla.org code. +* +* The Initial Developer of the Original Code is +* Netscape Communications Corporation. +* Portions created by the Initial Developer are Copyright (C) 2002 +* the Initial Developer. All Rights Reserved. +* +* Contributor(s): +* Conrad Carlen <ccarlen@netscape.com> +* Brendan Eich <brendan@mozilla.org> +* Colin Blake <colin@theblakes.com> +* Javier Pedemonte <pedemont@us.ibm.com> +* Mats Palmgren <mats.palmgren@bredband.net> +* +* Alternatively, the contents of this file may be used under the terms of +* either the GNU General Public License Version 2 or later (the "GPL"), or +* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +* in which case the provisions of the GPL or the LGPL are applicable instead +* of those above. If you wish to allow use of your version of this file only +* under the terms of either the GPL or the LGPL, and not to allow others to +* use your version of this file under the terms of the MPL, indicate your +* decision by deleting the provisions above and replace them with the notice +* and other provisions required by the GPL or the LGPL. If you do not delete +* the provisions above, a recipient may use your version of this file under +* the terms of any one of the MPL, the GPL or the LGPL. +* +* ***** END LICENSE BLOCK ***** */ + +class FirefoxProfileLock { + public: + explicit FirefoxProfileLock(const FilePath& path); + ~FirefoxProfileLock(); + + // Locks and releases the profile. + void Lock(); + void Unlock(); + + // Returns true if we lock the profile successfully. + bool HasAcquired(); + + private: + FRIEND_TEST_ALL_PREFIXES(FirefoxProfileLockTest, ProfileLock); + FRIEND_TEST_ALL_PREFIXES(FirefoxProfileLockTest, ProfileLockOrphaned); + + static const FilePath::CharType* kLockFileName; + static const FilePath::CharType* kOldLockFileName; + + void Init(); + + // Full path of the lock file in the profile folder. + FilePath lock_file_; + + // The handle of the lock file. +#if defined(OS_WIN) + HANDLE lock_handle_; +#elif defined(OS_POSIX) + int lock_fd_; + + // On Posix systems Firefox apparently first tries to put a fcntl lock + // on a file and if that fails, it does a regular exculsive open on another + // file. This variable contains the location of this other file. + FilePath old_lock_file_; + + // Method that tries to put a fcntl lock on file specified by |lock_file_|. + // Returns false if lock is already held by another process. true in all + // other cases. + bool LockWithFcntl(); +#endif + + DISALLOW_COPY_AND_ASSIGN(FirefoxProfileLock); +}; + +#endif // CHROME_BROWSER_IMPORTER_FIREFOX_PROFILE_LOCK_H__ diff --git a/chrome/browser/importer/firefox_profile_lock_posix.cc b/chrome/browser/importer/firefox_profile_lock_posix.cc new file mode 100644 index 0000000..919b04a --- /dev/null +++ b/chrome/browser/importer/firefox_profile_lock_posix.cc @@ -0,0 +1,123 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/firefox_profile_lock.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "base/file_util.h" + +// This class is based on Firefox code in: +// profile/dirserviceprovider/src/nsProfileLock.cpp +// The license block is: + +/* ***** BEGIN LICENSE BLOCK ***** +* Version: MPL 1.1/GPL 2.0/LGPL 2.1 +* +* The contents of this file are subject to the Mozilla Public License Version +* 1.1 (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* http://www.mozilla.org/MPL/ +* +* Software distributed under the License is distributed on an "AS IS" basis, +* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +* for the specific language governing rights and limitations under the +* License. +* +* The Original Code is mozilla.org code. +* +* The Initial Developer of the Original Code is +* Netscape Communications Corporation. +* Portions created by the Initial Developer are Copyright (C) 2002 +* the Initial Developer. All Rights Reserved. +* +* Contributor(s): +* Conrad Carlen <ccarlen@netscape.com> +* Brendan Eich <brendan@mozilla.org> +* Colin Blake <colin@theblakes.com> +* Javier Pedemonte <pedemont@us.ibm.com> +* Mats Palmgren <mats.palmgren@bredband.net> +* +* Alternatively, the contents of this file may be used under the terms of +* either the GNU General Public License Version 2 or later (the "GPL"), or +* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +* in which case the provisions of the GPL or the LGPL are applicable instead +* of those above. If you wish to allow use of your version of this file only +* under the terms of either the GPL or the LGPL, and not to allow others to +* use your version of this file under the terms of the MPL, indicate your +* decision by deleting the provisions above and replace them with the notice +* and other provisions required by the GPL or the LGPL. If you do not delete +* the provisions above, a recipient may use your version of this file under +* the terms of any one of the MPL, the GPL or the LGPL. +* +* ***** END LICENSE BLOCK ***** */ + +void FirefoxProfileLock::Init() { + lock_fd_ = -1; +} + +void FirefoxProfileLock::Lock() { + if (HasAcquired()) + return; + + bool fcntl_lock = LockWithFcntl(); + if (!fcntl_lock) { + return; + } else if (!HasAcquired()) { + old_lock_file_ = lock_file_.DirName().Append(kOldLockFileName); + lock_fd_ = open(old_lock_file_.value().c_str(), O_CREAT | O_EXCL, 0644); + } +} + +void FirefoxProfileLock::Unlock() { + if (!HasAcquired()) + return; + close(lock_fd_); + lock_fd_ = -1; + file_util::Delete(old_lock_file_, false); +} + +bool FirefoxProfileLock::HasAcquired() { + return (lock_fd_ >= 0); +} + +// This function tries to lock Firefox profile using fcntl(). The return +// value of this function together with HasAcquired() tells the current status +// of lock. +// if return == false: Another process has lock to the profile. +// if return == true && HasAcquired() == true: successfully acquired the lock. +// if return == false && HasAcquired() == false: Failed to acquire lock due +// to some error (so that we can try alternate method of profile lock). +bool FirefoxProfileLock::LockWithFcntl() { + lock_fd_ = open(lock_file_.value().c_str(), O_WRONLY | O_CREAT | O_TRUNC, + 0666); + if (lock_fd_ == -1) + return true; + + struct flock lock; + lock.l_start = 0; + lock.l_len = 0; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + + struct flock testlock = lock; + if (fcntl(lock_fd_, F_GETLK, &testlock) == -1) { + close(lock_fd_); + lock_fd_ = -1; + return true; + } else if (fcntl(lock_fd_, F_SETLK, &lock) == -1) { + close(lock_fd_); + lock_fd_ = -1; + if (errno == EAGAIN || errno == EACCES) + return false; + else + return true; + } else { + // We have the lock. + return true; + } +} diff --git a/chrome/browser/importer/firefox_profile_lock_unittest.cc b/chrome/browser/importer/firefox_profile_lock_unittest.cc new file mode 100644 index 0000000..09214b7 --- /dev/null +++ b/chrome/browser/importer/firefox_profile_lock_unittest.cc @@ -0,0 +1,133 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/string_util.h" +#include "build/build_config.h" +#include "chrome/browser/importer/firefox_profile_lock.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/test/file_test_utils.h" + +class FirefoxProfileLockTest : public testing::Test { + public: + protected: + virtual void SetUp() { + ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &test_path_)); + std::wstring dir_name(L"FirefoxProfileLockTest"); + dir_name.append(StringPrintf(L"-%d", base::GetCurrentProcId())); + test_path_ = test_path_.Append(FilePath::FromWStringHack(dir_name)); + file_util::Delete(test_path_, true); + file_util::CreateDirectory(test_path_); + } + + virtual void TearDown() { + ASSERT_TRUE(file_util::Delete(test_path_, true)); + ASSERT_FALSE(file_util::PathExists(test_path_)); + } + + FilePath test_path_; +}; + +TEST_F(FirefoxProfileLockTest, LockTest) { + FirefoxProfileLock lock1(test_path_); + ASSERT_TRUE(lock1.HasAcquired()); + lock1.Unlock(); + ASSERT_FALSE(lock1.HasAcquired()); + lock1.Lock(); + ASSERT_TRUE(lock1.HasAcquired()); +} + +// Tests basic functionality and verifies that the lock file is deleted after +// use. +TEST_F(FirefoxProfileLockTest, ProfileLock) { + FilePath test_path; + ASSERT_TRUE(file_util::CreateNewTempDirectory( + FILE_PATH_LITERAL("firefox_profile"), &test_path)); + FilePath lock_file_path = test_path; + FileAutoDeleter deleter(lock_file_path); + lock_file_path = lock_file_path.Append(FirefoxProfileLock::kLockFileName); + + scoped_ptr<FirefoxProfileLock> lock; + EXPECT_EQ(static_cast<FirefoxProfileLock*>(NULL), lock.get()); + EXPECT_FALSE(file_util::PathExists(lock_file_path)); + lock.reset(new FirefoxProfileLock(test_path)); + EXPECT_TRUE(lock->HasAcquired()); + EXPECT_TRUE(file_util::PathExists(lock_file_path)); + lock->Unlock(); + EXPECT_FALSE(lock->HasAcquired()); + + // In the posix code, we don't delete the file when releasing the lock. +#if !defined(OS_POSIX) + EXPECT_FALSE(file_util::PathExists(lock_file_path)); +#endif // !defined(OS_POSIX) + lock->Lock(); + EXPECT_TRUE(lock->HasAcquired()); + EXPECT_TRUE(file_util::PathExists(lock_file_path)); + lock->Lock(); + EXPECT_TRUE(lock->HasAcquired()); + lock->Unlock(); + EXPECT_FALSE(lock->HasAcquired()); + // In the posix code, we don't delete the file when releasing the lock. +#if !defined(OS_POSIX) + EXPECT_FALSE(file_util::PathExists(lock_file_path)); +#endif // !defined(OS_POSIX) +} + +// If for some reason the lock file is left behind by the previous owner, we +// should still be able to lock it, at least in the Windows implementation. +TEST_F(FirefoxProfileLockTest, ProfileLockOrphaned) { + FilePath test_path; + ASSERT_TRUE(file_util::CreateNewTempDirectory( + FILE_PATH_LITERAL("firefox_profile"), &test_path)); + FilePath lock_file_path = test_path; + FileAutoDeleter deleter(lock_file_path); + lock_file_path = lock_file_path.Append(FirefoxProfileLock::kLockFileName); + + // Create the orphaned lock file. + FILE* lock_file = file_util::OpenFile(lock_file_path, "w"); + ASSERT_TRUE(lock_file); + file_util::CloseFile(lock_file); + EXPECT_TRUE(file_util::PathExists(lock_file_path)); + + scoped_ptr<FirefoxProfileLock> lock; + EXPECT_EQ(static_cast<FirefoxProfileLock*>(NULL), lock.get()); + lock.reset(new FirefoxProfileLock(test_path)); + EXPECT_TRUE(lock->HasAcquired()); + lock->Unlock(); + EXPECT_FALSE(lock->HasAcquired()); +} + +// This is broken on POSIX since the same process is allowed to reacquire a +// lock. +#if !defined(OS_POSIX) +// Tests two locks contending for the same lock file. +TEST_F(FirefoxProfileLockTest, ProfileLockContention) { + FilePath test_path; + ASSERT_TRUE(file_util::CreateNewTempDirectory( + FILE_PATH_LITERAL("firefox_profile"), &test_path)); + FileAutoDeleter deleter(test_path); + + scoped_ptr<FirefoxProfileLock> lock1; + EXPECT_EQ(static_cast<FirefoxProfileLock*>(NULL), lock1.get()); + lock1.reset(new FirefoxProfileLock(test_path)); + EXPECT_TRUE(lock1->HasAcquired()); + + scoped_ptr<FirefoxProfileLock> lock2; + EXPECT_EQ(static_cast<FirefoxProfileLock*>(NULL), lock2.get()); + lock2.reset(new FirefoxProfileLock(test_path)); + EXPECT_FALSE(lock2->HasAcquired()); + + lock1->Unlock(); + EXPECT_FALSE(lock1->HasAcquired()); + + lock2->Lock(); + EXPECT_TRUE(lock2->HasAcquired()); + lock2->Unlock(); + EXPECT_FALSE(lock2->HasAcquired()); +} +#endif diff --git a/chrome/browser/importer/firefox_profile_lock_win.cc b/chrome/browser/importer/firefox_profile_lock_win.cc new file mode 100644 index 0000000..e0c46d9 --- /dev/null +++ b/chrome/browser/importer/firefox_profile_lock_win.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/firefox_profile_lock.h" + +#include <windows.h> + +// This class is based on Firefox code in: +// profile/dirserviceprovider/src/nsProfileLock.cpp +// The license block is: + +/* ***** BEGIN LICENSE BLOCK ***** +* Version: MPL 1.1/GPL 2.0/LGPL 2.1 +* +* The contents of this file are subject to the Mozilla Public License Version +* 1.1 (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* http://www.mozilla.org/MPL/ +* +* Software distributed under the License is distributed on an "AS IS" basis, +* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +* for the specific language governing rights and limitations under the +* License. +* +* The Original Code is mozilla.org code. +* +* The Initial Developer of the Original Code is +* Netscape Communications Corporation. +* Portions created by the Initial Developer are Copyright (C) 2002 +* the Initial Developer. All Rights Reserved. +* +* Contributor(s): +* Conrad Carlen <ccarlen@netscape.com> +* Brendan Eich <brendan@mozilla.org> +* Colin Blake <colin@theblakes.com> +* Javier Pedemonte <pedemont@us.ibm.com> +* Mats Palmgren <mats.palmgren@bredband.net> +* +* Alternatively, the contents of this file may be used under the terms of +* either the GNU General Public License Version 2 or later (the "GPL"), or +* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +* in which case the provisions of the GPL or the LGPL are applicable instead +* of those above. If you wish to allow use of your version of this file only +* under the terms of either the GPL or the LGPL, and not to allow others to +* use your version of this file under the terms of the MPL, indicate your +* decision by deleting the provisions above and replace them with the notice +* and other provisions required by the GPL or the LGPL. If you do not delete +* the provisions above, a recipient may use your version of this file under +* the terms of any one of the MPL, the GPL or the LGPL. +* +* ***** END LICENSE BLOCK ***** */ + +void FirefoxProfileLock::Init() { + lock_handle_ = INVALID_HANDLE_VALUE; +} + +void FirefoxProfileLock::Lock() { + if (HasAcquired()) + return; + lock_handle_ = CreateFile(lock_file_.value().c_str(), + GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, + FILE_FLAG_DELETE_ON_CLOSE, NULL); +} + +void FirefoxProfileLock::Unlock() { + if (!HasAcquired()) + return; + CloseHandle(lock_handle_); + lock_handle_ = INVALID_HANDLE_VALUE; +} + +bool FirefoxProfileLock::HasAcquired() { + return (lock_handle_ != INVALID_HANDLE_VALUE); +} diff --git a/chrome/browser/importer/firefox_proxy_settings.cc b/chrome/browser/importer/firefox_proxy_settings.cc new file mode 100644 index 0000000..0f8ff39 --- /dev/null +++ b/chrome/browser/importer/firefox_proxy_settings.cc @@ -0,0 +1,226 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/firefox_proxy_settings.h" + +#include "base/string_tokenizer.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/importer/firefox_importer_utils.h" +#include "net/proxy/proxy_config.h" + +namespace { + +const wchar_t* const kNetworkProxyTypeKey = L"network.proxy.type"; +const char* const kHTTPProxyKey = "network.proxy.http"; +const wchar_t* const kHTTPProxyPortKey = L"network.proxy.http_port"; +const char* const kSSLProxyKey = "network.proxy.ssl"; +const wchar_t* const kSSLProxyPortKey = L"network.proxy.ssl_port"; +const char* const kFTPProxyKey = "network.proxy.ftp"; +const wchar_t* const kFTPProxyPortKey = L"network.proxy.ftp_port"; +const char* const kGopherProxyKey = "network.proxy.gopher"; +const wchar_t* const kGopherProxyPortKey = L"network.proxy.gopher_port"; +const char* const kSOCKSHostKey = "network.proxy.socks"; +const wchar_t* const kSOCKSHostPortKey = L"network.proxy.socks_port"; +const wchar_t* const kSOCKSVersionKey = L"network.proxy.socks_version"; +const char* const kAutoconfigURL = "network.proxy.autoconfig_url"; +const char* const kNoProxyListKey = "network.proxy.no_proxies_on"; +const char* const kPrefFileName = "prefs.js"; + +FirefoxProxySettings::ProxyConfig IntToProxyConfig(int type) { + switch (type) { + case 1: + return FirefoxProxySettings::MANUAL; + case 2: + return FirefoxProxySettings::AUTO_FROM_URL; + case 4: + return FirefoxProxySettings::AUTO_DETECT; + case 5: + return FirefoxProxySettings::SYSTEM; + default: + LOG(ERROR) << "Unknown Firefox proxy config type: " << type; + return FirefoxProxySettings::NO_PROXY; + } +} + +FirefoxProxySettings::SOCKSVersion IntToSOCKSVersion(int type) { + switch (type) { + case 4: + return FirefoxProxySettings::V4; + case 5: + return FirefoxProxySettings::V5; + default: + LOG(ERROR) << "Unknown Firefox proxy config type: " << type; + return FirefoxProxySettings::UNKNONW; + } +} + +} // namespace + +FirefoxProxySettings::FirefoxProxySettings() { + Reset(); +} + +FirefoxProxySettings::~FirefoxProxySettings() { +} + +void FirefoxProxySettings::Reset() { + config_type_ = NO_PROXY; + http_proxy_.clear(); + http_proxy_port_ = 0; + ssl_proxy_.clear(); + ssl_proxy_port_ = 0; + ftp_proxy_.clear(); + ftp_proxy_port_ = 0; + gopher_proxy_.clear(); + gopher_proxy_port_ = 0; + socks_host_.clear(); + socks_port_ = 0; + socks_version_ = UNKNONW; + proxy_bypass_list_.clear(); + autoconfig_url_.clear(); +} + +// static +bool FirefoxProxySettings::GetSettings(FirefoxProxySettings* settings) { + DCHECK(settings); + settings->Reset(); + + FilePath profile_path = GetFirefoxProfilePath(); + if (profile_path.empty()) + return false; + FilePath pref_file = profile_path.AppendASCII(kPrefFileName); + return GetSettingsFromFile(pref_file, settings); +} + +bool FirefoxProxySettings::ToProxyConfig(net::ProxyConfig* config) { + switch (config_type()) { + case NO_PROXY: + *config = net::ProxyConfig::CreateDirect(); + return true; + case AUTO_DETECT: + *config = net::ProxyConfig::CreateAutoDetect(); + return true; + case AUTO_FROM_URL: + *config = net::ProxyConfig::CreateFromCustomPacURL( + GURL(autoconfig_url())); + return true; + case SYSTEM: + // Can't convert this directly to a ProxyConfig. + return false; + case MANUAL: + // Handled outside of the switch (since it is a lot of code.) + break; + default: + NOTREACHED(); + return false; + } + + // The rest of this funciton is for handling the MANUAL case. + DCHECK_EQ(MANUAL, config_type()); + + *config = net::ProxyConfig(); + config->proxy_rules().type = + net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME; + + if (!http_proxy().empty()) { + config->proxy_rules().proxy_for_http = net::ProxyServer( + net::ProxyServer::SCHEME_HTTP, + http_proxy(), + http_proxy_port()); + } + + if (!ftp_proxy().empty()) { + config->proxy_rules().proxy_for_ftp = net::ProxyServer( + net::ProxyServer::SCHEME_HTTP, + ftp_proxy(), + ftp_proxy_port()); + } + + if (!ssl_proxy().empty()) { + config->proxy_rules().proxy_for_https = net::ProxyServer( + net::ProxyServer::SCHEME_HTTP, + ssl_proxy(), + ssl_proxy_port()); + } + + if (!socks_host().empty()) { + net::ProxyServer::Scheme proxy_scheme = V5 == socks_version() ? + net::ProxyServer::SCHEME_SOCKS5 : net::ProxyServer::SCHEME_SOCKS4; + + config->proxy_rules().socks_proxy = net::ProxyServer( + proxy_scheme, + socks_host(), + socks_port()); + } + + config->proxy_rules().bypass_rules.ParseFromStringUsingSuffixMatching( + JoinString(proxy_bypass_list_, ';')); + + return true; +} + +// static +bool FirefoxProxySettings::GetSettingsFromFile(const FilePath& pref_file, + FirefoxProxySettings* settings) { + DictionaryValue dictionary; + if (!ParsePrefFile(pref_file, &dictionary)) + return false; + + int proxy_type = 0; + if (!dictionary.GetInteger(kNetworkProxyTypeKey, &proxy_type)) + return true; // No type means no proxy. + + settings->config_type_ = IntToProxyConfig(proxy_type); + if (settings->config_type_ == AUTO_FROM_URL) { + if (!dictionary.GetStringASCII(kAutoconfigURL, + &(settings->autoconfig_url_))) { + LOG(ERROR) << "Failed to retrieve Firefox proxy autoconfig URL"; + } + return true; + } + + if (settings->config_type_ == MANUAL) { + if (!dictionary.GetStringASCII(kHTTPProxyKey, &(settings->http_proxy_))) + LOG(ERROR) << "Failed to retrieve Firefox proxy HTTP host"; + if (!dictionary.GetInteger(kHTTPProxyPortKey, + &(settings->http_proxy_port_))) { + LOG(ERROR) << "Failed to retrieve Firefox proxy HTTP port"; + } + if (!dictionary.GetStringASCII(kSSLProxyKey, &(settings->ssl_proxy_))) + LOG(ERROR) << "Failed to retrieve Firefox proxy SSL host"; + if (!dictionary.GetInteger(kSSLProxyPortKey, &(settings->ssl_proxy_port_))) + LOG(ERROR) << "Failed to retrieve Firefox proxy SSL port"; + if (!dictionary.GetStringASCII(kFTPProxyKey, &(settings->ftp_proxy_))) + LOG(ERROR) << "Failed to retrieve Firefox proxy FTP host"; + if (!dictionary.GetInteger(kFTPProxyPortKey, &(settings->ftp_proxy_port_))) + LOG(ERROR) << "Failed to retrieve Firefox proxy SSL port"; + if (!dictionary.GetStringASCII(kGopherProxyKey, &(settings->gopher_proxy_))) + LOG(ERROR) << "Failed to retrieve Firefox proxy gopher host"; + if (!dictionary.GetInteger(kGopherProxyPortKey, + &(settings->gopher_proxy_port_))) { + LOG(ERROR) << "Failed to retrieve Firefox proxy gopher port"; + } + if (!dictionary.GetStringASCII(kSOCKSHostKey, &(settings->socks_host_))) + LOG(ERROR) << "Failed to retrieve Firefox SOCKS host"; + if (!dictionary.GetInteger(kSOCKSHostPortKey, &(settings->socks_port_))) + LOG(ERROR) << "Failed to retrieve Firefox SOCKS port"; + int socks_version; + if (dictionary.GetInteger(kSOCKSVersionKey, &socks_version)) + settings->socks_version_ = IntToSOCKSVersion(socks_version); + + std::string proxy_bypass; + if (dictionary.GetStringASCII(kNoProxyListKey, &proxy_bypass) && + !proxy_bypass.empty()) { + StringTokenizer string_tok(proxy_bypass, ","); + while (string_tok.GetNext()) { + std::string token = string_tok.token(); + TrimWhitespaceASCII(token, TRIM_ALL, &token); + if (!token.empty()) + settings->proxy_bypass_list_.push_back(token); + } + } + } + return true; +} diff --git a/chrome/browser/importer/firefox_proxy_settings.h b/chrome/browser/importer/firefox_proxy_settings.h new file mode 100644 index 0000000..80e1e24 --- /dev/null +++ b/chrome/browser/importer/firefox_proxy_settings.h @@ -0,0 +1,110 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_FIREFOX_PROXY_SETTINGS_H_ +#define CHROME_BROWSER_IMPORTER_FIREFOX_PROXY_SETTINGS_H_ + +#include <string> +#include <vector> + +#include "base/logging.h" + +class FilePath; + +namespace net { +class ProxyConfig; +} + +class FirefoxProxySettings { + public: + enum ProxyConfig { + NO_PROXY = 0, // No proxy are used. + AUTO_DETECT, // Automatically detected. + SYSTEM, // Using system proxy settings. + AUTO_FROM_URL, // Automatically configured from a URL. + MANUAL // User specified settings. + }; + + enum SOCKSVersion { + UNKNONW = 0, + V4, + V5 + }; + + FirefoxProxySettings(); + ~FirefoxProxySettings(); + + // Sets |settings| to the proxy settings for the current installed version of + // Firefox and returns true if successful. + // Returns false if Firefox is not installed or if the settings could not be + // retrieved. + static bool GetSettings(FirefoxProxySettings* settings); + + // Resets all the states of this FirefoxProxySettings to no proxy. + void Reset(); + + ProxyConfig config_type() const { return config_type_; } + + std::string http_proxy() const { return http_proxy_; } + int http_proxy_port() const { return http_proxy_port_; } + + std::string ssl_proxy() const { return ssl_proxy_; } + int ssl_proxy_port() const { return ssl_proxy_port_; } + + std::string ftp_proxy() const { return ftp_proxy_; } + int ftp_proxy_port() const { return ftp_proxy_port_; } + + std::string gopher_proxy() const { return gopher_proxy_; } + int gopher_proxy_port() const { return gopher_proxy_port_; } + + std::string socks_host() const { return socks_host_; } + int socks_port() const { return socks_port_; } + SOCKSVersion socks_version() const { return socks_version_; } + + std::vector<std::string> proxy_bypass_list() const { + return proxy_bypass_list_; + } + + const std::string autoconfig_url() const { + return autoconfig_url_; + } + + // Converts a FirefoxProxySettings object to a net::ProxyConfig. + // On success returns true and fills |config| with the result. + bool ToProxyConfig(net::ProxyConfig* config); + + protected: + // Gets the settings from the passed prefs.js file and returns true if + // successful. + // Protected for tests. + static bool GetSettingsFromFile(const FilePath& pref_file, + FirefoxProxySettings* settings); + + private: + ProxyConfig config_type_; + + std::string http_proxy_; + int http_proxy_port_; + + std::string ssl_proxy_; + int ssl_proxy_port_; + + std::string ftp_proxy_; + int ftp_proxy_port_; + + std::string gopher_proxy_; + int gopher_proxy_port_; + + std::string socks_host_; + int socks_port_; + SOCKSVersion socks_version_; + + std::vector<std::string> proxy_bypass_list_; + + std::string autoconfig_url_; + + DISALLOW_COPY_AND_ASSIGN(FirefoxProxySettings); +}; + +#endif // CHROME_BROWSER_IMPORTER_FIREFOX_PROXY_SETTINGS_H_ diff --git a/chrome/browser/importer/firefox_proxy_settings_unittest.cc b/chrome/browser/importer/firefox_proxy_settings_unittest.cc new file mode 100644 index 0000000..ed2a66c --- /dev/null +++ b/chrome/browser/importer/firefox_proxy_settings_unittest.cc @@ -0,0 +1,124 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <sstream> + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/file_path.h" +#include "base/path_service.h" +#include "chrome/browser/importer/firefox_proxy_settings.h" +#include "chrome/common/chrome_paths.h" +#include "net/proxy/proxy_config.h" + +class FirefoxProxySettingsTest : public testing::Test { +}; + +class TestFirefoxProxySettings : public FirefoxProxySettings { + public: + TestFirefoxProxySettings() {} + + static bool TestGetSettingsFromFile(const FilePath& pref_file, + FirefoxProxySettings* settings) { + return GetSettingsFromFile(pref_file, settings); + } +}; + +TEST_F(FirefoxProxySettingsTest, TestParse) { + FirefoxProxySettings settings; + + FilePath js_pref_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &js_pref_path)); + js_pref_path = js_pref_path.AppendASCII("firefox3_pref.js"); + + EXPECT_TRUE(TestFirefoxProxySettings::TestGetSettingsFromFile(js_pref_path, + &settings)); + EXPECT_EQ(FirefoxProxySettings::MANUAL, settings.config_type()); + EXPECT_EQ("http_proxy", settings.http_proxy()); + EXPECT_EQ(1111, settings.http_proxy_port()); + EXPECT_EQ("ssl_proxy", settings.ssl_proxy()); + EXPECT_EQ(2222, settings.ssl_proxy_port()); + EXPECT_EQ("ftp_proxy", settings.ftp_proxy()); + EXPECT_EQ(3333, settings.ftp_proxy_port()); + EXPECT_EQ("gopher_proxy", settings.gopher_proxy()); + EXPECT_EQ(4444, settings.gopher_proxy_port()); + EXPECT_EQ("socks_host", settings.socks_host()); + EXPECT_EQ(5555, settings.socks_port()); + EXPECT_EQ(FirefoxProxySettings::V4, settings.socks_version()); + ASSERT_EQ(3U, settings.proxy_bypass_list().size()); + EXPECT_EQ("localhost", settings.proxy_bypass_list()[0]); + EXPECT_EQ("127.0.0.1", settings.proxy_bypass_list()[1]); + EXPECT_EQ("noproxy.com", settings.proxy_bypass_list()[2]); + EXPECT_EQ("", settings.autoconfig_url()); + + // Test that ToProxyConfig() properly translates into a net::ProxyConfig. + net::ProxyConfig config; + EXPECT_TRUE(settings.ToProxyConfig(&config)); + + // Pretty-print |config| to a string (easy way to define the expectations). + std::ostringstream stream; + stream << config; + std::string pretty_printed_config = stream.str(); + + EXPECT_EQ( + "Automatic settings:\n" + " Auto-detect: No\n" + " Custom PAC script: [None]\n" + "Manual settings:\n" + " Proxy server: \n" + " HTTP: http_proxy:1111\n" + " HTTPS: ssl_proxy:2222\n" + " FTP: ftp_proxy:3333\n" + " SOCKS: socks4://socks_host:5555\n" + " Bypass list: \n" + " *localhost\n" + " 127.0.0.1\n" + " *noproxy.com", + pretty_printed_config); +} + +TEST_F(FirefoxProxySettingsTest, TestParseAutoConfigUrl) { + FirefoxProxySettings settings; + + FilePath js_pref_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &js_pref_path)); + js_pref_path = js_pref_path.AppendASCII("firefox3_pref_pac_url.js"); + + EXPECT_TRUE(TestFirefoxProxySettings::TestGetSettingsFromFile(js_pref_path, + &settings)); + EXPECT_EQ(FirefoxProxySettings::AUTO_FROM_URL, settings.config_type()); + + // Everything should be empty except for the autoconfig URL. + EXPECT_EQ("http://custom-pac-url/", settings.autoconfig_url()); + EXPECT_EQ("", settings.http_proxy()); + EXPECT_EQ(0, settings.http_proxy_port()); + EXPECT_EQ("", settings.ssl_proxy()); + EXPECT_EQ(0, settings.ssl_proxy_port()); + EXPECT_EQ("", settings.ftp_proxy()); + EXPECT_EQ(0, settings.ftp_proxy_port()); + EXPECT_EQ("", settings.gopher_proxy()); + EXPECT_EQ(0, settings.gopher_proxy_port()); + EXPECT_EQ("", settings.socks_host()); + EXPECT_EQ(0, settings.socks_port()); + EXPECT_EQ(0, settings.socks_port()); + EXPECT_EQ(0U, settings.proxy_bypass_list().size()); + + // Test that ToProxyConfig() properly translates into a net::ProxyConfig. + net::ProxyConfig config; + EXPECT_TRUE(settings.ToProxyConfig(&config)); + + // Pretty-print |config| to a string (easy way to define the expectations). + std::ostringstream stream; + stream << config; + std::string pretty_printed_config = stream.str(); + + EXPECT_EQ( + "Automatic settings:\n" + " Auto-detect: No\n" + " Custom PAC script: http://custom-pac-url/\n" + "Manual settings:\n" + " Proxy server: [None]\n" + " Bypass list: [None]", + pretty_printed_config); +} diff --git a/chrome/browser/importer/ie_importer.cc b/chrome/browser/importer/ie_importer.cc new file mode 100644 index 0000000..2824b40 --- /dev/null +++ b/chrome/browser/importer/ie_importer.cc @@ -0,0 +1,578 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/ie_importer.h" + +#include <ole2.h> +#include <intshcut.h> +#include <pstore.h> +#include <shlobj.h> +#include <urlhist.h> + +#include <algorithm> +#include <map> +#include <string> +#include <vector> + +#include "app/l10n_util.h" +#include "app/win_util.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/registry.h" +#include "base/scoped_comptr_win.h" +#include "base/string_util.h" +#include "base/time.h" +#include "base/win_util.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/importer/importer_bridge.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "chrome/browser/password_manager/ie7_password.h" +#include "chrome/browser/search_engines/template_url_model.h" +#include "chrome/common/time_format.h" +#include "chrome/common/url_constants.h" +#include "googleurl/src/gurl.h" +#include "grit/generated_resources.h" +#include "webkit/glue/password_form.h" + +using base::Time; +using webkit_glue::PasswordForm; + +namespace { + +// Gets the creation time of the given file or directory. +static Time GetFileCreationTime(const std::wstring& file) { + Time creation_time; + ScopedHandle file_handle( + CreateFile(file.c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL)); + FILETIME creation_filetime; + if (GetFileTime(file_handle, &creation_filetime, NULL, NULL)) + creation_time = Time::FromFileTime(creation_filetime); + return creation_time; +} + +} // namespace + +// static +// {E161255A-37C3-11D2-BCAA-00C04fD929DB} +const GUID IEImporter::kPStoreAutocompleteGUID = {0xe161255a, 0x37c3, 0x11d2, + {0xbc, 0xaa, 0x00, 0xc0, 0x4f, 0xd9, 0x29, 0xdb}}; +// {A79029D6-753E-4e27-B807-3D46AB1545DF} +const GUID IEImporter::kUnittestGUID = { 0xa79029d6, 0x753e, 0x4e27, + {0xb8, 0x7, 0x3d, 0x46, 0xab, 0x15, 0x45, 0xdf}}; + +void IEImporter::StartImport(ProfileInfo profile_info, + uint16 items, + ImporterBridge* bridge) { + bridge_ = bridge; + source_path_ = profile_info.source_path.ToWStringHack(); + + bridge_->NotifyStarted(); + + // Some IE settings (such as Protected Storage) are obtained via COM APIs. + win_util::ScopedCOMInitializer com_initializer; + + if ((items & importer::HOME_PAGE) && !cancelled()) + ImportHomepage(); // Doesn't have a UI item. + // The order here is important! + if ((items & importer::HISTORY) && !cancelled()) { + bridge_->NotifyItemStarted(importer::HISTORY); + ImportHistory(); + bridge_->NotifyItemEnded(importer::HISTORY); + } + if ((items & importer::FAVORITES) && !cancelled()) { + bridge_->NotifyItemStarted(importer::FAVORITES); + ImportFavorites(); + bridge_->NotifyItemEnded(importer::FAVORITES); + } + if ((items & importer::SEARCH_ENGINES) && !cancelled()) { + bridge_->NotifyItemStarted(importer::SEARCH_ENGINES); + ImportSearchEngines(); + bridge_->NotifyItemEnded(importer::SEARCH_ENGINES); + } + if ((items & importer::PASSWORDS) && !cancelled()) { + bridge_->NotifyItemStarted(importer::PASSWORDS); + // Always import IE6 passwords. + ImportPasswordsIE6(); + + if (CurrentIEVersion() >= 7) + ImportPasswordsIE7(); + bridge_->NotifyItemEnded(importer::PASSWORDS); + } + bridge_->NotifyEnded(); +} + +void IEImporter::ImportFavorites() { + std::wstring path; + + FavoritesInfo info; + if (!GetFavoritesInfo(&info)) + return; + + BookmarkVector bookmarks; + ParseFavoritesFolder(info, &bookmarks); + + if (!bookmarks.empty() && !cancelled()) { + const std::wstring& first_folder_name = + l10n_util::GetString(IDS_BOOKMARK_GROUP_FROM_IE); + int options = 0; + if (import_to_bookmark_bar()) + options = ProfileWriter::IMPORT_TO_BOOKMARK_BAR; + bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options); + } +} + +void IEImporter::ImportPasswordsIE6() { + GUID AutocompleteGUID = kPStoreAutocompleteGUID; + if (!source_path_.empty()) { + // We supply a fake GUID for testting. + AutocompleteGUID = kUnittestGUID; + } + + // The PStoreCreateInstance function retrieves an interface pointer + // to a storage provider. But this function has no associated import + // library or header file, we must call it using the LoadLibrary() + // and GetProcAddress() functions. + typedef HRESULT (WINAPI *PStoreCreateFunc)(IPStore**, DWORD, DWORD, DWORD); + HMODULE pstorec_dll = LoadLibrary(L"pstorec.dll"); + if (!pstorec_dll) + return; + PStoreCreateFunc PStoreCreateInstance = + (PStoreCreateFunc)GetProcAddress(pstorec_dll, "PStoreCreateInstance"); + if (!PStoreCreateInstance) { + FreeLibrary(pstorec_dll); + return; + } + + ScopedComPtr<IPStore, &IID_IPStore> pstore; + HRESULT result = PStoreCreateInstance(pstore.Receive(), 0, 0, 0); + if (result != S_OK) { + FreeLibrary(pstorec_dll); + return; + } + + std::vector<AutoCompleteInfo> ac_list; + + // Enumerates AutoComplete items in the protected database. + ScopedComPtr<IEnumPStoreItems, &IID_IEnumPStoreItems> item; + result = pstore->EnumItems(0, &AutocompleteGUID, + &AutocompleteGUID, 0, item.Receive()); + if (result != PST_E_OK) { + pstore.Release(); + FreeLibrary(pstorec_dll); + return; + } + + wchar_t* item_name; + while (!cancelled() && SUCCEEDED(item->Next(1, &item_name, 0))) { + DWORD length = 0; + unsigned char* buffer = NULL; + result = pstore->ReadItem(0, &AutocompleteGUID, &AutocompleteGUID, + item_name, &length, &buffer, NULL, 0); + if (SUCCEEDED(result)) { + AutoCompleteInfo ac; + ac.key = item_name; + std::wstring data; + data.insert(0, reinterpret_cast<wchar_t*>(buffer), + length / sizeof(wchar_t)); + + // The key name is always ended with ":StringData". + const wchar_t kDataSuffix[] = L":StringData"; + size_t i = ac.key.rfind(kDataSuffix); + if (i != std::wstring::npos && ac.key.substr(i) == kDataSuffix) { + ac.key.erase(i); + ac.is_url = (ac.key.find(L"://") != std::wstring::npos); + ac_list.push_back(ac); + SplitString(data, L'\0', &ac_list[ac_list.size() - 1].data); + } + CoTaskMemFree(buffer); + } + CoTaskMemFree(item_name); + } + // Releases them before unload the dll. + item.Release(); + pstore.Release(); + FreeLibrary(pstorec_dll); + + size_t i; + for (i = 0; i < ac_list.size(); i++) { + if (!ac_list[i].is_url || ac_list[i].data.size() < 2) + continue; + + GURL url(ac_list[i].key.c_str()); + if (!(LowerCaseEqualsASCII(url.scheme(), chrome::kHttpScheme) || + LowerCaseEqualsASCII(url.scheme(), chrome::kHttpsScheme))) { + continue; + } + + PasswordForm form; + GURL::Replacements rp; + rp.ClearUsername(); + rp.ClearPassword(); + rp.ClearQuery(); + rp.ClearRef(); + form.origin = url.ReplaceComponents(rp); + form.username_value = ac_list[i].data[0]; + form.password_value = ac_list[i].data[1]; + form.signon_realm = url.GetOrigin().spec(); + + // This is not precise, because a scheme of https does not imply a valid + // certificate was presented; however we assign it this way so that if we + // import a password from IE whose scheme is https, we give it the benefit + // of the doubt and DONT auto-fill it unless the form appears under + // valid SSL conditions. + form.ssl_valid = url.SchemeIsSecure(); + + // Goes through the list to find out the username field + // of the web page. + size_t list_it, item_it; + for (list_it = 0; list_it < ac_list.size(); ++list_it) { + if (ac_list[list_it].is_url) + continue; + + for (item_it = 0; item_it < ac_list[list_it].data.size(); ++item_it) + if (ac_list[list_it].data[item_it] == form.username_value) { + form.username_element = ac_list[list_it].key; + break; + } + } + + bridge_->SetPasswordForm(form); + } +} + +void IEImporter::ImportPasswordsIE7() { + if (!source_path_.empty()) { + // We have been called from the unit tests. Don't import real passwords. + return; + } + + const wchar_t kStorage2Path[] = + L"Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2"; + + RegKey key(HKEY_CURRENT_USER, kStorage2Path, KEY_READ); + RegistryValueIterator reg_iterator(HKEY_CURRENT_USER, kStorage2Path); + while (reg_iterator.Valid() && !cancelled()) { + // Get the size of the encrypted data. + DWORD value_len = 0; + if (key.ReadValue(reg_iterator.Name(), NULL, &value_len) && value_len) { + // Query the encrypted data. + std::vector<unsigned char> value; + value.resize(value_len); + if (key.ReadValue(reg_iterator.Name(), &value.front(), &value_len)) { + IE7PasswordInfo password_info; + password_info.url_hash = reg_iterator.Name(); + password_info.encrypted_data = value; + password_info.date_created = Time::Now(); + + bridge_->AddIE7PasswordInfo(password_info); + } + } + + ++reg_iterator; + } +} + +// Reads history information from COM interface. +void IEImporter::ImportHistory() { + const std::string kSchemes[] = {chrome::kHttpScheme, + chrome::kHttpsScheme, + chrome::kFtpScheme, + chrome::kFileScheme}; + int total_schemes = arraysize(kSchemes); + + ScopedComPtr<IUrlHistoryStg2> url_history_stg2; + HRESULT result; + result = url_history_stg2.CreateInstance(CLSID_CUrlHistory, NULL, + CLSCTX_INPROC_SERVER); + if (FAILED(result)) + return; + ScopedComPtr<IEnumSTATURL> enum_url; + if (SUCCEEDED(result = url_history_stg2->EnumUrls(enum_url.Receive()))) { + std::vector<history::URLRow> rows; + STATURL stat_url; + ULONG fetched; + while (!cancelled() && + (result = enum_url->Next(1, &stat_url, &fetched)) == S_OK) { + std::wstring url_string; + std::wstring title_string; + if (stat_url.pwcsUrl) { + url_string = stat_url.pwcsUrl; + CoTaskMemFree(stat_url.pwcsUrl); + } + if (stat_url.pwcsTitle) { + title_string = stat_url.pwcsTitle; + CoTaskMemFree(stat_url.pwcsTitle); + } + + GURL url(url_string); + // Skips the URLs that are invalid or have other schemes. + if (!url.is_valid() || + (std::find(kSchemes, kSchemes + total_schemes, url.scheme()) == + kSchemes + total_schemes)) + continue; + + history::URLRow row(url); + row.set_title(title_string); + row.set_last_visit(Time::FromFileTime(stat_url.ftLastVisited)); + if (stat_url.dwFlags == STATURL_QUERYFLAG_TOPLEVEL) { + row.set_visit_count(1); + row.set_hidden(false); + } else { + row.set_hidden(true); + } + + rows.push_back(row); + } + + if (!rows.empty() && !cancelled()) { + bridge_->SetHistoryItems(rows); + } + } +} + +void IEImporter::ImportSearchEngines() { + // On IE, search engines are stored in the registry, under: + // Software\Microsoft\Internet Explorer\SearchScopes + // Each key represents a search engine. The URL value contains the URL and + // the DisplayName the name. + // The default key's name is contained under DefaultScope. + const wchar_t kSearchScopePath[] = + L"Software\\Microsoft\\Internet Explorer\\SearchScopes"; + + RegKey key(HKEY_CURRENT_USER, kSearchScopePath, KEY_READ); + std::wstring default_search_engine_name; + const TemplateURL* default_search_engine = NULL; + std::map<std::string, TemplateURL*> search_engines_map; + key.ReadValue(L"DefaultScope", &default_search_engine_name); + RegistryKeyIterator key_iterator(HKEY_CURRENT_USER, kSearchScopePath); + while (key_iterator.Valid()) { + std::wstring sub_key_name = kSearchScopePath; + sub_key_name.append(L"\\").append(key_iterator.Name()); + RegKey sub_key(HKEY_CURRENT_USER, sub_key_name.c_str(), KEY_READ); + std::wstring wide_url; + if (!sub_key.ReadValue(L"URL", &wide_url) || wide_url.empty()) { + LOG(INFO) << "No URL for IE search engine at " << key_iterator.Name(); + ++key_iterator; + continue; + } + // For the name, we try the default value first (as Live Search uses a + // non displayable name in DisplayName, and the readable name under the + // default value). + std::wstring name; + if (!sub_key.ReadValue(NULL, &name) || name.empty()) { + // Try the displayable name. + if (!sub_key.ReadValue(L"DisplayName", &name) || name.empty()) { + LOG(INFO) << "No name for IE search engine at " << key_iterator.Name(); + ++key_iterator; + continue; + } + } + + std::string url(WideToUTF8(wide_url)); + std::map<std::string, TemplateURL*>::iterator t_iter = + search_engines_map.find(url); + TemplateURL* template_url = + (t_iter != search_engines_map.end()) ? t_iter->second : NULL; + if (!template_url) { + // First time we see that URL. + template_url = new TemplateURL(); + template_url->set_short_name(name); + template_url->SetURL(url, 0, 0); + // Give this a keyword to facilitate tab-to-search, if possible. + template_url->set_keyword(TemplateURLModel::GenerateKeyword(GURL(url), + false)); + template_url->set_show_in_default_list(true); + search_engines_map[url] = template_url; + } + if (template_url && key_iterator.Name() == default_search_engine_name) { + DCHECK(!default_search_engine); + default_search_engine = template_url; + } + ++key_iterator; + } + + // ProfileWriter::AddKeywords() requires a vector and we have a map. + std::map<std::string, TemplateURL*>::iterator t_iter; + std::vector<TemplateURL*> search_engines; + int default_search_engine_index = -1; + for (t_iter = search_engines_map.begin(); t_iter != search_engines_map.end(); + ++t_iter) { + search_engines.push_back(t_iter->second); + if (default_search_engine == t_iter->second) { + default_search_engine_index = + static_cast<int>(search_engines.size()) - 1; + } + } + bridge_->SetKeywords(search_engines, default_search_engine_index, true); +} + +void IEImporter::ImportHomepage() { + const wchar_t kIESettingsMain[] = + L"Software\\Microsoft\\Internet Explorer\\Main"; + const wchar_t kIEHomepage[] = L"Start Page"; + const wchar_t kIEDefaultHomepage[] = L"Default_Page_URL"; + + RegKey key(HKEY_CURRENT_USER, kIESettingsMain, KEY_READ); + std::wstring homepage_url; + if (!key.ReadValue(kIEHomepage, &homepage_url) || homepage_url.empty()) + return; + + GURL homepage = GURL(homepage_url); + if (!homepage.is_valid()) + return; + + // Check to see if this is the default website and skip import. + RegKey keyDefault(HKEY_LOCAL_MACHINE, kIESettingsMain, KEY_READ); + std::wstring default_homepage_url; + if (keyDefault.ReadValue(kIEDefaultHomepage, &default_homepage_url) && + !default_homepage_url.empty()) { + if (homepage.spec() == GURL(default_homepage_url).spec()) + return; + } + + bridge_->AddHomePage(homepage); +} + +bool IEImporter::GetFavoritesInfo(IEImporter::FavoritesInfo *info) { + if (!source_path_.empty()) { + // Source path exists during testing. + info->path = source_path_; + file_util::AppendToPath(&info->path, L"Favorites"); + info->links_folder = L"Links"; + return true; + } + + // IE stores the favorites in the Favorites under user profile's folder. + wchar_t buffer[MAX_PATH]; + if (FAILED(SHGetFolderPath(NULL, CSIDL_FAVORITES, NULL, + SHGFP_TYPE_CURRENT, buffer))) + return false; + info->path = buffer; + + // There is a Links folder under Favorites folder in Windows Vista, but it + // is not recording in Vista's registry. So in Vista, we assume the Links + // folder is under Favorites folder since it looks like there is not name + // different in every language version of Windows Vista. + if (win_util::GetWinVersion() < win_util::WINVERSION_VISTA) { + // The Link folder name is stored in the registry. + DWORD buffer_length = sizeof(buffer); + RegKey reg_key(HKEY_CURRENT_USER, + L"Software\\Microsoft\\Internet Explorer\\Toolbar"); + if (!reg_key.ReadValue(L"LinksFolderName", buffer, &buffer_length, NULL)) + return false; + info->links_folder = buffer; + } else { + info->links_folder = L"Links"; + } + + return true; +} + +void IEImporter::ParseFavoritesFolder(const FavoritesInfo& info, + BookmarkVector* bookmarks) { + std::wstring ie_folder = l10n_util::GetString(IDS_BOOKMARK_GROUP_FROM_IE); + BookmarkVector toolbar_bookmarks; + FilePath file; + std::vector<FilePath::StringType> file_list; + FilePath favorites_path(info.path); + // Favorites path length. Make sure it doesn't include the trailing \. + size_t favorites_path_len = + favorites_path.StripTrailingSeparators().value().size(); + file_util::FileEnumerator file_enumerator( + favorites_path, true, file_util::FileEnumerator::FILES); + while (!(file = file_enumerator.Next()).value().empty() && !cancelled()) + file_list.push_back(file.value()); + + // Keep the bookmarks in alphabetical order. + std::sort(file_list.begin(), file_list.end()); + + for (std::vector<FilePath::StringType>::iterator it = file_list.begin(); + it != file_list.end(); ++it) { + FilePath shortcut(*it); + if (!LowerCaseEqualsASCII(shortcut.Extension(), ".url")) + continue; + + // Skip the bookmark with invalid URL. + GURL url = GURL(ResolveInternetShortcut(*it)); + if (!url.is_valid()) + continue; + + // Make the relative path from the Favorites folder, without the basename. + // ex. Suppose that the Favorites folder is C:\Users\Foo\Favorites. + // C:\Users\Foo\Favorites\Foo.url -> "" + // C:\Users\Foo\Favorites\Links\Bar\Baz.url -> "Links\Bar" + FilePath::StringType relative_string = + shortcut.DirName().value().substr(favorites_path_len); + if (relative_string.size() > 0 && FilePath::IsSeparator(relative_string[0])) + relative_string = relative_string.substr(1); + FilePath relative_path(relative_string); + + ProfileWriter::BookmarkEntry entry; + // Remove the dot, the file extension, and the directory path. + entry.title = shortcut.RemoveExtension().BaseName().value(); + entry.url = url; + entry.creation_time = GetFileCreationTime(*it); + if (!relative_path.empty()) + relative_path.GetComponents(&entry.path); + + // Flatten the bookmarks in Link folder onto bookmark toolbar. Otherwise, + // put it into "Other bookmarks". + if (import_to_bookmark_bar() && + (entry.path.size() > 0 && entry.path[0] == info.links_folder)) { + entry.in_toolbar = true; + entry.path.erase(entry.path.begin()); + toolbar_bookmarks.push_back(entry); + } else { + // We put the bookmarks in a "Imported From IE" + // folder, so that we don't mess up the "Other bookmarks". + if (!import_to_bookmark_bar()) + entry.path.insert(entry.path.begin(), ie_folder); + bookmarks->push_back(entry); + } + } + bookmarks->insert(bookmarks->begin(), toolbar_bookmarks.begin(), + toolbar_bookmarks.end()); +} + +std::wstring IEImporter::ResolveInternetShortcut(const std::wstring& file) { + win_util::CoMemReleaser<wchar_t> url; + ScopedComPtr<IUniformResourceLocator> url_locator; + HRESULT result = url_locator.CreateInstance(CLSID_InternetShortcut, NULL, + CLSCTX_INPROC_SERVER); + if (FAILED(result)) + return std::wstring(); + + ScopedComPtr<IPersistFile> persist_file; + result = persist_file.QueryFrom(url_locator); + if (FAILED(result)) + return std::wstring(); + + // Loads the Internet Shortcut from persistent storage. + result = persist_file->Load(file.c_str(), STGM_READ); + if (FAILED(result)) + return std::wstring(); + + result = url_locator->GetURL(&url); + // GetURL can return S_FALSE (FAILED(S_FALSE) is false) when url == NULL. + if (FAILED(result) || (url == NULL)) + return std::wstring(); + + return std::wstring(url); +} + +int IEImporter::CurrentIEVersion() const { + static int version = -1; + if (version < 0) { + wchar_t buffer[128]; + DWORD buffer_length = sizeof(buffer); + RegKey reg_key(HKEY_LOCAL_MACHINE, + L"Software\\Microsoft\\Internet Explorer"); + bool result = reg_key.ReadValue(L"Version", buffer, &buffer_length, NULL); + version = (result ? _wtoi(buffer) : 0); + } + return version; +} diff --git a/chrome/browser/importer/ie_importer.h b/chrome/browser/importer/ie_importer.h new file mode 100644 index 0000000..d93405c --- /dev/null +++ b/chrome/browser/importer/ie_importer.h @@ -0,0 +1,84 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_IE_IMPORTER_H_ +#define CHROME_BROWSER_IMPORTER_IE_IMPORTER_H_ + +#include "base/gtest_prod_util.h" +#include "chrome/browser/importer/importer.h" + +class IEImporter : public Importer { + public: + IEImporter() {} + + // Importer methods. + virtual void StartImport(ProfileInfo browser_info, + uint16 items, + ImporterBridge* bridge); + + private: + FRIEND_TEST_ALL_PREFIXES(ImporterTest, IEImporter); + + virtual ~IEImporter() {} + + void ImportFavorites(); + void ImportHistory(); + + // Import password for IE6 stored in protected storage. + void ImportPasswordsIE6(); + + // Import password for IE7 and IE8 stored in Storage2. + void ImportPasswordsIE7(); + + virtual void ImportSearchEngines(); + + // Import the homepage setting of IE. Note: IE supports multiple home pages, + // whereas Chrome doesn't, so we import only the one defined under the + // 'Start Page' registry key. We don't import if the homepage is set to the + // machine default. + void ImportHomepage(); + + // Resolves what's the .url file actually targets. + // Returns empty string if failed. + std::wstring ResolveInternetShortcut(const std::wstring& file); + + // A struct hosts the information of IE Favorite folder. + struct FavoritesInfo { + std::wstring path; + std::wstring links_folder; + }; + typedef std::vector<ProfileWriter::BookmarkEntry> BookmarkVector; + + // Gets the information of Favorites folder. Returns true if successful. + bool GetFavoritesInfo(FavoritesInfo* info); + + // This function will read the files in the Favorite folder, and store + // the bookmark items in |bookmarks|. + void ParseFavoritesFolder(const FavoritesInfo& info, + BookmarkVector* bookmarks); + + // Determines which version of IE is in use. + int CurrentIEVersion() const; + + // IE PStore subkey GUID: AutoComplete password & form data. + static const GUID kPStoreAutocompleteGUID; + + // A fake GUID for unit test. + static const GUID kUnittestGUID; + + // A struct hosts the information of AutoComplete data in PStore. + struct AutoCompleteInfo { + std::wstring key; + std::vector<std::wstring> data; + bool is_url; + }; + + // IE does not have source path. It's used in unit tests only for + // providing a fake source. + std::wstring source_path_; + + DISALLOW_COPY_AND_ASSIGN(IEImporter); +}; + +#endif // CHROME_BROWSER_IMPORTER_IE_IMPORTER_H_ diff --git a/chrome/browser/importer/importer.cc b/chrome/browser/importer/importer.cc new file mode 100644 index 0000000..62376c3 --- /dev/null +++ b/chrome/browser/importer/importer.cc @@ -0,0 +1,588 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/importer.h" + +#include "app/l10n_util.h" +#include "base/thread.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browsing_instance.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/importer/firefox_profile_lock.h" +#include "chrome/browser/importer/importer_bridge.h" +#include "chrome/browser/renderer_host/site_instance.h" +#include "chrome/browser/search_engines/template_url_model.h" +#include "chrome/browser/webdata/web_data_service.h" +#include "chrome/common/notification_service.h" +#include "gfx/codec/png_codec.h" +#include "gfx/favicon_size.h" +#include "grit/generated_resources.h" +#include "skia/ext/image_operations.h" +#include "webkit/glue/image_decoder.h" + +// TODO(port): Port these files. +#if defined(OS_WIN) +#include "app/win_util.h" +#include "chrome/browser/views/importer_lock_view.h" +#include "views/window/window.h" +#elif defined(OS_MACOSX) +#include "chrome/browser/cocoa/importer_lock_dialog.h" +#elif defined(TOOLKIT_USES_GTK) +#include "chrome/browser/gtk/import_lock_dialog_gtk.h" +#endif + +using webkit_glue::PasswordForm; + +// Importer. + +Importer::Importer() + : cancelled_(false), + import_to_bookmark_bar_(false), + bookmark_bar_disabled_(false) { +} + +Importer::~Importer() { +} + +// static +bool Importer::ReencodeFavicon(const unsigned char* src_data, size_t src_len, + std::vector<unsigned char>* png_data) { + // Decode the favicon using WebKit's image decoder. + webkit_glue::ImageDecoder decoder(gfx::Size(kFavIconSize, kFavIconSize)); + SkBitmap decoded = decoder.Decode(src_data, src_len); + if (decoded.empty()) + return false; // Unable to decode. + + if (decoded.width() != kFavIconSize || decoded.height() != kFavIconSize) { + // The bitmap is not the correct size, re-sample. + int new_width = decoded.width(); + int new_height = decoded.height(); + calc_favicon_target_size(&new_width, &new_height); + decoded = skia::ImageOperations::Resize( + decoded, skia::ImageOperations::RESIZE_LANCZOS3, new_width, new_height); + } + + // Encode our bitmap as a PNG. + gfx::PNGCodec::EncodeBGRASkBitmap(decoded, false, png_data); + return true; +} + +// ImporterHost. + +ImporterHost::ImporterHost() + : profile_(NULL), + observer_(NULL), + task_(NULL), + importer_(NULL), + waiting_for_bookmarkbar_model_(false), + installed_bookmark_observer_(false), + is_source_readable_(true), + headless_(false), + parent_window_(NULL) { + importer_list_.DetectSourceProfiles(); +} + +ImporterHost::~ImporterHost() { + if (NULL != importer_) + importer_->Release(); + if (installed_bookmark_observer_) { + DCHECK(profile_); // Only way for waiting_for_bookmarkbar_model_ to be true + // is if we have a profile. + profile_->GetBookmarkModel()->RemoveObserver(this); + } +} + +void ImporterHost::Loaded(BookmarkModel* model) { + DCHECK(model->IsLoaded()); + model->RemoveObserver(this); + waiting_for_bookmarkbar_model_ = false; + installed_bookmark_observer_ = false; + + std::vector<GURL> starred_urls; + model->GetBookmarks(&starred_urls); + importer_->set_import_to_bookmark_bar(starred_urls.size() == 0); + InvokeTaskIfDone(); +} + +void ImporterHost::BookmarkModelBeingDeleted(BookmarkModel* model) { + installed_bookmark_observer_ = false; +} + +void ImporterHost::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NotificationType::TEMPLATE_URL_MODEL_LOADED); + registrar_.RemoveAll(); + InvokeTaskIfDone(); +} + +void ImporterHost::ShowWarningDialog() { + if (headless_) { + OnLockViewEnd(false); + } else { +#if defined(OS_WIN) + views::Window::CreateChromeWindow(GetActiveWindow(), gfx::Rect(), + new ImporterLockView(this))->Show(); +#elif defined(TOOLKIT_USES_GTK) + ImportLockDialogGtk::Show(parent_window_, this); +#else + ImportLockDialogCocoa::ShowWarning(this); +#endif + } +} + +void ImporterHost::OnLockViewEnd(bool is_continue) { + if (is_continue) { + // User chose to continue, then we check the lock again to make + // sure that Firefox has been closed. Try to import the settings + // if successful. Otherwise, show a warning dialog. + firefox_lock_->Lock(); + if (firefox_lock_->HasAcquired()) { + is_source_readable_ = true; + InvokeTaskIfDone(); + } else { + ShowWarningDialog(); + } + } else { + // User chose to skip the import process. We should delete + // the task and notify the ImporterHost to finish. + delete task_; + task_ = NULL; + importer_ = NULL; + ImportEnded(); + } +} + +void ImporterHost::StartImportSettings( + const importer::ProfileInfo& profile_info, + Profile* target_profile, + uint16 items, + ProfileWriter* writer, + bool first_run) { + DCHECK(!profile_); // We really only support importing from one host at a + // time. + profile_ = target_profile; + // Preserves the observer and creates a task, since we do async import + // so that it doesn't block the UI. When the import is complete, observer + // will be notified. + writer_ = writer; + importer_ = importer_list_.CreateImporterByType(profile_info.browser_type); + // If we fail to create Importer, exit as we cannot do anything. + if (!importer_) { + ImportEnded(); + return; + } + + importer_->AddRef(); + + importer_->set_import_to_bookmark_bar(ShouldImportToBookmarkBar(first_run)); + importer_->set_bookmark_bar_disabled(first_run); + scoped_refptr<ImporterBridge> bridge( + new InProcessImporterBridge(writer_.get(), this)); + task_ = NewRunnableMethod(importer_, &Importer::StartImport, + profile_info, items, bridge); + + CheckForFirefoxLock(profile_info, items, first_run); + +#if defined(OS_WIN) + // For google toolbar import, we need the user to log in and store their GAIA + // credentials. + if (profile_info.browser_type == importer::GOOGLE_TOOLBAR5) { + if (!toolbar_importer_utils::IsGoogleGAIACookieInstalled()) { + win_util::MessageBox( + NULL, + l10n_util::GetString(IDS_IMPORTER_GOOGLE_LOGIN_TEXT).c_str(), + L"", + MB_OK | MB_TOPMOST); + + GURL url("https://www.google.com/accounts/ServiceLogin"); + BrowsingInstance* instance = new BrowsingInstance(writer_->profile()); + SiteInstance* site = instance->GetSiteInstanceForURL(url); + Browser* browser = BrowserList::GetLastActive(); + browser->AddTabWithURL(url, GURL(), PageTransition::TYPED, -1, + TabStripModel::ADD_SELECTED, site, std::string()); + + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + this, &ImporterHost::OnLockViewEnd, false)); + + is_source_readable_ = false; + } + } +#endif + + CheckForLoadedModels(items); + AddRef(); + InvokeTaskIfDone(); +} + +void ImporterHost::Cancel() { + if (importer_) + importer_->Cancel(); +} + +void ImporterHost::SetObserver(Observer* observer) { + observer_ = observer; +} + +void ImporterHost::InvokeTaskIfDone() { + if (waiting_for_bookmarkbar_model_ || !registrar_.IsEmpty() || + !is_source_readable_) + return; + ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, task_); +} + +void ImporterHost::ImportItemStarted(importer::ImportItem item) { + if (observer_) + observer_->ImportItemStarted(item); +} + +void ImporterHost::ImportItemEnded(importer::ImportItem item) { + if (observer_) + observer_->ImportItemEnded(item); +} + +void ImporterHost::ImportStarted() { + if (observer_) + observer_->ImportStarted(); +} + +void ImporterHost::ImportEnded() { + firefox_lock_.reset(); // Release the Firefox profile lock. + if (observer_) + observer_->ImportEnded(); + Release(); +} + +bool ImporterHost::ShouldImportToBookmarkBar(bool first_run) { + bool import_to_bookmark_bar = first_run; + if (profile_ && profile_->GetBookmarkModel()->IsLoaded()) { + std::vector<GURL> starred_urls; + profile_->GetBookmarkModel()->GetBookmarks(&starred_urls); + import_to_bookmark_bar = (starred_urls.size() == 0); + } + return import_to_bookmark_bar; +} + +void ImporterHost::CheckForFirefoxLock( + const importer::ProfileInfo& profile_info, uint16 items, bool first_run) { + if (profile_info.browser_type == importer::FIREFOX2 || + profile_info.browser_type == importer::FIREFOX3) { + DCHECK(!firefox_lock_.get()); + firefox_lock_.reset(new FirefoxProfileLock(profile_info.source_path)); + if (!firefox_lock_->HasAcquired()) { + // If fail to acquire the lock, we set the source unreadable and + // show a warning dialog, unless running without UI. + is_source_readable_ = false; + if (!this->headless_) + ShowWarningDialog(); + } + } +} + +void ImporterHost::CheckForLoadedModels(uint16 items) { + // BookmarkModel should be loaded before adding IE favorites. So we observe + // the BookmarkModel if needed, and start the task after it has been loaded. + if ((items & importer::FAVORITES) && !writer_->BookmarkModelIsLoaded()) { + profile_->GetBookmarkModel()->AddObserver(this); + waiting_for_bookmarkbar_model_ = true; + installed_bookmark_observer_ = true; + } + + // Observes the TemplateURLModel if needed to import search engines from the + // other browser. We also check to see if we're importing bookmarks because + // we can import bookmark keywords from Firefox as search engines. + if ((items & importer::SEARCH_ENGINES) || (items & importer::FAVORITES)) { + if (!writer_->TemplateURLModelIsLoaded()) { + TemplateURLModel* model = profile_->GetTemplateURLModel(); + registrar_.Add(this, NotificationType::TEMPLATE_URL_MODEL_LOADED, + Source<TemplateURLModel>(model)); + model->Load(); + } + } +} + +ExternalProcessImporterHost::ExternalProcessImporterHost() + : cancelled_(false), + import_process_launched_(false) { +} + +void ExternalProcessImporterHost::Loaded(BookmarkModel* model) { + DCHECK(model->IsLoaded()); + model->RemoveObserver(this); + waiting_for_bookmarkbar_model_ = false; + installed_bookmark_observer_ = false; + + std::vector<GURL> starred_urls; + model->GetBookmarks(&starred_urls); + // Because the import process is running externally, the decision whether + // to import to the bookmark bar must be stored here so that it can be + // passed to the importer when the import task is invoked. + import_to_bookmark_bar_ = (starred_urls.size() == 0); + InvokeTaskIfDone(); +} + +void ExternalProcessImporterHost::Cancel() { + cancelled_ = true; + if (import_process_launched_) + client_->Cancel(); + ImportEnded(); // Tells the observer that we're done, and releases us. +} + +void ExternalProcessImporterHost::StartImportSettings( + const importer::ProfileInfo& profile_info, + Profile* target_profile, + uint16 items, + ProfileWriter* writer, + bool first_run) { + DCHECK(!profile_); + profile_ = target_profile; + writer_ = writer; + profile_info_ = &profile_info; + items_ = items; + + ImporterHost::AddRef(); // Balanced in ImporterHost::ImportEnded. + + import_to_bookmark_bar_ = ShouldImportToBookmarkBar(first_run); + CheckForFirefoxLock(profile_info, items, first_run); + CheckForLoadedModels(items); + + InvokeTaskIfDone(); +} + +void ExternalProcessImporterHost::InvokeTaskIfDone() { + if (waiting_for_bookmarkbar_model_ || !registrar_.IsEmpty() || + !is_source_readable_ || cancelled_) + return; + + // The in-process half of the bridge which catches data from the IPC pipe + // and feeds it to the ProfileWriter. The external process half of the + // bridge lives in the external process -- see ProfileImportThread. + // The ExternalProcessImporterClient created in the next line owns this + // bridge, and will delete it. + InProcessImporterBridge* bridge = + new InProcessImporterBridge(writer_.get(), this); + client_ = new ExternalProcessImporterClient(this, *profile_info_, items_, + bridge, import_to_bookmark_bar_); + import_process_launched_ = true; + client_->Start(); +} + +ExternalProcessImporterClient::ExternalProcessImporterClient( + ExternalProcessImporterHost* importer_host, + const importer::ProfileInfo& profile_info, + int items, + InProcessImporterBridge* bridge, + bool import_to_bookmark_bar) + : process_importer_host_(importer_host), + profile_info_(profile_info), + items_(items), + import_to_bookmark_bar_(import_to_bookmark_bar), + bridge_(bridge), + cancelled_(FALSE) { + bridge_->AddRef(); + process_importer_host_->ImportStarted(); +} + +ExternalProcessImporterClient::~ExternalProcessImporterClient() { + bridge_->Release(); +} + +void ExternalProcessImporterClient::Start() { + AddRef(); // balanced in Cleanup. + ChromeThread::ID thread_id; + CHECK(ChromeThread::GetCurrentThreadIdentifier(&thread_id)); + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableMethod(this, + &ExternalProcessImporterClient::StartProcessOnIOThread, + g_browser_process->resource_dispatcher_host(), thread_id)); +} + +void ExternalProcessImporterClient::StartProcessOnIOThread( + ResourceDispatcherHost* rdh, + ChromeThread::ID thread_id) { + profile_import_process_host_ = + new ProfileImportProcessHost(rdh, this, thread_id); + profile_import_process_host_->StartProfileImportProcess(profile_info_, + items_, import_to_bookmark_bar_); +} + +void ExternalProcessImporterClient::Cancel() { + if (cancelled_) + return; + + cancelled_ = true; + if (profile_import_process_host_) { + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableMethod(this, + &ExternalProcessImporterClient::CancelImportProcessOnIOThread)); + } + Release(); +} + +void ExternalProcessImporterClient::CancelImportProcessOnIOThread() { + profile_import_process_host_->CancelProfileImportProcess(); +} + +void ExternalProcessImporterClient::NotifyItemFinishedOnIOThread( + importer::ImportItem import_item) { + profile_import_process_host_->ReportImportItemFinished(import_item); +} + +void ExternalProcessImporterClient::OnProcessCrashed() { + if (cancelled_) + return; + + process_importer_host_->Cancel(); +} + +void ExternalProcessImporterClient::Cleanup() { + if (cancelled_) + return; + + if (process_importer_host_) + process_importer_host_->ImportEnded(); + Release(); +} + +void ExternalProcessImporterClient::OnImportStart() { + if (cancelled_) + return; + + bridge_->NotifyStarted(); +} + +void ExternalProcessImporterClient::OnImportFinished(bool succeeded, + std::string error_msg) { + if (cancelled_) + return; + + if (!succeeded) + LOG(WARNING) << "Import failed. Error: " << error_msg; + Cleanup(); +} + +void ExternalProcessImporterClient::OnImportItemStart(int item_data) { + if (cancelled_) + return; + + bridge_->NotifyItemStarted(static_cast<importer::ImportItem>(item_data)); +} + +void ExternalProcessImporterClient::OnImportItemFinished(int item_data) { + if (cancelled_) + return; + + importer::ImportItem import_item = + static_cast<importer::ImportItem>(item_data); + bridge_->NotifyItemEnded(import_item); + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableMethod(this, + &ExternalProcessImporterClient::NotifyItemFinishedOnIOThread, + import_item)); +} + +void ExternalProcessImporterClient::OnHistoryImportStart( + size_t total_history_rows_count) { + if (cancelled_) + return; + + total_history_rows_count_ = total_history_rows_count; + history_rows_.reserve(total_history_rows_count); +} + +void ExternalProcessImporterClient::OnHistoryImportGroup( + const std::vector<history::URLRow> &history_rows_group) { + if (cancelled_) + return; + + history_rows_.insert(history_rows_.end(), history_rows_group.begin(), + history_rows_group.end()); + if (history_rows_.size() == total_history_rows_count_) + bridge_->SetHistoryItems(history_rows_); +} + +void ExternalProcessImporterClient::OnHomePageImportReady( + const GURL& home_page) { + if (cancelled_) + return; + + bridge_->AddHomePage(home_page); +} + +void ExternalProcessImporterClient::OnBookmarksImportStart( + const std::wstring first_folder_name, + int options, size_t total_bookmarks_count) { + if (cancelled_) + return; + + bookmarks_first_folder_name_ = first_folder_name; + bookmarks_options_ = options; + total_bookmarks_count_ = total_bookmarks_count; + bookmarks_.reserve(total_bookmarks_count); +} + +void ExternalProcessImporterClient::OnBookmarksImportGroup( + const std::vector<ProfileWriter::BookmarkEntry>& bookmarks_group) { + if (cancelled_) + return; + + // Collect sets of bookmarks from importer process until we have reached + // total_bookmarks_count_: + bookmarks_.insert(bookmarks_.end(), bookmarks_group.begin(), + bookmarks_group.end()); + if (bookmarks_.size() == total_bookmarks_count_) { + bridge_->AddBookmarkEntries(bookmarks_, bookmarks_first_folder_name_, + bookmarks_options_); + } +} + +void ExternalProcessImporterClient::OnFavIconsImportStart( + size_t total_fav_icons_count) { + if (cancelled_) + return; + + total_fav_icons_count_ = total_fav_icons_count; + fav_icons_.reserve(total_fav_icons_count); +} + +void ExternalProcessImporterClient::OnFavIconsImportGroup( + const std::vector<history::ImportedFavIconUsage>& fav_icons_group) { + if (cancelled_) + return; + + fav_icons_.insert(fav_icons_.end(), fav_icons_group.begin(), + fav_icons_group.end()); + if (fav_icons_.size() == total_fav_icons_count_) + bridge_->SetFavIcons(fav_icons_); +} + +void ExternalProcessImporterClient::OnPasswordFormImportReady( + const webkit_glue::PasswordForm& form) { + if (cancelled_) + return; + + bridge_->SetPasswordForm(form); +} + +void ExternalProcessImporterClient::OnKeywordsImportReady( + const std::vector<TemplateURL>& template_urls, + int default_keyword_index, bool unique_on_host_and_path) { + if (cancelled_) + return; + + std::vector<TemplateURL*> template_url_vec; + template_url_vec.reserve(template_urls.size()); + std::vector<TemplateURL>::const_iterator iter; + for (iter = template_urls.begin(); + iter != template_urls.end(); + ++iter) { + template_url_vec.push_back(new TemplateURL(*iter)); + } + bridge_->SetKeywords(template_url_vec, default_keyword_index, + unique_on_host_and_path); +} diff --git a/chrome/browser/importer/importer.h b/chrome/browser/importer/importer.h new file mode 100644 index 0000000..eec498b --- /dev/null +++ b/chrome/browser/importer/importer.h @@ -0,0 +1,522 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_IMPORTER_H_ +#define CHROME_BROWSER_IMPORTER_IMPORTER_H_ + +#include <string> +#include <vector> + +#include "build/build_config.h" + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/time.h" +#include "chrome/browser/bookmarks/bookmark_model_observer.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "chrome/browser/importer/importer_list.h" +#include "chrome/browser/importer/profile_writer.h" +#include "chrome/browser/profile_import_process_host.h" +#include "chrome/common/notification_registrar.h" +#include "gfx/native_widget_types.h" +#include "googleurl/src/gurl.h" + +using importer::ImportItem; +using importer::ProfileInfo; + +class ExternalProcessImporterClient; +class ImporterBridge; +class InProcessImporterBridge; +class Profile; +class Task; +class TemplateURL; + +struct IE7PasswordInfo; + +namespace history { +struct ImportedFavIconUsage; +class URLRow; +} + +namespace webkit_glue { +struct PasswordForm; +} + +class FirefoxProfileLock; +class Importer; + +// This class hosts the importers. It enumerates profiles from other +// browsers dynamically, and controls the process of importing. When +// the import process is done, ImporterHost deletes itself. +class ImporterHost : public base::RefCountedThreadSafe<ImporterHost>, + public BookmarkModelObserver, + public NotificationObserver { + public: + ImporterHost(); + + // BookmarkModelObserver methods. + virtual void Loaded(BookmarkModel* model); + virtual void BookmarkNodeMoved(BookmarkModel* model, + const BookmarkNode* old_parent, + int old_index, + const BookmarkNode* new_parent, + int new_index) {} + virtual void BookmarkNodeAdded(BookmarkModel* model, + const BookmarkNode* parent, + int index) {} + virtual void BookmarkNodeRemoved(BookmarkModel* model, + const BookmarkNode* parent, + int old_index, + const BookmarkNode* node) {} + virtual void BookmarkNodeChanged(BookmarkModel* model, + const BookmarkNode* node) {} + virtual void BookmarkNodeChildrenReordered(BookmarkModel* model, + const BookmarkNode* node) {} + virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model, + const BookmarkNode* node) {} + virtual void BookmarkModelBeingDeleted(BookmarkModel* model); + + // NotificationObserver method. Called when TemplateURLModel has been loaded. + void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // ShowWarningDialog() asks user to close the application that is owning the + // lock. They can retry or skip the importing process. + void ShowWarningDialog(); + + // OnLockViewEnd() is called when user end the dialog by clicking a push + // button. |is_continue| is true when user clicked the "Continue" button. + void OnLockViewEnd(bool is_continue); + + // Starts the process of importing the settings and data depending on what + // the user selected. + // |profile_info| -- browser profile to import. + // |target_profile| -- profile to import into. + // |items| -- specifies which data to import (mask of ImportItems). + // |writer| -- called to actually write data back to the profile. + // |first_run| -- true if this method is being called during first run. + virtual void StartImportSettings(const importer::ProfileInfo& profile_info, + Profile* target_profile, + uint16 items, + ProfileWriter* writer, + bool first_run); + + // Cancel import. + virtual void Cancel(); + + // When in headless mode, the importer will not show the warning dialog and + // the outcome is as if the user had canceled the import operation. + void set_headless() { + headless_ = true; + } + + bool is_headless() const { + return headless_; + } + + void set_parent_window(gfx::NativeWindow parent_window) { + parent_window_ = parent_window; + } + + // An interface which an object can implement to be notified of events during + // the import process. + class Observer { + public: + virtual ~Observer() {} + // Invoked when data for the specified item is about to be collected. + virtual void ImportItemStarted(importer::ImportItem item) = 0; + + // Invoked when data for the specified item has been collected from the + // source profile and is now ready for further processing. + virtual void ImportItemEnded(importer::ImportItem item) = 0; + + // Invoked when the import begins. + virtual void ImportStarted() = 0; + + // Invoked when the source profile has been imported. + virtual void ImportEnded() = 0; + }; + void SetObserver(Observer* observer); + + // A series of functions invoked at the start, during and end of the end + // of the import process. The middle functions are notifications that the + // harvesting of a particular source of data (specified by |item|) is under + // way. + virtual void ImportStarted(); + virtual void ImportItemStarted(importer::ImportItem item); + virtual void ImportItemEnded(importer::ImportItem item); + virtual void ImportEnded(); + + int GetAvailableProfileCount() const { + return importer_list_.GetAvailableProfileCount(); + } + + // Returns the name of the profile at the 'index' slot. The profiles are + // ordered such that the profile at index 0 is the likely default browser. + std::wstring GetSourceProfileNameAt(int index) const { + return importer_list_.GetSourceProfileNameAt(index); + } + + // Returns the ProfileInfo at the specified index. The ProfileInfo should be + // passed to StartImportSettings(). + const importer::ProfileInfo& GetSourceProfileInfoAt(int index) const { + return importer_list_.GetSourceProfileInfoAt(index); + } + + // Returns the ProfileInfo with the given browser type. + const importer::ProfileInfo& GetSourceProfileInfoForBrowserType( + int browser_type) const { + return importer_list_.GetSourceProfileInfoForBrowserType(browser_type); + } + + protected: + friend class base::RefCountedThreadSafe<ImporterHost>; + + ~ImporterHost(); + + // Returns true if importer should import to bookmark bar. + bool ShouldImportToBookmarkBar(bool first_run); + + // Make sure that Firefox isn't running, if import browser is Firefox. Show + // the user a dialog to notify that they need to close FF to continue. + // |profile_info| holds the browser type and source path. + // |items| is a mask of all ImportItems that are to be imported. + // |first_run| is true if this method is being called during first run. + void CheckForFirefoxLock(const importer::ProfileInfo& profile_info, + uint16 items, bool first_run); + + // Make sure BookmarkModel and TemplateURLModel are loaded before import + // process starts, if bookmarks and / or search engines are among the items + // which are to be imported. + void CheckForLoadedModels(uint16 items); + + // Profile we're importing from. + Profile* profile_; + + Observer* observer_; + + // TODO(mirandac): task_ and importer_ should be private. Can't just put + // them there without changing the order of construct/destruct, so do this + // after main CL has been committed. + // The task is the process of importing settings from other browsers. + Task* task_; + + // The importer used in the task; + Importer* importer_; + + // Writes data from the importer back to the profile. + scoped_refptr<ProfileWriter> writer_; + + // True if we're waiting for the model to finish loading. + bool waiting_for_bookmarkbar_model_; + + // Have we installed a listener on the bookmark model? + bool installed_bookmark_observer_; + + // True if source profile is readable. + bool is_source_readable_; + + // True if UI is not to be shown. + bool headless_; + + // Receives notification when the TemplateURLModel has loaded. + NotificationRegistrar registrar_; + + // Parent Window to use when showing any modal dialog boxes. + gfx::NativeWindow parent_window_; + + // Firefox profile lock. + scoped_ptr<FirefoxProfileLock> firefox_lock_; + + private: + // Launches the thread that starts the import task, unless bookmark or + // template model are not yet loaded. If load is not detected, this method + // will be called when the loading observer sees that model loading is + // complete. + virtual void InvokeTaskIfDone(); + + // Used to create an importer of the appropriate type. + ImporterList importer_list_; + + DISALLOW_COPY_AND_ASSIGN(ImporterHost); +}; + +// This class manages the import process. It creates the in-process half of +// the importer bridge and the external process importer client. +class ExternalProcessImporterHost : public ImporterHost { + public: + ExternalProcessImporterHost(); + + // Called when the BookmarkModel has finished loading. Calls InvokeTaskIfDone + // to start importing. + virtual void Loaded(BookmarkModel* model); + + // Methods inherited from ImporterHost. + virtual void StartImportSettings(const importer::ProfileInfo& profile_info, + Profile* target_profile, + uint16 items, + ProfileWriter* writer, + bool first_run); + + virtual void Cancel(); + + protected: + // Launches the ExternalProcessImporterClient unless bookmark or template + // model are not yet loaded. If load is not detected, this method will be + // called when the loading observer sees that model loading is complete. + virtual void InvokeTaskIfDone(); + + private: + // Used to pass notifications from the browser side to the external process. + ExternalProcessImporterClient* client_; + + // Data for the external importer: ------------------------------------------ + // Information about a profile needed for importing. + const importer::ProfileInfo* profile_info_; + + // Mask of items to be imported (see importer::ImportItem). + uint16 items_; + + // Whether to import bookmarks to the bookmark bar. + bool import_to_bookmark_bar_; + + // True if the import process has been cancelled. + bool cancelled_; + + // True if the import process has been launched. This prevents race + // conditions on import cancel. + bool import_process_launched_; + + // End of external importer data -------------------------------------------- + + DISALLOW_COPY_AND_ASSIGN(ExternalProcessImporterHost); +}; + +// This class is the client for the ProfileImportProcessHost. It collects +// notifications from this process host and feeds data back to the importer +// host, who actually does the writing. +class ExternalProcessImporterClient + : public ProfileImportProcessHost::ImportProcessClient { + public: + ExternalProcessImporterClient(ExternalProcessImporterHost* importer_host, + const importer::ProfileInfo& profile_info, + int items, + InProcessImporterBridge* bridge, + bool import_to_bookmark_bar); + + ~ExternalProcessImporterClient(); + + // Launches the task to start the external process. + virtual void Start(); + + // Creates a new ProfileImportProcessHost, which launches the import process. + virtual void StartProcessOnIOThread(ResourceDispatcherHost* rdh, + ChromeThread::ID thread_id); + + // Called by the ExternalProcessImporterHost on import cancel. + virtual void Cancel(); + + // Cancel import process on IO thread. + void CancelImportProcessOnIOThread(); + + // Report item completely downloaded on IO thread. + void NotifyItemFinishedOnIOThread(importer::ImportItem import_item); + + // Cancel import on process crash. + virtual void OnProcessCrashed(); + + // Notifies the importerhost that import has finished, and calls Release(). + void Cleanup(); + + // ProfileImportProcessHost messages ---------------------------------------- + // The following methods are called by ProfileImportProcessHost when the + // corresponding message has been received from the import process. + virtual void OnImportStart(); + virtual void OnImportFinished(bool succeeded, std::string error_msg); + virtual void OnImportItemStart(int item_data); + virtual void OnImportItemFinished(int item_data); + + // Called on first message received when importing history; gives total + // number of rows to be imported. + virtual void OnHistoryImportStart(size_t total_history_rows_count); + + // Called when a group of URLRows has been received. + virtual void OnHistoryImportGroup( + const std::vector<history::URLRow> &history_rows_group); + + // Called when the home page has been received. + virtual void OnHomePageImportReady(const GURL& home_page); + + // First message received when importing bookmarks. + // |first_folder_name| can be NULL. + // |options| is described in ProfileWriter::BookmarkOptions. + // |total_bookmarks_count| is the total number of bookmarks to be imported. + virtual void OnBookmarksImportStart( + const std::wstring first_folder_name, + int options, size_t total_bookmarks_count); + + // Called when a group of bookmarks has been received. + virtual void OnBookmarksImportGroup( + const std::vector<ProfileWriter::BookmarkEntry>& bookmarks_group); + + // First message received when importing favicons. |total_fav_icons_size| + // gives the total number of fav icons to be imported. + virtual void OnFavIconsImportStart(size_t total_fav_icons_count); + + // Called when a group of favicons has been received. + virtual void OnFavIconsImportGroup( + const std::vector<history::ImportedFavIconUsage>& fav_icons_group); + + // Called when the passwordform has been received. + virtual void OnPasswordFormImportReady( + const webkit_glue::PasswordForm& form); + + // Called when search engines have been received. + virtual void OnKeywordsImportReady( + const std::vector<TemplateURL>& template_urls, + int default_keyword_index, bool unique_on_host_and_path); + + // End ProfileImportProcessHost messages ------------------------------------ + + private: + // These variables store data being collected from the importer until the + // entire group has been collected and is ready to be written to the profile. + std::vector<history::URLRow> history_rows_; + std::vector<ProfileWriter::BookmarkEntry> bookmarks_; + std::vector<history::ImportedFavIconUsage> fav_icons_; + + // Usually some variation on IDS_BOOKMARK_GROUP_...; the name of the folder + // under which imported bookmarks will be placed. + std::wstring bookmarks_first_folder_name_; + + // Determines how bookmarks should be added (ProfileWriter::BookmarkOptions). + int bookmarks_options_; + + // Total number of bookmarks to import. + size_t total_bookmarks_count_; + + // Total number of history items to import. + size_t total_history_rows_count_; + + // Total number of fav icons to import. + size_t total_fav_icons_count_; + + // Notifications received from the ProfileImportProcessHost are passed back + // to process_importer_host_, which calls the ProfileWriter to record the + // import data. When the import process is done, process_importer_host_ + // deletes itself. + ExternalProcessImporterHost* process_importer_host_; + + // Handles sending messages to the external process. Deletes itself when + // the external process dies (see ChildProcessHost::OnChildDied). + ProfileImportProcessHost* profile_import_process_host_; + + // Data to be passed from the importer host to the external importer. + const importer::ProfileInfo& profile_info_; + int items_; + bool import_to_bookmark_bar_; + + // Takes import data coming over IPC and delivers it to be written by the + // ProfileWriter. Released by ExternalProcessImporterClient in its + // destructor. + InProcessImporterBridge* bridge_; + + // True if import process has been cancelled. + bool cancelled_; + + DISALLOW_COPY_AND_ASSIGN(ExternalProcessImporterClient); +}; + +// The base class of all importers. +class Importer : public base::RefCountedThreadSafe<Importer> { + public: + // All importers should implement this method by adding their + // import logic. And it will be run in file thread by ImporterHost. + // + // Since we do async import, the importer should invoke + // ImporterHost::Finished() to notify its host that import + // stuff have been finished. + virtual void StartImport(importer::ProfileInfo profile_info, + uint16 items, + ImporterBridge* bridge) = 0; + + // Cancels the import process. + virtual void Cancel() { cancelled_ = true; } + + void set_import_to_bookmark_bar(bool import_to_bookmark_bar) { + import_to_bookmark_bar_ = import_to_bookmark_bar; + } + + void set_bookmark_bar_disabled(bool bookmark_bar_disabled) { + bookmark_bar_disabled_ = bookmark_bar_disabled; + } + + bool bookmark_bar_disabled() { + return bookmark_bar_disabled_; + } + + bool cancelled() const { return cancelled_; } + + protected: + friend class base::RefCountedThreadSafe<Importer>; + + Importer(); + virtual ~Importer(); + + // Given raw image data, decodes the icon, re-sampling to the correct size as + // necessary, and re-encodes as PNG data in the given output vector. Returns + // true on success. + static bool ReencodeFavicon(const unsigned char* src_data, size_t src_len, + std::vector<unsigned char>* png_data); + + bool import_to_bookmark_bar() const { return import_to_bookmark_bar_; } + + scoped_refptr<ImporterBridge> bridge_; + + private: + // True if the caller cancels the import process. + bool cancelled_; + + // True if the importer is created in the first run UI. + bool import_to_bookmark_bar_; + + // Whether bookmark bar is disabled (not shown) for importer. This is set + // true during first run to prevent out of process bookmark importer from + // updating bookmark bar settings. + bool bookmark_bar_disabled_; + + DISALLOW_COPY_AND_ASSIGN(Importer); +}; + +// An interface an object that calls StartImportingWithUI can call to be +// notified about the state of the import operation. +class ImportObserver { + public: + virtual ~ImportObserver() {} + // The import operation was canceled by the user. + // TODO (4164): this is never invoked, either rip it out or invoke it. + virtual void ImportCanceled() = 0; + + // The import operation was completed successfully. + virtual void ImportComplete() = 0; +}; + + +// Shows a UI for importing and begins importing the specified items from +// source_profile to target_profile. observer is notified when the process is +// complete, can be NULL. parent is the window to parent the UI to, can be NULL +// if there's nothing to parent to. first_run is true if it's invoked in the +// first run UI. +void StartImportingWithUI(gfx::NativeWindow parent_window, + int16 items, + ImporterHost* coordinator, + const importer::ProfileInfo& source_profile, + Profile* target_profile, + ImportObserver* observer, + bool first_run); + +#endif // CHROME_BROWSER_IMPORTER_IMPORTER_H_ diff --git a/chrome/browser/importer/importer_bridge.cc b/chrome/browser/importer/importer_bridge.cc new file mode 100644 index 0000000..72a73a6 --- /dev/null +++ b/chrome/browser/importer/importer_bridge.cc @@ -0,0 +1,190 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/importer_bridge.h" + +#include "app/l10n_util.h" +#include "base/scoped_ptr.h" +#include "base/values.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/browser/importer/importer.h" +#if defined(OS_WIN) +#include "chrome/browser/password_manager/ie7_password.h" +#endif +#include "chrome/common/child_thread.h" +#include "chrome/browser/importer/importer_messages.h" +#include "chrome/profile_import/profile_import_thread.h" +#include "webkit/glue/password_form.h" + +InProcessImporterBridge::InProcessImporterBridge(ProfileWriter* writer, + ImporterHost* host) + : writer_(writer), host_(host) { +} + +void InProcessImporterBridge::AddBookmarkEntries( + const std::vector<ProfileWriter::BookmarkEntry>& bookmarks, + const std::wstring& first_folder_name, + int options) { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod( + writer_, &ProfileWriter::AddBookmarkEntry, bookmarks, + first_folder_name, options)); +} + +void InProcessImporterBridge::AddHomePage(const GURL &home_page) { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(writer_, &ProfileWriter::AddHomepage, home_page)); +} + +#if defined(OS_WIN) +void InProcessImporterBridge::AddIE7PasswordInfo( + const IE7PasswordInfo password_info) { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(writer_, &ProfileWriter::AddIE7PasswordInfo, + password_info)); +} +#endif // OS_WIN + +void InProcessImporterBridge::SetFavIcons( + const std::vector<history::ImportedFavIconUsage>& fav_icons) { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(writer_, &ProfileWriter::AddFavicons, fav_icons)); +} + +void InProcessImporterBridge::SetHistoryItems( + const std::vector<history::URLRow> &rows) { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(writer_, &ProfileWriter::AddHistoryPage, rows)); +} + +void InProcessImporterBridge::SetKeywords( + const std::vector<TemplateURL*>& template_urls, + int default_keyword_index, + bool unique_on_host_and_path) { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod( + writer_, &ProfileWriter::AddKeywords, template_urls, + default_keyword_index, unique_on_host_and_path)); +} + +void InProcessImporterBridge::SetPasswordForm( + const webkit_glue::PasswordForm& form) { + LOG(ERROR) << "IPImporterBridge::SetPasswordForm"; + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(writer_, &ProfileWriter::AddPasswordForm, form)); +} + +void InProcessImporterBridge::NotifyItemStarted(importer::ImportItem item) { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(host_, &ImporterHost::ImportItemStarted, item)); +} + +void InProcessImporterBridge::NotifyItemEnded(importer::ImportItem item) { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(host_, &ImporterHost::ImportItemEnded, item)); +} + +void InProcessImporterBridge::NotifyStarted() { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(host_, &ImporterHost::ImportStarted)); +} + +void InProcessImporterBridge::NotifyEnded() { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(host_, &ImporterHost::ImportEnded)); +} + +std::wstring InProcessImporterBridge::GetLocalizedString(int message_id) { + return l10n_util::GetString(message_id); +} + +ExternalProcessImporterBridge::ExternalProcessImporterBridge( + ProfileImportThread* profile_import_thread, + const DictionaryValue& localized_strings) + : profile_import_thread_(profile_import_thread) { + // Bridge needs to make its own copy because OS 10.6 autoreleases the + // localized_strings value that is passed in (see http://crbug.com/46003 ). + localized_strings_.reset( + static_cast<DictionaryValue*>(localized_strings.DeepCopy())); +} + +void ExternalProcessImporterBridge::AddBookmarkEntries( + const std::vector<ProfileWriter::BookmarkEntry>& bookmarks, + const std::wstring& first_folder_name, int options) { + profile_import_thread_->NotifyBookmarksImportReady(bookmarks, + first_folder_name, options); +} + +void ExternalProcessImporterBridge::AddHomePage(const GURL &home_page) { + // TODO(mirandac): remove home page import from code base. + // http://crbug.com/45678 :-) + NOTIMPLEMENTED(); +} + +#if defined(OS_WIN) +void ExternalProcessImporterBridge::AddIE7PasswordInfo( + const IE7PasswordInfo password_info) { + NOTIMPLEMENTED(); +} +#endif + +void ExternalProcessImporterBridge::SetFavIcons( + const std::vector<history::ImportedFavIconUsage>& fav_icons) { + profile_import_thread_->NotifyFavIconsImportReady(fav_icons); +} + +void ExternalProcessImporterBridge::SetHistoryItems( + const std::vector<history::URLRow> &rows) { + profile_import_thread_->NotifyHistoryImportReady(rows); +} + +void ExternalProcessImporterBridge::SetKeywords( + const std::vector<TemplateURL*>& template_urls, + int default_keyword_index, + bool unique_on_host_and_path) { + profile_import_thread_->NotifyKeywordsReady(template_urls, + default_keyword_index, unique_on_host_and_path); +} + +void ExternalProcessImporterBridge::SetPasswordForm( + const webkit_glue::PasswordForm& form) { + profile_import_thread_->NotifyPasswordFormReady(form); +} + +void ExternalProcessImporterBridge::NotifyItemStarted( + importer::ImportItem item) { + profile_import_thread_->NotifyItemStarted(item); +} + +void ExternalProcessImporterBridge::NotifyItemEnded(importer::ImportItem item) { + profile_import_thread_->NotifyItemEnded(item); +} + +void ExternalProcessImporterBridge::NotifyStarted() { + profile_import_thread_->NotifyStarted(); +} + +void ExternalProcessImporterBridge::NotifyEnded() { + // The internal process detects import end when all items have been received. +} + +std::wstring ExternalProcessImporterBridge::GetLocalizedString( + int message_id) { + std::wstring message; + localized_strings_->GetString(IntToWString(message_id), &message); + return message; +} + diff --git a/chrome/browser/importer/importer_bridge.h b/chrome/browser/importer/importer_bridge.h new file mode 100644 index 0000000..993ec54 --- /dev/null +++ b/chrome/browser/importer/importer_bridge.h @@ -0,0 +1,168 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_IMPORTER_BRIDGE_H_ +#define CHROME_BROWSER_IMPORTER_IMPORTER_BRIDGE_H_ + +#include "build/build_config.h" + +#include <vector> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/string16.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/importer/importer_data_types.h" +// TODO: remove this, see friend declaration in ImporterBridge. +#include "chrome/browser/importer/toolbar_importer.h" + +class ProfileImportThread; +class DictionaryValue; +class ImporterHost; + +class ImporterBridge : public base::RefCountedThreadSafe<ImporterBridge> { + public: + ImporterBridge() { } + + virtual void AddBookmarkEntries( + const std::vector<ProfileWriter::BookmarkEntry>& bookmarks, + const std::wstring& first_folder_name, + int options) = 0; + virtual void AddHomePage(const GURL &home_page) = 0; + +#if defined(OS_WIN) + virtual void AddIE7PasswordInfo(const IE7PasswordInfo password_info) = 0; +#endif + + virtual void SetFavIcons( + const std::vector<history::ImportedFavIconUsage>& fav_icons) = 0; + virtual void SetHistoryItems(const std::vector<history::URLRow> &rows) = 0; + virtual void SetKeywords(const std::vector<TemplateURL*> &template_urls, + int default_keyword_index, + bool unique_on_host_and_path) = 0; + virtual void SetPasswordForm(const webkit_glue::PasswordForm& form) = 0; + + // Notifies the coordinator that the collection of data for the specified + // item has begun. + virtual void NotifyItemStarted(importer::ImportItem item) = 0; + + // Notifies the coordinator that the collection of data for the specified + // item has completed. + virtual void NotifyItemEnded(importer::ImportItem item) = 0; + + // Notifies the coordinator that the import operation has begun. + virtual void NotifyStarted() = 0; + + // Notifies the coordinator that the entire import operation has completed. + virtual void NotifyEnded() = 0; + + // For InProcessImporters this calls l10n_util. For ExternalProcessImporters + // this calls the set of strings we've ported over to the external process. + // It's good to avoid having to create a separate ResourceBundle for the + // external import process, since the importer only needs a few strings. + virtual std::wstring GetLocalizedString(int message_id) = 0; + + protected: + friend class base::RefCountedThreadSafe<ImporterBridge>; + // TODO: In order to run Toolbar5Importer OOP we need to cut this + // connection, but as an interim step we allow Toolbar5Import to break + // the abstraction here and assume import is in-process. + friend class Toolbar5Importer; + + virtual ~ImporterBridge() {} + + DISALLOW_COPY_AND_ASSIGN(ImporterBridge); +}; + +class InProcessImporterBridge : public ImporterBridge { + public: + InProcessImporterBridge(ProfileWriter* writer, + ImporterHost* host); + + // Methods inherited from ImporterBridge. On the internal side, these + // methods launch tasks to write the data to the profile with the |writer_|. + virtual void AddBookmarkEntries( + const std::vector<ProfileWriter::BookmarkEntry>& bookmarks, + const std::wstring& first_folder_name, + int options); + virtual void AddHomePage(const GURL &home_page); + +#if defined(OS_WIN) + virtual void AddIE7PasswordInfo(const IE7PasswordInfo password_info); +#endif + + virtual void SetFavIcons( + const std::vector<history::ImportedFavIconUsage>& fav_icons); + virtual void SetHistoryItems(const std::vector<history::URLRow> &rows); + virtual void SetKeywords(const std::vector<TemplateURL*>& template_urls, + int default_keyword_index, + bool unique_on_host_and_path); + virtual void SetPasswordForm(const webkit_glue::PasswordForm& form); + + virtual void NotifyItemStarted(importer::ImportItem item); + virtual void NotifyItemEnded(importer::ImportItem item); + virtual void NotifyStarted(); + virtual void NotifyEnded(); + virtual std::wstring GetLocalizedString(int message_id); + + private: + ~InProcessImporterBridge() {} + + ProfileWriter* const writer_; // weak + ImporterHost* const host_; // weak + + DISALLOW_COPY_AND_ASSIGN(InProcessImporterBridge); +}; + +// When the importer is run in an external process, the bridge is effectively +// split in half by the IPC infrastructure. The external bridge receives data +// and notifications from the importer, and sends it across IPC. The +// internal bridge gathers the data from the IPC host and writes it to the +// profile. +class ExternalProcessImporterBridge : public ImporterBridge { + public: + ExternalProcessImporterBridge(ProfileImportThread* profile_import_thread, + const DictionaryValue& localized_strings); + + // Methods inherited from ImporterBridge. On the external side, these + // methods gather data and give it to a ProfileImportThread to pass back + // to the browser process. + virtual void AddBookmarkEntries( + const std::vector<ProfileWriter::BookmarkEntry>& bookmarks, + const std::wstring& first_folder_name, int options); + virtual void AddHomePage(const GURL &home_page); + +#if defined(OS_WIN) + virtual void AddIE7PasswordInfo(const IE7PasswordInfo password_info); +#endif + + virtual void SetFavIcons( + const std::vector<history::ImportedFavIconUsage>& fav_icons); + virtual void SetHistoryItems(const std::vector<history::URLRow> &rows); + virtual void SetKeywords(const std::vector<TemplateURL*>& template_urls, + int default_keyword_index, + bool unique_on_host_and_path); + virtual void SetPasswordForm(const webkit_glue::PasswordForm& form); + + virtual void NotifyItemStarted(importer::ImportItem item); + virtual void NotifyItemEnded(importer::ImportItem item); + virtual void NotifyStarted(); + virtual void NotifyEnded(); + virtual std::wstring GetLocalizedString(int message_id); + + private: + ~ExternalProcessImporterBridge() {} + + // Call back to send data and messages across IPC. + ProfileImportThread* const profile_import_thread_; + + // Holds strings needed by the external importer because the resource + // bundle isn't available to the external process. + scoped_ptr<DictionaryValue> localized_strings_; + + DISALLOW_COPY_AND_ASSIGN(ExternalProcessImporterBridge); +}; + +#endif // CHROME_BROWSER_IMPORTER_IMPORTER_BRIDGE_H_ diff --git a/chrome/browser/importer/importer_data_types.h b/chrome/browser/importer/importer_data_types.h new file mode 100644 index 0000000..444cc45 --- /dev/null +++ b/chrome/browser/importer/importer_data_types.h @@ -0,0 +1,58 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_IMPORTER_DATA_TYPES_H_ +#define CHROME_BROWSER_IMPORTER_IMPORTER_DATA_TYPES_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/file_path.h" + +// Types needed for importing data from other browsers and the Google +// Toolbar. +namespace importer { + +// An enumeration of the type of data that can be imported. +enum ImportItem { + NONE = 0, + HISTORY = 1 << 0, + FAVORITES = 1 << 1, + COOKIES = 1 << 2, // Not supported yet. + PASSWORDS = 1 << 3, + SEARCH_ENGINES = 1 << 4, + HOME_PAGE = 1 << 5, + ALL = (1 << 6) - 1 // All the bits should be 1, hence the -1. +}; + +// An enumeration of the type of browsers that we support to import +// settings and data from them. Numbers added so that data can be +// reliably cast to ints and passed across IPC. +enum ProfileType { +#if defined(OS_WIN) + MS_IE = 0, +#endif + FIREFOX2 = 1, + FIREFOX3 = 2, +#if defined(OS_MACOSX) + SAFARI = 3, +#endif + GOOGLE_TOOLBAR5 = 4, + // Identifies a 'bookmarks.html' file. + BOOKMARKS_HTML = 5 +}; + +// Information about a profile needed by an importer to do import work. +struct ProfileInfo { + std::wstring description; + importer::ProfileType browser_type; + FilePath source_path; + FilePath app_path; + uint16 services_supported; // Bitmask of ImportItem. +}; + +} // namespace importer + +#endif // CHROME_BROWSER_IMPORTER_IMPORTER_DATA_TYPES_H_ + diff --git a/chrome/browser/importer/importer_list.cc b/chrome/browser/importer/importer_list.cc new file mode 100644 index 0000000..0919489 --- /dev/null +++ b/chrome/browser/importer/importer_list.cc @@ -0,0 +1,186 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/importer_list.h" + +#include "app/l10n_util.h" +#include "base/file_util.h" +#include "base/stl_util-inl.h" +#include "base/values.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/first_run.h" +#include "chrome/browser/importer/firefox2_importer.h" +#include "chrome/browser/importer/firefox3_importer.h" +#include "chrome/browser/importer/firefox_importer_utils.h" +#include "chrome/browser/importer/importer_bridge.h" +#include "chrome/browser/importer/toolbar_importer.h" +#include "chrome/browser/shell_integration.h" +#include "grit/generated_resources.h" + +#if defined(OS_WIN) +#include "app/win_util.h" +#include "chrome/browser/importer/ie_importer.h" +#include "chrome/browser/password_manager/ie7_password.h" +#endif +#if defined(OS_MACOSX) +#include "base/mac_util.h" +#include "chrome/browser/importer/safari_importer.h" +#endif + +ImporterList::ImporterList() { +} + +ImporterList::~ImporterList() { + STLDeleteContainerPointers(source_profiles_.begin(), source_profiles_.end()); +} + +void ImporterList::DetectSourceProfiles() { +#if defined(OS_WIN) + // The order in which detect is called determines the order + // in which the options appear in the dropdown combo-box + if (ShellIntegration::IsFirefoxDefaultBrowser()) { + DetectFirefoxProfiles(); + DetectIEProfiles(); + } else { + DetectIEProfiles(); + DetectFirefoxProfiles(); + } + // TODO(brg) : Current UI requires win_util. + DetectGoogleToolbarProfiles(); +#else +#if defined(OS_MACOSX) + DetectSafariProfiles(); +#endif + DetectFirefoxProfiles(); +#endif +} + +Importer* ImporterList::CreateImporterByType(importer::ProfileType type) { + switch (type) { +#if defined(OS_WIN) + case importer::MS_IE: + return new IEImporter(); +#endif + case importer::BOOKMARKS_HTML: + case importer::FIREFOX2: + return new Firefox2Importer(); + case importer::FIREFOX3: + return new Firefox3Importer(); + case importer::GOOGLE_TOOLBAR5: + return new Toolbar5Importer(); +#if defined(OS_MACOSX) + case importer::SAFARI: + return new SafariImporter(mac_util::GetUserLibraryPath()); +#endif // OS_MACOSX + } + NOTREACHED(); + return NULL; +} + +int ImporterList::GetAvailableProfileCount() const { + return static_cast<int>(source_profiles_.size()); +} + +std::wstring ImporterList::GetSourceProfileNameAt(int index) const { + DCHECK(index >=0 && index < GetAvailableProfileCount()); + return source_profiles_[index]->description; +} + +const importer::ProfileInfo& ImporterList::GetSourceProfileInfoAt( + int index) const { + DCHECK(index >=0 && index < GetAvailableProfileCount()); + return *source_profiles_[index]; +} + +const importer::ProfileInfo& ImporterList::GetSourceProfileInfoForBrowserType( + int browser_type) const { + int count = GetAvailableProfileCount(); + for (int i = 0; i < count; ++i) { + if (source_profiles_[i]->browser_type == browser_type) + return *source_profiles_[i]; + } + NOTREACHED(); + return *(new importer::ProfileInfo()); +} + +#if defined(OS_WIN) +void ImporterList::DetectIEProfiles() { + // IE always exists and don't have multiple profiles. + ProfileInfo* ie = new ProfileInfo(); + ie->description = l10n_util::GetString(IDS_IMPORT_FROM_IE); + ie->browser_type = importer::MS_IE; + ie->source_path.clear(); + ie->app_path.clear(); + ie->services_supported = importer::HISTORY | importer::FAVORITES | + importer::COOKIES | importer::PASSWORDS | importer::SEARCH_ENGINES; + source_profiles_.push_back(ie); +} +#endif + +void ImporterList::DetectFirefoxProfiles() { + FilePath profile_path = GetFirefoxProfilePath(); + if (profile_path.empty()) + return; + + // Detects which version of Firefox is installed. + importer::ProfileType firefox_type; + FilePath app_path; + int version = 0; +#if defined(OS_WIN) + version = GetCurrentFirefoxMajorVersionFromRegistry(); +#endif + if (version != 2 && version != 3) + GetFirefoxVersionAndPathFromProfile(profile_path, &version, &app_path); + + if (version == 2) { + firefox_type = importer::FIREFOX2; + } else if (version == 3) { + firefox_type = importer::FIREFOX3; + } else { + // Ignores other versions of firefox. + return; + } + + importer::ProfileInfo* firefox = new importer::ProfileInfo(); + firefox->description = l10n_util::GetString(IDS_IMPORT_FROM_FIREFOX); + firefox->browser_type = firefox_type; + firefox->source_path = profile_path; +#if defined(OS_WIN) + firefox->app_path = FilePath::FromWStringHack( + GetFirefoxInstallPathFromRegistry()); +#endif + if (firefox->app_path.empty()) + firefox->app_path = app_path; + firefox->services_supported = importer::HISTORY | importer::FAVORITES | + importer::PASSWORDS | importer::SEARCH_ENGINES; + source_profiles_.push_back(firefox); +} + +void ImporterList::DetectGoogleToolbarProfiles() { + if (!FirstRun::IsChromeFirstRun()) { + importer::ProfileInfo* google_toolbar = new importer::ProfileInfo(); + google_toolbar->browser_type = importer::GOOGLE_TOOLBAR5; + google_toolbar->description = l10n_util::GetString( + IDS_IMPORT_FROM_GOOGLE_TOOLBAR); + google_toolbar->source_path.clear(); + google_toolbar->app_path.clear(); + google_toolbar->services_supported = importer::FAVORITES; + source_profiles_.push_back(google_toolbar); + } +} + +#if defined(OS_MACOSX) +void ImporterList::DetectSafariProfiles() { + uint16 items = importer::NONE; + if (SafariImporter::CanImport(mac_util::GetUserLibraryPath(), &items)) { + importer::ProfileInfo* safari = new importer::ProfileInfo(); + safari->browser_type = importer::SAFARI; + safari->description = l10n_util::GetString(IDS_IMPORT_FROM_SAFARI); + safari->source_path.clear(); + safari->app_path.clear(); + safari->services_supported = items; + source_profiles_.push_back(safari); + } +} +#endif // OS_MACOSX diff --git a/chrome/browser/importer/importer_list.h b/chrome/browser/importer/importer_list.h new file mode 100644 index 0000000..c76aab3 --- /dev/null +++ b/chrome/browser/importer/importer_list.h @@ -0,0 +1,61 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_IMPORTER_LIST_H_ +#define CHROME_BROWSER_IMPORTER_IMPORTER_LIST_H_ + +#include <string> +#include <vector> + +#include "build/build_config.h" +#include "base/basictypes.h" +#include "chrome/browser/importer/importer_data_types.h" + +class Importer; + +class ImporterList { + public: + ImporterList(); + ~ImporterList(); + + // Detects the installed browsers and their associated profiles, then + // stores their information in a list. It returns the list of description + // of all profiles. + void DetectSourceProfiles(); + + Importer* CreateImporterByType(importer::ProfileType type); + + // Returns the number of different browser profiles you can import from. + int GetAvailableProfileCount() const; + + // Returns the name of the profile at the 'index' slot. The profiles are + // ordered such that the profile at index 0 is the likely default browser. + std::wstring GetSourceProfileNameAt(int index) const; + + // Returns the ProfileInfo at the specified index. The ProfileInfo should be + // passed to StartImportSettings(). + const importer::ProfileInfo& GetSourceProfileInfoAt(int index) const; + + // Returns the ProfileInfo with the given browser type. + const importer::ProfileInfo& GetSourceProfileInfoForBrowserType( + int browser_type) const; + + // Helper methods for detecting available profiles. +#if defined(OS_WIN) + void DetectIEProfiles(); +#endif + void DetectFirefoxProfiles(); + void DetectGoogleToolbarProfiles(); +#if defined(OS_MACOSX) + void DetectSafariProfiles(); +#endif + + private: + // The list of profiles with the default one first. + std::vector<importer::ProfileInfo*> source_profiles_; + + DISALLOW_COPY_AND_ASSIGN(ImporterList); +}; + +#endif // CHROME_BROWSER_IMPORTER_IMPORTER_LIST_H_ diff --git a/chrome/browser/importer/importer_messages.h b/chrome/browser/importer/importer_messages.h new file mode 100644 index 0000000..4bbba62 --- /dev/null +++ b/chrome/browser/importer/importer_messages.h @@ -0,0 +1,369 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_IMPORTER_MESSAGES_H_ +#define CHROME_BROWSER_IMPORTER_IMPORTER_MESSAGES_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "chrome/browser/importer/profile_writer.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/common/common_param_traits.h" +#include "ipc/ipc_message_utils.h" + +namespace IPC { + +// Traits for importer::ProfileInfo struct to pack/unpack. +template <> +struct ParamTraits<importer::ProfileInfo> { + typedef importer::ProfileInfo param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, p.description); + WriteParam(m, static_cast<int>(p.browser_type)); + WriteParam(m, p.source_path); + WriteParam(m, p.app_path); + WriteParam(m, static_cast<int>(p.services_supported)); + } + static bool Read(const Message* m, void** iter, param_type* p) { + if (!ReadParam(m, iter, &p->description)) + return false; + + int browser_type = 0; + if (!ReadParam(m, iter, &browser_type)) + return false; + p->browser_type = static_cast<importer::ProfileType>(browser_type); + + if (!ReadParam(m, iter, &p->source_path) || + !ReadParam(m, iter, &p->app_path)) + return false; + + int services_supported = 0; + if (!ReadParam(m, iter, &services_supported)) + return false; + p->services_supported = static_cast<uint16>(services_supported); + + return true; + } + static void Log(const param_type& p, std::wstring* l) { + l->append(L"("); + LogParam(p.description, l); + l->append(L", "); + LogParam(static_cast<int>(p.browser_type), l); + l->append(L", "); + LogParam(p.source_path, l); + l->append(L", "); + LogParam(p.app_path, l); + l->append(L", "); + LogParam(static_cast<int>(p.services_supported), l); + l->append(L")"); + } +}; // ParamTraits<importer::ProfileInfo> + +// Traits for history::URLRow to pack/unpack. +template <> +struct ParamTraits<history::URLRow> { + typedef history::URLRow param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, p.id()); + WriteParam(m, p.url()); + WriteParam(m, p.title()); + WriteParam(m, p.visit_count()); + WriteParam(m, p.typed_count()); + WriteParam(m, p.last_visit()); + WriteParam(m, p.hidden()); + WriteParam(m, p.favicon_id()); + } + static bool Read(const Message* m, void** iter, param_type* p) { + history::URLID id; + GURL url; + string16 title; + int visit_count, typed_count; + base::Time last_visit; + bool hidden; + history::FavIconID favicon_id; + if (!ReadParam(m, iter, &id) || + !ReadParam(m, iter, &url) || + !ReadParam(m, iter, &title) || + !ReadParam(m, iter, &visit_count) || + !ReadParam(m, iter, &typed_count) || + !ReadParam(m, iter, &last_visit) || + !ReadParam(m, iter, &hidden) || + !ReadParam(m, iter, &favicon_id)) + return false; + *p = history::URLRow(url, id); + p->set_title(title); + p->set_visit_count(visit_count); + p->set_typed_count(typed_count); + p->set_last_visit(last_visit); + p->set_hidden(hidden); + p->set_favicon_id(favicon_id); + return true; + } + static void Log(const param_type& p, std::wstring* l) { + l->append(L"("); + LogParam(p.id(), l); + l->append(L", "); + LogParam(p.url(), l); + l->append(L", "); + LogParam(p.title(), l); + l->append(L", "); + LogParam(p.visit_count(), l); + l->append(L", "); + LogParam(p.typed_count(), l); + l->append(L", "); + LogParam(p.last_visit(), l); + l->append(L", "); + LogParam(p.hidden(), l); + l->append(L", "); + LogParam(p.favicon_id(), l); + l->append(L")"); + } +}; // ParamTraits<history::URLRow> + +// Traits for ProfileWriter::BookmarkEntry to pack/unpack. +template <> +struct ParamTraits<ProfileWriter::BookmarkEntry> { + typedef ProfileWriter::BookmarkEntry param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, p.in_toolbar); + WriteParam(m, p.url); + WriteParam(m, p.path); + WriteParam(m, p.title); + WriteParam(m, p.creation_time); + } + static bool Read(const Message* m, void** iter, param_type* p) { + return + (ReadParam(m, iter, &p->in_toolbar)) && + (ReadParam(m, iter, &p->url)) && + (ReadParam(m, iter, &p->path)) && + (ReadParam(m, iter, &p->title)) && + (ReadParam(m, iter, &p->creation_time)); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(L"("); + LogParam(p.in_toolbar, l); + l->append(L", "); + LogParam(p.url, l); + l->append(L", "); + LogParam(p.path, l); + l->append(L", "); + LogParam(p.title, l); + l->append(L", "); + LogParam(p.creation_time, l); + l->append(L")"); + } +}; // ParamTraits<ProfileWriter::BookmarkEntry> + +// Traits for history::ImportedFavIconUsage. +template <> +struct ParamTraits<history::ImportedFavIconUsage> { + typedef history::ImportedFavIconUsage param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, p.favicon_url); + WriteParam(m, p.png_data); + WriteParam(m, p.urls); + } + static bool Read(const Message* m, void** iter, param_type* p) { + return + ReadParam(m, iter, &p->favicon_url) && + ReadParam(m, iter, &p->png_data) && + ReadParam(m, iter, &p->urls); + } + static void Log(const param_type& p, std::wstring* l) { + l->append(L"("); + LogParam(p.favicon_url, l); + l->append(L", "); + LogParam(p.png_data, l); + l->append(L", "); + LogParam(p.urls, l); + l->append(L")"); + } +}; // ParamTraits<history::ImportedFavIconUsage + +// Traits for TemplateURLRef +template <> +struct ParamTraits<TemplateURLRef> { + typedef TemplateURLRef param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, p.url()); + WriteParam(m, p.index_offset()); + WriteParam(m, p.page_offset()); + } + static bool Read(const Message* m, void** iter, param_type* p) { + std::string url; + int index_offset; + int page_offset; + if (!ReadParam(m, iter, &url) || + !ReadParam(m, iter, &index_offset) || + !ReadParam(m, iter, &page_offset)) + return false; + *p = TemplateURLRef(url, index_offset, page_offset); + return true; + } + static void Log(const param_type& p, std::wstring* l) { + l->append(L"<TemplateURLRef>"); + } +}; + +// Traits for TemplateURL::ImageRef +template <> +struct ParamTraits<TemplateURL::ImageRef> { + typedef TemplateURL::ImageRef param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, p.type); + WriteParam(m, p.width); + WriteParam(m, p.height); + WriteParam(m, p.url); + } + static bool Read(const Message* m, void** iter, param_type* p) { + std::wstring type; + int width; + int height; + GURL url; + if (!ReadParam(m, iter, &type) || + !ReadParam(m, iter, &width) || + !ReadParam(m, iter, &height) || + !ReadParam(m, iter, &url)) + return false; + *p = TemplateURL::ImageRef(type, width, height, url); // here in + return true; + } + static void Log(const param_type& p, std::wstring* l) { + l->append(L"<TemplateURL::ImageRef>"); + } +}; + +// Traits for TemplateURL +template <> +struct ParamTraits<TemplateURL> { + typedef TemplateURL param_type; + static void Write(Message* m, const param_type& p) { + WriteParam(m, p.short_name()); + WriteParam(m, p.description()); + if (p.suggestions_url()) { + WriteParam(m, true); + WriteParam(m, *p.suggestions_url()); + } else { + WriteParam(m, false); + } + WriteParam(m, *p.url()); + WriteParam(m, p.originating_url()); + WriteParam(m, p.keyword()); + WriteParam(m, p.autogenerate_keyword()); + WriteParam(m, p.show_in_default_list()); + WriteParam(m, p.safe_for_autoreplace()); + WriteParam(m, p.image_refs().size()); + + std::vector<TemplateURL::ImageRef>::const_iterator iter; + for (iter = p.image_refs().begin(); iter != p.image_refs().end(); ++iter) { + WriteParam(m, iter->type); + WriteParam(m, iter->width); + WriteParam(m, iter->height); + WriteParam(m, iter->url); + } + + WriteParam(m, p.languages()); + WriteParam(m, p.input_encodings()); + WriteParam(m, p.date_created()); + WriteParam(m, p.usage_count()); + WriteParam(m, p.prepopulate_id()); + } + static bool Read(const Message* m, void** iter, param_type* p) { + std::wstring short_name; + std::wstring description; + bool includes_suggestions_url; + TemplateURLRef suggestions_url; + TemplateURLRef url; + GURL originating_url; + std::wstring keyword; + bool autogenerate_keyword; + bool show_in_default_list; + bool safe_for_autoreplace; + std::vector<std::wstring> languages; + std::vector<std::string> input_encodings; + base::Time date_created; + int usage_count; + int prepopulate_id; + + if (!ReadParam(m, iter, &short_name) || + !ReadParam(m, iter, &description)) + return false; + + if (!ReadParam(m, iter, &includes_suggestions_url)) + return false; + if (includes_suggestions_url) { + if (!ReadParam(m, iter, &suggestions_url)) + return false; + } + + size_t image_refs_size = 0; + if (!ReadParam(m, iter, &url) || + !ReadParam(m, iter, &originating_url) || + !ReadParam(m, iter, &keyword) || + !ReadParam(m, iter, &autogenerate_keyword) || + !ReadParam(m, iter, &show_in_default_list) || + !ReadParam(m, iter, &safe_for_autoreplace) || + !ReadParam(m, iter, &image_refs_size)) + return false; + + *p = TemplateURL(); + for (size_t i = 0; i < image_refs_size; ++i) { + std::wstring type; + int width; + int height; + GURL url; + if (!ReadParam(m, iter, &type) || + !ReadParam(m, iter, &width) || + !ReadParam(m, iter, &height) || + !ReadParam(m, iter, &url)) + return false; + p->add_image_ref(TemplateURL::ImageRef(type, width, height, url)); + } + + if (!ReadParam(m, iter, &languages) || + !ReadParam(m, iter, &input_encodings) || + !ReadParam(m, iter, &date_created) || + !ReadParam(m, iter, &usage_count) || + !ReadParam(m, iter, &prepopulate_id)) + return false; + + p->set_short_name(short_name); + p->set_description(description); + p->SetSuggestionsURL(suggestions_url.url(), suggestions_url.index_offset(), + suggestions_url.page_offset()); + p->SetURL(url.url(), url.index_offset(), url.page_offset()); + p->set_originating_url(originating_url); + p->set_keyword(keyword); + p->set_autogenerate_keyword(autogenerate_keyword); + p->set_show_in_default_list(show_in_default_list); + p->set_safe_for_autoreplace(safe_for_autoreplace); + + std::vector<std::wstring>::const_iterator lang_iter; + for (lang_iter = languages.begin(); + lang_iter != languages.end(); + ++lang_iter) { + p->add_language(*lang_iter); + } + p->set_input_encodings(input_encodings); + p->set_date_created(date_created); + p->set_usage_count(usage_count); + p->set_prepopulate_id(prepopulate_id); + return true; + } + static void Log(const param_type& p, std::wstring* l) { + l->append(L"<TemplateURL>"); + } +}; + +} // namespace IPC + +#define MESSAGES_INTERNAL_FILE \ + "chrome/browser/importer/importer_messages_internal.h" +#include "ipc/ipc_message_macros.h" + +#endif // CHROME_BROWSER_IMPORTER_IMPORTER_MESSAGES_H_ diff --git a/chrome/browser/importer/importer_messages_internal.h b/chrome/browser/importer/importer_messages_internal.h new file mode 100644 index 0000000..b247237 --- /dev/null +++ b/chrome/browser/importer/importer_messages_internal.h @@ -0,0 +1,80 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> +#include <vector> + +#include "chrome/browser/history/history_types.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "ipc/ipc_message_macros.h" +#include "webkit/glue/password_form.h" + +//----------------------------------------------------------------------------- +// ProfileImportProcess messages +// These are messages sent from the browser to the profile import process. +IPC_BEGIN_MESSAGES(ProfileImportProcess) + IPC_MESSAGE_CONTROL4(ProfileImportProcessMsg_StartImport, + importer::ProfileInfo /* ProfileInfo struct */, + int /* bitmask of items to import */, + DictionaryValue /* localized strings */, + bool /* import to bookmark bar */) + + IPC_MESSAGE_CONTROL0(ProfileImportProcessMsg_CancelImport) + + IPC_MESSAGE_CONTROL1(ProfileImportProcessMsg_ReportImportItemFinished, + int /* ImportItem */) +IPC_END_MESSAGES(ProfileImportProcess) + +//--------------------------------------------------------------------------- +// ProfileImportProcessHost messages +// These are messages sent from the profile import process to the browser. +IPC_BEGIN_MESSAGES(ProfileImportProcessHost) + // These messages send information about the status of the import and + // individual import tasks. + IPC_MESSAGE_CONTROL0(ProfileImportProcessHostMsg_Import_Started) + + IPC_MESSAGE_CONTROL2(ProfileImportProcessHostMsg_Import_Finished, + bool /* was import successful? */, + std::string /* error message, if any */) + + IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_ImportItem_Started, + int /* ImportItem */) + + IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_ImportItem_Finished, + int /* ImportItem */) + + // These messages send data from the external importer process back to + // the process host so it can be written to the profile. + IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_NotifyHistoryImportStart, + int /* total number of history::URLRow items */) + + IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_NotifyHistoryImportGroup, + std::vector<history::URLRow>) + + IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_NotifyHomePageImportReady, + GURL /* GURL of home page */) + + IPC_MESSAGE_CONTROL3(ProfileImportProcessHostMsg_NotifyBookmarksImportStart, + std::wstring /* first folder name */, + int /* options */, + int /* total number of bookmarks */) + + IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_NotifyBookmarksImportGroup, + std::vector<ProfileWriter::BookmarkEntry>) + + IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_NotifyFavIconsImportStart, + int /* total number of FavIcons */) + + IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_NotifyFavIconsImportGroup, + std::vector<history::ImportedFavIconUsage> ) + + IPC_MESSAGE_CONTROL1(ProfileImportProcessHostMsg_NotifyPasswordFormReady, + webkit_glue::PasswordForm ) + + IPC_MESSAGE_CONTROL3(ProfileImportProcessHostMsg_NotifyKeywordsReady, + std::vector<TemplateURL>, + int, /* default keyword index */ + bool /* unique on host and path */) +IPC_END_MESSAGES(ProfileImportProcessHost) + diff --git a/chrome/browser/importer/importer_unittest.cc b/chrome/browser/importer/importer_unittest.cc new file mode 100644 index 0000000..1dce41e --- /dev/null +++ b/chrome/browser/importer/importer_unittest.cc @@ -0,0 +1,885 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/gtest/include/gtest/gtest.h" + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#include <unknwn.h> +#include <intshcut.h> +#include <pstore.h> +#include <urlhist.h> +#include <shlguid.h> +#endif + +#include <vector> + +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "base/stl_util-inl.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/browser/importer/importer.h" +#include "chrome/browser/importer/importer_bridge.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/common/chrome_paths.h" +#include "webkit/glue/password_form.h" + +#if defined(OS_WIN) +#include "base/scoped_comptr_win.h" +#include "app/win_util.h" +#include "chrome/browser/importer/ie_importer.h" +#include "chrome/browser/password_manager/ie7_password.h" +#endif + +using importer::FAVORITES; +using importer::FIREFOX2; +using importer::FIREFOX3; +using importer::HISTORY; +using importer::ImportItem; +#if defined(OS_WIN) +using importer::MS_IE; +#endif +using importer::PASSWORDS; +using importer::SEARCH_ENGINES; +using webkit_glue::PasswordForm; + +// TODO(estade): some of these are disabled on mac. http://crbug.com/48007 +#if defined(OS_MACOSX) +#define MAYBE(x) DISABLED_##x +#else +#define MAYBE(x) x +#endif + +class ImporterTest : public testing::Test { + public: + ImporterTest() + : ui_thread_(ChromeThread::UI, &message_loop_), + file_thread_(ChromeThread::FILE, &message_loop_) {} + protected: + virtual void SetUp() { + // Creates a new profile in a new subdirectory in the temp directory. + ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &test_path_)); + test_path_ = test_path_.AppendASCII("ImporterTest"); + file_util::Delete(test_path_, true); + file_util::CreateDirectory(test_path_); + profile_path_ = test_path_.AppendASCII("profile"); + app_path_ = test_path_.AppendASCII("app"); + file_util::CreateDirectory(app_path_); + } + + virtual void TearDown() { + // Deletes the profile and cleans up the profile directory. + ASSERT_TRUE(file_util::Delete(test_path_, true)); + ASSERT_FALSE(file_util::PathExists(test_path_)); + } + + void Firefox3xImporterTest(std::string profile_dir, + ImporterHost::Observer* observer, + ProfileWriter* writer, + bool import_search_plugins) { + FilePath data_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path)); + data_path = data_path.AppendASCII(profile_dir); + ASSERT_TRUE(file_util::CopyDirectory(data_path, profile_path_, true)); + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path)); + data_path = data_path.AppendASCII("firefox3_nss"); + ASSERT_TRUE(file_util::CopyDirectory(data_path, profile_path_, false)); + + FilePath search_engine_path = app_path_; + search_engine_path = search_engine_path.AppendASCII("searchplugins"); + file_util::CreateDirectory(search_engine_path); + if (import_search_plugins) { + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path)); + data_path = data_path.AppendASCII("firefox3_searchplugins"); + if (!file_util::PathExists(data_path)) { + // TODO(maruel): Create search test data that we can open source! + LOG(ERROR) << L"Missing internal test data"; + return; + } + ASSERT_TRUE(file_util::CopyDirectory(data_path, + search_engine_path, false)); + } + + MessageLoop* loop = MessageLoop::current(); + ProfileInfo profile_info; + profile_info.browser_type = FIREFOX3; + profile_info.app_path = app_path_; + profile_info.source_path = profile_path_; + scoped_refptr<ImporterHost> host = new ImporterHost(); + host->SetObserver(observer); + int items = HISTORY | PASSWORDS | FAVORITES; + if (import_search_plugins) + items = items | SEARCH_ENGINES; + loop->PostTask(FROM_HERE, NewRunnableMethod(host.get(), + &ImporterHost::StartImportSettings, profile_info, + static_cast<Profile*>(NULL), items, writer, true)); + loop->Run(); + } + + MessageLoopForUI message_loop_; + ChromeThread ui_thread_; + ChromeThread file_thread_; + FilePath test_path_; + FilePath profile_path_; + FilePath app_path_; +}; + +const int kMaxPathSize = 5; + +typedef struct { + const bool in_toolbar; + const size_t path_size; + const wchar_t* path[kMaxPathSize]; + const wchar_t* title; + const char* url; +} BookmarkList; + +typedef struct { + const char* origin; + const char* action; + const char* realm; + const wchar_t* username_element; + const wchar_t* username; + const wchar_t* password_element; + const wchar_t* password; + bool blacklisted; +} PasswordList; + +// Returns true if the |entry| is in the |list|. +bool FindBookmarkEntry(const ProfileWriter::BookmarkEntry& entry, + const BookmarkList* list, int list_size) { + for (int i = 0; i < list_size; ++i) { + if (list[i].in_toolbar == entry.in_toolbar && + list[i].path_size == entry.path.size() && + list[i].url == entry.url.spec() && + list[i].title == entry.title) { + bool equal = true; + for (size_t k = 0; k < list[i].path_size; ++k) + if (list[i].path[k] != entry.path[k]) { + equal = false; + break; + } + + if (equal) + return true; + } + } + return false; +} + +#if defined(OS_WIN) +static const BookmarkList kIEBookmarks[] = { + {true, 0, {}, + L"TheLink", + "http://www.links-thelink.com/"}, + {true, 1, {L"SubFolderOfLinks"}, + L"SubLink", + "http://www.links-sublink.com/"}, + {false, 0, {}, + L"Google Home Page", + "http://www.google.com/"}, + {false, 0, {}, + L"TheLink", + "http://www.links-thelink.com/"}, + {false, 1, {L"SubFolder"}, + L"Title", + "http://www.link.com/"}, + {false, 0, {}, + L"WithPortAndQuery", + "http://host:8080/cgi?q=query"}, + {false, 1, {L"a"}, + L"\x4E2D\x6587", + "http://chinese-title-favorite/"}, +}; + +static const wchar_t* kIEIdentifyUrl = + L"http://A79029D6-753E-4e27-B807-3D46AB1545DF.com:8080/path?key=value"; +static const wchar_t* kIEIdentifyTitle = + L"Unittest GUID"; + +bool IsWindowsVista() { + OSVERSIONINFO info = {0}; + info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&info); + return (info.dwMajorVersion >=6); +} + +class TestObserver : public ProfileWriter, + public ImporterHost::Observer { + public: + TestObserver() : ProfileWriter(NULL) { + bookmark_count_ = 0; + history_count_ = 0; + password_count_ = 0; + } + + virtual void ImportItemStarted(importer::ImportItem item) {} + virtual void ImportItemEnded(importer::ImportItem item) {} + virtual void ImportStarted() {} + virtual void ImportEnded() { + MessageLoop::current()->Quit(); + EXPECT_EQ(arraysize(kIEBookmarks), bookmark_count_); + EXPECT_EQ(1, history_count_); +#if 0 // This part of the test is disabled. See bug #2466 + if (IsWindowsVista()) + EXPECT_EQ(0, password_count_); + else + EXPECT_EQ(1, password_count_); +#endif + } + + virtual bool BookmarkModelIsLoaded() const { + // Profile is ready for writing. + return true; + } + + virtual bool TemplateURLModelIsLoaded() const { + return true; + } + + virtual void AddPasswordForm(const PasswordForm& form) { + // Importer should obtain this password form only. + EXPECT_EQ(GURL("http://localhost:8080/security/index.htm"), form.origin); + EXPECT_EQ("http://localhost:8080/", form.signon_realm); + EXPECT_EQ(L"user", form.username_element); + EXPECT_EQ(L"1", form.username_value); + EXPECT_EQ(L"", form.password_element); + EXPECT_EQ(L"2", form.password_value); + EXPECT_EQ("", form.action.spec()); + ++password_count_; + } + + virtual void AddHistoryPage(const std::vector<history::URLRow>& page) { + // Importer should read the specified URL. + for (size_t i = 0; i < page.size(); ++i) + if (page[i].title() == kIEIdentifyTitle && + page[i].url() == GURL(kIEIdentifyUrl)) + ++history_count_; + } + + virtual void AddBookmarkEntry(const std::vector<BookmarkEntry>& bookmark, + const std::wstring& first_folder_name, + int options) { + // Importer should import the IE Favorites folder the same as the list. + for (size_t i = 0; i < bookmark.size(); ++i) { + if (FindBookmarkEntry(bookmark[i], kIEBookmarks, + arraysize(kIEBookmarks))) + ++bookmark_count_; + } + } + + virtual void AddKeyword(std::vector<TemplateURL*> template_url, + int default_keyword_index) { + // TODO(jcampan): bug 1169230: we should test keyword importing for IE. + // In order to do that we'll probably need to mock the Windows registry. + NOTREACHED(); + STLDeleteContainerPointers(template_url.begin(), template_url.end()); + } + + private: + ~TestObserver() {} + + size_t bookmark_count_; + size_t history_count_; + size_t password_count_; +}; + +bool CreateUrlFile(std::wstring file, std::wstring url) { + ScopedComPtr<IUniformResourceLocator> locator; + HRESULT result = locator.CreateInstance(CLSID_InternetShortcut, NULL, + CLSCTX_INPROC_SERVER); + if (FAILED(result)) + return false; + ScopedComPtr<IPersistFile> persist_file; + result = persist_file.QueryFrom(locator); + if (FAILED(result)) + return false; + result = locator->SetURL(url.c_str(), 0); + if (FAILED(result)) + return false; + result = persist_file->Save(file.c_str(), TRUE); + if (FAILED(result)) + return false; + return true; +} + +void ClearPStoreType(IPStore* pstore, const GUID* type, const GUID* subtype) { + ScopedComPtr<IEnumPStoreItems, NULL> item; + HRESULT result = pstore->EnumItems(0, type, subtype, 0, item.Receive()); + if (result == PST_E_OK) { + wchar_t* item_name; + while (SUCCEEDED(item->Next(1, &item_name, 0))) { + pstore->DeleteItem(0, type, subtype, item_name, NULL, 0); + CoTaskMemFree(item_name); + } + } + pstore->DeleteSubtype(0, type, subtype, 0); + pstore->DeleteType(0, type, 0); +} + +void WritePStore(IPStore* pstore, const GUID* type, const GUID* subtype) { + struct PStoreItem { + wchar_t* name; + int data_size; + char* data; + } items[] = { + {L"http://localhost:8080/security/index.htm#ref:StringData", 8, + "\x31\x00\x00\x00\x32\x00\x00\x00"}, + {L"http://localhost:8080/security/index.htm#ref:StringIndex", 20, + "\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00" + "\x00\x00\x2f\x00\x74\x00\x01\x00\x00\x00"}, + {L"user:StringData", 4, + "\x31\x00\x00\x00"}, + {L"user:StringIndex", 20, + "\x57\x49\x43\x4b\x18\x00\x00\x00\x01\x00" + "\x00\x00\x2f\x00\x74\x00\x00\x00\x00\x00"}, + }; + + for (int i = 0; i < arraysize(items); ++i) { + HRESULT res = pstore->WriteItem(0, type, subtype, items[i].name, + items[i].data_size, reinterpret_cast<BYTE*>(items[i].data), + NULL, 0, 0); + ASSERT_TRUE(res == PST_E_OK); + } +} + +TEST_F(ImporterTest, IEImporter) { + // Sets up a favorites folder. + win_util::ScopedCOMInitializer com_init; + std::wstring path = test_path_.ToWStringHack(); + file_util::AppendToPath(&path, L"Favorites"); + CreateDirectory(path.c_str(), NULL); + CreateDirectory((path + L"\\SubFolder").c_str(), NULL); + CreateDirectory((path + L"\\Links").c_str(), NULL); + CreateDirectory((path + L"\\Links\\SubFolderOfLinks").c_str(), NULL); + CreateDirectory((path + L"\\\x0061").c_str(), NULL); + ASSERT_TRUE(CreateUrlFile(path + L"\\Google Home Page.url", + L"http://www.google.com/")); + ASSERT_TRUE(CreateUrlFile(path + L"\\SubFolder\\Title.url", + L"http://www.link.com/")); + ASSERT_TRUE(CreateUrlFile(path + L"\\TheLink.url", + L"http://www.links-thelink.com/")); + ASSERT_TRUE(CreateUrlFile(path + L"\\WithPortAndQuery.url", + L"http://host:8080/cgi?q=query")); + ASSERT_TRUE(CreateUrlFile(path + L"\\\x0061\\\x4E2D\x6587.url", + L"http://chinese-title-favorite/")); + ASSERT_TRUE(CreateUrlFile(path + L"\\Links\\TheLink.url", + L"http://www.links-thelink.com/")); + ASSERT_TRUE(CreateUrlFile(path + L"\\Links\\SubFolderOfLinks\\SubLink.url", + L"http://www.links-sublink.com/")); + file_util::WriteFile(path + L"\\InvalidUrlFile.url", "x", 1); + file_util::WriteFile(path + L"\\PlainTextFile.txt", "x", 1); + + // Sets up dummy password data. + HRESULT res; +#if 0 // This part of the test is disabled. See bug #2466 + ScopedComPtr<IPStore> pstore; + HMODULE pstorec_dll; + GUID type = IEImporter::kUnittestGUID; + GUID subtype = IEImporter::kUnittestGUID; + // PStore is read-only in Windows Vista. + if (!IsWindowsVista()) { + typedef HRESULT (WINAPI *PStoreCreateFunc)(IPStore**, DWORD, DWORD, DWORD); + pstorec_dll = LoadLibrary(L"pstorec.dll"); + PStoreCreateFunc PStoreCreateInstance = + (PStoreCreateFunc)GetProcAddress(pstorec_dll, "PStoreCreateInstance"); + res = PStoreCreateInstance(pstore.Receive(), 0, 0, 0); + ASSERT_TRUE(res == S_OK); + ClearPStoreType(pstore, &type, &subtype); + PST_TYPEINFO type_info; + type_info.szDisplayName = L"TestType"; + type_info.cbSize = 8; + pstore->CreateType(0, &type, &type_info, 0); + pstore->CreateSubtype(0, &type, &subtype, &type_info, NULL, 0); + WritePStore(pstore, &type, &subtype); + } +#endif + + // Sets up a special history link. + ScopedComPtr<IUrlHistoryStg2> url_history_stg2; + res = url_history_stg2.CreateInstance(CLSID_CUrlHistory, NULL, + CLSCTX_INPROC_SERVER); + ASSERT_TRUE(res == S_OK); + res = url_history_stg2->AddUrl(kIEIdentifyUrl, kIEIdentifyTitle, 0); + ASSERT_TRUE(res == S_OK); + + // Starts to import the above settings. + MessageLoop* loop = MessageLoop::current(); + scoped_refptr<ImporterHost> host = new ImporterHost(); + + TestObserver* observer = new TestObserver(); + host->SetObserver(observer); + ProfileInfo profile_info; + profile_info.browser_type = MS_IE; + profile_info.source_path = test_path_; + + loop->PostTask(FROM_HERE, NewRunnableMethod(host.get(), + &ImporterHost::StartImportSettings, profile_info, + static_cast<Profile*>(NULL), HISTORY | PASSWORDS | FAVORITES, observer, + true)); + loop->Run(); + + // Cleans up. + url_history_stg2->DeleteUrl(kIEIdentifyUrl, 0); + url_history_stg2.Release(); +#if 0 // This part of the test is disabled. See bug #2466 + if (!IsWindowsVista()) { + ClearPStoreType(pstore, &type, &subtype); + // Releases it befor unload the dll. + pstore.Release(); + FreeLibrary(pstorec_dll); + } +#endif +} + +TEST_F(ImporterTest, IE7Importer) { + // This is the unencrypted values of my keys under Storage2. + // The passwords have been manually changed to abcdef... but the size remains + // the same. + unsigned char data1[] = "\x0c\x00\x00\x00\x38\x00\x00\x00\x2c\x00\x00\x00" + "\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00\x00\x00" + "\x67\x00\x72\x00\x01\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x4e\xfa\x67\x76\x22\x94\xc8\x01" + "\x08\x00\x00\x00\x12\x00\x00\x00\x4e\xfa\x67\x76" + "\x22\x94\xc8\x01\x0c\x00\x00\x00\x61\x00\x62\x00" + "\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00" + "\x00\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00" + "\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00" + "\x6c\x00\x00\x00"; + + unsigned char data2[] = "\x0c\x00\x00\x00\x38\x00\x00\x00\x24\x00\x00\x00" + "\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00\x00\x00" + "\x67\x00\x72\x00\x01\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\xa8\xea\xf4\xe5\x9f\x9a\xc8\x01" + "\x09\x00\x00\x00\x14\x00\x00\x00\xa8\xea\xf4\xe5" + "\x9f\x9a\xc8\x01\x07\x00\x00\x00\x61\x00\x62\x00" + "\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00" + "\x69\x00\x00\x00\x61\x00\x62\x00\x63\x00\x64\x00" + "\x65\x00\x66\x00\x67\x00\x00\x00"; + + + + std::vector<unsigned char> decrypted_data1; + decrypted_data1.resize(arraysize(data1)); + memcpy(&decrypted_data1.front(), data1, sizeof(data1)); + + std::vector<unsigned char> decrypted_data2; + decrypted_data2.resize(arraysize(data2)); + memcpy(&decrypted_data2.front(), data2, sizeof(data2)); + + std::wstring password; + std::wstring username; + ASSERT_TRUE(ie7_password::GetUserPassFromData(decrypted_data1, &username, + &password)); + EXPECT_EQ(L"abcdefgh", username); + EXPECT_EQ(L"abcdefghijkl", password); + + ASSERT_TRUE(ie7_password::GetUserPassFromData(decrypted_data2, &username, + &password)); + EXPECT_EQ(L"abcdefghi", username); + EXPECT_EQ(L"abcdefg", password); +} +#endif // defined(OS_WIN) + +static const BookmarkList kFirefox2Bookmarks[] = { + {true, 1, {L"Folder"}, + L"On Toolbar's Subfolder", + "http://on.toolbar/bookmark/folder"}, + {true, 0, {}, + L"On Bookmark Toolbar", + "http://on.toolbar/bookmark"}, + {false, 1, {L"Folder"}, + L"New Bookmark", + "http://domain/"}, + {false, 0, {}, + L"<Name>", + "http://domain.com/q?a=%22er%22&b=%3C%20%20%3E"}, + {false, 0, {}, + L"Google Home Page", + "http://www.google.com/"}, + {false, 0, {}, + L"\x4E2D\x6587", + "http://chinese.site.cn/path?query=1#ref"}, + {false, 0, {}, + L"mail", + "mailto:username@host"}, +}; + +static const PasswordList kFirefox2Passwords[] = { + {"https://www.google.com/", "", "https://www.google.com/", + L"", L"", L"", L"", true}, + {"http://localhost:8080/", "", "http://localhost:8080/corp.google.com", + L"", L"http", L"", L"Http1+1abcdefg", false}, + {"http://localhost:8080/", "http://localhost:8080/", "http://localhost:8080/", + L"loginuser", L"usr", L"loginpass", L"pwd", false}, + {"http://localhost:8080/", "http://localhost:8080/", "http://localhost:8080/", + L"loginuser", L"firefox", L"loginpass", L"firefox", false}, + {"http://localhost/", "", "http://localhost/", + L"loginuser", L"hello", L"", L"world", false}, +}; + +typedef struct { + const wchar_t* keyword; + const char* url; +} KeywordList; + +static const KeywordList kFirefox2Keywords[] = { + // Searh plugins + { L"amazon.com", + "http://www.amazon.com/exec/obidos/external-search/?field-keywords=" + "{searchTerms}&mode=blended" }, + { L"answers.com", + "http://www.answers.com/main/ntquery?s={searchTerms}&gwp=13" }, + { L"search.creativecommons.org", + "http://search.creativecommons.org/?q={searchTerms}" }, + { L"search.ebay.com", + "http://search.ebay.com/search/search.dll?query={searchTerms}&" + "MfcISAPICommand=GetResult&ht=1&ebaytag1=ebayreg&srchdesc=n&" + "maxRecordsReturned=300&maxRecordsPerPage=50&SortProperty=MetaEndSort" }, + { L"google.com", + "http://www.google.com/search?q={searchTerms}&ie=utf-8&oe=utf-8&aq=t" }, + { L"search.yahoo.com", + "http://search.yahoo.com/search?p={searchTerms}&ei=UTF-8" }, + { L"flickr.com", + "http://www.flickr.com/photos/tags/?q={searchTerms}" }, + { L"imdb.com", + "http://www.imdb.com/find?q={searchTerms}" }, + { L"webster.com", + "http://www.webster.com/cgi-bin/dictionary?va={searchTerms}" }, + // Search keywords. + { L"google", "http://www.google.com/" }, + { L"< > & \" ' \\ /", "http://g.cn/"}, +}; + +static const int kDefaultFirefox2KeywordIndex = 8; + +class FirefoxObserver : public ProfileWriter, + public ImporterHost::Observer { + public: + FirefoxObserver() : ProfileWriter(NULL) { + bookmark_count_ = 0; + history_count_ = 0; + password_count_ = 0; + keyword_count_ = 0; + } + + virtual void ImportItemStarted(importer::ImportItem item) {} + virtual void ImportItemEnded(importer::ImportItem item) {} + virtual void ImportStarted() {} + virtual void ImportEnded() { + MessageLoop::current()->Quit(); + EXPECT_EQ(arraysize(kFirefox2Bookmarks), bookmark_count_); + EXPECT_EQ(1U, history_count_); + EXPECT_EQ(arraysize(kFirefox2Passwords), password_count_); + EXPECT_EQ(arraysize(kFirefox2Keywords), keyword_count_); + EXPECT_EQ(kFirefox2Keywords[kDefaultFirefox2KeywordIndex].keyword, + default_keyword_); + EXPECT_EQ(kFirefox2Keywords[kDefaultFirefox2KeywordIndex].url, + default_keyword_url_); + } + + virtual bool BookmarkModelIsLoaded() const { + // Profile is ready for writing. + return true; + } + + virtual bool TemplateURLModelIsLoaded() const { + return true; + } + + virtual void AddPasswordForm(const PasswordForm& form) { + PasswordList p = kFirefox2Passwords[password_count_]; + EXPECT_EQ(p.origin, form.origin.spec()); + EXPECT_EQ(p.realm, form.signon_realm); + EXPECT_EQ(p.action, form.action.spec()); + EXPECT_EQ(WideToUTF16(p.username_element), form.username_element); + EXPECT_EQ(WideToUTF16(p.username), form.username_value); + EXPECT_EQ(WideToUTF16(p.password_element), form.password_element); + EXPECT_EQ(WideToUTF16(p.password), form.password_value); + EXPECT_EQ(p.blacklisted, form.blacklisted_by_user); + ++password_count_; + } + + virtual void AddHistoryPage(const std::vector<history::URLRow>& page) { + ASSERT_EQ(1U, page.size()); + EXPECT_EQ("http://en-us.www.mozilla.com/", page[0].url().spec()); + EXPECT_EQ(ASCIIToUTF16("Firefox Updated"), page[0].title()); + ++history_count_; + } + + virtual void AddBookmarkEntry(const std::vector<BookmarkEntry>& bookmark, + const std::wstring& first_folder_name, + int options) { + for (size_t i = 0; i < bookmark.size(); ++i) { + if (FindBookmarkEntry(bookmark[i], kFirefox2Bookmarks, + arraysize(kFirefox2Bookmarks))) + ++bookmark_count_; + } + } + + virtual void AddKeywords(const std::vector<TemplateURL*>& template_urls, + int default_keyword_index, + bool unique_on_host_and_path) { + for (size_t i = 0; i < template_urls.size(); ++i) { + // The order might not be deterministic, look in the expected list for + // that template URL. + bool found = false; + std::wstring keyword = template_urls[i]->keyword(); + for (size_t j = 0; j < arraysize(kFirefox2Keywords); ++j) { + if (template_urls[i]->keyword() == kFirefox2Keywords[j].keyword) { + EXPECT_EQ(kFirefox2Keywords[j].url, template_urls[i]->url()->url()); + found = true; + break; + } + } + EXPECT_TRUE(found); + ++keyword_count_; + } + + if (default_keyword_index != -1) { + EXPECT_LT(default_keyword_index, static_cast<int>(template_urls.size())); + TemplateURL* default_turl = template_urls[default_keyword_index]; + default_keyword_ = default_turl->keyword(); + default_keyword_url_ = default_turl->url()->url(); + } + + STLDeleteContainerPointers(template_urls.begin(), template_urls.end()); + } + + void AddFavicons(const std::vector<history::ImportedFavIconUsage>& favicons) { + } + + private: + ~FirefoxObserver() {} + + size_t bookmark_count_; + size_t history_count_; + size_t password_count_; + size_t keyword_count_; + std::wstring default_keyword_; + std::string default_keyword_url_; +}; + +TEST_F(ImporterTest, MAYBE(Firefox2Importer)) { + FilePath data_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path)); + data_path = data_path.AppendASCII("firefox2_profile"); + ASSERT_TRUE(file_util::CopyDirectory(data_path, profile_path_, true)); + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path)); + data_path = data_path.AppendASCII("firefox2_nss"); + ASSERT_TRUE(file_util::CopyDirectory(data_path, profile_path_, false)); + + FilePath search_engine_path = app_path_; + search_engine_path = search_engine_path.AppendASCII("searchplugins"); + file_util::CreateDirectory(search_engine_path); + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &data_path)); + data_path = data_path.AppendASCII("firefox2_searchplugins"); + if (!file_util::PathExists(data_path)) { + // TODO(maruel): Create test data that we can open source! + LOG(ERROR) << L"Missing internal test data"; + return; + } + ASSERT_TRUE(file_util::CopyDirectory(data_path, search_engine_path, false)); + + MessageLoop* loop = MessageLoop::current(); + scoped_refptr<ImporterHost> host = new ImporterHost(); + FirefoxObserver* observer = new FirefoxObserver(); + host->SetObserver(observer); + ProfileInfo profile_info; + profile_info.browser_type = FIREFOX2; + profile_info.app_path = app_path_; + profile_info.source_path = profile_path_; + + loop->PostTask(FROM_HERE, NewRunnableMethod(host.get(), + &ImporterHost::StartImportSettings, profile_info, + static_cast<Profile*>(NULL), + HISTORY | PASSWORDS | FAVORITES | SEARCH_ENGINES, observer, true)); + loop->Run(); +} + +static const BookmarkList kFirefox3Bookmarks[] = { + {true, 0, {}, + L"Toolbar", + "http://site/"}, + {false, 0, {}, + L"Title", + "http://www.google.com/"}, +}; + +static const PasswordList kFirefox3Passwords[] = { + {"http://localhost:8080/", "http://localhost:8080/", "http://localhost:8080/", + L"loginuser", L"abc", L"loginpass", L"123", false}, + {"http://localhost:8080/", "", "http://localhost:8080/localhost", + L"", L"http", L"", L"Http1+1abcdefg", false}, +}; + +static const KeywordList kFirefox3Keywords[] = { + { L"amazon.com", + "http://www.amazon.com/exec/obidos/external-search/?field-keywords=" + "{searchTerms}&mode=blended" }, + { L"answers.com", + "http://www.answers.com/main/ntquery?s={searchTerms}&gwp=13" }, + { L"search.creativecommons.org", + "http://search.creativecommons.org/?q={searchTerms}" }, + { L"search.ebay.com", + "http://search.ebay.com/search/search.dll?query={searchTerms}&" + "MfcISAPICommand=GetResult&ht=1&ebaytag1=ebayreg&srchdesc=n&" + "maxRecordsReturned=300&maxRecordsPerPage=50&SortProperty=MetaEndSort" }, + { L"google.com", + "http://www.google.com/search?q={searchTerms}&ie=utf-8&oe=utf-8&aq=t" }, + { L"en.wikipedia.org", + "http://en.wikipedia.org/wiki/Special:Search?search={searchTerms}" }, + { L"search.yahoo.com", + "http://search.yahoo.com/search?p={searchTerms}&ei=UTF-8" }, + { L"flickr.com", + "http://www.flickr.com/photos/tags/?q={searchTerms}" }, + { L"imdb.com", + "http://www.imdb.com/find?q={searchTerms}" }, + { L"webster.com", + "http://www.webster.com/cgi-bin/dictionary?va={searchTerms}" }, + // Search keywords. + { L"\x4E2D\x6587", "http://www.google.com/" }, +}; + +static const int kDefaultFirefox3KeywordIndex = 8; + +class Firefox3Observer : public ProfileWriter, + public ImporterHost::Observer { + public: + Firefox3Observer() + : ProfileWriter(NULL), bookmark_count_(0), history_count_(0), + password_count_(0), keyword_count_(0), import_search_engines_(true) { + } + + explicit Firefox3Observer(bool import_search_engines) + : ProfileWriter(NULL), bookmark_count_(0), history_count_(0), + password_count_(0), keyword_count_(0), + import_search_engines_(import_search_engines) { + } + + virtual void ImportItemStarted(importer::ImportItem item) {} + virtual void ImportItemEnded(importer::ImportItem item) {} + virtual void ImportStarted() {} + virtual void ImportEnded() { + MessageLoop::current()->Quit(); + EXPECT_EQ(arraysize(kFirefox3Bookmarks), bookmark_count_); + EXPECT_EQ(1U, history_count_); + EXPECT_EQ(arraysize(kFirefox3Passwords), password_count_); + if (import_search_engines_) { + EXPECT_EQ(arraysize(kFirefox3Keywords), keyword_count_); + EXPECT_EQ(kFirefox3Keywords[kDefaultFirefox3KeywordIndex].keyword, + default_keyword_); + EXPECT_EQ(kFirefox3Keywords[kDefaultFirefox3KeywordIndex].url, + default_keyword_url_); + } + } + + virtual bool BookmarkModelIsLoaded() const { + // Profile is ready for writing. + return true; + } + + virtual bool TemplateURLModelIsLoaded() const { + return true; + } + + virtual void AddPasswordForm(const PasswordForm& form) { + PasswordList p = kFirefox3Passwords[password_count_]; + EXPECT_EQ(p.origin, form.origin.spec()); + EXPECT_EQ(p.realm, form.signon_realm); + EXPECT_EQ(p.action, form.action.spec()); + EXPECT_EQ(WideToUTF16(p.username_element), form.username_element); + EXPECT_EQ(WideToUTF16(p.username), form.username_value); + EXPECT_EQ(WideToUTF16(p.password_element), form.password_element); + EXPECT_EQ(WideToUTF16(p.password), form.password_value); + EXPECT_EQ(p.blacklisted, form.blacklisted_by_user); + ++password_count_; + } + + virtual void AddHistoryPage(const std::vector<history::URLRow>& page) { + ASSERT_EQ(3U, page.size()); + EXPECT_EQ("http://www.google.com/", page[0].url().spec()); + EXPECT_EQ(ASCIIToUTF16("Google"), page[0].title()); + EXPECT_EQ("http://www.google.com/", page[1].url().spec()); + EXPECT_EQ(ASCIIToUTF16("Google"), page[1].title()); + EXPECT_EQ("http://www.cs.unc.edu/~jbs/resources/perl/perl-cgi/programs/form1-POST.html", + page[2].url().spec()); + EXPECT_EQ(ASCIIToUTF16("example form (POST)"), page[2].title()); + ++history_count_; + } + + virtual void AddBookmarkEntry(const std::vector<BookmarkEntry>& bookmark, + const std::wstring& first_folder_name, + int options) { + for (size_t i = 0; i < bookmark.size(); ++i) { + if (FindBookmarkEntry(bookmark[i], kFirefox3Bookmarks, + arraysize(kFirefox3Bookmarks))) + ++bookmark_count_; + } + } + + void AddKeywords(const std::vector<TemplateURL*>& template_urls, + int default_keyword_index, + bool unique_on_host_and_path) { + for (size_t i = 0; i < template_urls.size(); ++i) { + // The order might not be deterministic, look in the expected list for + // that template URL. + bool found = false; + std::wstring keyword = template_urls[i]->keyword(); + for (size_t j = 0; j < arraysize(kFirefox3Keywords); ++j) { + if (template_urls[i]->keyword() == kFirefox3Keywords[j].keyword) { + EXPECT_EQ(kFirefox3Keywords[j].url, template_urls[i]->url()->url()); + found = true; + break; + } + } + EXPECT_TRUE(found); + ++keyword_count_; + } + + if (default_keyword_index != -1) { + EXPECT_LT(default_keyword_index, static_cast<int>(template_urls.size())); + TemplateURL* default_turl = template_urls[default_keyword_index]; + default_keyword_ = default_turl->keyword(); + default_keyword_url_ = default_turl->url()->url(); + } + + STLDeleteContainerPointers(template_urls.begin(), template_urls.end()); + } + + void AddFavicons(const std::vector<history::ImportedFavIconUsage>& favicons) { + } + + private: + ~Firefox3Observer() {} + + size_t bookmark_count_; + size_t history_count_; + size_t password_count_; + size_t keyword_count_; + bool import_search_engines_; + std::wstring default_keyword_; + std::string default_keyword_url_; +}; + +TEST_F(ImporterTest, MAYBE(Firefox30Importer)) { + scoped_refptr<Firefox3Observer> observer = new Firefox3Observer(); + Firefox3xImporterTest("firefox3_profile", observer.get(), observer.get(), + true); +} + +TEST_F(ImporterTest, MAYBE(Firefox35Importer)) { + bool import_search_engines = false; + scoped_refptr<Firefox3Observer> observer = + new Firefox3Observer(import_search_engines); + Firefox3xImporterTest("firefox35_profile", observer.get(), observer.get(), + import_search_engines); +} diff --git a/chrome/browser/importer/mork_reader.cc b/chrome/browser/importer/mork_reader.cc new file mode 100644 index 0000000..49dadb0 --- /dev/null +++ b/chrome/browser/importer/mork_reader.cc @@ -0,0 +1,585 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Mork Reader. + * + * The Initial Developer of the Original Code is + * Google Inc. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian Ryner <bryner@brianryner.com> (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// Source: +// http://mxr.mozilla.org/firefox/source/db/morkreader/nsMorkReader.cpp +// This file has been converted to google style. + +#include "chrome/browser/importer/mork_reader.h" + +#include <algorithm> + +#include "base/file_path.h" +#include "base/i18n/icu_string_conversions.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/browser/importer/firefox_importer_utils.h" +#include "chrome/browser/importer/importer.h" +#include "chrome/browser/importer/importer_bridge.h" + +using base::Time; + +namespace { + +// Convert a hex character (0-9, A-F) to its corresponding byte value. +// Returns -1 if the character is invalid. +inline int HexCharToInt(char c) { + if ('0' <= c && c <= '9') + return c - '0'; + if ('A' <= c && c <= 'F') + return c - 'A' + 10; + return -1; +} + +// Unescape a Mork value. Mork uses $xx escaping to encode non-ASCII +// characters. Additionally, '$' and '\' are backslash-escaped. +// The result of the unescape is in returned. +std::string MorkUnescape(const std::string& input) { + // We optimize for speed over space here -- size the result buffer to + // the size of the source, which is an upper bound on the size of the + // unescaped string. + std::string result; + size_t input_length = input.size(); + result.reserve(input_length); + + for (size_t i = 0; i < input_length; i++) { + char c = input[i]; + if (c == '\\') { + // Escaped literal, slip the backslash, append the next character. + i++; + if (i < input_length) + result.push_back(input[i]); + } else if (c == '$') { + // Dollar sign denotes a hex character. + if (i < input_length - 2) { + // Would be nice to use ToInteger() here, but it currently + // requires a null-terminated string. + int first = HexCharToInt(input[++i]); + int second = HexCharToInt(input[++i]); + if (first >= 0 && second >= 0) + result.push_back((first << 4) | second); + } + } else { + // Regular character, just append. + result.push_back(input[i]); + } + } + return result; +} + +} // namespace + +MorkReader::MorkReader() { +} + +MorkReader::~MorkReader() { + // Need to delete all the pointers to vectors we have in the table. + for (RowMap::iterator i = table_.begin(); i != table_.end(); ++i) + delete i->second; +} + +bool MorkReader::Read(const FilePath& path) { + stream_.open(path.value().c_str()); + if (!stream_.is_open()) + return false; + + std::string line; + if (!ReadLine(&line) || + line.compare("// <!-- <mdb:mork:z v=\"1.4\"/> -->") != 0) + return false; // Unexpected file format. + + IndexMap column_map; + while (ReadLine(&line)) { + // Trim off leading spaces + size_t idx = 0; + size_t len = line.size(); + while (idx < len && line[idx] == ' ') + ++idx; + if (idx >= len) + continue; + + // Look at the line to figure out what section type this is + if (StartsWithASCII(&line[idx], "< <(a=c)>", true)) { + // Column map. We begin by creating a hash of column id to column name. + StringMap column_name_map; + ParseMap(line, idx, &column_name_map); + + // Now that we have the list of columns, we put them into a flat array. + // Rows will have value arrays of the same size, with indexes that + // correspond to the columns array. As we insert each column into the + // array, we also make an entry in columnMap so that we can look up the + // index given the column id. + columns_.reserve(column_name_map.size()); + + for (StringMap::const_iterator i = column_name_map.begin(); + i != column_name_map.end(); ++i) { + column_map[i->first] = static_cast<int>(columns_.size()); + MorkColumn col(i->first, i->second); + columns_.push_back(col); + } + } else if (StartsWithASCII(&line[idx], "<(", true)) { + // Value map. + ParseMap(line, idx, &value_map_); + } else if (line[idx] == '{' || line[idx] == '[') { + // Table / table row. + ParseTable(line, idx, &column_map); + } else { + // Don't know, hopefully don't care. + } + } + return true; +} + +// Parses a key/value map of the form +// <(k1=v1)(k2=v2)...> +bool MorkReader::ParseMap(const std::string& first_line, + size_t start_index, + StringMap* map) { + // If the first line is the a=c line (column map), just skip over it. + std::string line(first_line); + if (StartsWithASCII(line, "< <(a=c)>", true)) + ReadLine(&line); + + std::string key; + do { + size_t idx = start_index; + size_t len = line.size(); + size_t token_start; + + while (idx < len) { + switch (line[idx++]) { + case '(': + // Beginning of a key/value pair. + if (!key.empty()) { + DLOG(WARNING) << "unterminated key/value pair?"; + key.clear(); + } + + token_start = idx; + while (idx < len && line[idx] != '=') + ++idx; + key.assign(&line[token_start], idx - token_start); + break; + + case '=': { + // Beginning of the value. + if (key.empty()) { + DLOG(WARNING) << "stray value"; + break; + } + + token_start = idx; + while (idx < len && line[idx] != ')') { + if (line[idx] == '\\') + ++idx; // Skip escaped ')' characters. + ++idx; + } + size_t token_end = std::min(idx, len); + ++idx; + + std::string value = MorkUnescape( + std::string(&line[token_start], token_end - token_start)); + (*map)[key] = value; + key.clear(); + break; + } + case '>': + // End of the map. + DLOG_IF(WARNING, key.empty()) << + "map terminates inside of key/value pair"; + return true; + } + } + + // We should start reading the next line at the beginning. + start_index = 0; + } while (ReadLine(&line)); + + // We ran out of lines and the map never terminated. This probably indicates + // a parsing error. + DLOG(WARNING) << "didn't find end of key/value map"; + return false; +} + +// Parses a table row of the form [123(^45^67)..] +// (row id 123 has the value with id 67 for the column with id 45). +// A '^' prefix for a column or value references an entry in the column or +// value map. '=' is used as the separator when the value is a literal. +void MorkReader::ParseTable(const std::string& first_line, + size_t start_index, + const IndexMap* column_map) { + std::string line(first_line); + + // Column index of the cell we're parsing, minus one if invalid. + int column_index = -1; + + // Points to the current row we're parsing inside of the |table_|, will be + // NULL if we're not inside a row. + ColumnDataList* current_row = NULL; + + bool in_meta_row = false; + + do { + size_t idx = start_index; + size_t len = line.size(); + + while (idx < len) { + switch (line[idx++]) { + case '{': + // This marks the beginning of a table section. There's a lot of + // junk before the first row that looks like cell values but isn't. + // Skip to the first '['. + while (idx < len && line[idx] != '[') { + if (line[idx] == '{') { + in_meta_row = true; // The meta row is enclosed in { } + } else if (line[idx] == '}') { + in_meta_row = false; + } + ++idx; + } + break; + + case '[': { + // Start of a new row. Consume the row id, up to the first '('. + // Row edits also have a table namespace, separated from the row id + // by a colon. We don't make use of the namespace, but we need to + // make sure not to consider it part of the row id. + if (current_row) { + DLOG(WARNING) << "unterminated row?"; + current_row = NULL; + } + + // Check for a '-' at the start of the id. This signifies that + // if the row already exists, we should delete all columns from it + // before adding the new values. + bool cut_columns; + if (idx < len && line[idx] == '-') { + cut_columns = true; + ++idx; + } else { + cut_columns = false; + } + + // Locate the range of the ID. + size_t token_start = idx; // Index of the first char of the token. + while (idx < len && + line[idx] != '(' && + line[idx] != ']' && + line[idx] != ':') { + ++idx; + } + size_t token_end = idx; // Index of the char following the token. + while (idx < len && line[idx] != '(' && line[idx] != ']') { + ++idx; + } + + if (in_meta_row) { + // Need to create the meta row. + meta_row_.resize(columns_.size()); + current_row = &meta_row_; + } else { + // Find or create the regular row for this. + IDString row_id(&line[token_start], token_end - token_start); + RowMap::iterator found_row = table_.find(row_id); + if (found_row == table_.end()) { + // We don't already have this row, create a new one for it. + current_row = new ColumnDataList(columns_.size()); + table_[row_id] = current_row; + } else { + // The row already exists and we're adding/replacing things. + current_row = found_row->second; + } + } + if (cut_columns) { + for (size_t i = 0; i < current_row->size(); ++i) + (*current_row)[i].clear(); + } + break; + } + + case ']': + // We're done with the row. + current_row = NULL; + in_meta_row = false; + break; + + case '(': { + if (!current_row) { + DLOG(WARNING) << "cell value outside of row"; + break; + } + + bool column_is_atom; + if (line[idx] == '^') { + column_is_atom = true; + ++idx; // This is not part of the column id, advance past it. + } else { + column_is_atom = false; + } + size_t token_start = idx; + while (idx < len && line[idx] != '^' && line[idx] != '=') { + if (line[idx] == '\\') + ++idx; // Skip escaped characters. + ++idx; + } + + size_t token_end = std::min(idx, len); + + IDString column; + if (column_is_atom) + column.assign(&line[token_start], token_end - token_start); + else + column = MorkUnescape(line.substr(token_start, + token_end - token_start)); + + IndexMap::const_iterator found_column = column_map->find(column); + if (found_column == column_map->end()) { + DLOG(WARNING) << "Column not in column map, discarding it"; + column_index = -1; + } else { + column_index = found_column->second; + } + break; + } + + case '=': + case '^': { + if (column_index == -1) { + DLOG(WARNING) << "stray ^ or = marker"; + break; + } + + bool value_is_atom = (line[idx - 1] == '^'); + size_t token_start = idx - 1; // Include the '=' or '^' marker. + while (idx < len && line[idx] != ')') { + if (line[idx] == '\\') + ++idx; // Skip escaped characters. + ++idx; + } + size_t token_end = std::min(idx, len); + ++idx; + + if (value_is_atom) { + (*current_row)[column_index].assign(&line[token_start], + token_end - token_start); + } else { + (*current_row)[column_index] = + MorkUnescape(line.substr(token_start, token_end - token_start)); + } + column_index = -1; + } + break; + } + } + + // Start parsing the next line at the beginning. + start_index = 0; + } while (current_row && ReadLine(&line)); +} + +bool MorkReader::ReadLine(std::string* line) { + line->resize(256); + std::getline(stream_, *line); + if (stream_.eof() || stream_.bad()) + return false; + + while (!line->empty() && (*line)[line->size() - 1] == '\\') { + // There is a continuation for this line. Read it and append. + std::string new_line; + std::getline(stream_, new_line); + if (stream_.eof()) + return false; + line->erase(line->size() - 1); + line->append(new_line); + } + + return true; +} + +void MorkReader::NormalizeValue(std::string* value) const { + if (value->empty()) + return; + MorkReader::StringMap::const_iterator i; + switch (value->at(0)) { + case '^': + // Hex ID, lookup the name for it in the |value_map_|. + i = value_map_.find(value->substr(1)); + if (i == value_map_.end()) + value->clear(); + else + *value = i->second; + break; + case '=': + // Just use the literal after the equals sign. + value->erase(value->begin()); + break; + default: + // Anything else is invalid. + value->clear(); + break; + } +} + +// Source: +// http://mxr.mozilla.org/firefox/source/toolkit/components/places/src/nsMorkHistoryImporter.cpp + +// Columns for entry (non-meta) history rows +enum { + kURLColumn, + kNameColumn, + kVisitCountColumn, + kHiddenColumn, + kTypedColumn, + kLastVisitColumn, + kColumnCount // Keep me last. +}; + +static const char * const gColumnNames[] = { + "URL", "Name", "VisitCount", "Hidden", "Typed", "LastVisitDate" +}; + +struct TableReadClosure { + explicit TableReadClosure(const MorkReader& r) + : reader(r), + swap_bytes(false), + byte_order_column(-1) { + for (int i = 0; i < kColumnCount; ++i) + column_indexes[i] = -1; + } + + // Backpointers to the reader and history we're operating on. + const MorkReader& reader; + + // Whether we need to swap bytes (file format is other-endian). + bool swap_bytes; + + // Indexes of the columns that we care about. + int column_indexes[kColumnCount]; + int byte_order_column; +}; + +void AddToHistory(MorkReader::ColumnDataList* column_values, + const TableReadClosure& data, + std::vector<history::URLRow>* rows) { + std::string values[kColumnCount]; + + for (size_t i = 0; i < kColumnCount; ++i) { + if (data.column_indexes[i] != -1) { + values[i] = column_values->at(data.column_indexes[i]); + data.reader.NormalizeValue(&values[i]); + // Do not import hidden records. + if (i == kHiddenColumn && values[i] == "1") + return; + } + } + + GURL url(values[kURLColumn]); + + if (CanImportURL(url)) { + history::URLRow row(url); + + string16 title; + if (data.swap_bytes) { + base::CodepageToUTF16(values[kNameColumn], base::kCodepageUTF16BE, + base::OnStringConversionError::SKIP, &title); + } else { + base::CodepageToUTF16(values[kNameColumn], base::kCodepageUTF16LE, + base::OnStringConversionError::SKIP, &title); + } + row.set_title(title); + + int count = atoi(values[kVisitCountColumn].c_str()); + if (count == 0) + count = 1; + row.set_visit_count(count); + + time_t date = StringToInt64(values[kLastVisitColumn]); + if (date != 0) + row.set_last_visit(Time::FromTimeT(date/1000000)); + + bool is_typed = (values[kTypedColumn] == "1"); + if (is_typed) + row.set_typed_count(1); + + rows->push_back(row); + } +} + +// It sets up the file stream and loops over the lines in the file to +// parse them, then adds the resulting row set to history. +void ImportHistoryFromFirefox2(const FilePath& file, ImporterBridge* bridge) { + MorkReader reader; + reader.Read(file); + + // Gather up the column ids so we don't need to find them on each row + TableReadClosure data(reader); + const MorkReader::MorkColumnList& columns = reader.columns(); + for (size_t i = 0; i < columns.size(); ++i) { + for (int j = 0; j < kColumnCount; ++j) + if (columns[i].name == gColumnNames[j]) { + data.column_indexes[j] = static_cast<int>(i); + break; + } + if (columns[i].name == "ByteOrder") + data.byte_order_column = static_cast<int>(i); + } + + // Determine the byte order from the table's meta-row. + const MorkReader::ColumnDataList& meta_row = reader.meta_row(); + if (!meta_row.empty() && data.byte_order_column != -1) { + std::string byte_order = meta_row[data.byte_order_column]; + if (!byte_order.empty()) { + // Note whether the file uses a non-native byte ordering. + // If it does, we'll have to swap bytes for PRUnichar values. + // "BE" and "LE" are the only recognized values, anything + // else is garbage and the file will be treated as native-endian + // (no swapping). + std::string byte_order_value(byte_order); + reader.NormalizeValue(&byte_order_value); + data.swap_bytes = (byte_order_value == "BE"); + } + } + + std::vector<history::URLRow> rows; + for (MorkReader::iterator i = reader.begin(); i != reader.end(); ++i) + AddToHistory(i->second, data, &rows); + if (!rows.empty()) + bridge->SetHistoryItems(rows); +} diff --git a/chrome/browser/importer/mork_reader.h b/chrome/browser/importer/mork_reader.h new file mode 100644 index 0000000..cb61be5 --- /dev/null +++ b/chrome/browser/importer/mork_reader.h @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Mork Reader. + * + * The Initial Developer of the Original Code is + * Google Inc. + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brian Ryner <bryner@brianryner.com> (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// Source: +// http://mxr.mozilla.org/firefox/source/db/morkreader/nsMorkReader.h + +#ifndef CHROME_BROWSER_IMPORTER_MORK_READER_H__ +#define CHROME_BROWSER_IMPORTER_MORK_READER_H__ + +#include <iosfwd> +#include <fstream> +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" + +class FilePath; +class ImporterBridge; +class MessageLoop; +class ProfileWriter; + +// The nsMorkReader object allows a consumer to read in a mork-format +// file and enumerate the rows that it contains. It does not provide +// any functionality for modifying mork tables. + +// References: +// http://www.mozilla.org/mailnews/arch/mork/primer.txt +// http://www.mozilla.org/mailnews/arch/mork/grammar.txt +// http://www.jwz.org/hacks/mork.pl + +class MorkReader { + public: + // The IDString type has built-in storage for the hex string representation + // of a 32-bit row id or atom map key, plus the terminating null. + // We use STL string here so that is can be operated with STL containers. + typedef std::string IDString; + + // Lists the contents of a series of columns. + typedef std::vector<std::string> ColumnDataList; + + // A MorkColumn describes a column of the table. + struct MorkColumn { + MorkColumn(IDString i, const std::string& n) : id(i), name(n) { } + + IDString id; + std::string name; + }; + typedef std::vector<MorkColumn> MorkColumnList; + + // The key for each row is the identifier for it, and the data is a pointer + // to an array for each column. + typedef std::map<IDString, ColumnDataList*> RowMap; + + typedef RowMap::const_iterator iterator; + + MorkReader(); + ~MorkReader(); + + // Read in the given mork file. Returns true on success. + // Note: currently, only single-table mork files are supported + bool Read(const FilePath& filename); + + // Returns the list of columns in the current table. + const MorkColumnList& columns() const { return columns_; } + + // Get the "meta row" for the table. Each table has at most one meta row, + // which records information about the table. Like normal rows, the + // meta row contains columns in the same order as returned by columns(). + // Returns null if there is no meta row for this table. + const ColumnDataList& meta_row() const { return meta_row_; } + + // Normalizes the cell value (resolves references to the value map). + // |value| is modified in-place. + void NormalizeValue(std::string* value) const; + + // Allow iteration over the table cells using STL iterators. The iterator's + // |first| will be the row ID, and the iterator's |second| will be a + // pointer to a ColumnDataList containing the cell data. + iterator begin() const { return table_.begin(); } + iterator end() const { return table_.end(); } + + private: + // A convenience typedef for an ID-to-string mapping. + typedef std::map<IDString, std::string> StringMap; + + // A convenience typdef for an ID-to-index mapping, used for the column index + // hashtable. + typedef std::map<IDString, int> IndexMap; + + // Parses a line of the file which contains key/value pairs (either + // the column map or the value map). The starting line is parsed starting at + // the given index. Additional lines are read from stream_ if the line ends + // mid-entry. The pairs are added to the map. + bool ParseMap(const std::string& first_line, + size_t start_index, + StringMap* map); + + // Parses a line of the file which contains a table or row definition, + // starting at the given offset within the line. Additional lines are read + // from |stream_| of the line ends mid-row. An entry is added to |table_| + // using the row ID as the key, which contains a column array for the row. + // The supplied column hash table maps from column id to an index in + // |columns_|. + void ParseTable(const std::string& first_line, + size_t start_index, + const IndexMap* column_map); + + // Reads a single logical line from mStream into aLine. + // Any continuation lines are consumed and appended to the line. + bool ReadLine(std::string* line); + + std::ifstream stream_; + + // Lists the names of the columns for the table. + MorkColumnList columns_; + + // Maps hex string IDs to the corrsponding names. + StringMap value_map_; + + // The data of the columns in the meta row. + ColumnDataList meta_row_; + + // The contents of the mork database. This array pointer is owned by this + // class and must be deleted. + RowMap table_; +}; + +// ImportHistoryFromFirefox2 is the main entry point to the importer. +void ImportHistoryFromFirefox2(const FilePath& file, ImporterBridge* bridge); + +#endif // CHROME_BROWSER_IMPORTER_MORK_READER_H__ diff --git a/chrome/browser/importer/nss_decryptor.cc b/chrome/browser/importer/nss_decryptor.cc new file mode 100644 index 0000000..91149e2 --- /dev/null +++ b/chrome/browser/importer/nss_decryptor.cc @@ -0,0 +1,301 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/nss_decryptor.h" + +#include <string> +#include <vector> + +#include "base/scoped_ptr.h" +#include "build/build_config.h" +#include "chrome/common/sqlite_utils.h" + +#if defined(USE_NSS) +#include <pk11pub.h> +#include <pk11sdr.h> +#endif // defined(USE_NSS) + +#include "base/base64.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "webkit/glue/password_form.h" + +using webkit_glue::PasswordForm; + +// This method is based on some Firefox code in +// security/manager/ssl/src/nsSDR.cpp +// The license block is: + +/* ***** BEGIN LICENSE BLOCK ***** +* Version: MPL 1.1/GPL 2.0/LGPL 2.1 +* +* The contents of this file are subject to the Mozilla Public License Version +* 1.1 (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* http://www.mozilla.org/MPL/ +* +* Software distributed under the License is distributed on an "AS IS" basis, +* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +* for the specific language governing rights and limitations under the +* License. +* +* The Original Code is the Netscape security libraries. +* +* The Initial Developer of the Original Code is +* Netscape Communications Corporation. +* Portions created by the Initial Developer are Copyright (C) 1994-2000 +* the Initial Developer. All Rights Reserved. +* +* Contributor(s): +* +* Alternatively, the contents of this file may be used under the terms of +* either the GNU General Public License Version 2 or later (the "GPL"), or +* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +* in which case the provisions of the GPL or the LGPL are applicable instead +* of those above. If you wish to allow use of your version of this file only +* under the terms of either the GPL or the LGPL, and not to allow others to +* use your version of this file under the terms of the MPL, indicate your +* decision by deleting the provisions above and replace them with the notice +* and other provisions required by the GPL or the LGPL. If you do not delete +* the provisions above, a recipient may use your version of this file under +* the terms of any one of the MPL, the GPL or the LGPL. +* +* ***** END LICENSE BLOCK ***** */ + +string16 NSSDecryptor::Decrypt(const std::string& crypt) const { + // Do nothing if NSS is not loaded. + if (!is_nss_initialized_) + return string16(); + + // The old style password is encoded in base64. They are identified + // by a leading '~'. Otherwise, we should decrypt the text. + std::string plain; + if (crypt[0] != '~') { + std::string decoded_data; + base::Base64Decode(crypt, &decoded_data); + PK11SlotInfo* slot = GetKeySlotForDB(); + SECStatus result = PK11_Authenticate(slot, PR_TRUE, NULL); + if (result != SECSuccess) { + FreeSlot(slot); + return string16(); + } + + SECItem request; + request.data = reinterpret_cast<unsigned char*>( + const_cast<char*>(decoded_data.data())); + request.len = static_cast<unsigned int>(decoded_data.size()); + SECItem reply; + reply.data = NULL; + reply.len = 0; +#if defined(USE_NSS) + result = PK11SDR_DecryptWithSlot(slot, &request, &reply, NULL); +#else + result = PK11SDR_Decrypt(&request, &reply, NULL); +#endif // defined(USE_NSS) + if (result == SECSuccess) + plain.assign(reinterpret_cast<char*>(reply.data), reply.len); + + SECITEM_FreeItem(&reply, PR_FALSE); + FreeSlot(slot); + } else { + // Deletes the leading '~' before decoding. + base::Base64Decode(crypt.substr(1), &plain); + } + + return UTF8ToUTF16(plain); +} + +// There are three versions of password files. They store saved user +// names and passwords. +// References: +// http://kb.mozillazine.org/Signons.txt +// http://kb.mozillazine.org/Signons2.txt +// http://kb.mozillazine.org/Signons3.txt +void NSSDecryptor::ParseSignons(const std::string& content, + std::vector<PasswordForm>* forms) { + forms->clear(); + + // Splits the file content into lines. + std::vector<std::string> lines; + SplitString(content, '\n', &lines); + + // The first line is the file version. We skip the unknown versions. + if (lines.empty()) + return; + int version; + if (lines[0] == "#2c") + version = 1; + else if (lines[0] == "#2d") + version = 2; + else if (lines[0] == "#2e") + version = 3; + else + return; + + GURL::Replacements rep; + rep.ClearQuery(); + rep.ClearRef(); + rep.ClearUsername(); + rep.ClearPassword(); + + // Reads never-saved list. Domains are stored one per line. + size_t i; + for (i = 1; i < lines.size() && lines[i].compare(".") != 0; ++i) { + PasswordForm form; + form.origin = GURL(lines[i]).ReplaceComponents(rep); + form.signon_realm = form.origin.GetOrigin().spec(); + form.blacklisted_by_user = true; + forms->push_back(form); + } + ++i; + + // Reads saved passwords. The information is stored in blocks + // seperated by lines that only contain a dot. We find a block + // by the seperator and parse them one by one. + while (i < lines.size()) { + size_t begin = i; + size_t end = i + 1; + while (end < lines.size() && lines[end].compare(".") != 0) + ++end; + i = end + 1; + + // A block has at least five lines. + if (end - begin < 5) + continue; + + PasswordForm form; + + // The first line is the site URL. + // For HTTP authentication logins, the URL may contain http realm, + // which will be in bracket: + // sitename:8080 (realm) + GURL url; + std::string realm; + const char kRealmBracketBegin[] = " ("; + const char kRealmBracketEnd[] = ")"; + if (lines[begin].find(kRealmBracketBegin) != std::string::npos) { + // In this case, the scheme may not exsit. We assume that the + // scheme is HTTP. + if (lines[begin].find("://") == std::string::npos) + lines[begin] = "http://" + lines[begin]; + + size_t start = lines[begin].find(kRealmBracketBegin); + url = GURL(lines[begin].substr(0, start)); + + start += std::string(kRealmBracketBegin).size(); + size_t end = lines[begin].rfind(kRealmBracketEnd); + realm = lines[begin].substr(start, end - start); + } else { + // Don't have http realm. It is the URL that the following passwords + // belong to. + url = GURL(lines[begin]); + } + // Skips this block if the URL is not valid. + if (!url.is_valid()) + continue; + form.origin = url.ReplaceComponents(rep); + form.signon_realm = form.origin.GetOrigin().spec(); + if (!realm.empty()) + form.signon_realm += realm; + form.ssl_valid = form.origin.SchemeIsSecure(); + ++begin; + + // There may be multiple username/password pairs for this site. + // In this case, they are saved in one block without a seperated + // line (contains a dot). + while (begin + 4 < end) { + // The user name. + form.username_element = UTF8ToUTF16(lines[begin++]); + form.username_value = Decrypt(lines[begin++]); + // The element name has a leading '*'. + if (lines[begin].at(0) == '*') { + form.password_element = UTF8ToUTF16(lines[begin++].substr(1)); + form.password_value = Decrypt(lines[begin++]); + } else { + // Maybe the file is bad, we skip to next block. + break; + } + // The action attribute from the form element. This line exists + // in versin 2 or above. + if (version >= 2) { + if (begin < end) + form.action = GURL(lines[begin]).ReplaceComponents(rep); + ++begin; + } + // Version 3 has an extra line for further use. + if (version == 3) { + ++begin; + } + + forms->push_back(form); + } + } +} + +bool NSSDecryptor::ReadAndParseSignons(const FilePath& sqlite_file, + std::vector<webkit_glue::PasswordForm>* forms) { + sqlite3* sqlite; + if (sqlite_utils::OpenSqliteDb(sqlite_file, &sqlite) != SQLITE_OK) + return false; + sqlite_utils::scoped_sqlite_db_ptr db(sqlite); + + SQLStatement s; + const char* stmt = "SELECT hostname FROM moz_disabledHosts"; + if (s.prepare(db.get(), stmt) != SQLITE_OK) + return false; + + GURL::Replacements rep; + rep.ClearQuery(); + rep.ClearRef(); + rep.ClearUsername(); + rep.ClearPassword(); + // Read domains for which passwords are never saved. + while (s.step() == SQLITE_ROW) { + PasswordForm form; + form.origin = GURL(s.column_string(0)).ReplaceComponents(rep); + form.signon_realm = form.origin.GetOrigin().spec(); + form.blacklisted_by_user = true; + forms->push_back(form); + } + + SQLStatement s2; + const char* stmt2 = "SELECT hostname, httpRealm, formSubmitURL, " + "usernameField, passwordField, encryptedUsername, " + "encryptedPassword FROM moz_logins"; + + if (s2.prepare(db.get(), stmt2) != SQLITE_OK) + return false; + + while (s2.step() == SQLITE_ROW) { + GURL url; + std::string realm(s2.column_string(1)); + if (!realm.empty()) { + // In this case, the scheme may not exsit. Assume HTTP. + std::string host(s2.column_string(0)); + if (host.find("://") == std::string::npos) + host = "http://" + host; + url = GURL(host); + } else { + url = GURL(s2.column_string(0)); + } + // Skip this row if the URL is not valid. + if (!url.is_valid()) + continue; + + PasswordForm form; + form.origin = url.ReplaceComponents(rep); + form.signon_realm = form.origin.GetOrigin().spec(); + if (!realm.empty()) + form.signon_realm += realm; + form.ssl_valid = form.origin.SchemeIsSecure(); + // The user name, password and action. + form.username_element = UTF8ToUTF16(s2.column_string(3)); + form.username_value = Decrypt(s2.column_string(5)); + form.password_element = UTF8ToUTF16(s2.column_string(4)); + form.password_value = Decrypt(s2.column_string(6)); + form.action = GURL(s2.column_string(2)).ReplaceComponents(rep); + forms->push_back(form); + } + return true; +} diff --git a/chrome/browser/importer/nss_decryptor.h b/chrome/browser/importer/nss_decryptor.h new file mode 100644 index 0000000..c133665 --- /dev/null +++ b/chrome/browser/importer/nss_decryptor.h @@ -0,0 +1,18 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_H_ +#define CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_H_ + +#include "build/build_config.h" + +#if defined(OS_MACOSX) +#include "chrome/browser/importer/nss_decryptor_mac.h" +#elif defined(OS_WIN) +#include "chrome/browser/importer/nss_decryptor_win.h" +#elif defined(USE_NSS) +#include "chrome/browser/importer/nss_decryptor_system_nss.h" +#endif + +#endif // CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_H_ diff --git a/chrome/browser/importer/nss_decryptor_mac.h b/chrome/browser/importer/nss_decryptor_mac.h new file mode 100644 index 0000000..af1b004 --- /dev/null +++ b/chrome/browser/importer/nss_decryptor_mac.h @@ -0,0 +1,162 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_MAC_H_ +#define CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_MAC_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/logging.h" + +// The following declarations of functions and types are from Firefox +// NSS library. +// source code: +// security/nss/lib/util/seccomon.h +// security/nss/lib/nss/nss.h +// The license block is: + +/* ***** BEGIN LICENSE BLOCK ***** +* Version: MPL 1.1/GPL 2.0/LGPL 2.1 +* +* The contents of this file are subject to the Mozilla Public License Version +* 1.1 (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* http://www.mozilla.org/MPL/ +* +* Software distributed under the License is distributed on an "AS IS" basis, +* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +* for the specific language governing rights and limitations under the +* License. +* +* The Original Code is the Netscape security libraries. +* +* The Initial Developer of the Original Code is +* Netscape Communications Corporation. +* Portions created by the Initial Developer are Copyright (C) 1994-2000 +* the Initial Developer. All Rights Reserved. +* +* Contributor(s): +* +* Alternatively, the contents of this file may be used under the terms of +* either the GNU General Public License Version 2 or later (the "GPL"), or +* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +* in which case the provisions of the GPL or the LGPL are applicable instead +* of those above. If you wish to allow use of your version of this file only +* under the terms of either the GPL or the LGPL, and not to allow others to +* use your version of this file under the terms of the MPL, indicate your +* decision by deleting the provisions above and replace them with the notice +* and other provisions required by the GPL or the LGPL. If you do not delete +* the provisions above, a recipient may use your version of this file under +* the terms of any one of the MPL, the GPL or the LGPL. +* +* ***** END LICENSE BLOCK ***** */ + +enum SECItemType { + siBuffer = 0, + siClearDataBuffer = 1, + siCipherDataBuffer = 2, + siDERCertBuffer = 3, + siEncodedCertBuffer = 4, + siDERNameBuffer = 5, + siEncodedNameBuffer = 6, + siAsciiNameString = 7, + siAsciiString = 8, + siDEROID = 9, + siUnsignedInteger = 10, + siUTCTime = 11, + siGeneralizedTime = 12 +}; + +struct SECItem { + SECItemType type; + unsigned char *data; + unsigned int len; +}; + +enum SECStatus { + SECWouldBlock = -2, + SECFailure = -1, + SECSuccess = 0 +}; + +typedef int PRBool; +#define PR_TRUE 1 +#define PR_FALSE 0 + +typedef enum { PR_FAILURE = -1, PR_SUCCESS = 0 } PRStatus; + +typedef struct PK11SlotInfoStr PK11SlotInfo; + +typedef SECStatus (*NSSInitFunc)(const char *configdir); +typedef SECStatus (*NSSShutdownFunc)(void); +typedef PK11SlotInfo* (*PK11GetInternalKeySlotFunc)(void); +typedef void (*PK11FreeSlotFunc)(PK11SlotInfo *slot); +typedef SECStatus (*PK11CheckUserPasswordFunc)(PK11SlotInfo *slot, char *pw); +typedef SECStatus + (*PK11AuthenticateFunc)(PK11SlotInfo *slot, PRBool loadCerts, void *wincx); +typedef SECStatus + (*PK11SDRDecryptFunc)(SECItem *data, SECItem *result, void *cx); +typedef void (*SECITEMFreeItemFunc)(SECItem *item, PRBool free_it); +typedef void (*PLArenaFinishFunc)(void); +typedef PRStatus (*PRCleanupFunc)(void); +namespace webkit_glue { +struct PasswordForm; +} + +// A wrapper for Firefox NSS decrypt component. +class NSSDecryptor { + public: + NSSDecryptor() + : NSS_Init(NULL), NSS_Shutdown(NULL), PK11_GetInternalKeySlot(NULL), + PK11_CheckUserPassword(NULL), PK11_FreeSlot(NULL), + PK11_Authenticate(NULL), PK11SDR_Decrypt(NULL), SECITEM_FreeItem(NULL), + is_nss_initialized_(false) {} + ~NSSDecryptor(); + + // Initializes NSS if it hasn't already been initialized. + bool Init(const std::wstring& /* dll_path */, + const std::wstring& /* db_path */); + + // Decrypts Firefox stored passwords. Before using this method, + // make sure Init() returns true. + string16 Decrypt(const std::string& crypt) const; + + // Parses the Firefox password file content, decrypts the + // username/password and reads other related information. + // The result will be stored in |forms|. + void ParseSignons(const std::string& content, + std::vector<webkit_glue::PasswordForm>* forms); + + // Reads and parses the Firefox password sqlite db, decrypts the + // username/password and reads other related information. + // The result will be stored in |forms|. + bool ReadAndParseSignons(const FilePath& sqlite_file, + std::vector<webkit_glue::PasswordForm>* forms); + private: + PK11SlotInfo* GetKeySlotForDB() const { return PK11_GetInternalKeySlot(); } + void FreeSlot(PK11SlotInfo* slot) const { PK11_FreeSlot(slot); } + + // Methods in Firefox security components. + NSSInitFunc NSS_Init; + NSSShutdownFunc NSS_Shutdown; + PK11GetInternalKeySlotFunc PK11_GetInternalKeySlot; + PK11CheckUserPasswordFunc PK11_CheckUserPassword; + PK11FreeSlotFunc PK11_FreeSlot; + PK11AuthenticateFunc PK11_Authenticate; + PK11SDRDecryptFunc PK11SDR_Decrypt; + SECITEMFreeItemFunc SECITEM_FreeItem; + + // Libraries necessary for decrypting the passwords. + static const wchar_t kNSS3Library[]; + + // True if NSS_Init() has been called + bool is_nss_initialized_; + + DISALLOW_COPY_AND_ASSIGN(NSSDecryptor); +}; + +#endif // CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_MAC_H_ diff --git a/chrome/browser/importer/nss_decryptor_mac.mm b/chrome/browser/importer/nss_decryptor_mac.mm new file mode 100644 index 0000000..e319c74 --- /dev/null +++ b/chrome/browser/importer/nss_decryptor_mac.mm @@ -0,0 +1,75 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <Cocoa/Cocoa.h> + +#include <dlfcn.h> + +#include "base/sys_string_conversions.h" + +#include "chrome/browser/importer/nss_decryptor_mac.h" +#include "chrome/browser/importer/firefox_importer_utils.h" + +#include "base/logging.h" + +// static +const wchar_t NSSDecryptor::kNSS3Library[] = L"libnss3.dylib"; + +// Important!! : On OS X the nss3 libraries are compiled with depedencies +// on one another, referenced using dyld's @executable_path directive. +// To make a long story short in order to get the libraries to load, dyld's +// fallback path needs to be set to the directory containing the libraries. +// To do so, the process this function runs in must have the +// DYLD_FALLBACK_LIBRARY_PATH set on startup to said directory. +bool NSSDecryptor::Init(const std::wstring& dll_path, + const std::wstring& db_path) { + if (getenv("DYLD_FALLBACK_LIBRARY_PATH") == NULL) { + LOG(ERROR) << "DYLD_FALLBACK_LIBRARY_PATH variable not set"; + return false; + } + FilePath dylib_file_path = FilePath::FromWStringHack(dll_path); + FilePath nss3_path = dylib_file_path.Append("libnss3.dylib"); + + void *nss_3_lib = dlopen(nss3_path.value().c_str(), RTLD_LAZY); + if (!nss_3_lib) { + LOG(ERROR) << "Failed to load nss3 lib" << dlerror(); + return false; + } + + NSS_Init = (NSSInitFunc)dlsym(nss_3_lib, "NSS_Init"); + NSS_Shutdown = (NSSShutdownFunc)dlsym(nss_3_lib, "NSS_Shutdown"); + PK11_GetInternalKeySlot = + (PK11GetInternalKeySlotFunc)dlsym(nss_3_lib, "PK11_GetInternalKeySlot"); + PK11_CheckUserPassword = + (PK11CheckUserPasswordFunc)dlsym(nss_3_lib, "PK11_CheckUserPassword"); + PK11_FreeSlot = (PK11FreeSlotFunc)dlsym(nss_3_lib, "PK11_FreeSlot"); + PK11_Authenticate = + (PK11AuthenticateFunc)dlsym(nss_3_lib, "PK11_Authenticate"); + PK11SDR_Decrypt = (PK11SDRDecryptFunc)dlsym(nss_3_lib, "PK11SDR_Decrypt"); + SECITEM_FreeItem = (SECITEMFreeItemFunc)dlsym(nss_3_lib, "SECITEM_FreeItem"); + + if (!NSS_Init || !NSS_Shutdown || !PK11_GetInternalKeySlot || + !PK11_CheckUserPassword || !PK11_FreeSlot || !PK11_Authenticate || + !PK11SDR_Decrypt || !SECITEM_FreeItem) { + LOG(ERROR) << "NSS3 importer couldn't find entry points"; + return false; + } + + SECStatus result = NSS_Init(base::SysWideToNativeMB(db_path).c_str()); + + if (result != SECSuccess) { + LOG(ERROR) << "NSS_Init Failed returned: " << result; + return false; + } + + is_nss_initialized_ = true; + return true; +} + +NSSDecryptor::~NSSDecryptor() { + if (NSS_Shutdown && is_nss_initialized_) { + NSS_Shutdown(); + is_nss_initialized_ = false; + } +} diff --git a/chrome/browser/importer/nss_decryptor_system_nss.cc b/chrome/browser/importer/nss_decryptor_system_nss.cc new file mode 100644 index 0000000..06be5da --- /dev/null +++ b/chrome/browser/importer/nss_decryptor_system_nss.cc @@ -0,0 +1,269 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/nss_decryptor_system_nss.h" + +#include <pk11pub.h> +#include <pk11sdr.h> + +#include "base/basictypes.h" +#include "base/nss_util.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" + +NSSDecryptor::NSSDecryptor() : is_nss_initialized_(false), db_slot_(NULL) {} +NSSDecryptor::~NSSDecryptor() { + if (db_slot_) { + // Deliberately leave the user db open, just in case we need to open more + // than one, because there's an NSS bug with reopening user dbs. + // https://bugzilla.mozilla.org/show_bug.cgi?id=506140 + // SECMOD_CloseUserDB(db_slot_); + PK11_FreeSlot(db_slot_); + } +} + +bool NSSDecryptor::Init(const std::wstring& /* dll_path */, + const std::wstring& db_path) { + base::EnsureNSSInit(); + is_nss_initialized_ = true; + const std::string modspec = + StringPrintf("configDir='%s' tokenDescription='Firefox NSS database' " + "flags=readOnly", base::SysWideToNativeMB(db_path).c_str()); + db_slot_ = SECMOD_OpenUserDB(modspec.c_str()); + return db_slot_ != NULL; +} + +// This method is based on some NSS code in +// security/nss/lib/pk11wrap/pk11sdr.c, CVS revision 1.22 +// This code is copied because the implementation assumes the use of the +// internal key slot for decryption, but we need to use another slot. +// The license block is: +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1994-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * thayes@netscape.com + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * Data structure and template for encoding the result of an SDR operation + * This is temporary. It should include the algorithm ID of the encryption + * mechanism + */ +struct SDRResult +{ + SECItem keyid; + SECAlgorithmID alg; + SECItem data; +}; +typedef struct SDRResult SDRResult; + +static SEC_ASN1Template g_template[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof (SDRResult) }, + { SEC_ASN1_OCTET_STRING, offsetof(SDRResult, keyid) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(SDRResult, alg), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_OCTET_STRING, offsetof(SDRResult, data) }, + { 0 } +}; + +static SECStatus +unpadBlock(SECItem *data, int blockSize, SECItem *result) +{ + SECStatus rv = SECSuccess; + int padLength; + int i; + + result->data = 0; + result->len = 0; + + /* Remove the padding from the end if the input data */ + if (data->len == 0 || data->len % blockSize != 0) { + rv = SECFailure; + goto loser; + } + + padLength = data->data[data->len-1]; + if (padLength > blockSize) { rv = SECFailure; goto loser; } + + /* verify padding */ + for (i=data->len - padLength; static_cast<uint32>(i) < data->len; i++) { + if (data->data[i] != padLength) { + rv = SECFailure; + goto loser; + } + } + + result->len = data->len - padLength; + result->data = (unsigned char *)PORT_Alloc(result->len); + if (!result->data) { rv = SECFailure; goto loser; } + + PORT_Memcpy(result->data, data->data, result->len); + + if (padLength < 2) { + return SECWouldBlock; + } + +loser: + return rv; +} + +/* decrypt a block */ +static SECStatus +pk11Decrypt(PK11SlotInfo *slot, PLArenaPool *arena, + CK_MECHANISM_TYPE type, PK11SymKey *key, + SECItem *params, SECItem *in, SECItem *result) +{ + PK11Context *ctx = 0; + SECItem paddedResult; + SECStatus rv; + + paddedResult.len = 0; + paddedResult.data = 0; + + ctx = PK11_CreateContextBySymKey(type, CKA_DECRYPT, key, params); + if (!ctx) { rv = SECFailure; goto loser; } + + paddedResult.len = in->len; + paddedResult.data = static_cast<unsigned char*>( + PORT_ArenaAlloc(arena, paddedResult.len)); + + rv = PK11_CipherOp(ctx, paddedResult.data, + (int*)&paddedResult.len, paddedResult.len, + in->data, in->len); + if (rv != SECSuccess) goto loser; + + PK11_Finalize(ctx); + + /* Remove the padding */ + rv = unpadBlock(&paddedResult, PK11_GetBlockSize(type, 0), result); + if (rv) goto loser; + +loser: + if (ctx) PK11_DestroyContext(ctx, PR_TRUE); + return rv; +} + +SECStatus NSSDecryptor::PK11SDR_DecryptWithSlot( + PK11SlotInfo* slot, SECItem* data, SECItem* result, void* cx) const { + SECStatus rv = SECSuccess; + PK11SymKey *key = 0; + CK_MECHANISM_TYPE type; + SDRResult sdrResult; + SECItem *params = 0; + SECItem possibleResult = { siBuffer, NULL, 0 }; + PLArenaPool *arena = 0; + + arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if (!arena) { rv = SECFailure; goto loser; } + + /* Decode the incoming data */ + memset(&sdrResult, 0, sizeof sdrResult); + rv = SEC_QuickDERDecodeItem(arena, &sdrResult, g_template, data); + if (rv != SECSuccess) goto loser; /* Invalid format */ + + /* Get the parameter values from the data */ + params = PK11_ParamFromAlgid(&sdrResult.alg); + if (!params) { rv = SECFailure; goto loser; } + + /* Use triple-DES (Should look up the algorithm) */ + type = CKM_DES3_CBC; + key = PK11_FindFixedKey(slot, type, &sdrResult.keyid, cx); + if (!key) { + rv = SECFailure; + } else { + rv = pk11Decrypt(slot, arena, type, key, params, + &sdrResult.data, result); + } + + /* + * if the pad value was too small (1 or 2), then it's statistically + * 'likely' that (1 in 256) that we may not have the correct key. + * Check the other keys for a better match. If we find none, use + * this result. + */ + if (rv == SECWouldBlock) + possibleResult = *result; + + /* + * handle the case where your key indicies may have been broken + */ + if (rv != SECSuccess) { + PK11SymKey *keyList = PK11_ListFixedKeysInSlot(slot, NULL, cx); + PK11SymKey *testKey = NULL; + PK11SymKey *nextKey = NULL; + + for (testKey = keyList; testKey; + testKey = PK11_GetNextSymKey(testKey)) { + rv = pk11Decrypt(slot, arena, type, testKey, params, + &sdrResult.data, result); + if (rv == SECSuccess) + break; + + /* found a close match. If it's our first remember it */ + if (rv == SECWouldBlock) { + if (possibleResult.data) { + /* this is unlikely but possible. If we hit this condition, + * we have no way of knowing which possibility to prefer. + * in this case we just match the key the application + * thought was the right one */ + SECITEM_ZfreeItem(result, PR_FALSE); + } else { + possibleResult = *result; + } + } + } + + /* free the list */ + for (testKey = keyList; testKey; testKey = nextKey) { + nextKey = PK11_GetNextSymKey(testKey); + PK11_FreeSymKey(testKey); + } + } + + /* we didn't find a better key, use the one with a small pad value */ + if ((rv != SECSuccess) && (possibleResult.data)) { + *result = possibleResult; + possibleResult.data = NULL; + rv = SECSuccess; + } + + loser: + if (arena) PORT_FreeArena(arena, PR_TRUE); + if (key) PK11_FreeSymKey(key); + if (params) SECITEM_ZfreeItem(params, PR_TRUE); + if (possibleResult.data) SECITEM_ZfreeItem(&possibleResult, PR_FALSE); + + return rv; +} diff --git a/chrome/browser/importer/nss_decryptor_system_nss.h b/chrome/browser/importer/nss_decryptor_system_nss.h new file mode 100644 index 0000000..7071428 --- /dev/null +++ b/chrome/browser/importer/nss_decryptor_system_nss.h @@ -0,0 +1,59 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_SYSTEM_NSS_H_ +#define CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_SYSTEM_NSS_H_ + +#include <secmodt.h> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/file_path.h" + +namespace webkit_glue { +struct PasswordForm; +} + +// A wrapper for Firefox NSS decrypt component. +class NSSDecryptor { + public: + NSSDecryptor(); + ~NSSDecryptor(); + + // Initializes NSS if it hasn't already been initialized. + bool Init(const std::wstring& /* dll_path */, + const std::wstring& db_path); + + // Decrypts Firefox stored passwords. Before using this method, + // make sure Init() returns true. + string16 Decrypt(const std::string& crypt) const; + + // Parses the Firefox password file content, decrypts the + // username/password and reads other related information. + // The result will be stored in |forms|. + void ParseSignons(const std::string& content, + std::vector<webkit_glue::PasswordForm>* forms); + + // Reads and parses the Firefox password sqlite db, decrypts the + // username/password and reads other related information. + // The result will be stored in |forms|. + bool ReadAndParseSignons(const FilePath& sqlite_file, + std::vector<webkit_glue::PasswordForm>* forms); + private: + // Does not actually free the slot, since we'll free it when NSSDecryptor is + // destroyed. + void FreeSlot(PK11SlotInfo* slot) const {} + PK11SlotInfo* GetKeySlotForDB() const { return db_slot_; } + + SECStatus PK11SDR_DecryptWithSlot( + PK11SlotInfo* slot, SECItem* data, SECItem* result, void* cx) const; + + bool is_nss_initialized_; + PK11SlotInfo* db_slot_; + + DISALLOW_COPY_AND_ASSIGN(NSSDecryptor); +}; + +#endif // CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_SYSTEM_NSS_H_ diff --git a/chrome/browser/importer/nss_decryptor_win.cc b/chrome/browser/importer/nss_decryptor_win.cc new file mode 100644 index 0000000..a662abb --- /dev/null +++ b/chrome/browser/importer/nss_decryptor_win.cc @@ -0,0 +1,178 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/nss_decryptor_win.h" +#include "base/file_util.h" +#include "base/sys_string_conversions.h" + +namespace { + +typedef BOOL (WINAPI* SetDllDirectoryFunc)(LPCTSTR lpPathName); + +// A helper class whose destructor calls SetDllDirectory(NULL) to undo the +// effects of a previous SetDllDirectory call. +class SetDllDirectoryCaller { + public: + explicit SetDllDirectoryCaller() : func_(NULL) { } + + ~SetDllDirectoryCaller() { + if (func_) + func_(NULL); + } + + // Sets the SetDllDirectory function pointer to activates this object. + void set_func(SetDllDirectoryFunc func) { func_ = func; } + + private: + SetDllDirectoryFunc func_; +}; + +} // namespace + +// static +const wchar_t NSSDecryptor::kNSS3Library[] = L"nss3.dll"; +const wchar_t NSSDecryptor::kSoftokn3Library[] = L"softokn3.dll"; +const wchar_t NSSDecryptor::kPLDS4Library[] = L"plds4.dll"; +const wchar_t NSSDecryptor::kNSPR4Library[] = L"nspr4.dll"; + +bool NSSDecryptor::Init(const std::wstring& dll_path, + const std::wstring& db_path) { + // We call SetDllDirectory to work around a Purify bug (GetModuleHandle + // fails inside Purify under certain conditions). SetDllDirectory only + // exists on Windows XP SP1 or later, so we look up its address at run time. + HMODULE kernel32_dll = GetModuleHandle(L"kernel32.dll"); + if (kernel32_dll == NULL) + return false; + SetDllDirectoryFunc set_dll_directory = + (SetDllDirectoryFunc)GetProcAddress(kernel32_dll, "SetDllDirectoryW"); + SetDllDirectoryCaller caller; + + if (set_dll_directory != NULL) { + if (!set_dll_directory(dll_path.c_str())) + return false; + caller.set_func(set_dll_directory); + nss3_dll_ = LoadLibrary(kNSS3Library); + if (nss3_dll_ == NULL) + return false; + } else { + // Fall back on LoadLibraryEx if SetDllDirectory isn't available. We + // actually prefer this method because it doesn't change the DLL search + // path, which is a process-wide property. + std::wstring path = dll_path; + file_util::AppendToPath(&path, kNSS3Library); + nss3_dll_ = LoadLibraryEx(path.c_str(), NULL, + LOAD_WITH_ALTERED_SEARCH_PATH); + if (nss3_dll_ == NULL) + return false; + + // Firefox 2 uses NSS 3.11. Firefox 3 uses NSS 3.12. NSS 3.12 has two + // changes in its DLLs: + // 1. nss3.dll is not linked with softokn3.dll at build time, but rather + // loads softokn3.dll using LoadLibrary in NSS_Init. + // 2. softokn3.dll has a new dependency sqlite3.dll. + // NSS_Init's LoadLibrary call has trouble finding sqlite3.dll. To help + // it out, we preload softokn3.dll using LoadLibraryEx with the + // LOAD_WITH_ALTERED_SEARCH_PATH flag. This helps because LoadLibrary + // doesn't load a DLL again if it's already loaded. This workaround is + // harmless for NSS 3.11. + path = dll_path; + file_util::AppendToPath(&path, kSoftokn3Library); + softokn3_dll_ = LoadLibraryEx(path.c_str(), NULL, + LOAD_WITH_ALTERED_SEARCH_PATH); + if (softokn3_dll_ == NULL) { + Free(); + return false; + } + } + HMODULE plds4_dll = GetModuleHandle(kPLDS4Library); + HMODULE nspr4_dll = GetModuleHandle(kNSPR4Library); + + return InitNSS(db_path, plds4_dll, nspr4_dll); +} + +NSSDecryptor::NSSDecryptor() + : NSS_Init(NULL), NSS_Shutdown(NULL), PK11_GetInternalKeySlot(NULL), + PK11_CheckUserPassword(NULL), PK11_FreeSlot(NULL), + PK11_Authenticate(NULL), PK11SDR_Decrypt(NULL), SECITEM_FreeItem(NULL), + PL_ArenaFinish(NULL), PR_Cleanup(NULL), + nss3_dll_(NULL), softokn3_dll_(NULL), + is_nss_initialized_(false) { +} + +NSSDecryptor::~NSSDecryptor() { + Free(); +} + +bool NSSDecryptor::InitNSS(const std::wstring& db_path, + base::NativeLibrary plds4_dll, + base::NativeLibrary nspr4_dll) { + // NSPR DLLs are already loaded now. + if (plds4_dll == NULL || nspr4_dll == NULL) { + Free(); + return false; + } + + // Gets the function address. + NSS_Init = (NSSInitFunc) + base::GetFunctionPointerFromNativeLibrary(nss3_dll_, "NSS_Init"); + NSS_Shutdown = (NSSShutdownFunc) + base::GetFunctionPointerFromNativeLibrary(nss3_dll_, "NSS_Shutdown"); + PK11_GetInternalKeySlot = (PK11GetInternalKeySlotFunc) + base::GetFunctionPointerFromNativeLibrary(nss3_dll_, + "PK11_GetInternalKeySlot"); + PK11_FreeSlot = (PK11FreeSlotFunc) + base::GetFunctionPointerFromNativeLibrary(nss3_dll_, "PK11_FreeSlot"); + PK11_Authenticate = (PK11AuthenticateFunc) + base::GetFunctionPointerFromNativeLibrary(nss3_dll_, "PK11_Authenticate"); + PK11SDR_Decrypt = (PK11SDRDecryptFunc) + base::GetFunctionPointerFromNativeLibrary(nss3_dll_, "PK11SDR_Decrypt"); + SECITEM_FreeItem = (SECITEMFreeItemFunc) + base::GetFunctionPointerFromNativeLibrary(nss3_dll_, "SECITEM_FreeItem"); + PL_ArenaFinish = (PLArenaFinishFunc) + base::GetFunctionPointerFromNativeLibrary(plds4_dll, "PL_ArenaFinish"); + PR_Cleanup = (PRCleanupFunc) + base::GetFunctionPointerFromNativeLibrary(nspr4_dll, "PR_Cleanup"); + + if (NSS_Init == NULL || NSS_Shutdown == NULL || + PK11_GetInternalKeySlot == NULL || PK11_FreeSlot == NULL || + PK11_Authenticate == NULL || PK11SDR_Decrypt == NULL || + SECITEM_FreeItem == NULL || PL_ArenaFinish == NULL || + PR_Cleanup == NULL) { + Free(); + return false; + } + + SECStatus result = NSS_Init(base::SysWideToNativeMB(db_path).c_str()); + if (result != SECSuccess) { + Free(); + return false; + } + + is_nss_initialized_ = true; + return true; +} + +void NSSDecryptor::Free() { + if (is_nss_initialized_) { + NSS_Shutdown(); + PL_ArenaFinish(); + PR_Cleanup(); + is_nss_initialized_ = false; + } + if (softokn3_dll_ != NULL) + base::UnloadNativeLibrary(softokn3_dll_); + if (nss3_dll_ != NULL) + base::UnloadNativeLibrary(nss3_dll_); + NSS_Init = NULL; + NSS_Shutdown = NULL; + PK11_GetInternalKeySlot = NULL; + PK11_FreeSlot = NULL; + PK11_Authenticate = NULL; + PK11SDR_Decrypt = NULL; + SECITEM_FreeItem = NULL; + PL_ArenaFinish = NULL; + PR_Cleanup = NULL; + nss3_dll_ = NULL; + softokn3_dll_ = NULL; +} diff --git a/chrome/browser/importer/nss_decryptor_win.h b/chrome/browser/importer/nss_decryptor_win.h new file mode 100644 index 0000000..f0b2345 --- /dev/null +++ b/chrome/browser/importer/nss_decryptor_win.h @@ -0,0 +1,177 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_WIN_H_ +#define CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_WIN_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/native_library.h" + +// The following declarations of functions and types are from Firefox +// NSS library. +// source code: +// security/nss/lib/util/seccomon.h +// security/nss/lib/nss/nss.h +// The license block is: + +/* ***** BEGIN LICENSE BLOCK ***** +* Version: MPL 1.1/GPL 2.0/LGPL 2.1 +* +* The contents of this file are subject to the Mozilla Public License Version +* 1.1 (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* http://www.mozilla.org/MPL/ +* +* Software distributed under the License is distributed on an "AS IS" basis, +* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +* for the specific language governing rights and limitations under the +* License. +* +* The Original Code is the Netscape security libraries. +* +* The Initial Developer of the Original Code is +* Netscape Communications Corporation. +* Portions created by the Initial Developer are Copyright (C) 1994-2000 +* the Initial Developer. All Rights Reserved. +* +* Contributor(s): +* +* Alternatively, the contents of this file may be used under the terms of +* either the GNU General Public License Version 2 or later (the "GPL"), or +* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +* in which case the provisions of the GPL or the LGPL are applicable instead +* of those above. If you wish to allow use of your version of this file only +* under the terms of either the GPL or the LGPL, and not to allow others to +* use your version of this file under the terms of the MPL, indicate your +* decision by deleting the provisions above and replace them with the notice +* and other provisions required by the GPL or the LGPL. If you do not delete +* the provisions above, a recipient may use your version of this file under +* the terms of any one of the MPL, the GPL or the LGPL. +* +* ***** END LICENSE BLOCK ***** */ + +enum SECItemType { + siBuffer = 0, + siClearDataBuffer = 1, + siCipherDataBuffer = 2, + siDERCertBuffer = 3, + siEncodedCertBuffer = 4, + siDERNameBuffer = 5, + siEncodedNameBuffer = 6, + siAsciiNameString = 7, + siAsciiString = 8, + siDEROID = 9, + siUnsignedInteger = 10, + siUTCTime = 11, + siGeneralizedTime = 12 +}; + +struct SECItem { + SECItemType type; + unsigned char *data; + unsigned int len; +}; + +enum SECStatus { + SECWouldBlock = -2, + SECFailure = -1, + SECSuccess = 0 +}; + +typedef int PRBool; +#define PR_TRUE 1 +#define PR_FALSE 0 + +typedef enum { PR_FAILURE = -1, PR_SUCCESS = 0 } PRStatus; + +typedef struct PK11SlotInfoStr PK11SlotInfo; + +typedef SECStatus (*NSSInitFunc)(const char *configdir); +typedef SECStatus (*NSSShutdownFunc)(void); +typedef PK11SlotInfo* (*PK11GetInternalKeySlotFunc)(void); +typedef void (*PK11FreeSlotFunc)(PK11SlotInfo *slot); +typedef SECStatus (*PK11CheckUserPasswordFunc)(PK11SlotInfo *slot, char *pw); +typedef SECStatus + (*PK11AuthenticateFunc)(PK11SlotInfo *slot, PRBool loadCerts, void *wincx); +typedef SECStatus + (*PK11SDRDecryptFunc)(SECItem *data, SECItem *result, void *cx); +typedef void (*SECITEMFreeItemFunc)(SECItem *item, PRBool free_it); +typedef void (*PLArenaFinishFunc)(void); +typedef PRStatus (*PRCleanupFunc)(void); + +namespace webkit_glue { +struct PasswordForm; +} + +// A wrapper for Firefox NSS decrypt component. +class NSSDecryptor { + public: + NSSDecryptor(); + ~NSSDecryptor(); + + // Loads NSS3 library and returns true if successful. + // |dll_path| indicates the location of NSS3 DLL files, and |db_path| + // is the location of the database file that stores the keys. + bool Init(const std::wstring& dll_path, const std::wstring& db_path); + + // Frees the libraries. + void Free(); + + // Decrypts Firefox stored passwords. Before using this method, + // make sure Init() returns true. + std::wstring Decrypt(const std::string& crypt) const; + + // Parses the Firefox password file content, decrypts the + // username/password and reads other related information. + // The result will be stored in |forms|. + void ParseSignons(const std::string& content, + std::vector<webkit_glue::PasswordForm>* forms); + + // Reads and parses the Firefox password sqlite db, decrypts the + // username/password and reads other related information. + // The result will be stored in |forms|. + bool ReadAndParseSignons(const FilePath& sqlite_file, + std::vector<webkit_glue::PasswordForm>* forms); + + private: + // Performs tasks common across all platforms to initialize NSS. + bool InitNSS(const std::wstring& db_path, + base::NativeLibrary plds4_dll, + base::NativeLibrary nspr4_dll); + + PK11SlotInfo* GetKeySlotForDB() const { return PK11_GetInternalKeySlot(); } + void FreeSlot(PK11SlotInfo* slot) const { PK11_FreeSlot(slot); } + + // Methods in Firefox security components. + NSSInitFunc NSS_Init; + NSSShutdownFunc NSS_Shutdown; + PK11GetInternalKeySlotFunc PK11_GetInternalKeySlot; + PK11CheckUserPasswordFunc PK11_CheckUserPassword; + PK11FreeSlotFunc PK11_FreeSlot; + PK11AuthenticateFunc PK11_Authenticate; + PK11SDRDecryptFunc PK11SDR_Decrypt; + SECITEMFreeItemFunc SECITEM_FreeItem; + PLArenaFinishFunc PL_ArenaFinish; + PRCleanupFunc PR_Cleanup; + + // Libraries necessary for decrypting the passwords. + static const wchar_t kNSS3Library[]; + static const wchar_t kSoftokn3Library[]; + static const wchar_t kPLDS4Library[]; + static const wchar_t kNSPR4Library[]; + + // NSS3 module handles. + base::NativeLibrary nss3_dll_; + base::NativeLibrary softokn3_dll_; + + // True if NSS_Init() has been called + bool is_nss_initialized_; + + DISALLOW_COPY_AND_ASSIGN(NSSDecryptor); +}; + +#endif // CHROME_BROWSER_IMPORTER_NSS_DECRYPTOR_WIN_H_ diff --git a/chrome/browser/importer/profile_writer.cc b/chrome/browser/importer/profile_writer.cc new file mode 100644 index 0000000..0061efc --- /dev/null +++ b/chrome/browser/importer/profile_writer.cc @@ -0,0 +1,348 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/profile_writer.h" + +#include "base/string_util.h" +#include "base/thread.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/importer/importer.h" +#include "chrome/browser/password_manager/password_store.h" +#include "chrome/browser/pref_service.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/search_engines/template_url_model.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/pref_names.h" + +using webkit_glue::PasswordForm; + +bool ProfileWriter::BookmarkModelIsLoaded() const { + return profile_->GetBookmarkModel()->IsLoaded(); +} + +bool ProfileWriter::TemplateURLModelIsLoaded() const { + return profile_->GetTemplateURLModel()->loaded(); +} + +void ProfileWriter::AddPasswordForm(const PasswordForm& form) { + profile_->GetPasswordStore(Profile::EXPLICIT_ACCESS)->AddLogin(form); +} + +#if defined(OS_WIN) +void ProfileWriter::AddIE7PasswordInfo(const IE7PasswordInfo& info) { + profile_->GetWebDataService(Profile::EXPLICIT_ACCESS)->AddIE7Login(info); +} +#endif + +void ProfileWriter::AddHistoryPage(const std::vector<history::URLRow>& page) { + profile_->GetHistoryService(Profile::EXPLICIT_ACCESS)-> + AddPagesWithDetails(page); +} + +void ProfileWriter::AddHomepage(const GURL& home_page) { + DCHECK(profile_); + + PrefService* prefs = profile_->GetPrefs(); + // NOTE: We set the kHomePage value, but keep the NewTab page as the homepage. + const PrefService::Preference* pref = prefs->FindPreference(prefs::kHomePage); + if (pref && !pref->IsManaged()) { + prefs->SetString(prefs::kHomePage, home_page.spec()); + prefs->ScheduleSavePersistentPrefs(); + } +} + +void ProfileWriter::AddBookmarkEntry( + const std::vector<BookmarkEntry>& bookmark, + const std::wstring& first_folder_name, + int options) { + BookmarkModel* model = profile_->GetBookmarkModel(); + DCHECK(model->IsLoaded()); + + bool import_to_bookmark_bar = ((options & IMPORT_TO_BOOKMARK_BAR) != 0); + std::wstring real_first_folder = import_to_bookmark_bar ? first_folder_name : + GenerateUniqueFolderName(model, first_folder_name); + + bool show_bookmark_toolbar = false; + std::set<const BookmarkNode*> groups_added_to; + bool import_mode = false; + if (bookmark.size() > 1) { + model->BeginImportMode(); + import_mode = true; + } + for (std::vector<BookmarkEntry>::const_iterator it = bookmark.begin(); + it != bookmark.end(); ++it) { + // Don't insert this url if it isn't valid. + if (!it->url.is_valid()) + continue; + + // We suppose that bookmarks are unique by Title, URL, and Folder. Since + // checking for uniqueness may not be always the user's intention we have + // this as an option. + if (options & ADD_IF_UNIQUE && DoesBookmarkExist(model, *it, + real_first_folder, import_to_bookmark_bar)) + continue; + + // Set up groups in BookmarkModel in such a way that path[i] is + // the subgroup of path[i-1]. Finally they construct a path in the + // model: + // path[0] \ path[1] \ ... \ path[size() - 1] + const BookmarkNode* parent = + (it->in_toolbar ? model->GetBookmarkBarNode() : model->other_node()); + for (std::vector<std::wstring>::const_iterator i = it->path.begin(); + i != it->path.end(); ++i) { + const BookmarkNode* child = NULL; + const std::wstring& folder_name = (!import_to_bookmark_bar && + !it->in_toolbar && (i == it->path.begin())) ? real_first_folder : *i; + + for (int index = 0; index < parent->GetChildCount(); ++index) { + const BookmarkNode* node = parent->GetChild(index); + if ((node->type() == BookmarkNode::BOOKMARK_BAR || + node->type() == BookmarkNode::FOLDER) && + node->GetTitle() == folder_name) { + child = node; + break; + } + } + if (child == NULL) + child = model->AddGroup(parent, parent->GetChildCount(), folder_name); + parent = child; + } + groups_added_to.insert(parent); + model->AddURLWithCreationTime(parent, parent->GetChildCount(), + it->title, it->url, it->creation_time); + + // If some items are put into toolbar, it looks like the user was using + // it in their last browser. We turn on the bookmarks toolbar. + if (it->in_toolbar) + show_bookmark_toolbar = true; + } + + // Reset the date modified time of the groups we added to. We do this to + // make sure the 'recently added to' combobox in the bubble doesn't get random + // groups. + for (std::set<const BookmarkNode*>::const_iterator i = + groups_added_to.begin(); + i != groups_added_to.end(); ++i) { + model->ResetDateGroupModified(*i); + } + + if (import_mode) { + model->EndImportMode(); + } + + if (show_bookmark_toolbar && !(options & BOOKMARK_BAR_DISABLED)) + ShowBookmarkBar(); +} + +void ProfileWriter::AddFavicons( + const std::vector<history::ImportedFavIconUsage>& favicons) { + profile_->GetFaviconService(Profile::EXPLICIT_ACCESS)-> + SetImportedFavicons(favicons); +} + +typedef std::map<std::string, const TemplateURL*> HostPathMap; + +// Returns the key for the map built by BuildHostPathMap. If url_string is not +// a valid URL, an empty string is returned, otherwise host+path is returned. +static std::string HostPathKeyForURL(const GURL& url) { + return url.is_valid() ? url.host() + url.path() : std::string(); +} + +// Builds the key to use in HostPathMap for the specified TemplateURL. Returns +// an empty string if a host+path can't be generated for the TemplateURL. +// If an empty string is returned, the TemplateURL should not be added to +// HostPathMap. +// +// If |try_url_if_invalid| is true, and |t_url| isn't valid, a string is built +// from the raw TemplateURL string. Use a value of true for |try_url_if_invalid| +// when checking imported URLs as the imported URL may not be valid yet may +// match the host+path of one of the default URLs. This is used to catch the +// case of IE using an invalid OSDD URL for Live Search, yet the host+path +// matches our prepopulate data. IE's URL for Live Search is something like +// 'http://...{Language}...'. As {Language} is not a valid OSDD parameter value +// the TemplateURL is invalid. +static std::string BuildHostPathKey(const TemplateURL* t_url, + bool try_url_if_invalid) { + if (t_url->url()) { + if (try_url_if_invalid && !t_url->url()->IsValid()) + return HostPathKeyForURL(GURL(t_url->url()->url())); + + if (t_url->url()->SupportsReplacement()) { + return HostPathKeyForURL(GURL( + t_url->url()->ReplaceSearchTerms( + *t_url, L"random string", + TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, std::wstring()))); + } + } + return std::string(); +} + +// Builds a set that contains an entry of the host+path for each TemplateURL in +// the TemplateURLModel that has a valid search url. +static void BuildHostPathMap(const TemplateURLModel& model, + HostPathMap* host_path_map) { + std::vector<const TemplateURL*> template_urls = model.GetTemplateURLs(); + for (size_t i = 0; i < template_urls.size(); ++i) { + const std::string host_path = BuildHostPathKey(template_urls[i], false); + if (!host_path.empty()) { + const TemplateURL* existing_turl = (*host_path_map)[host_path]; + if (!existing_turl || + (template_urls[i]->show_in_default_list() && + !existing_turl->show_in_default_list())) { + // If there are multiple TemplateURLs with the same host+path, favor + // those shown in the default list. If there are multiple potential + // defaults, favor the first one, which should be the more commonly used + // one. + (*host_path_map)[host_path] = template_urls[i]; + } + } // else case, TemplateURL doesn't have a search url, doesn't support + // replacement, or doesn't have valid GURL. Ignore it. + } +} + +void ProfileWriter::AddKeywords(const std::vector<TemplateURL*>& template_urls, + int default_keyword_index, + bool unique_on_host_and_path) { + TemplateURLModel* model = profile_->GetTemplateURLModel(); + HostPathMap host_path_map; + if (unique_on_host_and_path) + BuildHostPathMap(*model, &host_path_map); + + for (std::vector<TemplateURL*>::const_iterator i = template_urls.begin(); + i != template_urls.end(); ++i) { + TemplateURL* t_url = *i; + bool default_keyword = + default_keyword_index >= 0 && + (i - template_urls.begin() == default_keyword_index); + + // TemplateURLModel requires keywords to be unique. If there is already a + // TemplateURL with this keyword, don't import it again. + const TemplateURL* turl_with_keyword = + model->GetTemplateURLForKeyword(t_url->keyword()); + if (turl_with_keyword != NULL) { + if (default_keyword) + model->SetDefaultSearchProvider(turl_with_keyword); + delete t_url; + continue; + } + + // For search engines if there is already a keyword with the same + // host+path, we don't import it. This is done to avoid both duplicate + // search providers (such as two Googles, or two Yahoos) as well as making + // sure the search engines we provide aren't replaced by those from the + // imported browser. + if (unique_on_host_and_path && + host_path_map.find( + BuildHostPathKey(t_url, true)) != host_path_map.end()) { + if (default_keyword) { + const TemplateURL* turl_with_host_path = + host_path_map[BuildHostPathKey(t_url, true)]; + if (turl_with_host_path) + model->SetDefaultSearchProvider(turl_with_host_path); + else + NOTREACHED(); // BuildHostPathMap should only insert non-null values. + } + delete t_url; + continue; + } + if (t_url->url() && t_url->url()->IsValid()) { + model->Add(t_url); + if (default_keyword && TemplateURL::SupportsReplacement(t_url)) + model->SetDefaultSearchProvider(t_url); + } else { + // Don't add invalid TemplateURLs to the model. + delete t_url; + } + } +} + +void ProfileWriter::ShowBookmarkBar() { + DCHECK(profile_); + + PrefService* prefs = profile_->GetPrefs(); + // Check whether the bookmark bar is shown in current pref. + if (!prefs->GetBoolean(prefs::kShowBookmarkBar)) { + // Set the pref and notify the notification service. + prefs->SetBoolean(prefs::kShowBookmarkBar, true); + prefs->ScheduleSavePersistentPrefs(); + Source<Profile> source(profile_); + NotificationService::current()->Notify( + NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED, source, + NotificationService::NoDetails()); + } +} + +std::wstring ProfileWriter::GenerateUniqueFolderName( + BookmarkModel* model, + const std::wstring& folder_name) { + // Build a set containing the folder names of the other folder. + std::set<std::wstring> other_folder_names; + const BookmarkNode* other = model->other_node(); + + for (int i = 0, child_count = other->GetChildCount(); i < child_count; ++i) { + const BookmarkNode* node = other->GetChild(i); + if (node->is_folder()) + other_folder_names.insert(node->GetTitle()); + } + + if (other_folder_names.find(folder_name) == other_folder_names.end()) + return folder_name; // Name is unique, use it. + + // Otherwise iterate until we find a unique name. + for (int i = 1; i < 100; ++i) { + std::wstring name = folder_name + StringPrintf(L" (%d)", i); + if (other_folder_names.find(name) == other_folder_names.end()) + return name; + } + + return folder_name; +} + +bool ProfileWriter::DoesBookmarkExist( + BookmarkModel* model, + const BookmarkEntry& entry, + const std::wstring& first_folder_name, + bool import_to_bookmark_bar) { + std::vector<const BookmarkNode*> nodes_with_same_url; + model->GetNodesByURL(entry.url, &nodes_with_same_url); + if (nodes_with_same_url.empty()) + return false; + + for (size_t i = 0; i < nodes_with_same_url.size(); ++i) { + const BookmarkNode* node = nodes_with_same_url[i]; + if (entry.title != node->GetTitle()) + continue; + + // Does the path match? + bool found_match = true; + const BookmarkNode* parent = node->GetParent(); + for (std::vector<std::wstring>::const_reverse_iterator path_it = + entry.path.rbegin(); + (path_it != entry.path.rend()) && found_match; ++path_it) { + const std::wstring& folder_name = + (!import_to_bookmark_bar && path_it + 1 == entry.path.rend()) ? + first_folder_name : *path_it; + if (NULL == parent || *path_it != folder_name) + found_match = false; + else + parent = parent->GetParent(); + } + + // We need a post test to differentiate checks such as + // /home/hello and /hello. The parent should either by the other folder + // node, or the bookmarks bar, depending upon import_to_bookmark_bar and + // entry.in_toolbar. + if (found_match && + ((import_to_bookmark_bar && entry.in_toolbar && parent != + model->GetBookmarkBarNode()) || + ((!import_to_bookmark_bar || !entry.in_toolbar) && + parent != model->other_node()))) { + found_match = false; + } + + if (found_match) + return true; // Found a match with the same url path and title. + } + return false; +} diff --git a/chrome/browser/importer/profile_writer.h b/chrome/browser/importer/profile_writer.h new file mode 100644 index 0000000..38e04d8 --- /dev/null +++ b/chrome/browser/importer/profile_writer.h @@ -0,0 +1,136 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_PROFILE_WRITER_H_ +#define CHROME_BROWSER_IMPORTER_PROFILE_WRITER_H_ + +#include <string> +#include <vector> + +#include "base/ref_counted.h" +#include "base/time.h" +#include "chrome/browser/bookmarks/bookmark_model_observer.h" +#include "googleurl/src/gurl.h" + +class Profile; +class TemplateURL; + +struct IE7PasswordInfo; + +namespace history { +struct ImportedFavIconUsage; +class URLRow; +} + +namespace webkit_glue { +struct PasswordForm; +} + +// ProfileWriter encapsulates profile for writing entries into it. +// This object must be invoked on UI thread. +class ProfileWriter : public base::RefCountedThreadSafe<ProfileWriter> { + public: + // Used to identify how the bookmarks are added. + enum BookmarkOptions { + // Indicates the bookmark should only be added if unique. Uniqueness + // is done by title, url and path. That is, if this is passed to + // AddBookmarkEntry the bookmark is added only if there is no other + // URL with the same url, path and title. + ADD_IF_UNIQUE = 1 << 0, + + // Indicates the bookmarks should be added to the bookmark bar. + IMPORT_TO_BOOKMARK_BAR = 1 << 1, + + // Indicates the bookmark bar is not shown. + BOOKMARK_BAR_DISABLED = 1 << 2 + }; + + explicit ProfileWriter(Profile* profile) : profile_(profile) {} + + // These functions return true if the corresponding model has been loaded. + // If the models haven't been loaded, the importer waits to run until they've + // completed. + virtual bool BookmarkModelIsLoaded() const; + virtual bool TemplateURLModelIsLoaded() const; + + // A bookmark entry. + // TODO(mirandac): remove instances of wstring from ProfileWriter + // (http://crbug.com/43460). + struct BookmarkEntry { + BookmarkEntry() : in_toolbar(false) {} + bool in_toolbar; + GURL url; + std::vector<std::wstring> path; + std::wstring title; + base::Time creation_time; + }; + + // Helper methods for adding data to local stores. + virtual void AddPasswordForm(const webkit_glue::PasswordForm& form); +#if defined(OS_WIN) + virtual void AddIE7PasswordInfo(const IE7PasswordInfo& info); +#endif + virtual void AddHistoryPage(const std::vector<history::URLRow>& page); + virtual void AddHomepage(const GURL& homepage); + // Adds the bookmarks to the BookmarkModel. + // |options| is a bitmask of BookmarkOptions and dictates how and + // which bookmarks are added. If the bitmask contains IMPORT_TO_BOOKMARK_BAR, + // then any entries with a value of true for in_toolbar are added to + // the bookmark bar. If the bitmask does not contain IMPORT_TO_BOOKMARK_BAR + // then the folder name the bookmarks are added to is uniqued based on + // |first_folder_name|. For example, if |first_folder_name| is 'foo' + // and a folder with the name 'foo' already exists in the other + // bookmarks folder, then the folder name 'foo 2' is used. + // If |options| contains ADD_IF_UNIQUE, then the bookmark is added only + // if another bookmarks does not exist with the same title, path and + // url. + virtual void AddBookmarkEntry(const std::vector<BookmarkEntry>& bookmark, + const std::wstring& first_folder_name, + int options); + virtual void AddFavicons( + const std::vector<history::ImportedFavIconUsage>& favicons); + // Add the TemplateURLs in |template_urls| to the local store and make the + // TemplateURL at |default_keyword_index| the default keyword (does not set + // a default keyword if it is -1). The local store becomes the owner of the + // TemplateURLs. Some TemplateURLs in |template_urls| may conflict (same + // keyword or same host name in the URL) with existing TemplateURLs in the + // local store, in which case the existing ones takes precedence and the + // duplicate in |template_urls| are deleted. + // If unique_on_host_and_path a TemplateURL is only added if there is not an + // existing TemplateURL that has a replaceable search url with the same + // host+path combination. + virtual void AddKeywords(const std::vector<TemplateURL*>& template_urls, + int default_keyword_index, + bool unique_on_host_and_path); + + // Shows the bookmarks toolbar. + void ShowBookmarkBar(); + + Profile* profile() const { return profile_; } + + protected: + friend class base::RefCountedThreadSafe<ProfileWriter>; + + virtual ~ProfileWriter() {} + + private: + // Generates a unique folder name. If folder_name is not unique, then this + // repeatedly tests for '|folder_name| + (i)' until a unique name is found. + std::wstring GenerateUniqueFolderName(BookmarkModel* model, + const std::wstring& folder_name); + + // Returns true if a bookmark exists with the same url, title and path + // as |entry|. |first_folder_name| is the name to use for the first + // path entry if |import_to_bookmark_bar| is true. + bool DoesBookmarkExist(BookmarkModel* model, + const BookmarkEntry& entry, + const std::wstring& first_folder_name, + bool import_to_bookmark_bar); + + Profile* const profile_; + + DISALLOW_COPY_AND_ASSIGN(ProfileWriter); +}; + +#endif // CHROME_BROWSER_IMPORTER_PROFILE_WRITER_H_ diff --git a/chrome/browser/importer/safari_importer.h b/chrome/browser/importer/safari_importer.h new file mode 100644 index 0000000..6b686e9 --- /dev/null +++ b/chrome/browser/importer/safari_importer.h @@ -0,0 +1,100 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_IMPORTER_SAFARI_IMPORTER_H_ +#define CHROME_BROWSER_IMPORTER_SAFARI_IMPORTER_H_ + +#include "chrome/browser/importer/importer.h" + +#include <map> +#include <set> +#include <vector> + +#include "base/file_path.h" +#include "base/gtest_prod_util.h" +#include "chrome/common/sqlite_utils.h" +#include "chrome/browser/importer/importer_data_types.h" + +#if __OBJC__ +@class NSDictionary; +@class NSString; +#else +class NSDictionary; +class NSString; +#endif + +// Importer for Safari on OS X. +class SafariImporter : public Importer { + public: + // |library_dir| is the full path to the ~/Library directory, + // We pass it in as a parameter for testing purposes. + explicit SafariImporter(const FilePath& library_dir); + + // Importer methods. + virtual void StartImport(importer::ProfileInfo profile_info, + uint16 items, + ImporterBridge* bridge); + + + // Does this user account have a Safari Profile and if so, what items + // are supported? + // in: library_dir - ~/Library or a standin for testing purposes. + // out: services_supported - the service supported for import. + // returns true if we can import the Safari profile. + static bool CanImport(const FilePath& library_dir, uint16 *services_supported); + private: + FRIEND_TEST_ALL_PREFIXES(SafariImporterTest, BookmarkImport); + FRIEND_TEST_ALL_PREFIXES(SafariImporterTest, FavIconImport); + FRIEND_TEST_ALL_PREFIXES(SafariImporterTest, HistoryImport); + + virtual ~SafariImporter(); + + // Multiple URLs can share the same FavIcon, this is a map + // of URLs -> IconIDs that we load as a temporary step before + // actually loading the icons. + typedef std::map<int64, std::set<GURL> > FaviconMap; + + void ImportBookmarks(); + void ImportPasswords(); + void ImportHistory(); + + // Parse Safari's stored bookmarks. + void ParseBookmarks(std::vector<ProfileWriter::BookmarkEntry>* bookmarks); + + // Function to recursively read Bookmarks out of Safari plist. + // |bookmark_folder| The dictionary containing a folder to parse. + // |parent_path_elements| Path elements up to this point. + // |is_in_toolbar| Is this folder in the toolbar. + // |out_bookmarks| BookMark element array to write into. + void RecursiveReadBookmarksFolder( + NSDictionary* bookmark_folder, + const std::vector<std::wstring>& parent_path_elements, + bool is_in_toolbar, + std::vector<ProfileWriter::BookmarkEntry>* out_bookmarks); + + + // Converts history time stored by Safari as a double serialized as a string, + // to seconds-since-UNIX-Ephoch-format used by Chrome. + double HistoryTimeToEpochTime(NSString* history_time); + + // Parses Safari's history and loads it into the input array. + void ParseHistoryItems(std::vector<history::URLRow>* history_items); + + // Loads the favicon Database file, returns NULL on failure. + sqlite3* OpenFavIconDB(); + + // Loads the urls associated with the favicons into favicon_map; + void ImportFavIconURLs(sqlite3* db, FaviconMap* favicon_map); + + // Loads and reencodes the individual favicons. + void LoadFaviconData(sqlite3* db, + const FaviconMap& favicon_map, + std::vector<history::ImportedFavIconUsage>* favicons); + + FilePath library_dir_; + + DISALLOW_COPY_AND_ASSIGN(SafariImporter); +}; + +#endif // CHROME_BROWSER_IMPORTER_SAFARI_IMPORTER_H_ diff --git a/chrome/browser/importer/safari_importer.mm b/chrome/browser/importer/safari_importer.mm new file mode 100644 index 0000000..9a30073 --- /dev/null +++ b/chrome/browser/importer/safari_importer.mm @@ -0,0 +1,395 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <Cocoa/Cocoa.h> + +#include "chrome/browser/importer/safari_importer.h" + +#include <map> +#include <vector> + +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/scoped_nsobject.h" +#include "base/string16.h" +#include "base/sys_string_conversions.h" +#include "base/time.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/browser/importer/importer_bridge.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "chrome/common/sqlite_utils.h" +#include "chrome/common/url_constants.h" +#include "googleurl/src/gurl.h" +#include "grit/generated_resources.h" +#include "net/base/data_url.h" + +using importer::FAVORITES; +using importer::HISTORY; +using importer::NONE; +using importer::PASSWORDS; +using importer::ProfileInfo; + +namespace { + +// A function like this is used by other importers in order to filter out +// URLS we don't want to import. +// For now it's pretty basic, but I've split it out so it's easy to slot +// in necessary logic for filtering URLS, should we need it. +bool CanImportSafariURL(const GURL& url) { + // The URL is not valid. + if (!url.is_valid()) + return false; + + return true; +} + +} // namespace + +SafariImporter::SafariImporter(const FilePath& library_dir) + : library_dir_(library_dir) { +} + +SafariImporter::~SafariImporter() { +} + +// static +bool SafariImporter::CanImport(const FilePath& library_dir, + uint16 *services_supported) { + DCHECK(services_supported); + *services_supported = importer::NONE; + + // Import features are toggled by the following: + // bookmarks import: existence of ~/Library/Safari/Bookmarks.plist file. + // history import: existence of ~/Library/Safari/History.plist file. + FilePath safari_dir = library_dir.Append("Safari"); + FilePath bookmarks_path = safari_dir.Append("Bookmarks.plist"); + FilePath history_path = safari_dir.Append("History.plist"); + + using file_util::PathExists; + if (PathExists(bookmarks_path)) + *services_supported |= importer::FAVORITES; + if (PathExists(history_path)) + *services_supported |= importer::HISTORY; + + return *services_supported != importer::NONE; +} + +void SafariImporter::StartImport(importer::ProfileInfo profile_info, + uint16 services_supported, + ImporterBridge* bridge) { + bridge_ = bridge; + // The order here is important! + bridge_->NotifyStarted(); + // In keeping with import on other platforms (and for other browsers), we + // don't import the home page (since it may lead to a useless homepage); see + // crbug.com/25603. + if ((services_supported & importer::HISTORY) && !cancelled()) { + bridge_->NotifyItemStarted(importer::HISTORY); + ImportHistory(); + bridge_->NotifyItemEnded(importer::HISTORY); + } + if ((services_supported & importer::FAVORITES) && !cancelled()) { + bridge_->NotifyItemStarted(importer::FAVORITES); + ImportBookmarks(); + bridge_->NotifyItemEnded(importer::FAVORITES); + } + if ((services_supported & importer::PASSWORDS) && !cancelled()) { + bridge_->NotifyItemStarted(importer::PASSWORDS); + ImportPasswords(); + bridge_->NotifyItemEnded(importer::PASSWORDS); + } + bridge_->NotifyEnded(); +} + +void SafariImporter::ImportBookmarks() { + std::vector<ProfileWriter::BookmarkEntry> bookmarks; + ParseBookmarks(&bookmarks); + + // Write bookmarks into profile. + if (!bookmarks.empty() && !cancelled()) { + const std::wstring& first_folder_name = + bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP_FROM_SAFARI); + int options = 0; + if (import_to_bookmark_bar()) + options = ProfileWriter::IMPORT_TO_BOOKMARK_BAR; + bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options); + } + + // Import favicons. + sqlite_utils::scoped_sqlite_db_ptr db(OpenFavIconDB()); + FaviconMap favicon_map; + ImportFavIconURLs(db.get(), &favicon_map); + // Write favicons into profile. + if (!favicon_map.empty() && !cancelled()) { + std::vector<history::ImportedFavIconUsage> favicons; + LoadFaviconData(db.get(), favicon_map, &favicons); + bridge_->SetFavIcons(favicons); + } +} + +sqlite3* SafariImporter::OpenFavIconDB() { + // Construct ~/Library/Safari/WebIcons.db path + NSString* library_dir = [NSString + stringWithUTF8String:library_dir_.value().c_str()]; + NSString* safari_dir = [library_dir + stringByAppendingPathComponent:@"Safari"]; + NSString* favicons_db_path = [safari_dir + stringByAppendingPathComponent:@"WebpageIcons.db"]; + + sqlite3* favicons_db; + const char* safariicons_dbname = [favicons_db_path fileSystemRepresentation]; + if (sqlite3_open(safariicons_dbname, &favicons_db) != SQLITE_OK) + return NULL; + + return favicons_db; +} + +void SafariImporter::ImportFavIconURLs(sqlite3* db, FaviconMap* favicon_map) { + SQLStatement s; + const char* stmt = "SELECT iconID, url FROM PageURL;"; + if (s.prepare(db, stmt) != SQLITE_OK) + return; + + while (s.step() == SQLITE_ROW && !cancelled()) { + int64 icon_id = s.column_int(0); + GURL url = GURL(s.column_string(1)); + (*favicon_map)[icon_id].insert(url); + } +} + +void SafariImporter::LoadFaviconData(sqlite3* db, + const FaviconMap& favicon_map, + std::vector<history::ImportedFavIconUsage>* favicons) { + SQLStatement s; + const char* stmt = "SELECT i.url, d.data " + "FROM IconInfo i JOIN IconData d " + "ON i.iconID = d.iconID " + "WHERE i.iconID = ?;"; + if (s.prepare(db, stmt) != SQLITE_OK) + return; + + for (FaviconMap::const_iterator i = favicon_map.begin(); + i != favicon_map.end(); ++i) { + s.bind_int64(0, i->first); + if (s.step() == SQLITE_ROW) { + history::ImportedFavIconUsage usage; + + usage.favicon_url = GURL(s.column_string(0)); + if (!usage.favicon_url.is_valid()) + continue; // Don't bother importing favicons with invalid URLs. + + std::vector<unsigned char> data; + if (!s.column_blob_as_vector(1, &data) || data.empty()) + continue; // Data definitely invalid. + + if (!ReencodeFavicon(&data[0], data.size(), &usage.png_data)) + continue; // Unable to decode. + + usage.urls = i->second; + favicons->push_back(usage); + } + s.reset(); + } +} + +void SafariImporter::RecursiveReadBookmarksFolder( + NSDictionary* bookmark_folder, + const std::vector<std::wstring>& parent_path_elements, + bool is_in_toolbar, + std::vector<ProfileWriter::BookmarkEntry>* out_bookmarks) { + DCHECK(bookmark_folder); + + NSString* type = [bookmark_folder objectForKey:@"WebBookmarkType"]; + NSString* title = [bookmark_folder objectForKey:@"Title"]; + + // Are we the dictionary that contains all other bookmarks? + // We need to know this so we don't add it to the path. + bool is_top_level_bookmarks_container = [bookmark_folder + objectForKey:@"WebBookmarkFileVersion"] != nil; + + // We're expecting a list of bookmarks here, if that isn't what we got, fail. + if (!is_top_level_bookmarks_container) { + // Top level containers sometimes don't have title attributes. + if (![type isEqualToString:@"WebBookmarkTypeList"] || !title) { + DCHECK(false) << "Type =(" + << (type ? base::SysNSStringToUTF8(type) : "Null Type") + << ") Title=(" << (title ? base::SysNSStringToUTF8(title) : "Null title") + << ")"; + return; + } + } + + std::vector<std::wstring> path_elements(parent_path_elements); + // Is this the toolbar folder? + if ([title isEqualToString:@"BookmarksBar"]) { + // Be defensive, the toolbar items shouldn't have a prepended path. + path_elements.clear(); + is_in_toolbar = true; + } else if ([title isEqualToString:@"BookmarksMenu"]) { + // top level container for normal bookmarks. + path_elements.clear(); + } else if (!is_top_level_bookmarks_container) { + if (title) + path_elements.push_back(base::SysNSStringToWide(title)); + } + + NSArray* elements = [bookmark_folder objectForKey:@"Children"]; + // TODO(jeremy) Does Chrome support importing empty folders? + if (!elements) + return; + + // Iterate over individual bookmarks. + for (NSDictionary* bookmark in elements) { + NSString* type = [bookmark objectForKey:@"WebBookmarkType"]; + if (!type) + continue; + + // If this is a folder, recurse. + if ([type isEqualToString:@"WebBookmarkTypeList"]) { + RecursiveReadBookmarksFolder(bookmark, + path_elements, + is_in_toolbar, + out_bookmarks); + } + + // If we didn't see a bookmark folder, then we're expecting a bookmark + // item, if that's not what we got then ignore it. + if (![type isEqualToString:@"WebBookmarkTypeLeaf"]) + continue; + + NSString* url = [bookmark objectForKey:@"URLString"]; + NSString* title = [[bookmark objectForKey:@"URIDictionary"] + objectForKey:@"title"]; + + if (!url || !title) + continue; + + // Output Bookmark. + ProfileWriter::BookmarkEntry entry; + // Safari doesn't specify a creation time for the bookmark. + entry.creation_time = base::Time::Now(); + entry.title = base::SysNSStringToWide(title); + entry.url = GURL(base::SysNSStringToUTF8(url)); + entry.path = path_elements; + entry.in_toolbar = is_in_toolbar; + + out_bookmarks->push_back(entry); + } +} + +void SafariImporter::ParseBookmarks( + std::vector<ProfileWriter::BookmarkEntry>* bookmarks) { + DCHECK(bookmarks); + + // Construct ~/Library/Safari/Bookmarks.plist path + NSString* library_dir = [NSString + stringWithUTF8String:library_dir_.value().c_str()]; + NSString* safari_dir = [library_dir + stringByAppendingPathComponent:@"Safari"]; + NSString* bookmarks_plist = [safari_dir + stringByAppendingPathComponent:@"Bookmarks.plist"]; + + // Load the plist file. + NSDictionary* bookmarks_dict = [NSDictionary + dictionaryWithContentsOfFile:bookmarks_plist]; + if (!bookmarks_dict) + return; + + // Recursively read in bookmarks. + std::vector<std::wstring> parent_path_elements; + RecursiveReadBookmarksFolder(bookmarks_dict, parent_path_elements, false, + bookmarks); +} + +void SafariImporter::ImportPasswords() { + // Safari stores it's passwords in the Keychain, same as us so we don't need + // to import them. + // Note: that we don't automatically pick them up, there is some logic around + // the user needing to explicitly input his username in a page and blurring + // the field before we pick it up, but the details of that are beyond the + // scope of this comment. +} + +void SafariImporter::ImportHistory() { + std::vector<history::URLRow> rows; + ParseHistoryItems(&rows); + + if (!rows.empty() && !cancelled()) { + bridge_->SetHistoryItems(rows); + } +} + +double SafariImporter::HistoryTimeToEpochTime(NSString* history_time) { + DCHECK(history_time); + // Add Difference between Unix epoch and CFAbsoluteTime epoch in seconds. + // Unix epoch is 1970-01-01 00:00:00.0 UTC, + // CF epoch is 2001-01-01 00:00:00.0 UTC. + return CFStringGetDoubleValue(reinterpret_cast<CFStringRef>(history_time)) + + kCFAbsoluteTimeIntervalSince1970; +} + +void SafariImporter::ParseHistoryItems( + std::vector<history::URLRow>* history_items) { + DCHECK(history_items); + + // Construct ~/Library/Safari/History.plist path + NSString* library_dir = [NSString + stringWithUTF8String:library_dir_.value().c_str()]; + NSString* safari_dir = [library_dir + stringByAppendingPathComponent:@"Safari"]; + NSString* history_plist = [safari_dir + stringByAppendingPathComponent:@"History.plist"]; + + // Load the plist file. + NSDictionary* history_dict = [NSDictionary + dictionaryWithContentsOfFile:history_plist]; + if (!history_dict) + return; + + NSArray* safari_history_items = [history_dict + objectForKey:@"WebHistoryDates"]; + + for (NSDictionary* history_item in safari_history_items) { + using base::SysNSStringToUTF8; + using base::SysNSStringToUTF16; + NSString* url_ns = [history_item objectForKey:@""]; + if (!url_ns) + continue; + + GURL url(SysNSStringToUTF8(url_ns)); + + if (!CanImportSafariURL(url)) + continue; + + history::URLRow row(url); + NSString* title_ns = [history_item objectForKey:@"title"]; + + // Sometimes items don't have a title, in which case we just substitue + // the url. + if (!title_ns) + title_ns = url_ns; + + row.set_title(SysNSStringToUTF16(title_ns)); + int visit_count = [[history_item objectForKey:@"visitCount"] + intValue]; + row.set_visit_count(visit_count); + // Include imported URLs in autocompletion - don't hide them. + row.set_hidden(0); + // Item was never typed before in the omnibox. + row.set_typed_count(0); + + NSString* last_visit_str = [history_item objectForKey:@"lastVisitedDate"]; + // The last visit time should always be in the history item, but if not + /// just continue without this item. + DCHECK(last_visit_str); + if (!last_visit_str) + continue; + + // Convert Safari's last visit time to Unix Epoch time. + double seconds_since_unix_epoch = HistoryTimeToEpochTime(last_visit_str); + row.set_last_visit(base::Time::FromDoubleT(seconds_since_unix_epoch)); + + history_items->push_back(row); + } +} diff --git a/chrome/browser/importer/safari_importer_unittest.mm b/chrome/browser/importer/safari_importer_unittest.mm new file mode 100644 index 0000000..081c26e --- /dev/null +++ b/chrome/browser/importer/safari_importer_unittest.mm @@ -0,0 +1,171 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/safari_importer.h" + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/test/file_test_utils.h" +#include "chrome/browser/importer/importer_bridge.h" +#include "testing/platform_test.h" + +using importer::COOKIES; +using importer::FAVORITES; +using importer::HISTORY; +using importer::HOME_PAGE; +using importer::NONE; +using importer::PASSWORDS; +using importer::SEARCH_ENGINES; + +// In order to test the Safari import functionality effectively, we store a +// simulated Library directory containing dummy data files in the same +// structure as ~/Library in the Chrome test data directory. +// This function returns the path to that directory. +FilePath GetTestSafariLibraryPath() { + FilePath test_dir; + PathService::Get(chrome::DIR_TEST_DATA, &test_dir); + + // Our simulated ~/Library directory + test_dir = test_dir.AppendASCII("safari_import"); + return test_dir; +} + +class SafariImporterTest : public PlatformTest { + public: + SafariImporter* GetSafariImporter() { + FilePath test_library_dir = GetTestSafariLibraryPath(); + CHECK(file_util::PathExists(test_library_dir)) << + "Missing test data directory"; + + return new SafariImporter(test_library_dir); + } +}; + +TEST_F(SafariImporterTest, HistoryImport) { + scoped_refptr<SafariImporter> importer(GetSafariImporter()); + + std::vector<history::URLRow> history_items; + importer->ParseHistoryItems(&history_items); + + // Should be 2 history items. + ASSERT_EQ(history_items.size(), 2U); + + history::URLRow& it1 = history_items[0]; + EXPECT_EQ(it1.url(), GURL("http://www.firsthistoryitem.com/")); + EXPECT_EQ(it1.title(), UTF8ToUTF16("First History Item Title")); + EXPECT_EQ(it1.visit_count(), 1); + EXPECT_EQ(it1.hidden(), 0); + EXPECT_EQ(it1.typed_count(), 0); + EXPECT_EQ(it1.last_visit().ToDoubleT(), + importer->HistoryTimeToEpochTime(@"270598264.4")); + + history::URLRow& it2 = history_items[1]; + std::string second_item_title("http://www.secondhistoryitem.com/"); + EXPECT_EQ(it2.url(), GURL(second_item_title)); + // The second item lacks a title so we expect the URL to be substituted. + EXPECT_EQ(UTF16ToUTF8(it2.title()), second_item_title.c_str()); + EXPECT_EQ(it2.visit_count(), 55); + EXPECT_EQ(it2.hidden(), 0); + EXPECT_EQ(it2.typed_count(), 0); + EXPECT_EQ(it2.last_visit().ToDoubleT(), + importer->HistoryTimeToEpochTime(@"270598231.4")); +} + +TEST_F(SafariImporterTest, BookmarkImport) { + // Expected results + const struct { + bool in_toolbar; + GURL url; + // If the path array for an element is entry set this to true. + bool path_is_empty; + // We ony support one level of nesting in paths, this makes testing a little + // easier. + std::wstring path; + std::wstring title; + } kImportedBookmarksData[] = { + {true, GURL("http://www.apple.com/"), true, L"", L"Apple"}, + {true, GURL("http://www.yahoo.com/"), true, L"", L"Yahoo!"}, + {true, GURL("http://www.cnn.com/"), false, L"News", L"CNN"}, + {true, GURL("http://www.nytimes.com/"), false, L"News", + L"The New York Times"}, + {false, GURL("http://www.reddit.com/"), true, L"", + L"reddit.com: what's new online!"}, + }; + + scoped_refptr<SafariImporter> importer(GetSafariImporter()); + std::vector<ProfileWriter::BookmarkEntry> bookmarks; + importer->ParseBookmarks(&bookmarks); + size_t num_bookmarks = bookmarks.size(); + EXPECT_EQ(num_bookmarks, ARRAYSIZE_UNSAFE(kImportedBookmarksData)); + + for (size_t i = 0; i < num_bookmarks; ++i) { + ProfileWriter::BookmarkEntry& entry = bookmarks[i]; + EXPECT_EQ(entry.in_toolbar, kImportedBookmarksData[i].in_toolbar); + EXPECT_EQ(entry.url, kImportedBookmarksData[i].url); + if (kImportedBookmarksData[i].path_is_empty) { + EXPECT_EQ(entry.path.size(), 0U); + } else { + EXPECT_EQ(entry.path.size(), 1U); + EXPECT_EQ(entry.path[0], kImportedBookmarksData[i].path); + EXPECT_EQ(entry.title, kImportedBookmarksData[i].title); + } + } +} + +TEST_F(SafariImporterTest, FavIconImport) { + scoped_refptr<SafariImporter> importer(GetSafariImporter()); + sqlite_utils::scoped_sqlite_db_ptr db(importer->OpenFavIconDB()); + ASSERT_TRUE(db.get() != NULL); + + SafariImporter::FaviconMap favicon_map; + importer->ImportFavIconURLs(db.get(), &favicon_map); + + std::vector<history::ImportedFavIconUsage> favicons; + importer->LoadFaviconData(db.get(), favicon_map, &favicons); + + size_t num_favicons = favicons.size(); + ASSERT_EQ(num_favicons, 2U); + + history::ImportedFavIconUsage &fav0 = favicons[0]; + EXPECT_EQ("http://s.ytimg.com/yt/favicon-vfl86270.ico", + fav0.favicon_url.spec()); + EXPECT_GT(fav0.png_data.size(), 0U); + EXPECT_EQ(fav0.urls.size(), 1U); + EXPECT_TRUE(fav0.urls.find(GURL("http://www.youtube.com/")) + != fav0.urls.end()); + + history::ImportedFavIconUsage &fav1 = favicons[1]; + EXPECT_EQ("http://www.opensearch.org/favicon.ico", + fav1.favicon_url.spec()); + EXPECT_GT(fav1.png_data.size(), 0U); + EXPECT_EQ(fav1.urls.size(), 2U); + EXPECT_TRUE(fav1.urls.find(GURL("http://www.opensearch.org/Home")) + != fav1.urls.end()); + + EXPECT_TRUE(fav1.urls.find( + GURL("http://www.opensearch.org/Special:Search?search=lalala&go=Search")) + != fav1.urls.end()); +} + +TEST_F(SafariImporterTest, CanImport) { + uint16 items = NONE; + EXPECT_TRUE(SafariImporter::CanImport(GetTestSafariLibraryPath(), &items)); + EXPECT_EQ(items, HISTORY | FAVORITES); + EXPECT_EQ(items & COOKIES, NONE); + EXPECT_EQ(items & PASSWORDS, NONE); + EXPECT_EQ(items & SEARCH_ENGINES, NONE); + EXPECT_EQ(items & HOME_PAGE, NONE); + + // Check that we don't import anything from a bogus library directory. + FilePath fake_library_dir; + file_util::CreateNewTempDirectory("FakeSafariLibrary", &fake_library_dir); + FileAutoDeleter deleter(fake_library_dir); + EXPECT_FALSE(SafariImporter::CanImport(fake_library_dir, &items)); +} diff --git a/chrome/browser/importer/toolbar_importer.cc b/chrome/browser/importer/toolbar_importer.cc new file mode 100644 index 0000000..5b0a93c --- /dev/null +++ b/chrome/browser/importer/toolbar_importer.cc @@ -0,0 +1,597 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/toolbar_importer.h" + +#include <limits> + +#include "base/rand_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/first_run.h" +#include "chrome/browser/importer/importer_bridge.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "chrome/browser/profile.h" +#include "chrome/common/libxml_utils.h" +#include "chrome/common/net/url_request_context_getter.h" +#include "grit/generated_resources.h" +#include "net/base/cookie_monster.h" +#include "net/base/data_url.h" +#include "net/url_request/url_request_context.h" + +using importer::FAVORITES; +using importer::NONE; +using importer::ProfileInfo; + +// +// ToolbarImporterUtils +// +static const char* kGoogleDomainUrl = "http://.google.com/"; +static const wchar_t kSplitStringToken = L';'; +static const char* kGoogleDomainSecureCookieId = "SID="; + +bool toolbar_importer_utils::IsGoogleGAIACookieInstalled() { + net::CookieStore* store = + Profile::GetDefaultRequestContext()->GetCookieStore(); + GURL url(kGoogleDomainUrl); + net::CookieOptions options; + options.set_include_httponly(); // The SID cookie might be httponly. + std::string cookies = store->GetCookiesWithOptions(url, options); + std::vector<std::string> cookie_list; + SplitString(cookies, kSplitStringToken, &cookie_list); + for (std::vector<std::string>::iterator current = cookie_list.begin(); + current != cookie_list.end(); + ++current) { + size_t position = (*current).find(kGoogleDomainSecureCookieId); + if (0 == position) + return true; + } + return false; +} + +// +// Toolbar5Importer +// +const char Toolbar5Importer::kXmlApiReplyXmlTag[] = "xml_api_reply"; +const char Toolbar5Importer::kBookmarksXmlTag[] = "bookmarks"; +const char Toolbar5Importer::kBookmarkXmlTag[] = "bookmark"; +const char Toolbar5Importer::kTitleXmlTag[] = "title"; +const char Toolbar5Importer::kUrlXmlTag[] = "url"; +const char Toolbar5Importer::kTimestampXmlTag[] = "timestamp"; +const char Toolbar5Importer::kLabelsXmlTag[] = "labels"; +const char Toolbar5Importer::kLabelsXmlCloseTag[] = "/labels"; +const char Toolbar5Importer::kLabelXmlTag[] = "label"; +const char Toolbar5Importer::kAttributesXmlTag[] = "attributes"; + +const char Toolbar5Importer::kRandomNumberToken[] = "{random_number}"; +const char Toolbar5Importer::kAuthorizationToken[] = "{auth_token}"; +const char Toolbar5Importer::kAuthorizationTokenPrefix[] = "/*"; +const char Toolbar5Importer::kAuthorizationTokenSuffix[] = "*/"; +const char Toolbar5Importer::kMaxNumToken[] = "{max_num}"; +const char Toolbar5Importer::kMaxTimestampToken[] = "{max_timestamp}"; + +const char Toolbar5Importer::kT5AuthorizationTokenUrl[] = + "http://www.google.com/notebook/token?zx={random_number}"; +const char Toolbar5Importer::kT5FrontEndUrlTemplate[] = + "http://www.google.com/notebook/toolbar?cmd=list&tok={auth_token}&" + "num={max_num}&min={max_timestamp}&all=0&zx={random_number}"; + +// Importer methods. + +// The constructor should set the initial state to NOT_USED. +Toolbar5Importer::Toolbar5Importer() + : state_(NOT_USED), + items_to_import_(importer::NONE), + token_fetcher_(NULL), + data_fetcher_(NULL) { +} + +// The destructor insures that the fetchers are currently not being used, as +// their thread-safe implementation requires that they are cancelled from the +// thread in which they were constructed. +Toolbar5Importer::~Toolbar5Importer() { + DCHECK(!token_fetcher_); + DCHECK(!data_fetcher_); +} + +void Toolbar5Importer::StartImport(importer::ProfileInfo profile_info, + uint16 items, + ImporterBridge* bridge) { + DCHECK(bridge); + + bridge_ = bridge; + items_to_import_ = items; + state_ = INITIALIZED; + + bridge_->NotifyStarted(); + ContinueImport(); +} + +// The public cancel method serves two functions, as a callback from the UI +// as well as an internal callback in case of cancel. An internal callback +// is required since the URLFetcher must be destroyed from the thread it was +// created. +void Toolbar5Importer::Cancel() { + // In the case when the thread is not importing messages we are to + // cancel as soon as possible. + Importer::Cancel(); + + // If we are conducting network operations, post a message to the importer + // thread for synchronization. + if (ChromeThread::CurrentlyOn(ChromeThread::UI)) { + EndImport(); + } else { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &Toolbar5Importer::Cancel)); + } +} + +void Toolbar5Importer::OnURLFetchComplete( + const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data) { + if (cancelled()) { + EndImport(); + return; + } + + if (200 != response_code) { // HTTP/Ok + // Cancelling here will update the UI and bypass the rest of bookmark + // import. + EndImportBookmarks(); + return; + } + + switch (state_) { + case GET_AUTHORIZATION_TOKEN: + GetBookmarkDataFromServer(data); + break; + case GET_BOOKMARKS: + GetBookmarksFromServerDataResponse(data); + break; + default: + NOTREACHED() << "Invalid state."; + EndImportBookmarks(); + break; + } +} + +void Toolbar5Importer::ContinueImport() { + DCHECK((items_to_import_ == importer::FAVORITES) || + (items_to_import_ == importer::NONE)) << + "The items requested are not supported"; + + // The order here is important. Each Begin... will clear the flag + // of its item before its task finishes and re-enters this method. + if (importer::NONE == items_to_import_) { + EndImport(); + return; + } + if ((items_to_import_ & importer::FAVORITES) && !cancelled()) { + items_to_import_ &= ~importer::FAVORITES; + BeginImportBookmarks(); + return; + } + // TODO(brg): Import history, autocomplete, other toolbar information + // in a future release. + + // This code should not be reached, but gracefully handles the possibility + // that StartImport was called with unsupported items_to_import. + if (!cancelled()) + EndImport(); +} + +void Toolbar5Importer::EndImport() { + if (state_ != DONE) { + state_ = DONE; + // By spec the fetchers must be destroyed within the same + // thread they are created. The importer is destroyed in the ui_thread + // so when we complete in the file_thread we destroy them first. + if (NULL != token_fetcher_) { + delete token_fetcher_; + token_fetcher_ = NULL; + } + + if (NULL != data_fetcher_) { + delete data_fetcher_; + data_fetcher_ = NULL; + } + + bridge_->NotifyEnded(); + } +} + +void Toolbar5Importer::BeginImportBookmarks() { + bridge_->NotifyItemStarted(importer::FAVORITES); + GetAuthenticationFromServer(); +} + +void Toolbar5Importer::EndImportBookmarks() { + bridge_->NotifyItemEnded(importer::FAVORITES); + ContinueImport(); +} + + +// Notebook front-end connection manager implementation follows. +void Toolbar5Importer::GetAuthenticationFromServer() { + if (cancelled()) { + EndImport(); + return; + } + + // Authentication is a token string retrieved from the authentication server + // To access it we call the url below with a random number replacing the + // value in the string. + state_ = GET_AUTHORIZATION_TOKEN; + + // Random number construction. + int random = base::RandInt(0, std::numeric_limits<int>::max()); + std::string random_string = UintToString(random); + + // Retrieve authorization token from the network. + std::string url_string(kT5AuthorizationTokenUrl); + url_string.replace(url_string.find(kRandomNumberToken), + arraysize(kRandomNumberToken) - 1, + random_string); + GURL url(url_string); + + token_fetcher_ = new URLFetcher(url, URLFetcher::GET, this); + token_fetcher_->set_request_context(Profile::GetDefaultRequestContext()); + token_fetcher_->Start(); +} + +void Toolbar5Importer::GetBookmarkDataFromServer(const std::string& response) { + if (cancelled()) { + EndImport(); + return; + } + + state_ = GET_BOOKMARKS; + + // Parse and verify the authorization token from the response. + std::string token; + if (!ParseAuthenticationTokenResponse(response, &token)) { + EndImportBookmarks(); + return; + } + + // Build the Toolbar FE connection string, and call the server for + // the xml blob. We must tag the connection string with a random number. + std::string conn_string = kT5FrontEndUrlTemplate; + int random = base::RandInt(0, std::numeric_limits<int>::max()); + std::string random_string = UintToString(random); + conn_string.replace(conn_string.find(kRandomNumberToken), + arraysize(kRandomNumberToken) - 1, + random_string); + conn_string.replace(conn_string.find(kAuthorizationToken), + arraysize(kAuthorizationToken) - 1, + token); + GURL url(conn_string); + + data_fetcher_ = new URLFetcher(url, URLFetcher::GET, this); + data_fetcher_->set_request_context(Profile::GetDefaultRequestContext()); + data_fetcher_->Start(); +} + +void Toolbar5Importer::GetBookmarksFromServerDataResponse( + const std::string& response) { + if (cancelled()) { + EndImport(); + return; + } + + state_ = PARSE_BOOKMARKS; + + XmlReader reader; + if (reader.Load(response) && !cancelled()) { + // Construct Bookmarks + std::vector<ProfileWriter::BookmarkEntry> bookmarks; + if (ParseBookmarksFromReader(&reader, &bookmarks, + WideToUTF16(bridge_->GetLocalizedString( + IDS_BOOKMARK_GROUP_FROM_GOOGLE_TOOLBAR)))) + AddBookmarksToChrome(bookmarks); + } + EndImportBookmarks(); +} + +bool Toolbar5Importer::ParseAuthenticationTokenResponse( + const std::string& response, + std::string* token) { + DCHECK(token); + + *token = response; + size_t position = token->find(kAuthorizationTokenPrefix); + if (0 != position) + return false; + token->replace(position, arraysize(kAuthorizationTokenPrefix) - 1, ""); + + position = token->find(kAuthorizationTokenSuffix); + if (token->size() != (position + (arraysize(kAuthorizationTokenSuffix) - 1))) + return false; + token->replace(position, arraysize(kAuthorizationTokenSuffix) - 1, ""); + + return true; +} + +// Parsing +bool Toolbar5Importer::ParseBookmarksFromReader( + XmlReader* reader, + std::vector<ProfileWriter::BookmarkEntry>* bookmarks, + const string16& bookmark_group_string) { + DCHECK(reader); + DCHECK(bookmarks); + + // The XML blob returned from the server is described in the + // Toolbar-Notebook/Bookmarks Protocol document located at + // https://docs.google.com/a/google.com/Doc?docid=cgt3m7dr_24djt62m&hl=en + // We are searching for the section with structure + // <bookmarks><bookmark>...</bookmark><bookmark>...</bookmark></bookmarks> + + // Locate the |bookmarks| blob. + if (!reader->SkipToElement()) + return false; + + if (!LocateNextTagByName(reader, kBookmarksXmlTag)) + return false; + + // Parse each |bookmark| blob + while (LocateNextTagWithStopByName(reader, kBookmarkXmlTag, + kBookmarksXmlTag)) { + ProfileWriter::BookmarkEntry bookmark_entry; + std::vector<BookmarkFolderType> folders; + if (ExtractBookmarkInformation(reader, &bookmark_entry, &folders, + bookmark_group_string)) { + // For each folder we create a new bookmark entry. Duplicates will + // be detected when we attempt to create the bookmark in the profile. + for (std::vector<BookmarkFolderType>::iterator folder = folders.begin(); + folder != folders.end(); + ++folder) { + bookmark_entry.path = *folder; + bookmarks->push_back(bookmark_entry); + } + } + } + + if (0 == bookmarks->size()) + return false; + + return true; +} + +bool Toolbar5Importer::LocateNextOpenTag(XmlReader* reader) { + DCHECK(reader); + + while (!reader->SkipToElement()) { + if (!reader->Read()) + return false; + } + return true; +} + +bool Toolbar5Importer::LocateNextTagByName(XmlReader* reader, + const std::string& tag) { + DCHECK(reader); + + // Locate the |tag| blob. + while (tag != reader->NodeName()) { + if (!reader->Read() || !LocateNextOpenTag(reader)) + return false; + } + return true; +} + +bool Toolbar5Importer::LocateNextTagWithStopByName(XmlReader* reader, + const std::string& tag, + const std::string& stop) { + DCHECK(reader); + + DCHECK_NE(tag, stop); + // Locate the |tag| blob. + while (tag != reader->NodeName()) { + // Move to the next open tag. + if (!reader->Read() || !LocateNextOpenTag(reader)) + return false; + // If we encounter the stop word return false. + if (stop == reader->NodeName()) + return false; + } + return true; +} + +bool Toolbar5Importer::ExtractBookmarkInformation( + XmlReader* reader, + ProfileWriter::BookmarkEntry* bookmark_entry, + std::vector<BookmarkFolderType>* bookmark_folders, + const string16& bookmark_group_string) { + DCHECK(reader); + DCHECK(bookmark_entry); + DCHECK(bookmark_folders); + + // The following is a typical bookmark entry. + // The reader should be pointing to the <title> tag at the moment. + // + // <bookmark> + // <title>MyTitle</title> + // <url>http://www.sohu.com/</url> + // <timestamp>1153328691085181</timestamp> + // <id>N123nasdf239</id> + // <notebook_id>Bxxxxxxx</notebook_id> (for bookmarks, a special id is used) + // <section_id>Sxxxxxx</section_id> + // <has_highlight>0</has_highlight> + // <labels> + // <label>China</label> + // <label>^k</label> (if this special label is present, the note is deleted) + // </labels> + // <attributes> + // <attribute> + // <name>favicon_url</name> + // <value>http://www.sohu.com/favicon.ico</value> + // </attribute> + // <attribute> + // <name>favicon_timestamp</name> + // <value>1153328653</value> + // </attribute> + // <attribute> + // <name>notebook_name</name> + // <value>My notebook 0</value> + // </attribute> + // <attribute> + // <name>section_name</name> + // <value>My section 0</value> + // </attribute> + // </attributes> + // </bookmark> + // + // We parse the blob in order, title->url->timestamp etc. Any failure + // causes us to skip this bookmark. + + if (!ExtractTitleFromXmlReader(reader, bookmark_entry)) + return false; + if (!ExtractUrlFromXmlReader(reader, bookmark_entry)) + return false; + if (!ExtractTimeFromXmlReader(reader, bookmark_entry)) + return false; + if (!ExtractFoldersFromXmlReader(reader, bookmark_folders, + bookmark_group_string)) + return false; + + return true; +} + +bool Toolbar5Importer::ExtractNamedValueFromXmlReader(XmlReader* reader, + const std::string& name, + std::string* buffer) { + DCHECK(reader); + DCHECK(buffer); + + if (name != reader->NodeName()) + return false; + if (!reader->ReadElementContent(buffer)) + return false; + return true; +} + +bool Toolbar5Importer::ExtractTitleFromXmlReader( + XmlReader* reader, + ProfileWriter::BookmarkEntry* entry) { + DCHECK(reader); + DCHECK(entry); + + if (!LocateNextTagWithStopByName(reader, kTitleXmlTag, kUrlXmlTag)) + return false; + std::string buffer; + if (!ExtractNamedValueFromXmlReader(reader, kTitleXmlTag, &buffer)) { + return false; + } + entry->title = UTF8ToWide(buffer); + return true; +} + +bool Toolbar5Importer::ExtractUrlFromXmlReader( + XmlReader* reader, + ProfileWriter::BookmarkEntry* entry) { + DCHECK(reader); + DCHECK(entry); + + if (!LocateNextTagWithStopByName(reader, kUrlXmlTag, kTimestampXmlTag)) + return false; + std::string buffer; + if (!ExtractNamedValueFromXmlReader(reader, kUrlXmlTag, &buffer)) { + return false; + } + entry->url = GURL(buffer); + return true; +} + +bool Toolbar5Importer::ExtractTimeFromXmlReader( + XmlReader* reader, + ProfileWriter::BookmarkEntry* entry) { + DCHECK(reader); + DCHECK(entry); + if (!LocateNextTagWithStopByName(reader, kTimestampXmlTag, kLabelsXmlTag)) + return false; + std::string buffer; + if (!ExtractNamedValueFromXmlReader(reader, kTimestampXmlTag, &buffer)) { + return false; + } + int64 timestamp; + if (!StringToInt64(buffer, ×tamp)) { + return false; + } + entry->creation_time = base::Time::FromTimeT(timestamp); + return true; +} + +bool Toolbar5Importer::ExtractFoldersFromXmlReader( + XmlReader* reader, + std::vector<BookmarkFolderType>* bookmark_folders, + const string16& bookmark_group_string) { + DCHECK(reader); + DCHECK(bookmark_folders); + + // Read in the labels for this bookmark from the xml. There may be many + // labels for any one bookmark. + if (!LocateNextTagWithStopByName(reader, kLabelsXmlTag, kAttributesXmlTag)) + return false; + + // It is within scope to have an empty labels section, so we do not + // return false if the labels are empty. + if (!reader->Read() || !LocateNextOpenTag(reader)) + return false; + + std::vector<std::wstring> label_vector; + while (kLabelXmlTag == reader->NodeName()) { + std::string label_buffer; + if (!reader->ReadElementContent(&label_buffer)) { + label_buffer = ""; + } + label_vector.push_back(UTF8ToWide(label_buffer)); + LocateNextOpenTag(reader); + } + + if (0 == label_vector.size()) { + if (!FirstRun::IsChromeFirstRun()) { + bookmark_folders->resize(1); + (*bookmark_folders)[0].push_back(UTF16ToWide(bookmark_group_string)); + } + return true; + } + + // We will be making one bookmark folder for each label. + bookmark_folders->resize(label_vector.size()); + + for (size_t index = 0; index < label_vector.size(); ++index) { + // If this is the first run then we place favorites with no labels + // in the title bar. Else they are placed in the "Google Toolbar" folder. + if (!FirstRun::IsChromeFirstRun() || !label_vector[index].empty()) { + (*bookmark_folders)[index].push_back(UTF16ToWide(bookmark_group_string)); + } + + // If the label and is in the form "xxx:yyy:zzz" this was created from an + // IE or Firefox folder. We undo the label creation and recreate the + // correct folder. + std::vector<std::wstring> folder_names; + SplitString(label_vector[index], L':', &folder_names); + (*bookmark_folders)[index].insert((*bookmark_folders)[index].end(), + folder_names.begin(), folder_names.end()); + } + + return true; +} + +// Bookmark creation +void Toolbar5Importer::AddBookmarksToChrome( + const std::vector<ProfileWriter::BookmarkEntry>& bookmarks) { + if (!bookmarks.empty() && !cancelled()) { + const std::wstring& first_folder_name = + bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP_FROM_GOOGLE_TOOLBAR); + int options = ProfileWriter::ADD_IF_UNIQUE | + (import_to_bookmark_bar() ? ProfileWriter::IMPORT_TO_BOOKMARK_BAR : 0); + bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options); + } +} diff --git a/chrome/browser/importer/toolbar_importer.h b/chrome/browser/importer/toolbar_importer.h new file mode 100644 index 0000000..0f31832 --- /dev/null +++ b/chrome/browser/importer/toolbar_importer.h @@ -0,0 +1,172 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// The functionality provided here allows the user to import their bookmarks +// (favorites) from Google Toolbar. + +#ifndef CHROME_BROWSER_IMPORTER_TOOLBAR_IMPORTER_H_ +#define CHROME_BROWSER_IMPORTER_TOOLBAR_IMPORTER_H_ + +#include <string> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/string16.h" +#include "chrome/browser/importer/importer.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "chrome/common/net/url_fetcher.h" + +class ImporterBridge; +class XmlReader; + +// Currently the only configuration information we need is to check whether or +// not the user currently has their GAIA cookie. This is done by the function +// exposed through the ToolbarImportUtils namespace. +namespace toolbar_importer_utils { +bool IsGoogleGAIACookieInstalled(); +} // namespace toolbar_importer_utils + +// Toolbar5Importer is a class which exposes the functionality needed to +// communicate with the Google Toolbar v5 front-end, negotiate the download of +// Toolbar bookmarks, parse them, and install them on the client. +// Toolbar5Importer should not have StartImport called more than once. Futher +// if StartImport is called, then the class must not be destroyed until it +// has either completed or Toolbar5Importer->Cancel() has been called. +class Toolbar5Importer : public URLFetcher::Delegate, public Importer { + public: + Toolbar5Importer(); + + // Importer view calls this method to begin the process. The items parameter + // should only either be NONE or FAVORITES, since as of right now these are + // the only items this importer supports. This method provides implementation + // of Importer::StartImport. + virtual void StartImport(importer::ProfileInfo profile_info, + uint16 items, + ImporterBridge* bridge); + + // Importer view call this method when the user clicks the cancel button + // in the ImporterView UI. We need to post a message to our loop + // to cancel network retrieval. + virtual void Cancel(); + + // URLFetcher::Delegate method called back from the URLFetcher object. + virtual void OnURLFetchComplete(const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + + private: + FRIEND_TEST_ALL_PREFIXES(Toolbar5ImporterTest, BookmarkParse); + + virtual ~Toolbar5Importer(); + + // Internal states of the toolbar importer. + enum InternalStateEnum { + NOT_USED = -1, + INITIALIZED, + GET_AUTHORIZATION_TOKEN, + GET_BOOKMARKS, + PARSE_BOOKMARKS, + DONE + }; + + typedef std::vector<std::wstring> BookmarkFolderType; + + // URLs for connecting to the toolbar front end are defined below. + static const char kT5AuthorizationTokenUrl[]; + static const char kT5FrontEndUrlTemplate[]; + + // Token replacement tags are defined below. + static const char kRandomNumberToken[]; + static const char kAuthorizationToken[]; + static const char kAuthorizationTokenPrefix[]; + static const char kAuthorizationTokenSuffix[]; + static const char kMaxNumToken[]; + static const char kMaxTimestampToken[]; + + // XML tag names are defined below. + static const char kXmlApiReplyXmlTag[]; + static const char kBookmarksXmlTag[]; + static const char kBookmarkXmlTag[]; + static const char kTitleXmlTag[]; + static const char kUrlXmlTag[]; + static const char kTimestampXmlTag[]; + static const char kLabelsXmlTag[]; + static const char kLabelsXmlCloseTag[]; + static const char kLabelXmlTag[]; + static const char kAttributesXmlTag[]; + + // Flow control for asynchronous import is controlled by the methods below. + // ContinueImport is called back by each import action taken. BeginXXX + // and EndXXX are responsible for updating the state of the asynchronous + // import. EndImport is responsible for state cleanup and notifying the + // caller that import has completed. + void ContinueImport(); + void EndImport(); + void BeginImportBookmarks(); + void EndImportBookmarks(); + + // Network I/O is done by the methods below. These three methods are called + // in the order provided. The last two are called back with the HTML + // response provided by the Toolbar server. + void GetAuthenticationFromServer(); + void GetBookmarkDataFromServer(const std::string& response); + void GetBookmarksFromServerDataResponse(const std::string& response); + + // XML Parsing is implemented with the methods below. + bool ParseAuthenticationTokenResponse(const std::string& response, + std::string* token); + + static bool ParseBookmarksFromReader( + XmlReader* reader, + std::vector<ProfileWriter::BookmarkEntry>* bookmarks, + const string16& bookmark_group_string); + + static bool LocateNextOpenTag(XmlReader* reader); + static bool LocateNextTagByName(XmlReader* reader, const std::string& tag); + static bool LocateNextTagWithStopByName( + XmlReader* reader, + const std::string& tag, + const std::string& stop); + + static bool ExtractBookmarkInformation( + XmlReader* reader, + ProfileWriter::BookmarkEntry* bookmark_entry, + std::vector<BookmarkFolderType>* bookmark_folders, + const string16& bookmark_group_string); + static bool ExtractNamedValueFromXmlReader(XmlReader* reader, + const std::string& name, + std::string* buffer); + static bool ExtractTitleFromXmlReader(XmlReader* reader, + ProfileWriter::BookmarkEntry* entry); + static bool ExtractUrlFromXmlReader(XmlReader* reader, + ProfileWriter::BookmarkEntry* entry); + static bool ExtractTimeFromXmlReader(XmlReader* reader, + ProfileWriter::BookmarkEntry* entry); + static bool ExtractFoldersFromXmlReader( + XmlReader* reader, + std::vector<BookmarkFolderType>* bookmark_folders, + const string16& bookmark_group_string); + + // Bookmark creation is done by the method below. + void AddBookmarksToChrome( + const std::vector<ProfileWriter::BookmarkEntry>& bookmarks); + + // Internal state is stored in state_. + InternalStateEnum state_; + + // Bitmask of Importer::ImportItem is stored in items_to_import_. + uint16 items_to_import_; + + // The fetchers need to be available to cancel the network call on user cancel + // hence they are stored as member variables. + URLFetcher* token_fetcher_; + URLFetcher* data_fetcher_; + + DISALLOW_COPY_AND_ASSIGN(Toolbar5Importer); +}; + +#endif // CHROME_BROWSER_IMPORTER_TOOLBAR_IMPORTER_H_ diff --git a/chrome/browser/importer/toolbar_importer_unittest.cc b/chrome/browser/importer/toolbar_importer_unittest.cc new file mode 100644 index 0000000..0bc6ab5 --- /dev/null +++ b/chrome/browser/importer/toolbar_importer_unittest.cc @@ -0,0 +1,482 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/gtest/include/gtest/gtest.h" + +#include <string> +#include <vector> + +#include "base/string16.h" +#include "chrome/browser/first_run.h" +#include "chrome/browser/importer/importer.h" +#include "chrome/browser/importer/toolbar_importer.h" +#include "chrome/common/libxml_utils.h" +#include "googleurl/src/gurl.h" + +// See http://crbug.com/11838 +TEST(Toolbar5ImporterTest, BookmarkParse) { +static const wchar_t* kTitle = L"MyTitle"; +static const char* kUrl = "http://www.google.com/"; +static const wchar_t* kFolder = L"Google"; +static const wchar_t* kFolder2 = L"Homepage"; +static const wchar_t* kFolderArray[3] = {L"Google", L"Search", L"Page"}; + +static const wchar_t* kOtherTitle = L"MyOtherTitle"; +static const char* kOtherUrl = "http://www.google.com/mail"; +static const wchar_t* kOtherFolder = L"Mail"; + +static const string16 kBookmarkGroupTitle = ASCIIToUTF16("BookmarkGroupTitle"); + +// Since the following is very dense to read I enumerate the test cases here. +// 1. Correct bookmark structure with one label. +// 2. Correct bookmark structure with no labels. +// 3. Correct bookmark structure with two labels. +// 4. Correct bookmark structure with a folder->label translation by toolbar. +// 5. Correct bookmark structure with no favicon. +// 6. Two correct bookmarks. +// The following are error cases by removing sections from the xml: +// 7. Empty string passed as xml. +// 8. No <bookmarks> section in the xml. +// 9. No <bookmark> section below the <bookmarks> section. +// 10. No <title> in a <bookmark> section. +// 11. No <url> in a <bookmark> section. +// 12. No <timestamp> in a <bookmark> section. +// 13. No <labels> in a <bookmark> section. +static const char* kGoodBookmark = + "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>" + " <bookmark> " + "<title>MyTitle</title> " + "<url>http://www.google.com/</url> " + "<timestamp>1153328691085181</timestamp> " + "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> " + "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>" + "<labels> <label>Google</label> </labels> " + "<attributes> " + "<attribute> " + "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> " + "</attribute> " + "<attribute> " + "<name>favicon_timestamp</name> <value>1153328653</value> " + "</attribute> " + "<attribute> <name>notebook_name</name> <value>My notebook 0</value> " + "</attribute> " + "<attribute> <name>section_name</name> <value>My section 0 " + "</value> </attribute> </attributes> " + "</bookmark> </bookmarks>"; +static const char* kGoodBookmarkNoLabel = + "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>" + " <bookmark> " + "<title>MyTitle</title> " + "<url>http://www.google.com/</url> " + "<timestamp>1153328691085181</timestamp> " + "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> " + "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>" + "<labels> </labels> " + "<attributes> " + "<attribute> " + "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> " + "</attribute> " + "<attribute> " + "<name>favicon_timestamp</name> <value>1153328653</value> " + "</attribute> " + "<attribute> <name>notebook_name</name> <value>My notebook 0</value> " + "</attribute> " + "<attribute> <name>section_name</name> <value>My section 0 " + "</value> </attribute> </attributes> " + "</bookmark> </bookmarks>"; +static const char* kGoodBookmarkTwoLabels = + "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>" + " <bookmark> " + "<title>MyTitle</title> " + "<url>http://www.google.com/</url> " + "<timestamp>1153328691085181</timestamp> " + "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> " + "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>" + "<labels> <label>Google</label> <label>Homepage</label> </labels> " + "<attributes> " + "<attribute> " + "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> " + "</attribute> " + "<attribute> " + "<name>favicon_timestamp</name> <value>1153328653</value> " + "</attribute> " + "<attribute> <name>notebook_name</name> <value>My notebook 0</value> " + "</attribute> " + "<attribute> <name>section_name</name> <value>My section 0 " + "</value> </attribute> </attributes> " + "</bookmark> </bookmarks>"; +static const char* kGoodBookmarkFolderLabel = + "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>" + " <bookmark> " + "<title>MyTitle</title> " + "<url>http://www.google.com/</url> " + "<timestamp>1153328691085181</timestamp> " + "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> " + "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>" + "<labels> <label>Google:Search:Page</label> </labels> " + "<attributes> " + "<attribute> " + "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> " + "</attribute> " + "<attribute> " + "<name>favicon_timestamp</name> <value>1153328653</value> " + "</attribute> " + "<attribute> <name>notebook_name</name> <value>My notebook 0</value> " + "</attribute> " + "<attribute> <name>section_name</name> <value>My section 0 " + "</value> </attribute> </attributes> " + "</bookmark> </bookmarks>"; +static const char* kGoodBookmarkNoFavicon = + "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>" + " <bookmark> " + "<title>MyTitle</title> " + "<url>http://www.google.com/</url> " + "<timestamp>1153328691085181</timestamp> " + "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> " + "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>" + "<labels> <label>Google</label> </labels> " + "<attributes> " + "<attribute> " + "<name>favicon_timestamp</name> <value>1153328653</value> " + "</attribute> " + "<attribute> <name>notebook_name</name> <value>My notebook 0</value> " + "</attribute> " + "<attribute> <name>section_name</name> <value>My section 0 " + "</value> </attribute> </attributes> " + "</bookmark> </bookmarks>"; +static const char* kGoodBookmark2Items = + "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>" + " <bookmark> " + "<title>MyTitle</title> " + "<url>http://www.google.com/</url> " + "<timestamp>1153328691085181</timestamp> " + "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> " + "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>" + "<labels> <label>Google</label> </labels> " + "<attributes> " + "<attribute> " + "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> " + "</attribute> " + "<attribute> " + "<name>favicon_timestamp</name> <value>1153328653</value> " + "</attribute> " + "<attribute> <name>notebook_name</name> <value>My notebook 0</value> " + "</attribute> " + "<attribute> <name>section_name</name> <value>My section 0 " + "</value> </attribute> </attributes> " + "</bookmark>" + " <bookmark> " + "<title>MyOtherTitle</title> " + "<url>http://www.google.com/mail</url> " + "<timestamp>1153328691085181</timestamp> " + "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> " + "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>" + "<labels> <label>Mail</label> </labels> " + "<attributes> " + "<attribute> " + "<name>favicon_url</name>" + "<value>http://www.google.com/mail/favicon.ico</value> " + "</attribute> " + "<attribute> " + "<name>favicon_timestamp</name> <value>1253328653</value> " + "</attribute> " + "<attribute> <name>notebook_name</name> <value>My notebook 0</value> " + "</attribute> " + "<attribute> <name>section_name</name> <value>My section 0 " + "</value> </attribute> </attributes> " + "</bookmark>" + "</bookmarks>"; +static const char* kEmptyString = ""; +static const char* kBadBookmarkNoBookmarks = + " <bookmark> " + "<title>MyTitle</title> " + "<url>http://www.google.com/</url> " + "<timestamp>1153328691085181</timestamp> " + "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> " + "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>" + "<labels> <label>Google</label> </labels> " + "<attributes> " + "<attribute> " + "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> " + "</attribute> " + "<attribute> " + "<name>favicon_timestamp</name> <value>1153328653</value> " + "</attribute> " + "<attribute> <name>notebook_name</name> <value>My notebook 0</value> " + "</attribute> " + "<attribute> <name>section_name</name> <value>My section 0 " + "</value> </attribute> </attributes> " + "</bookmark> </bookmarks>"; +static const char* kBadBookmarkNoBookmark = + "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>" + "<title>MyTitle</title> " + "<url>http://www.google.com/</url> " + "<timestamp>1153328691085181</timestamp> " + "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> " + "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>" + "<labels> <label>Google</label> </labels> " + "<attributes> " + "<attribute> " + "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> " + "</attribute> " + "<attribute> " + "<name>favicon_timestamp</name> <value>1153328653</value> " + "</attribute> " + "<attribute> <name>notebook_name</name> <value>My notebook 0</value> " + "</attribute> " + "<attribute> <name>section_name</name> <value>My section 0 " + "</value> </attribute> </attributes> " + "</bookmark> </bookmarks>"; +static const char* kBadBookmarkNoTitle = + "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>" + " <bookmark> " + "<url>http://www.google.com/</url> " + "<timestamp>1153328691085181</timestamp> " + "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> " + "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>" + "<labels> <label>Google</label> </labels> " + "<attributes> " + "<attribute> " + "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> " + "</attribute> " + "<attribute> " + "<name>favicon_timestamp</name> <value>1153328653</value> " + "</attribute> " + "<attribute> <name>notebook_name</name> <value>My notebook 0</value> " + "</attribute> " + "<attribute> <name>section_name</name> <value>My section 0 " + "</value> </attribute> </attributes> " + "</bookmark> </bookmarks>"; +static const char* kBadBookmarkNoUrl = + "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>" + " <bookmark> " + "<title>MyTitle</title> " + "<timestamp>1153328691085181</timestamp> " + "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> " + "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>" + "<labels> <label>Google</label> </labels> " + "<attributes> " + "<attribute> " + "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> " + "</attribute> " + "<attribute> " + "<name>favicon_timestamp</name> <value>1153328653</value> " + "</attribute> " + "<attribute> <name>notebook_name</name> <value>My notebook 0</value> " + "</attribute> " + "<attribute> <name>section_name</name> <value>My section 0 " + "</value> </attribute> </attributes> " + "</bookmark> </bookmarks>"; +static const char* kBadBookmarkNoTimestamp = + "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>" + " <bookmark> " + "<title>MyTitle</title> " + "<url>http://www.google.com/</url> " + "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> " + "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>" + "<labels> <label>Google</label> </labels> " + "<attributes> " + "<attribute> " + "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> " + "</attribute> " + "<attribute> " + "<name>favicon_timestamp</name> <value>1153328653</value> " + "</attribute> " + "<attribute> <name>notebook_name</name> <value>My notebook 0</value> " + "</attribute> " + "<attribute> <name>section_name</name> <value>My section 0 " + "</value> </attribute> </attributes> " + "</bookmark> </bookmarks>"; +static const char* kBadBookmarkNoLabels = + "<?xml version=\"1.0\" ?> <xml_api_reply version=\"1\"> <bookmarks>" + " <bookmark> " + "<title>MyTitle</title> " + "<url>http://www.google.com/</url> " + "<timestamp>1153328691085181</timestamp> " + "<id>N123nasdf239</id> <notebook_id>Bxxxxxxx</notebook_id> " + "<section_id>Sxxxxxx</section_id> <has_highlight>0</has_highlight>" + "<attributes> " + "<attribute> " + "<name>favicon_url</name> <value>http://www.google.com/favicon.ico</value> " + "</attribute> " + "<attribute> " + "<name>favicon_timestamp</name> <value>1153328653</value> " + "</attribute> " + "<attribute> <name>notebook_name</name> <value>My notebook 0</value> " + "</attribute> " + "<attribute> <name>section_name</name> <value>My section 0 " + "</value> </attribute> </attributes> " + "</bookmark> </bookmarks>"; + + XmlReader reader; + std::string bookmark_xml; + std::vector<ProfileWriter::BookmarkEntry> bookmarks; + + const GURL url(kUrl); + const GURL other_url(kOtherUrl); + + // Test doesn't work if the importer thinks this is the first run of Chromium. + // Mark this as a subsequent run of the browser. + FirstRun::CreateSentinel(); + + // Test case 1 is parsing a basic bookmark with a single label. + bookmark_xml = kGoodBookmark; + bookmarks.clear(); + XmlReader reader1; + EXPECT_TRUE(reader1.Load(bookmark_xml)); + EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader1, &bookmarks, + kBookmarkGroupTitle)); + + ASSERT_EQ(1U, bookmarks.size()); + EXPECT_FALSE(bookmarks[0].in_toolbar); + EXPECT_EQ(kTitle, bookmarks[0].title); + EXPECT_EQ(url, bookmarks[0].url); + ASSERT_EQ(2U, bookmarks[0].path.size()); + EXPECT_EQ(kFolder, bookmarks[0].path[1]); + + // Test case 2 is parsing a single bookmark with no label. + bookmark_xml = kGoodBookmarkNoLabel; + bookmarks.clear(); + XmlReader reader2; + EXPECT_TRUE(reader2.Load(bookmark_xml)); + EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader2, &bookmarks, + kBookmarkGroupTitle)); + + ASSERT_EQ(1U, bookmarks.size()); + EXPECT_FALSE(bookmarks[0].in_toolbar); + EXPECT_EQ(kTitle, bookmarks[0].title); + EXPECT_EQ(url, bookmarks[0].url); + EXPECT_EQ(1U, bookmarks[0].path.size()); + + // Test case 3 is parsing a single bookmark with two labels. + bookmark_xml = kGoodBookmarkTwoLabels; + bookmarks.clear(); + XmlReader reader3; + EXPECT_TRUE(reader3.Load(bookmark_xml)); + EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader3, &bookmarks, + kBookmarkGroupTitle)); + + ASSERT_EQ(2U, bookmarks.size()); + EXPECT_FALSE(bookmarks[0].in_toolbar); + EXPECT_FALSE(bookmarks[1].in_toolbar); + EXPECT_EQ(kTitle, bookmarks[0].title); + EXPECT_EQ(kTitle, bookmarks[1].title); + EXPECT_EQ(url, bookmarks[0].url); + EXPECT_EQ(url, bookmarks[1].url); + ASSERT_EQ(2U, bookmarks[0].path.size()); + EXPECT_EQ(kFolder, bookmarks[0].path[1]); + ASSERT_EQ(2U, bookmarks[1].path.size()); + EXPECT_EQ(kFolder2, bookmarks[1].path[1]); + + // Test case 4 is parsing a single bookmark which has a label with a colon, + // this test file name translation between Toolbar and Chrome. + bookmark_xml = kGoodBookmarkFolderLabel; + bookmarks.clear(); + XmlReader reader4; + EXPECT_TRUE(reader4.Load(bookmark_xml)); + EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader4, &bookmarks, + kBookmarkGroupTitle)); + + ASSERT_EQ(1U, bookmarks.size()); + EXPECT_FALSE(bookmarks[0].in_toolbar); + EXPECT_EQ(kTitle, bookmarks[0].title); + EXPECT_EQ(url, bookmarks[0].url); + ASSERT_EQ(4U, bookmarks[0].path.size()); + EXPECT_EQ(std::wstring(kFolderArray[0]), + bookmarks[0].path[1]); + EXPECT_EQ(std::wstring(kFolderArray[1]), + bookmarks[0].path[2]); + EXPECT_EQ(std::wstring(kFolderArray[2]), + bookmarks[0].path[3]); + + // Test case 5 is parsing a single bookmark without a favicon URL. + bookmark_xml = kGoodBookmarkNoFavicon; + bookmarks.clear(); + XmlReader reader5; + EXPECT_TRUE(reader5.Load(bookmark_xml)); + EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader5, &bookmarks, + kBookmarkGroupTitle)); + + ASSERT_EQ(1U, bookmarks.size()); + EXPECT_FALSE(bookmarks[0].in_toolbar); + EXPECT_EQ(kTitle, bookmarks[0].title); + EXPECT_EQ(url, bookmarks[0].url); + ASSERT_EQ(2U, bookmarks[0].path.size()); + EXPECT_EQ(kFolder, bookmarks[0].path[1]); + + // Test case 6 is parsing two bookmarks. + bookmark_xml = kGoodBookmark2Items; + bookmarks.clear(); + XmlReader reader6; + EXPECT_TRUE(reader6.Load(bookmark_xml)); + EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader6, &bookmarks, + kBookmarkGroupTitle)); + + ASSERT_EQ(2U, bookmarks.size()); + EXPECT_FALSE(bookmarks[0].in_toolbar); + EXPECT_FALSE(bookmarks[1].in_toolbar); + EXPECT_EQ(kTitle, bookmarks[0].title); + EXPECT_EQ(kOtherTitle, bookmarks[1].title); + EXPECT_EQ(url, bookmarks[0].url); + EXPECT_EQ(other_url, bookmarks[1].url); + ASSERT_EQ(2U, bookmarks[0].path.size()); + EXPECT_EQ(kFolder, bookmarks[0].path[1]); + ASSERT_EQ(2U, bookmarks[1].path.size()); + EXPECT_EQ(kOtherFolder, bookmarks[1].path[1]); + + // Test case 7 is parsing an empty string for bookmarks. + bookmark_xml = kEmptyString; + bookmarks.clear(); + XmlReader reader7; + EXPECT_FALSE(reader7.Load(bookmark_xml)); + + // Test case 8 is testing the error when no <bookmarks> section is present. + bookmark_xml = kBadBookmarkNoBookmarks; + bookmarks.clear(); + XmlReader reader8; + EXPECT_TRUE(reader8.Load(bookmark_xml)); + EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader8, + &bookmarks, kBookmarkGroupTitle)); + + // Test case 9 tests when no <bookmark> section is present. + bookmark_xml = kBadBookmarkNoBookmark; + bookmarks.clear(); + XmlReader reader9; + EXPECT_TRUE(reader9.Load(bookmark_xml)); + EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader9, + &bookmarks, kBookmarkGroupTitle)); + + + // Test case 10 tests when a bookmark has no <title> section. + bookmark_xml = kBadBookmarkNoTitle; + bookmarks.clear(); + XmlReader reader10; + EXPECT_TRUE(reader10.Load(bookmark_xml)); + EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader10, + &bookmarks, kBookmarkGroupTitle)); + + // Test case 11 tests when a bookmark has no <url> section. + bookmark_xml = kBadBookmarkNoUrl; + bookmarks.clear(); + XmlReader reader11; + EXPECT_TRUE(reader11.Load(bookmark_xml)); + EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader11, + &bookmarks, kBookmarkGroupTitle)); + + // Test case 12 tests when a bookmark has no <timestamp> section. + bookmark_xml = kBadBookmarkNoTimestamp; + bookmarks.clear(); + XmlReader reader12; + EXPECT_TRUE(reader12.Load(bookmark_xml)); + EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader12, + &bookmarks, kBookmarkGroupTitle)); + + // Test case 13 tests when a bookmark has no <labels> section. + bookmark_xml = kBadBookmarkNoLabels; + bookmarks.clear(); + XmlReader reader13; + EXPECT_TRUE(reader13.Load(bookmark_xml)); + EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader13, + &bookmarks, kBookmarkGroupTitle)); +} |