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