1// Copyright 2013 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/utility/importer/firefox_importer.h"
6
7#include <set>
8
9#include "base/files/file_enumerator.h"
10#include "base/files/file_util.h"
11#include "base/json/json_file_value_serializer.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/message_loop/message_loop.h"
14#include "base/stl_util.h"
15#include "base/strings/string_util.h"
16#include "base/strings/utf_string_conversions.h"
17#include "chrome/common/importer/firefox_importer_utils.h"
18#include "chrome/common/importer/firefox_importer_utils.h"
19#include "chrome/common/importer/imported_bookmark_entry.h"
20#include "chrome/common/importer/imported_favicon_usage.h"
21#include "chrome/common/importer/importer_autofill_form_data_entry.h"
22#include "chrome/common/importer/importer_bridge.h"
23#include "chrome/common/importer/importer_url_row.h"
24#include "chrome/grit/generated_resources.h"
25#include "chrome/utility/importer/bookmark_html_reader.h"
26#include "chrome/utility/importer/favicon_reencode.h"
27#include "chrome/utility/importer/nss_decryptor.h"
28#include "components/autofill/core/common/password_form.h"
29#include "sql/connection.h"
30#include "sql/statement.h"
31#include "url/gurl.h"
32
33namespace {
34
35// Original definition is in http://mxr.mozilla.org/firefox/source/toolkit/
36//  components/places/public/nsINavBookmarksService.idl
37enum BookmarkItemType {
38  TYPE_BOOKMARK = 1,
39  TYPE_FOLDER = 2,
40  TYPE_SEPARATOR = 3,
41  TYPE_DYNAMIC_CONTAINER = 4
42};
43
44// Loads the default bookmarks in the Firefox installed at |app_path|,
45// and stores their locations in |urls|.
46void LoadDefaultBookmarks(const base::FilePath& app_path,
47                          std::set<GURL>* urls) {
48  base::FilePath file = app_path.AppendASCII("defaults")
49      .AppendASCII("profile")
50      .AppendASCII("bookmarks.html");
51  urls->clear();
52
53  std::vector<ImportedBookmarkEntry> bookmarks;
54  bookmark_html_reader::ImportBookmarksFile(base::Callback<bool(void)>(),
55                                            base::Callback<bool(const GURL&)>(),
56                                            file,
57                                            &bookmarks,
58                                            NULL);
59  for (size_t i = 0; i < bookmarks.size(); ++i)
60    urls->insert(bookmarks[i].url);
61}
62
63// Returns true if |url| has a valid scheme that we allow to import. We
64// filter out the URL with a unsupported scheme.
65bool CanImportURL(const GURL& url) {
66  // The URL is not valid.
67  if (!url.is_valid())
68    return false;
69
70  // Filter out the URLs with unsupported schemes.
71  const char* const kInvalidSchemes[] = {"wyciwyg", "place", "about", "chrome"};
72  for (size_t i = 0; i < arraysize(kInvalidSchemes); ++i) {
73    if (url.SchemeIs(kInvalidSchemes[i]))
74      return false;
75  }
76
77  return true;
78}
79
80}  // namespace
81
82struct FirefoxImporter::BookmarkItem {
83  int parent;
84  int id;
85  GURL url;
86  base::string16 title;
87  BookmarkItemType type;
88  std::string keyword;
89  base::Time date_added;
90  int64 favicon;
91  bool empty_folder;
92};
93
94FirefoxImporter::FirefoxImporter() {
95}
96
97FirefoxImporter::~FirefoxImporter() {
98}
99
100void FirefoxImporter::StartImport(
101    const importer::SourceProfile& source_profile,
102    uint16 items,
103    ImporterBridge* bridge) {
104  bridge_ = bridge;
105  source_path_ = source_profile.source_path;
106  app_path_ = source_profile.app_path;
107
108#if defined(OS_POSIX)
109  locale_ = source_profile.locale;
110#endif
111
112  // The order here is important!
113  bridge_->NotifyStarted();
114  if ((items & importer::HOME_PAGE) && !cancelled()) {
115    bridge_->NotifyItemStarted(importer::HOME_PAGE);
116    ImportHomepage();  // Doesn't have a UI item.
117    bridge_->NotifyItemEnded(importer::HOME_PAGE);
118  }
119
120  // Note history should be imported before bookmarks because bookmark import
121  // will also import favicons and we store favicon for a URL only if the URL
122  // exist in history or bookmarks.
123  if ((items & importer::HISTORY) && !cancelled()) {
124    bridge_->NotifyItemStarted(importer::HISTORY);
125    ImportHistory();
126    bridge_->NotifyItemEnded(importer::HISTORY);
127  }
128
129  if ((items & importer::FAVORITES) && !cancelled()) {
130    bridge_->NotifyItemStarted(importer::FAVORITES);
131    ImportBookmarks();
132    bridge_->NotifyItemEnded(importer::FAVORITES);
133  }
134  if ((items & importer::SEARCH_ENGINES) && !cancelled()) {
135    bridge_->NotifyItemStarted(importer::SEARCH_ENGINES);
136    ImportSearchEngines();
137    bridge_->NotifyItemEnded(importer::SEARCH_ENGINES);
138  }
139  if ((items & importer::PASSWORDS) && !cancelled()) {
140    bridge_->NotifyItemStarted(importer::PASSWORDS);
141    ImportPasswords();
142    bridge_->NotifyItemEnded(importer::PASSWORDS);
143  }
144  if ((items & importer::AUTOFILL_FORM_DATA) && !cancelled()) {
145    bridge_->NotifyItemStarted(importer::AUTOFILL_FORM_DATA);
146    ImportAutofillFormData();
147    bridge_->NotifyItemEnded(importer::AUTOFILL_FORM_DATA);
148  }
149  bridge_->NotifyEnded();
150}
151
152void FirefoxImporter::ImportHistory() {
153  base::FilePath file = source_path_.AppendASCII("places.sqlite");
154  if (!base::PathExists(file))
155    return;
156
157  sql::Connection db;
158  if (!db.Open(file))
159    return;
160
161  // |visit_type| represent the transition type of URLs (typed, click,
162  // redirect, bookmark, etc.) We eliminate some URLs like sub-frames and
163  // redirects, since we don't want them to appear in history.
164  // Firefox transition types are defined in:
165  //   toolkit/components/places/public/nsINavHistoryService.idl
166  const char* query = "SELECT h.url, h.title, h.visit_count, "
167                      "h.hidden, h.typed, v.visit_date "
168                      "FROM moz_places h JOIN moz_historyvisits v "
169                      "ON h.id = v.place_id "
170                      "WHERE v.visit_type <= 3";
171
172  sql::Statement s(db.GetUniqueStatement(query));
173
174  std::vector<ImporterURLRow> rows;
175  while (s.Step() && !cancelled()) {
176    GURL url(s.ColumnString(0));
177
178    // Filter out unwanted URLs.
179    if (!CanImportURL(url))
180      continue;
181
182    ImporterURLRow row(url);
183    row.title = s.ColumnString16(1);
184    row.visit_count = s.ColumnInt(2);
185    row.hidden = s.ColumnInt(3) == 1;
186    row.typed_count = s.ColumnInt(4);
187    row.last_visit = base::Time::FromTimeT(s.ColumnInt64(5)/1000000);
188
189    rows.push_back(row);
190  }
191
192  if (!rows.empty() && !cancelled())
193    bridge_->SetHistoryItems(rows, importer::VISIT_SOURCE_FIREFOX_IMPORTED);
194}
195
196void FirefoxImporter::ImportBookmarks() {
197  base::FilePath file = source_path_.AppendASCII("places.sqlite");
198  if (!base::PathExists(file))
199    return;
200
201  sql::Connection db;
202  if (!db.Open(file))
203    return;
204
205  // Get the bookmark folders that we are interested in.
206  int toolbar_folder_id = -1;
207  int menu_folder_id = -1;
208  int unsorted_folder_id = -1;
209  LoadRootNodeID(&db, &toolbar_folder_id, &menu_folder_id, &unsorted_folder_id);
210
211  // Load livemark IDs.
212  std::set<int> livemark_id;
213  LoadLivemarkIDs(&db, &livemark_id);
214
215  // Load the default bookmarks.
216  std::set<GURL> default_urls;
217  LoadDefaultBookmarks(app_path_, &default_urls);
218
219  BookmarkList list;
220  GetTopBookmarkFolder(&db, toolbar_folder_id, &list);
221  GetTopBookmarkFolder(&db, menu_folder_id, &list);
222  GetTopBookmarkFolder(&db, unsorted_folder_id, &list);
223  size_t count = list.size();
224  for (size_t i = 0; i < count; ++i)
225    GetWholeBookmarkFolder(&db, &list, i, NULL);
226
227  std::vector<ImportedBookmarkEntry> bookmarks;
228  std::vector<importer::URLKeywordInfo> url_keywords;
229  FaviconMap favicon_map;
230
231  // TODO(jcampan): http://b/issue?id=1196285 we do not support POST based
232  //                keywords yet.  We won't include them in the list.
233  std::set<int> post_keyword_ids;
234  const char* query = "SELECT b.id FROM moz_bookmarks b "
235      "INNER JOIN moz_items_annos ia ON ia.item_id = b.id "
236      "INNER JOIN moz_anno_attributes aa ON ia.anno_attribute_id = aa.id "
237      "WHERE aa.name = 'bookmarkProperties/POSTData'";
238  sql::Statement s(db.GetUniqueStatement(query));
239
240  if (!s.is_valid())
241    return;
242
243  while (s.Step() && !cancelled())
244    post_keyword_ids.insert(s.ColumnInt(0));
245
246  for (size_t i = 0; i < list.size(); ++i) {
247    BookmarkItem* item = list[i];
248
249    if (item->type == TYPE_FOLDER) {
250      // Folders are added implicitly on adding children, so we only explicitly
251      // add empty folders.
252      if (!item->empty_folder)
253        continue;
254    } else if (item->type == TYPE_BOOKMARK) {
255      // Import only valid bookmarks
256      if (!CanImportURL(item->url))
257        continue;
258    } else {
259      continue;
260    }
261
262    // Skip the default bookmarks and unwanted URLs.
263    if (default_urls.find(item->url) != default_urls.end() ||
264        post_keyword_ids.find(item->id) != post_keyword_ids.end())
265      continue;
266
267    // Find the bookmark path by tracing their links to parent folders.
268    std::vector<base::string16> path;
269    BookmarkItem* child = item;
270    bool found_path = false;
271    bool is_in_toolbar = false;
272    while (child->parent >= 0) {
273      BookmarkItem* parent = list[child->parent];
274      if (livemark_id.find(parent->id) != livemark_id.end()) {
275        // Don't import live bookmarks.
276        break;
277      }
278
279      if (parent->id != menu_folder_id) {
280        // To avoid excessive nesting, omit the name for the bookmarks menu
281        // folder.
282        path.insert(path.begin(), parent->title);
283      }
284
285      if (parent->id == toolbar_folder_id)
286        is_in_toolbar = true;
287
288      if (parent->id == toolbar_folder_id ||
289          parent->id == menu_folder_id ||
290          parent->id == unsorted_folder_id) {
291        // We've reached a root node, hooray!
292        found_path = true;
293        break;
294      }
295
296      child = parent;
297    }
298
299    if (!found_path)
300      continue;
301
302    ImportedBookmarkEntry entry;
303    entry.creation_time = item->date_added;
304    entry.title = item->title;
305    entry.url = item->url;
306    entry.path = path;
307    entry.in_toolbar = is_in_toolbar;
308    entry.is_folder = item->type == TYPE_FOLDER;
309
310    bookmarks.push_back(entry);
311
312    if (item->type == TYPE_BOOKMARK) {
313      if (item->favicon)
314        favicon_map[item->favicon].insert(item->url);
315
316      // This bookmark has a keyword, we should import it.
317      if (!item->keyword.empty() && item->url.is_valid()) {
318        importer::URLKeywordInfo url_keyword_info;
319        url_keyword_info.url = item->url;
320        url_keyword_info.keyword.assign(base::UTF8ToUTF16(item->keyword));
321        url_keyword_info.display_name = item->title;
322        url_keywords.push_back(url_keyword_info);
323      }
324    }
325  }
326
327  STLDeleteElements(&list);
328
329  // Write into profile.
330  if (!bookmarks.empty() && !cancelled()) {
331    const base::string16& first_folder_name =
332        bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP_FROM_FIREFOX);
333    bridge_->AddBookmarks(bookmarks, first_folder_name);
334  }
335  if (!url_keywords.empty() && !cancelled()) {
336    bridge_->SetKeywords(url_keywords, false);
337  }
338  if (!favicon_map.empty() && !cancelled()) {
339    std::vector<ImportedFaviconUsage> favicons;
340    LoadFavicons(&db, favicon_map, &favicons);
341    bridge_->SetFavicons(favicons);
342  }
343}
344
345void FirefoxImporter::ImportPasswords() {
346  // Initializes NSS3.
347  NSSDecryptor decryptor;
348  if (!decryptor.Init(source_path_, source_path_) &&
349      !decryptor.Init(app_path_, source_path_)) {
350    return;
351  }
352
353  std::vector<autofill::PasswordForm> forms;
354  base::FilePath source_path = source_path_;
355  base::FilePath file = source_path.AppendASCII("signons.sqlite");
356  if (base::PathExists(file)) {
357    // Since Firefox 3.1, passwords are in signons.sqlite db.
358    decryptor.ReadAndParseSignons(file, &forms);
359  } else {
360    // Firefox 3.0 uses signons3.txt to store the passwords.
361    file = source_path.AppendASCII("signons3.txt");
362    if (!base::PathExists(file))
363      file = source_path.AppendASCII("signons2.txt");
364
365    std::string content;
366    base::ReadFileToString(file, &content);
367    decryptor.ParseSignons(content, &forms);
368  }
369
370  if (!cancelled()) {
371    for (size_t i = 0; i < forms.size(); ++i) {
372      bridge_->SetPasswordForm(forms[i]);
373    }
374  }
375}
376
377void FirefoxImporter::ImportSearchEngines() {
378  std::vector<std::string> search_engine_data;
379  GetSearchEnginesXMLData(&search_engine_data);
380
381  bridge_->SetFirefoxSearchEnginesXMLData(search_engine_data);
382}
383
384void FirefoxImporter::ImportHomepage() {
385  GURL home_page = GetHomepage(source_path_);
386  if (home_page.is_valid() && !IsDefaultHomepage(home_page, app_path_)) {
387    bridge_->AddHomePage(home_page);
388  }
389}
390
391void FirefoxImporter::ImportAutofillFormData() {
392  base::FilePath file = source_path_.AppendASCII("formhistory.sqlite");
393  if (!base::PathExists(file))
394    return;
395
396  sql::Connection db;
397  if (!db.Open(file))
398    return;
399
400  const char query[] =
401      "SELECT fieldname, value, timesUsed, firstUsed, lastUsed FROM "
402      "moz_formhistory";
403
404  sql::Statement s(db.GetUniqueStatement(query));
405
406  std::vector<ImporterAutofillFormDataEntry> form_entries;
407  while (s.Step() && !cancelled()) {
408    ImporterAutofillFormDataEntry form_entry;
409    form_entry.name = s.ColumnString16(0);
410    form_entry.value = s.ColumnString16(1);
411    form_entry.times_used = s.ColumnInt(2);
412    form_entry.first_used = base::Time::FromTimeT(s.ColumnInt64(3) / 1000000);
413    form_entry.last_used = base::Time::FromTimeT(s.ColumnInt64(4) / 1000000);
414
415    // Don't import search bar history.
416    if (base::UTF16ToUTF8(form_entry.name) == "searchbar-history")
417      continue;
418
419    form_entries.push_back(form_entry);
420  }
421
422  if (!form_entries.empty() && !cancelled())
423    bridge_->SetAutofillFormData(form_entries);
424}
425
426void FirefoxImporter::GetSearchEnginesXMLData(
427    std::vector<std::string>* search_engine_data) {
428  base::FilePath file = source_path_.AppendASCII("search.sqlite");
429  if (!base::PathExists(file)) {
430    // Since Firefox 3.5, search engines are no longer stored in search.sqlite.
431    // Instead, search.json is used for storing search engines.
432    GetSearchEnginesXMLDataFromJSON(search_engine_data);
433    return;
434  }
435
436  sql::Connection db;
437  if (!db.Open(file))
438    return;
439
440  const char* query = "SELECT engineid FROM engine_data "
441                      "WHERE engineid NOT IN "
442                      "(SELECT engineid FROM engine_data "
443                      "WHERE name='hidden') "
444                      "ORDER BY value ASC";
445
446  sql::Statement s(db.GetUniqueStatement(query));
447  if (!s.is_valid())
448    return;
449
450  const base::FilePath searchplugins_path(FILE_PATH_LITERAL("searchplugins"));
451  // Search engine definitions are XMLs stored in two directories. Default
452  // engines are in the app directory (app_path_) and custom engines are
453  // in the profile directory (source_path_).
454
455  // Since Firefox 21, app_path_ engines are in 'browser' subdirectory:
456  base::FilePath app_path =
457      app_path_.AppendASCII("browser").Append(searchplugins_path);
458  if (!base::PathExists(app_path)) {
459    // This might be an older Firefox, try old location without the 'browser'
460    // path component:
461    app_path = app_path_.Append(searchplugins_path);
462  }
463
464  base::FilePath profile_path = source_path_.Append(searchplugins_path);
465
466  // Firefox doesn't store a search engine in its sqlite database unless the
467  // user has added a engine. So we get search engines from sqlite db as well
468  // as from the file system.
469  if (s.Step()) {
470    const std::string kAppPrefix("[app]/");
471    const std::string kProfilePrefix("[profile]/");
472    do {
473      base::FilePath file;
474      std::string engine(s.ColumnString(0));
475
476      // The string contains [app]/<name>.xml or [profile]/<name>.xml where
477      // the [app] and [profile] need to be replaced with the actual app or
478      // profile path.
479      size_t index = engine.find(kAppPrefix);
480      if (index != std::string::npos) {
481        // Remove '[app]/'.
482        file = app_path.AppendASCII(engine.substr(index + kAppPrefix.length()));
483      } else if ((index = engine.find(kProfilePrefix)) != std::string::npos) {
484        // Remove '[profile]/'.
485          file = profile_path.AppendASCII(
486              engine.substr(index + kProfilePrefix.length()));
487      } else {
488        // Looks like absolute path to the file.
489        file = base::FilePath::FromUTF8Unsafe(engine);
490      }
491      std::string file_data;
492      base::ReadFileToString(file, &file_data);
493      search_engine_data->push_back(file_data);
494    } while (s.Step() && !cancelled());
495  }
496
497#if defined(OS_POSIX)
498  // Ubuntu-flavored Firefox supports locale-specific search engines via
499  // locale-named subdirectories. They fall back to en-US.
500  // See http://crbug.com/53899
501  // TODO(jshin): we need to make sure our locale code matches that of
502  // Firefox.
503  DCHECK(!locale_.empty());
504  base::FilePath locale_app_path = app_path.AppendASCII(locale_);
505  base::FilePath default_locale_app_path = app_path.AppendASCII("en-US");
506  if (base::DirectoryExists(locale_app_path))
507    app_path = locale_app_path;
508  else if (base::DirectoryExists(default_locale_app_path))
509    app_path = default_locale_app_path;
510#endif
511
512  // Get search engine definition from file system.
513  base::FileEnumerator engines(app_path, false, base::FileEnumerator::FILES);
514  for (base::FilePath engine_path = engines.Next();
515       !engine_path.value().empty(); engine_path = engines.Next()) {
516    std::string file_data;
517    base::ReadFileToString(file, &file_data);
518    search_engine_data->push_back(file_data);
519  }
520}
521
522void FirefoxImporter::GetSearchEnginesXMLDataFromJSON(
523    std::vector<std::string>* search_engine_data) {
524  // search-metadata.json contains keywords for search engines. This
525  // file exists only if the user has set keywords for search engines.
526  base::FilePath search_metadata_json_file =
527      source_path_.AppendASCII("search-metadata.json");
528  JSONFileValueSerializer metadata_serializer(search_metadata_json_file);
529  scoped_ptr<base::Value> metadata_root(
530      metadata_serializer.Deserialize(NULL, NULL));
531  const base::DictionaryValue* search_metadata_root = NULL;
532  if (metadata_root)
533    metadata_root->GetAsDictionary(&search_metadata_root);
534
535  // search.json contains information about search engines to import.
536  base::FilePath search_json_file = source_path_.AppendASCII("search.json");
537  if (!base::PathExists(search_json_file))
538    return;
539
540  JSONFileValueSerializer serializer(search_json_file);
541  scoped_ptr<base::Value> root(serializer.Deserialize(NULL, NULL));
542  const base::DictionaryValue* search_root = NULL;
543  if (!root || !root->GetAsDictionary(&search_root))
544    return;
545
546  const std::string kDirectories("directories");
547  const base::DictionaryValue* search_directories = NULL;
548  if (!search_root->GetDictionary(kDirectories, &search_directories))
549    return;
550
551  // Dictionary |search_directories| contains a list of search engines
552  // (default and installed). The list can be found from key <engines>
553  // of the dictionary. Key <engines> is a grandchild of key <directories>.
554  // However, key <engines> parent's key is dynamic which depends on
555  // operating systems. For example,
556  //   Ubuntu (for default search engine):
557  //     /usr/lib/firefox/distribution/searchplugins/locale/en-US
558  //   Ubuntu (for installed search engines):
559  //     /home/<username>/.mozilla/firefox/lcd50n4n.default/searchplugins
560  //   Windows (for default search engine):
561  //     C:\\Program Files (x86)\\Mozilla Firefox\\browser\\searchplugins
562  // Therefore, it needs to be retrieved by searching.
563
564  for (base::DictionaryValue::Iterator it(*search_directories); !it.IsAtEnd();
565       it.Advance()) {
566    // The key of |it| may contains dot (.) which cannot be used as <key>
567    // for retrieving <engines>. Hence, it is needed to get |it| as dictionary.
568    // The resulted dictionary can be used for retrieving <engines>.
569    const std::string kEngines("engines");
570    const base::DictionaryValue* search_directory = NULL;
571    if (!it.value().GetAsDictionary(&search_directory))
572      continue;
573
574    const base::ListValue* search_engines = NULL;
575    if (!search_directory->GetList(kEngines, &search_engines))
576      continue;
577
578    const std::string kFilePath("filePath");
579    const std::string kHidden("_hidden");
580    for (size_t i = 0; i < search_engines->GetSize(); ++i) {
581      const base::DictionaryValue* engine_info = NULL;
582      if (!search_engines->GetDictionary(i, &engine_info))
583        continue;
584
585      bool is_hidden = false;
586      std::string file_path;
587      if (!engine_info->GetBoolean(kHidden, &is_hidden) ||
588          !engine_info->GetString(kFilePath, &file_path))
589        continue;
590
591      if (!is_hidden) {
592        const std::string kAppPrefix("[app]/");
593        const std::string kProfilePrefix("[profile]/");
594        base::FilePath xml_file = base::FilePath::FromUTF8Unsafe(file_path);
595
596        // If |file_path| contains [app] or [profile] then they need to be
597        // replaced with the actual app or profile path.
598        size_t index = file_path.find(kAppPrefix);
599        if (index != std::string::npos) {
600          // Replace '[app]/' with actual app path.
601          xml_file = app_path_.AppendASCII("searchplugins").AppendASCII(
602              file_path.substr(index + kAppPrefix.length()));
603        } else if ((index = file_path.find(kProfilePrefix)) !=
604                   std::string::npos) {
605          // Replace '[profile]/' with actual profile path.
606          xml_file = source_path_.AppendASCII("searchplugins").AppendASCII(
607              file_path.substr(index + kProfilePrefix.length()));
608        }
609
610        std::string file_data;
611        base::ReadFileToString(xml_file, &file_data);
612
613        // If a keyword is mentioned for this search engine, then add
614        // it to the XML string as an <Alias> element and use this updated
615        // string.
616        const base::DictionaryValue* search_xml_path = NULL;
617        if (search_metadata_root && search_metadata_root->HasKey(file_path) &&
618            search_metadata_root->GetDictionaryWithoutPathExpansion(
619                file_path, &search_xml_path)) {
620          std::string alias;
621          search_xml_path->GetString("alias", &alias);
622
623          // Add <Alias> element as the last child element.
624          size_t end_of_parent = file_data.find("</SearchPlugin>");
625          if (end_of_parent != std::string::npos && !alias.empty())
626            file_data.insert(end_of_parent, "<Alias>" + alias + "</Alias> \n");
627        }
628        search_engine_data->push_back(file_data);
629      }
630    }
631  }
632}
633
634void FirefoxImporter::LoadRootNodeID(sql::Connection* db,
635                                      int* toolbar_folder_id,
636                                      int* menu_folder_id,
637                                      int* unsorted_folder_id) {
638  static const char* kToolbarFolderName = "toolbar";
639  static const char* kMenuFolderName = "menu";
640  static const char* kUnsortedFolderName = "unfiled";
641
642  const char* query = "SELECT root_name, folder_id FROM moz_bookmarks_roots";
643  sql::Statement s(db->GetUniqueStatement(query));
644
645  while (s.Step()) {
646    std::string folder = s.ColumnString(0);
647    int id = s.ColumnInt(1);
648    if (folder == kToolbarFolderName)
649      *toolbar_folder_id = id;
650    else if (folder == kMenuFolderName)
651      *menu_folder_id = id;
652    else if (folder == kUnsortedFolderName)
653      *unsorted_folder_id = id;
654  }
655}
656
657void FirefoxImporter::LoadLivemarkIDs(sql::Connection* db,
658                                       std::set<int>* livemark) {
659  static const char* kFeedAnnotation = "livemark/feedURI";
660  livemark->clear();
661
662  const char* query = "SELECT b.item_id "
663                      "FROM moz_anno_attributes a "
664                      "JOIN moz_items_annos b ON a.id = b.anno_attribute_id "
665                      "WHERE a.name = ? ";
666  sql::Statement s(db->GetUniqueStatement(query));
667  s.BindString(0, kFeedAnnotation);
668
669  while (s.Step() && !cancelled())
670    livemark->insert(s.ColumnInt(0));
671}
672
673void FirefoxImporter::GetTopBookmarkFolder(sql::Connection* db,
674                                            int folder_id,
675                                            BookmarkList* list) {
676  const char* query = "SELECT b.title "
677                     "FROM moz_bookmarks b "
678                     "WHERE b.type = 2 AND b.id = ? "
679                     "ORDER BY b.position";
680  sql::Statement s(db->GetUniqueStatement(query));
681  s.BindInt(0, folder_id);
682
683  if (s.Step()) {
684    BookmarkItem* item = new BookmarkItem;
685    item->parent = -1;  // The top level folder has no parent.
686    item->id = folder_id;
687    item->title = s.ColumnString16(0);
688    item->type = TYPE_FOLDER;
689    item->favicon = 0;
690    item->empty_folder = true;
691    list->push_back(item);
692  }
693}
694
695void FirefoxImporter::GetWholeBookmarkFolder(sql::Connection* db,
696                                              BookmarkList* list,
697                                              size_t position,
698                                              bool* empty_folder) {
699  if (position >= list->size()) {
700    NOTREACHED();
701    return;
702  }
703
704  const char* query = "SELECT b.id, h.url, COALESCE(b.title, h.title), "
705         "b.type, k.keyword, b.dateAdded, h.favicon_id "
706         "FROM moz_bookmarks b "
707         "LEFT JOIN moz_places h ON b.fk = h.id "
708         "LEFT JOIN moz_keywords k ON k.id = b.keyword_id "
709         "WHERE b.type IN (1,2) AND b.parent = ? "
710         "ORDER BY b.position";
711  sql::Statement s(db->GetUniqueStatement(query));
712  s.BindInt(0, (*list)[position]->id);
713
714  BookmarkList temp_list;
715  while (s.Step()) {
716    BookmarkItem* item = new BookmarkItem;
717    item->parent = static_cast<int>(position);
718    item->id = s.ColumnInt(0);
719    item->url = GURL(s.ColumnString(1));
720    item->title = s.ColumnString16(2);
721    item->type = static_cast<BookmarkItemType>(s.ColumnInt(3));
722    item->keyword = s.ColumnString(4);
723    item->date_added = base::Time::FromTimeT(s.ColumnInt64(5)/1000000);
724    item->favicon = s.ColumnInt64(6);
725    item->empty_folder = true;
726
727    temp_list.push_back(item);
728    if (empty_folder != NULL)
729      *empty_folder = false;
730  }
731
732  // Appends all items to the list.
733  for (BookmarkList::iterator i = temp_list.begin();
734       i != temp_list.end(); ++i) {
735    list->push_back(*i);
736    // Recursive add bookmarks in sub-folders.
737    if ((*i)->type == TYPE_FOLDER)
738      GetWholeBookmarkFolder(db, list, list->size() - 1, &(*i)->empty_folder);
739  }
740}
741
742void FirefoxImporter::LoadFavicons(
743    sql::Connection* db,
744    const FaviconMap& favicon_map,
745    std::vector<ImportedFaviconUsage>* favicons) {
746  const char* query = "SELECT url, data FROM moz_favicons WHERE id=?";
747  sql::Statement s(db->GetUniqueStatement(query));
748
749  if (!s.is_valid())
750    return;
751
752  for (FaviconMap::const_iterator i = favicon_map.begin();
753       i != favicon_map.end(); ++i) {
754    s.BindInt64(0, i->first);
755    if (s.Step()) {
756      ImportedFaviconUsage usage;
757
758      usage.favicon_url = GURL(s.ColumnString(0));
759      if (!usage.favicon_url.is_valid())
760        continue;  // Don't bother importing favicons with invalid URLs.
761
762      std::vector<unsigned char> data;
763      s.ColumnBlobAsVector(1, &data);
764      if (data.empty())
765        continue;  // Data definitely invalid.
766
767      if (!importer::ReencodeFavicon(&data[0], data.size(), &usage.png_data))
768        continue;  // Unable to decode.
769
770      usage.urls = i->second;
771      favicons->push_back(usage);
772    }
773    s.Reset(true);
774  }
775}
776