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