toolbar_importer.cc revision 513209b27ff55e2841eac0e4120199c23acce758
1// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/importer/toolbar_importer.h" 6 7#include <limits> 8 9#include "base/rand_util.h" 10#include "base/string_split.h" 11#include "base/string_util.h" 12#include "base/string_number_conversions.h" 13#include "base/utf_string_conversions.h" 14#include "base/values.h" 15#include "chrome/browser/browser_thread.h" 16#include "chrome/browser/first_run/first_run.h" 17#include "chrome/browser/importer/importer_bridge.h" 18#include "chrome/browser/importer/importer_data_types.h" 19#include "chrome/browser/profile.h" 20#include "chrome/common/libxml_utils.h" 21#include "chrome/common/net/url_request_context_getter.h" 22#include "grit/generated_resources.h" 23#include "net/base/cookie_monster.h" 24#include "net/base/data_url.h" 25#include "net/url_request/url_request_context.h" 26 27using importer::FAVORITES; 28using importer::NONE; 29using importer::ProfileInfo; 30 31// 32// ToolbarImporterUtils 33// 34static const char* kGoogleDomainUrl = "http://.google.com/"; 35static const wchar_t kSplitStringToken = L';'; 36static const char* kGoogleDomainSecureCookieId = "SID="; 37 38bool toolbar_importer_utils::IsGoogleGAIACookieInstalled() { 39 net::CookieStore* store = 40 Profile::GetDefaultRequestContext()->GetCookieStore(); 41 GURL url(kGoogleDomainUrl); 42 net::CookieOptions options; 43 options.set_include_httponly(); // The SID cookie might be httponly. 44 std::string cookies = store->GetCookiesWithOptions(url, options); 45 std::vector<std::string> cookie_list; 46 base::SplitString(cookies, kSplitStringToken, &cookie_list); 47 for (std::vector<std::string>::iterator current = cookie_list.begin(); 48 current != cookie_list.end(); 49 ++current) { 50 size_t position = (*current).find(kGoogleDomainSecureCookieId); 51 if (0 == position) 52 return true; 53 } 54 return false; 55} 56 57// 58// Toolbar5Importer 59// 60const char Toolbar5Importer::kXmlApiReplyXmlTag[] = "xml_api_reply"; 61const char Toolbar5Importer::kBookmarksXmlTag[] = "bookmarks"; 62const char Toolbar5Importer::kBookmarkXmlTag[] = "bookmark"; 63const char Toolbar5Importer::kTitleXmlTag[] = "title"; 64const char Toolbar5Importer::kUrlXmlTag[] = "url"; 65const char Toolbar5Importer::kTimestampXmlTag[] = "timestamp"; 66const char Toolbar5Importer::kLabelsXmlTag[] = "labels"; 67const char Toolbar5Importer::kLabelsXmlCloseTag[] = "/labels"; 68const char Toolbar5Importer::kLabelXmlTag[] = "label"; 69const char Toolbar5Importer::kAttributesXmlTag[] = "attributes"; 70 71const char Toolbar5Importer::kRandomNumberToken[] = "{random_number}"; 72const char Toolbar5Importer::kAuthorizationToken[] = "{auth_token}"; 73const char Toolbar5Importer::kAuthorizationTokenPrefix[] = "/*"; 74const char Toolbar5Importer::kAuthorizationTokenSuffix[] = "*/"; 75const char Toolbar5Importer::kMaxNumToken[] = "{max_num}"; 76const char Toolbar5Importer::kMaxTimestampToken[] = "{max_timestamp}"; 77 78const char Toolbar5Importer::kT5AuthorizationTokenUrl[] = 79 "http://www.google.com/notebook/token?zx={random_number}"; 80const char Toolbar5Importer::kT5FrontEndUrlTemplate[] = 81 "http://www.google.com/notebook/toolbar?cmd=list&tok={auth_token}&" 82 "num={max_num}&min={max_timestamp}&all=0&zx={random_number}"; 83 84// Importer methods. 85 86// The constructor should set the initial state to NOT_USED. 87Toolbar5Importer::Toolbar5Importer() 88 : state_(NOT_USED), 89 items_to_import_(importer::NONE), 90 token_fetcher_(NULL), 91 data_fetcher_(NULL) { 92} 93 94// The destructor insures that the fetchers are currently not being used, as 95// their thread-safe implementation requires that they are cancelled from the 96// thread in which they were constructed. 97Toolbar5Importer::~Toolbar5Importer() { 98 DCHECK(!token_fetcher_); 99 DCHECK(!data_fetcher_); 100} 101 102void Toolbar5Importer::StartImport(const importer::ProfileInfo& profile_info, 103 uint16 items, 104 ImporterBridge* bridge) { 105 DCHECK(bridge); 106 107 bridge_ = bridge; 108 items_to_import_ = items; 109 state_ = INITIALIZED; 110 111 bridge_->NotifyStarted(); 112 ContinueImport(); 113} 114 115// The public cancel method serves two functions, as a callback from the UI 116// as well as an internal callback in case of cancel. An internal callback 117// is required since the URLFetcher must be destroyed from the thread it was 118// created. 119void Toolbar5Importer::Cancel() { 120 // In the case when the thread is not importing messages we are to 121 // cancel as soon as possible. 122 Importer::Cancel(); 123 124 // If we are conducting network operations, post a message to the importer 125 // thread for synchronization. 126 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { 127 EndImport(); 128 } else { 129 BrowserThread::PostTask( 130 BrowserThread::UI, FROM_HERE, 131 NewRunnableMethod(this, &Toolbar5Importer::Cancel)); 132 } 133} 134 135void Toolbar5Importer::OnURLFetchComplete( 136 const URLFetcher* source, 137 const GURL& url, 138 const URLRequestStatus& status, 139 int response_code, 140 const ResponseCookies& cookies, 141 const std::string& data) { 142 if (cancelled()) { 143 EndImport(); 144 return; 145 } 146 147 if (200 != response_code) { // HTTP/Ok 148 // Cancelling here will update the UI and bypass the rest of bookmark 149 // import. 150 EndImportBookmarks(); 151 return; 152 } 153 154 switch (state_) { 155 case GET_AUTHORIZATION_TOKEN: 156 GetBookmarkDataFromServer(data); 157 break; 158 case GET_BOOKMARKS: 159 GetBookmarksFromServerDataResponse(data); 160 break; 161 default: 162 NOTREACHED() << "Invalid state."; 163 EndImportBookmarks(); 164 break; 165 } 166} 167 168void Toolbar5Importer::ContinueImport() { 169 DCHECK((items_to_import_ == importer::FAVORITES) || 170 (items_to_import_ == importer::NONE)) << 171 "The items requested are not supported"; 172 173 // The order here is important. Each Begin... will clear the flag 174 // of its item before its task finishes and re-enters this method. 175 if (importer::NONE == items_to_import_) { 176 EndImport(); 177 return; 178 } 179 if ((items_to_import_ & importer::FAVORITES) && !cancelled()) { 180 items_to_import_ &= ~importer::FAVORITES; 181 BeginImportBookmarks(); 182 return; 183 } 184 // TODO(brg): Import history, autocomplete, other toolbar information 185 // in a future release. 186 187 // This code should not be reached, but gracefully handles the possibility 188 // that StartImport was called with unsupported items_to_import. 189 if (!cancelled()) 190 EndImport(); 191} 192 193void Toolbar5Importer::EndImport() { 194 if (state_ != DONE) { 195 state_ = DONE; 196 // By spec the fetchers must be destroyed within the same 197 // thread they are created. The importer is destroyed in the ui_thread 198 // so when we complete in the file_thread we destroy them first. 199 if (NULL != token_fetcher_) { 200 delete token_fetcher_; 201 token_fetcher_ = NULL; 202 } 203 204 if (NULL != data_fetcher_) { 205 delete data_fetcher_; 206 data_fetcher_ = NULL; 207 } 208 209 if (bridge_) 210 bridge_->NotifyEnded(); 211 } 212} 213 214void Toolbar5Importer::BeginImportBookmarks() { 215 bridge_->NotifyItemStarted(importer::FAVORITES); 216 GetAuthenticationFromServer(); 217} 218 219void Toolbar5Importer::EndImportBookmarks() { 220 bridge_->NotifyItemEnded(importer::FAVORITES); 221 ContinueImport(); 222} 223 224 225// Notebook front-end connection manager implementation follows. 226void Toolbar5Importer::GetAuthenticationFromServer() { 227 if (cancelled()) { 228 EndImport(); 229 return; 230 } 231 232 // Authentication is a token string retrieved from the authentication server 233 // To access it we call the url below with a random number replacing the 234 // value in the string. 235 state_ = GET_AUTHORIZATION_TOKEN; 236 237 // Random number construction. 238 int random = base::RandInt(0, std::numeric_limits<int>::max()); 239 std::string random_string = base::UintToString(random); 240 241 // Retrieve authorization token from the network. 242 std::string url_string(kT5AuthorizationTokenUrl); 243 url_string.replace(url_string.find(kRandomNumberToken), 244 arraysize(kRandomNumberToken) - 1, 245 random_string); 246 GURL url(url_string); 247 248 token_fetcher_ = new URLFetcher(url, URLFetcher::GET, this); 249 token_fetcher_->set_request_context(Profile::GetDefaultRequestContext()); 250 token_fetcher_->Start(); 251} 252 253void Toolbar5Importer::GetBookmarkDataFromServer(const std::string& response) { 254 if (cancelled()) { 255 EndImport(); 256 return; 257 } 258 259 state_ = GET_BOOKMARKS; 260 261 // Parse and verify the authorization token from the response. 262 std::string token; 263 if (!ParseAuthenticationTokenResponse(response, &token)) { 264 EndImportBookmarks(); 265 return; 266 } 267 268 // Build the Toolbar FE connection string, and call the server for 269 // the xml blob. We must tag the connection string with a random number. 270 std::string conn_string = kT5FrontEndUrlTemplate; 271 int random = base::RandInt(0, std::numeric_limits<int>::max()); 272 std::string random_string = base::UintToString(random); 273 conn_string.replace(conn_string.find(kRandomNumberToken), 274 arraysize(kRandomNumberToken) - 1, 275 random_string); 276 conn_string.replace(conn_string.find(kAuthorizationToken), 277 arraysize(kAuthorizationToken) - 1, 278 token); 279 GURL url(conn_string); 280 281 data_fetcher_ = new URLFetcher(url, URLFetcher::GET, this); 282 data_fetcher_->set_request_context(Profile::GetDefaultRequestContext()); 283 data_fetcher_->Start(); 284} 285 286void Toolbar5Importer::GetBookmarksFromServerDataResponse( 287 const std::string& response) { 288 if (cancelled()) { 289 EndImport(); 290 return; 291 } 292 293 state_ = PARSE_BOOKMARKS; 294 295 XmlReader reader; 296 if (reader.Load(response) && !cancelled()) { 297 // Construct Bookmarks 298 std::vector<ProfileWriter::BookmarkEntry> bookmarks; 299 if (ParseBookmarksFromReader(&reader, &bookmarks, 300 WideToUTF16(bridge_->GetLocalizedString( 301 IDS_BOOKMARK_GROUP_FROM_GOOGLE_TOOLBAR)))) 302 AddBookmarksToChrome(bookmarks); 303 } 304 EndImportBookmarks(); 305} 306 307bool Toolbar5Importer::ParseAuthenticationTokenResponse( 308 const std::string& response, 309 std::string* token) { 310 DCHECK(token); 311 312 *token = response; 313 size_t position = token->find(kAuthorizationTokenPrefix); 314 if (0 != position) 315 return false; 316 token->replace(position, arraysize(kAuthorizationTokenPrefix) - 1, ""); 317 318 position = token->find(kAuthorizationTokenSuffix); 319 if (token->size() != (position + (arraysize(kAuthorizationTokenSuffix) - 1))) 320 return false; 321 token->replace(position, arraysize(kAuthorizationTokenSuffix) - 1, ""); 322 323 return true; 324} 325 326// Parsing 327bool Toolbar5Importer::ParseBookmarksFromReader( 328 XmlReader* reader, 329 std::vector<ProfileWriter::BookmarkEntry>* bookmarks, 330 const string16& bookmark_group_string) { 331 DCHECK(reader); 332 DCHECK(bookmarks); 333 334 // The XML blob returned from the server is described in the 335 // Toolbar-Notebook/Bookmarks Protocol document located at 336 // https://docs.google.com/a/google.com/Doc?docid=cgt3m7dr_24djt62m&hl=en 337 // We are searching for the section with structure 338 // <bookmarks><bookmark>...</bookmark><bookmark>...</bookmark></bookmarks> 339 340 // Locate the |bookmarks| blob. 341 if (!reader->SkipToElement()) 342 return false; 343 344 if (!LocateNextTagByName(reader, kBookmarksXmlTag)) 345 return false; 346 347 // Parse each |bookmark| blob 348 while (LocateNextTagWithStopByName(reader, kBookmarkXmlTag, 349 kBookmarksXmlTag)) { 350 ProfileWriter::BookmarkEntry bookmark_entry; 351 std::vector<BookmarkFolderType> folders; 352 if (ExtractBookmarkInformation(reader, &bookmark_entry, &folders, 353 bookmark_group_string)) { 354 // For each folder we create a new bookmark entry. Duplicates will 355 // be detected when we attempt to create the bookmark in the profile. 356 for (std::vector<BookmarkFolderType>::iterator folder = folders.begin(); 357 folder != folders.end(); 358 ++folder) { 359 bookmark_entry.path = *folder; 360 bookmarks->push_back(bookmark_entry); 361 } 362 } 363 } 364 365 if (0 == bookmarks->size()) 366 return false; 367 368 return true; 369} 370 371bool Toolbar5Importer::LocateNextOpenTag(XmlReader* reader) { 372 DCHECK(reader); 373 374 while (!reader->SkipToElement()) { 375 if (!reader->Read()) 376 return false; 377 } 378 return true; 379} 380 381bool Toolbar5Importer::LocateNextTagByName(XmlReader* reader, 382 const std::string& tag) { 383 DCHECK(reader); 384 385 // Locate the |tag| blob. 386 while (tag != reader->NodeName()) { 387 if (!reader->Read() || !LocateNextOpenTag(reader)) 388 return false; 389 } 390 return true; 391} 392 393bool Toolbar5Importer::LocateNextTagWithStopByName(XmlReader* reader, 394 const std::string& tag, 395 const std::string& stop) { 396 DCHECK(reader); 397 398 DCHECK_NE(tag, stop); 399 // Locate the |tag| blob. 400 while (tag != reader->NodeName()) { 401 // Move to the next open tag. 402 if (!reader->Read() || !LocateNextOpenTag(reader)) 403 return false; 404 // If we encounter the stop word return false. 405 if (stop == reader->NodeName()) 406 return false; 407 } 408 return true; 409} 410 411bool Toolbar5Importer::ExtractBookmarkInformation( 412 XmlReader* reader, 413 ProfileWriter::BookmarkEntry* bookmark_entry, 414 std::vector<BookmarkFolderType>* bookmark_folders, 415 const string16& bookmark_group_string) { 416 DCHECK(reader); 417 DCHECK(bookmark_entry); 418 DCHECK(bookmark_folders); 419 420 // The following is a typical bookmark entry. 421 // The reader should be pointing to the <title> tag at the moment. 422 // 423 // <bookmark> 424 // <title>MyTitle</title> 425 // <url>http://www.sohu.com/</url> 426 // <timestamp>1153328691085181</timestamp> 427 // <id>N123nasdf239</id> 428 // <notebook_id>Bxxxxxxx</notebook_id> (for bookmarks, a special id is used) 429 // <section_id>Sxxxxxx</section_id> 430 // <has_highlight>0</has_highlight> 431 // <labels> 432 // <label>China</label> 433 // <label>^k</label> (if this special label is present, the note is deleted) 434 // </labels> 435 // <attributes> 436 // <attribute> 437 // <name>favicon_url</name> 438 // <value>http://www.sohu.com/favicon.ico</value> 439 // </attribute> 440 // <attribute> 441 // <name>favicon_timestamp</name> 442 // <value>1153328653</value> 443 // </attribute> 444 // <attribute> 445 // <name>notebook_name</name> 446 // <value>My notebook 0</value> 447 // </attribute> 448 // <attribute> 449 // <name>section_name</name> 450 // <value>My section 0</value> 451 // </attribute> 452 // </attributes> 453 // </bookmark> 454 // 455 // We parse the blob in order, title->url->timestamp etc. Any failure 456 // causes us to skip this bookmark. 457 458 if (!ExtractTitleFromXmlReader(reader, bookmark_entry)) 459 return false; 460 if (!ExtractUrlFromXmlReader(reader, bookmark_entry)) 461 return false; 462 if (!ExtractTimeFromXmlReader(reader, bookmark_entry)) 463 return false; 464 if (!ExtractFoldersFromXmlReader(reader, bookmark_folders, 465 bookmark_group_string)) 466 return false; 467 468 return true; 469} 470 471bool Toolbar5Importer::ExtractNamedValueFromXmlReader(XmlReader* reader, 472 const std::string& name, 473 std::string* buffer) { 474 DCHECK(reader); 475 DCHECK(buffer); 476 477 if (name != reader->NodeName()) 478 return false; 479 if (!reader->ReadElementContent(buffer)) 480 return false; 481 return true; 482} 483 484bool Toolbar5Importer::ExtractTitleFromXmlReader( 485 XmlReader* reader, 486 ProfileWriter::BookmarkEntry* entry) { 487 DCHECK(reader); 488 DCHECK(entry); 489 490 if (!LocateNextTagWithStopByName(reader, kTitleXmlTag, kUrlXmlTag)) 491 return false; 492 std::string buffer; 493 if (!ExtractNamedValueFromXmlReader(reader, kTitleXmlTag, &buffer)) { 494 return false; 495 } 496 entry->title = UTF8ToWide(buffer); 497 return true; 498} 499 500bool Toolbar5Importer::ExtractUrlFromXmlReader( 501 XmlReader* reader, 502 ProfileWriter::BookmarkEntry* entry) { 503 DCHECK(reader); 504 DCHECK(entry); 505 506 if (!LocateNextTagWithStopByName(reader, kUrlXmlTag, kTimestampXmlTag)) 507 return false; 508 std::string buffer; 509 if (!ExtractNamedValueFromXmlReader(reader, kUrlXmlTag, &buffer)) { 510 return false; 511 } 512 entry->url = GURL(buffer); 513 return true; 514} 515 516bool Toolbar5Importer::ExtractTimeFromXmlReader( 517 XmlReader* reader, 518 ProfileWriter::BookmarkEntry* entry) { 519 DCHECK(reader); 520 DCHECK(entry); 521 if (!LocateNextTagWithStopByName(reader, kTimestampXmlTag, kLabelsXmlTag)) 522 return false; 523 std::string buffer; 524 if (!ExtractNamedValueFromXmlReader(reader, kTimestampXmlTag, &buffer)) { 525 return false; 526 } 527 int64 timestamp; 528 if (!base::StringToInt64(buffer, ×tamp)) { 529 return false; 530 } 531 entry->creation_time = base::Time::FromTimeT(timestamp); 532 return true; 533} 534 535bool Toolbar5Importer::ExtractFoldersFromXmlReader( 536 XmlReader* reader, 537 std::vector<BookmarkFolderType>* bookmark_folders, 538 const string16& bookmark_group_string) { 539 DCHECK(reader); 540 DCHECK(bookmark_folders); 541 542 // Read in the labels for this bookmark from the xml. There may be many 543 // labels for any one bookmark. 544 if (!LocateNextTagWithStopByName(reader, kLabelsXmlTag, kAttributesXmlTag)) 545 return false; 546 547 // It is within scope to have an empty labels section, so we do not 548 // return false if the labels are empty. 549 if (!reader->Read() || !LocateNextOpenTag(reader)) 550 return false; 551 552 std::vector<std::wstring> label_vector; 553 while (kLabelXmlTag == reader->NodeName()) { 554 std::string label_buffer; 555 if (!reader->ReadElementContent(&label_buffer)) { 556 label_buffer = ""; 557 } 558 label_vector.push_back(UTF8ToWide(label_buffer)); 559 LocateNextOpenTag(reader); 560 } 561 562 if (0 == label_vector.size()) { 563 if (!FirstRun::IsChromeFirstRun()) { 564 bookmark_folders->resize(1); 565 (*bookmark_folders)[0].push_back(UTF16ToWide(bookmark_group_string)); 566 } 567 return true; 568 } 569 570 // We will be making one bookmark folder for each label. 571 bookmark_folders->resize(label_vector.size()); 572 573 for (size_t index = 0; index < label_vector.size(); ++index) { 574 // If this is the first run then we place favorites with no labels 575 // in the title bar. Else they are placed in the "Google Toolbar" folder. 576 if (!FirstRun::IsChromeFirstRun() || !label_vector[index].empty()) { 577 (*bookmark_folders)[index].push_back(UTF16ToWide(bookmark_group_string)); 578 } 579 580 // If the label and is in the form "xxx:yyy:zzz" this was created from an 581 // IE or Firefox folder. We undo the label creation and recreate the 582 // correct folder. 583 std::vector<std::wstring> folder_names; 584 base::SplitString(label_vector[index], L':', &folder_names); 585 (*bookmark_folders)[index].insert((*bookmark_folders)[index].end(), 586 folder_names.begin(), folder_names.end()); 587 } 588 589 return true; 590} 591 592// Bookmark creation 593void Toolbar5Importer::AddBookmarksToChrome( 594 const std::vector<ProfileWriter::BookmarkEntry>& bookmarks) { 595 if (!bookmarks.empty() && !cancelled()) { 596 const std::wstring& first_folder_name = 597 bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP_FROM_GOOGLE_TOOLBAR); 598 int options = ProfileWriter::ADD_IF_UNIQUE | 599 (import_to_bookmark_bar() ? ProfileWriter::IMPORT_TO_BOOKMARK_BAR : 0); 600 bridge_->AddBookmarkEntries(bookmarks, first_folder_name, options); 601 } 602} 603