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, ×tamp)) {
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