diff options
author | brg@chromium.com <brg@chromium.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-16 22:59:02 +0000 |
---|---|---|
committer | brg@chromium.com <brg@chromium.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-16 22:59:02 +0000 |
commit | 9b7ef69113c8a5c7eb6c1e51af506547c32d6fe4 (patch) | |
tree | 6abfdc143b6050a4b0d480baed150260dbdade57 /chrome/browser/importer | |
parent | 6256397c0cbb5d88e47676a7414f71669ddc638a (diff) | |
download | chromium_src-9b7ef69113c8a5c7eb6c1e51af506547c32d6fe4.zip chromium_src-9b7ef69113c8a5c7eb6c1e51af506547c32d6fe4.tar.gz chromium_src-9b7ef69113c8a5c7eb6c1e51af506547c32d6fe4.tar.bz2 |
Re-introducing the toolbar importer code.This is the same code that was removed in CL http://codereview.chromium.org/18550.The UI will be reworked in a CL immediately following review/submission of this code.Please contact Nick Baum for information regarding this change.
Review URL: http://codereview.chromium.org/66065
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@13895 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/importer')
-rw-r--r-- | chrome/browser/importer/importer.cc | 44 | ||||
-rw-r--r-- | chrome/browser/importer/importer.h | 2 | ||||
-rw-r--r-- | chrome/browser/importer/toolbar_importer.cc | 592 | ||||
-rw-r--r-- | chrome/browser/importer/toolbar_importer.h | 169 | ||||
-rw-r--r-- | chrome/browser/importer/toolbar_importer_unittest.cc | 453 |
5 files changed, 1260 insertions, 0 deletions
diff --git a/chrome/browser/importer/importer.cc b/chrome/browser/importer/importer.cc index 6e29b59..7710c83 100644 --- a/chrome/browser/importer/importer.cc +++ b/chrome/browser/importer/importer.cc @@ -22,6 +22,7 @@ #if defined(OS_WIN) #include "chrome/browser/importer/ie_importer.h" #endif +#include "chrome/browser/importer/toolbar_importer.h" #include "chrome/browser/search_engines/template_url_model.h" #include "chrome/browser/shell_integration.h" #include "chrome/browser/tab_contents/site_instance.h" @@ -512,6 +513,32 @@ void ImporterHost::StartImportSettings(const ProfileInfo& profile_info, } } + #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 == 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_->GetProfile()); + SiteInstance* site = instance->GetSiteInstanceForURL(url); + Browser* browser = BrowserList::GetLastActive(); + browser->AddTabWithURL(url, GURL(), PageTransition::TYPED, true, -1, + site); + + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + this, &ImporterHost::OnLockViewEnd, false)); + + is_source_readable_ = false; + } + } +#endif + // BookmarkModel should be loaded before adding IE favorites. So we observe // the BookmarkModel if needed, and start the task after it has been loaded. if ((items & FAVORITES) && !writer_->BookmarkModelIsLoaded()) { @@ -582,6 +609,8 @@ Importer* ImporterHost::CreateImporterByType(ProfileType type) { return new Firefox2Importer(); case FIREFOX3: return new Firefox3Importer(); + case GOOGLE_TOOLBAR5: + return new Toolbar5Importer(); } NOTREACHED(); return NULL; @@ -612,6 +641,8 @@ void ImporterHost::DetectSourceProfiles() { DetectIEProfiles(); DetectFirefoxProfiles(); } + // TODO(brg) : Current UI requires win_util. + DetectGoogleToolbarProfiles(); #else DetectFirefoxProfiles(); #endif @@ -707,3 +738,16 @@ void ImporterHost::DetectFirefoxProfiles() { source_profiles_.push_back(firefox); } } + +void ImporterHost::DetectGoogleToolbarProfiles() { + if (!FirstRun::IsChromeFirstRun()) { + ProfileInfo* google_toolbar = new ProfileInfo(); + google_toolbar->browser_type = 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 = FAVORITES; + source_profiles_.push_back(google_toolbar); + } +} diff --git a/chrome/browser/importer/importer.h b/chrome/browser/importer/importer.h index f4f4f07..46ce03e 100644 --- a/chrome/browser/importer/importer.h +++ b/chrome/browser/importer/importer.h @@ -32,6 +32,7 @@ enum ProfileType { #endif FIREFOX2, FIREFOX3, + GOOGLE_TOOLBAR5, // Identifies a 'bookmarks.html' file. BOOKMARKS_HTML }; @@ -284,6 +285,7 @@ class ImporterHost : public base::RefCounted<ImporterHost>, void DetectIEProfiles(); #endif void DetectFirefoxProfiles(); + void DetectGoogleToolbarProfiles(); // The list of profiles with the default one first. std::vector<ProfileInfo*> source_profiles_; diff --git a/chrome/browser/importer/toolbar_importer.cc b/chrome/browser/importer/toolbar_importer.cc new file mode 100644 index 0000000..22594e6 --- /dev/null +++ b/chrome/browser/importer/toolbar_importer.cc @@ -0,0 +1,592 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/importer/toolbar_importer.h" + +#include <limits> + +#include "base/string_util.h" +#include "base/rand_util.h" +#include "chrome/browser/first_run.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/libxml_utils.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" + +// +// ToolbarImporterUtils +// +static const char* kGoogleDomainUrl = "http://.google.com/"; +static const wchar_t kSplitStringToken = L';'; +static const char* kGoogleDomainSecureCookieId = "SID="; + +bool toolbar_importer_utils::IsGoogleGAIACookieInstalled() { + URLRequestContext* context = Profile::GetDefaultRequestContext(); + net::CookieMonster* store = context->cookie_store(); + GURL url(kGoogleDomainUrl); + net::CookieMonster::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() + : writer_(NULL), + state_(NOT_USED), + items_to_import_(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(ProfileInfo profile_info, + uint16 items, + ProfileWriter* writer, + MessageLoop* delagate_loop, + ImporterHost* host) { + DCHECK(writer); + DCHECK(host); + + importer_host_ = host; + delagate_loop_ = delagate_loop; + writer_ = writer; + items_to_import_ = items; + state_ = INITIALIZED; + + 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 (NULL != delagate_loop_) { + if (delagate_loop_ != MessageLoop::current()) { + delagate_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &Toolbar5Importer::Cancel)); + } else { + EndImport(); + } + } +} + +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_ == FAVORITES) || (items_to_import_ == 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 (NONE == items_to_import_) { + EndImport(); + return; + } + if ((items_to_import_ & FAVORITES) && !cancelled()) { + items_to_import_ &= ~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; + } + + NotifyEnded(); + } +} + +void Toolbar5Importer::BeginImportBookmarks() { + NotifyItemStarted(FAVORITES); + GetAuthenticationFromServer(); +} + +void Toolbar5Importer::EndImportBookmarks() { + NotifyItemEnded(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)) + 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) { + 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)) { + // 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) { + 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)) + 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) { + 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( + l10n_util::GetString(IDS_BOOKMARK_GROUP_FROM_GOOGLE_TOOLBAR)); + } + 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( + l10n_util::GetString(IDS_BOOKMARK_GROUP_FROM_GOOGLE_TOOLBAR)); + } + + // 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()) { + int options = ProfileWriter::ADD_IF_UNIQUE | + (first_run() ? ProfileWriter::FIRST_RUN : 0); + main_loop_->PostTask(FROM_HERE, NewRunnableMethod(writer_, + &ProfileWriter::AddBookmarkEntry, bookmarks, + l10n_util::GetString(IDS_BOOKMARK_GROUP_FROM_GOOGLE_TOOLBAR), + options)); + } +} diff --git a/chrome/browser/importer/toolbar_importer.h b/chrome/browser/importer/toolbar_importer.h new file mode 100644 index 0000000..24b5b28 --- /dev/null +++ b/chrome/browser/importer/toolbar_importer.h @@ -0,0 +1,169 @@ +// 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. + +// 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 "chrome/browser/importer/importer.h" +#include "chrome/browser/net/url_fetcher.h" + +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(); + virtual ~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(ProfileInfo profile_info, + uint16 items, + ProfileWriter* writer, + MessageLoop* delegate_loop, + ImporterHost* host); + + // 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(Toolbar5ImporterTest, BookmarkParse); + + // 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); + + 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); + 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); + + // Bookmark creation is done by the method below. + void AddBookmarksToChrome( + const std::vector<ProfileWriter::BookmarkEntry>& bookmarks); + + // The writer used in this importer is stored in writer_. + ProfileWriter* writer_; + + // 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..7f9ec16 --- /dev/null +++ b/chrome/browser/importer/toolbar_importer_unittest.cc @@ -0,0 +1,453 @@ +// 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 "chrome/browser/importer/importer.h" +#include "chrome/browser/importer/toolbar_importer.h" +#include "chrome/common/libxml_utils.h" +#include "googleurl/src/gurl.h" + + +namespace toolbar_importer_unittest { +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"; + +// 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>"; +} // namespace toolbar_importer_unittest + +// The parsing tests for Toolbar5Importer use the string above. For a +// description of all the tests run please see the comments immediately before +// the string constants above. +TEST(Toolbar5ImporterTest, BookmarkParse) { + XmlReader reader; + std::string bookmark_xml; + std::vector<ProfileWriter::BookmarkEntry> bookmarks; + + const GURL url(toolbar_importer_unittest::kUrl); + const GURL other_url(toolbar_importer_unittest::kOtherUrl); + + // Test case 1 is parsing a basic bookmark with a single label. + bookmark_xml = toolbar_importer_unittest::kGoodBookmark; + bookmarks.clear(); + EXPECT_TRUE(reader.Load(bookmark_xml)); + EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader, &bookmarks)); + + EXPECT_EQ(bookmarks.size(), (size_t) 1); + EXPECT_FALSE(bookmarks[0].in_toolbar); + EXPECT_EQ(toolbar_importer_unittest::kTitle, bookmarks[0].title); + EXPECT_EQ(url, bookmarks[0].url); + EXPECT_EQ(bookmarks[0].path.size(), (size_t) 2); + EXPECT_EQ(toolbar_importer_unittest::kFolder, bookmarks[0].path[1]); + + // Test case 2 is parsing a single bookmark with no label. + bookmark_xml = toolbar_importer_unittest::kGoodBookmarkNoLabel; + bookmarks.clear(); + EXPECT_TRUE(reader.Load(bookmark_xml)); + EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader, &bookmarks)); + + EXPECT_EQ(bookmarks.size(), (size_t) 1); + EXPECT_FALSE(bookmarks[0].in_toolbar); + EXPECT_EQ(toolbar_importer_unittest::kTitle, bookmarks[0].title); + EXPECT_EQ(bookmarks[0].url, url); + EXPECT_EQ(bookmarks[0].path.size(), (size_t) 1); + + // Test case 3 is parsing a single bookmark with two labels. + bookmark_xml = toolbar_importer_unittest::kGoodBookmarkTwoLabels; + bookmarks.clear(); + EXPECT_TRUE(reader.Load(bookmark_xml)); + EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader, &bookmarks)); + + EXPECT_EQ(bookmarks.size(), (size_t) 2); + EXPECT_FALSE(bookmarks[0].in_toolbar); + EXPECT_FALSE(bookmarks[1].in_toolbar); + EXPECT_EQ(toolbar_importer_unittest::kTitle, bookmarks[0].title); + EXPECT_EQ(toolbar_importer_unittest::kTitle, bookmarks[1].title); + EXPECT_EQ(bookmarks[0].url, url); + EXPECT_EQ(bookmarks[1].url, url); + EXPECT_EQ(toolbar_importer_unittest::kFolder, bookmarks[0].path[1]); + EXPECT_EQ(toolbar_importer_unittest::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 = toolbar_importer_unittest::kGoodBookmarkFolderLabel; + bookmarks.clear(); + EXPECT_TRUE(reader.Load(bookmark_xml)); + EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader, &bookmarks)); + + EXPECT_EQ(bookmarks.size(), (size_t) 1); + EXPECT_FALSE(bookmarks[0].in_toolbar); + EXPECT_EQ(toolbar_importer_unittest::kTitle, bookmarks[0].title); + EXPECT_EQ(bookmarks[0].url, url); + EXPECT_EQ(bookmarks[0].path.size(), (size_t) 4); + EXPECT_TRUE(toolbar_importer_unittest::kFolderArray[0] == + bookmarks[0].path[1]); + EXPECT_TRUE(toolbar_importer_unittest::kFolderArray[1] == + bookmarks[0].path[2]); + EXPECT_TRUE(toolbar_importer_unittest::kFolderArray[2] == + bookmarks[0].path[3]); + + // Test case 5 is parsing a single bookmark without a favicon URL. + bookmark_xml = toolbar_importer_unittest::kGoodBookmarkNoFavicon; + bookmarks.clear(); + EXPECT_TRUE(reader.Load(bookmark_xml)); + EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader, &bookmarks)); + + EXPECT_EQ(bookmarks.size(), (size_t) 1); + EXPECT_FALSE(bookmarks[0].in_toolbar); + EXPECT_EQ(toolbar_importer_unittest::kTitle, bookmarks[0].title); + EXPECT_EQ(bookmarks[0].url, url); + EXPECT_EQ(bookmarks[0].path.size(), (size_t) 2); + EXPECT_EQ(toolbar_importer_unittest::kFolder, bookmarks[0].path[1]); + + // Test case 6 is parsing two bookmarks. + bookmark_xml = toolbar_importer_unittest::kGoodBookmark2Items; + bookmarks.clear(); + EXPECT_TRUE(reader.Load(bookmark_xml)); + EXPECT_TRUE(Toolbar5Importer::ParseBookmarksFromReader(&reader, &bookmarks)); + + EXPECT_EQ(bookmarks.size(), (size_t) 2); + EXPECT_FALSE(bookmarks[0].in_toolbar); + EXPECT_FALSE(bookmarks[1].in_toolbar); + EXPECT_EQ(toolbar_importer_unittest::kTitle, bookmarks[0].title); + EXPECT_EQ(toolbar_importer_unittest::kOtherTitle, bookmarks[1].title); + EXPECT_EQ(bookmarks[0].url, url); + EXPECT_EQ(bookmarks[1].url, other_url); + EXPECT_EQ(bookmarks[0].path.size(), (size_t) 2); + EXPECT_EQ(toolbar_importer_unittest::kFolder, bookmarks[0].path[1]); + EXPECT_EQ(bookmarks[0].path.size(), (size_t) 2); + EXPECT_EQ(toolbar_importer_unittest::kOtherFolder, bookmarks[1].path[1]); + + // Test case 7 is parsing an empty string for bookmarks. + bookmark_xml = toolbar_importer_unittest::kEmptyString; + bookmarks.clear(); + EXPECT_FALSE(reader.Load(bookmark_xml)); + + // Test case 8 is testing the error when no <bookmarks> section is present. + bookmark_xml = toolbar_importer_unittest::kBadBookmarkNoBookmarks; + bookmarks.clear(); + EXPECT_TRUE(reader.Load(bookmark_xml)); + EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader, &bookmarks)); + + // Test case 9 tests when no <bookmark> section is present. + bookmark_xml = toolbar_importer_unittest::kBadBookmarkNoBookmark; + bookmarks.clear(); + EXPECT_TRUE(reader.Load(bookmark_xml)); + EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader, &bookmarks)); + + + // Test case 10 tests when a bookmark has no <title> section. + bookmark_xml = toolbar_importer_unittest::kBadBookmarkNoTitle; + bookmarks.clear(); + EXPECT_TRUE(reader.Load(bookmark_xml)); + EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader, &bookmarks)); + + + // Test case 11 tests when a bookmark has no <url> section. + bookmark_xml = toolbar_importer_unittest::kBadBookmarkNoUrl; + bookmarks.clear(); + EXPECT_TRUE(reader.Load(bookmark_xml)); + EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader, &bookmarks)); + + // Test case 12 tests when a bookmark has no <timestamp> section. + bookmark_xml = toolbar_importer_unittest::kBadBookmarkNoTimestamp; + bookmarks.clear(); + EXPECT_TRUE(reader.Load(bookmark_xml)); + EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader, &bookmarks)); + + // Test case 13 tests when a bookmark has no <labels> section. + bookmark_xml = toolbar_importer_unittest::kBadBookmarkNoLabels; + bookmarks.clear(); + EXPECT_TRUE(reader.Load(bookmark_xml)); + EXPECT_FALSE(Toolbar5Importer::ParseBookmarksFromReader(&reader, &bookmarks)); +} |