• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
Toolbar5Importer()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.
~Toolbar5Importer()56 Toolbar5Importer::~Toolbar5Importer() {
57   DCHECK(!token_fetcher_);
58   DCHECK(!data_fetcher_);
59 }
60 
StartImport(const importer::SourceProfile & source_profile,uint16 items,ImporterBridge * bridge)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.
Cancel()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 
OnURLFetchComplete(const URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const ResponseCookies & cookies,const std::string & data)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 
ContinueImport()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 
EndImport()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 
BeginImportBookmarks()174 void Toolbar5Importer::BeginImportBookmarks() {
175   bridge_->NotifyItemStarted(importer::FAVORITES);
176   GetAuthenticationFromServer();
177 }
178 
EndImportBookmarks()179 void Toolbar5Importer::EndImportBookmarks() {
180   bridge_->NotifyItemEnded(importer::FAVORITES);
181   ContinueImport();
182 }
183 
184 
185 // Notebook front-end connection manager implementation follows.
GetAuthenticationFromServer()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 
GetBookmarkDataFromServer(const std::string & response)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 
GetBookmarksFromServerDataResponse(const std::string & response)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 
ParseAuthenticationTokenResponse(const std::string & response,std::string * token)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
ParseBookmarksFromReader(XmlReader * reader,std::vector<ProfileWriter::BookmarkEntry> * bookmarks,const string16 & bookmark_group_string)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 
LocateNextOpenTag(XmlReader * reader)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 
LocateNextTagByName(XmlReader * reader,const std::string & tag)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 
LocateNextTagWithStopByName(XmlReader * reader,const std::string & tag,const std::string & stop)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 
ExtractBookmarkInformation(XmlReader * reader,ProfileWriter::BookmarkEntry * bookmark_entry,std::vector<BookmarkFolderType> * bookmark_folders,const string16 & bookmark_group_string)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 
ExtractNamedValueFromXmlReader(XmlReader * reader,const std::string & name,std::string * buffer)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 
ExtractTitleFromXmlReader(XmlReader * reader,ProfileWriter::BookmarkEntry * entry)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 
ExtractUrlFromXmlReader(XmlReader * reader,ProfileWriter::BookmarkEntry * entry)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 
ExtractTimeFromXmlReader(XmlReader * reader,ProfileWriter::BookmarkEntry * entry)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 
ExtractFoldersFromXmlReader(XmlReader * reader,std::vector<BookmarkFolderType> * bookmark_folders,const string16 & bookmark_group_string)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 
AddBookmarksToChrome(const std::vector<ProfileWriter::BookmarkEntry> & bookmarks)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