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