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 |