Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(149)

Unified Diff: chrome/utility/importer/edge_importer_win.cc

Issue 1465853002: Implement support for importing favorites from Edge on Windows 10. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fixed nits and renamed importer registry overrider Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/utility/importer/edge_importer_win.cc
diff --git a/chrome/utility/importer/edge_importer_win.cc b/chrome/utility/importer/edge_importer_win.cc
new file mode 100644
index 0000000000000000000000000000000000000000..614f3276f46ef6284eff370440bc169879427ea0
--- /dev/null
+++ b/chrome/utility/importer/edge_importer_win.cc
@@ -0,0 +1,555 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/utility/importer/edge_importer_win.h"
+
+#define JET_UNICODE
+#include <esent.h>
+#undef JET_UNICODE
+#include <Shlobj.h>
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "base/win/windows_version.h"
+#include "chrome/common/importer/edge_importer_utils_win.h"
+#include "chrome/common/importer/imported_bookmark_entry.h"
+#include "chrome/common/importer/importer_bridge.h"
+#include "chrome/grit/generated_resources.h"
+#include "chrome/utility/importer/favicon_reencode.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+namespace {
+
+// Toolbar favorites are placed under this special folder name.
+const base::char16 kFavoritesBarTitle[] = L"_Favorites_Bar_";
+const base::char16 kSpartanDatabaseFile[] = L"spartan.edb";
+// This is an arbitary size chosen for the database error message buffer.
+const size_t kErrorMessageSize = 1024;
+
+class EdgeErrorObject {
ananta 2015/12/01 19:36:35 Please move the Edge database reading classes to t
forshaw 2015/12/02 18:21:54 Acknowledged.
+ public:
+ EdgeErrorObject() : last_error_(JET_errSuccess) {}
+
+ base::string16 GetErrorMessage() const {
+ WCHAR error_message[kErrorMessageSize] = {};
+ JET_API_PTR err = last_error_;
+ JET_ERR result = JetGetSystemParameter(
+ JET_instanceNil, JET_sesidNil, JET_paramErrorToString, &err,
+ error_message, sizeof(error_message));
+ if (result != JET_errSuccess)
+ return L"";
+
+ return error_message;
+ }
+
+ protected:
+ JET_ERR last_error() const { return last_error_; }
+
+ // This function returns true if the passed error parameter is equal
+ // to JET_errSuccess
+ bool SetLastError(JET_ERR error) {
+ last_error_ = error;
+ return error == JET_errSuccess;
+ }
+
+ private:
+
+ JET_ERR last_error_;
+
+ DISALLOW_COPY_AND_ASSIGN(EdgeErrorObject);
+};
+
+class EdgeDatabaseTableEnumerator : public EdgeErrorObject {
+ public:
+ EdgeDatabaseTableEnumerator(const base::string16& table_name,
+ JET_SESID session_id,
+ JET_TABLEID table_id)
+ : table_id_(table_id), table_name_(table_name), session_id_(session_id) {}
+
+ ~EdgeDatabaseTableEnumerator() {
+ if (table_id_ != JET_tableidNil)
+ JetCloseTable(session_id_, table_id_);
+ }
+
+ const base::string16& table_name() { return table_name_; }
+
+ bool Reset() {
+ return SetLastError(JetMove(session_id_, table_id_, JET_MoveFirst, 0));
+ }
+
+ bool Next() {
+ return SetLastError(JetMove(session_id_, table_id_, JET_MoveNext, 0));
+ }
+
+ template <typename T>
+ bool RetrieveColumn(const base::string16& column_name, T* value) {
+ const JET_COLUMNBASE& column_base = GetColumnByName(column_name);
+ if (column_base.cbMax == 0)
+ return false;
+ std::vector<uint8_t> column_data(column_base.cbMax);
+ unsigned long actual_size = 0;
+ JET_ERR err = JetRetrieveColumn(
+ session_id_, table_id_, column_base.columnid, &column_data[0],
+ column_data.size(), &actual_size, 0, nullptr);
+ SetLastError(err);
+ if (err != JET_errSuccess && err != JET_wrnColumnNull) {
+ return false;
+ }
+
+ if (err == JET_errSuccess) {
+ column_data.resize(actual_size);
+ if (!ValidateAndConvertValue(column_base.coltyp, column_data, value))
+ return false;
+ } else {
+ *value = T();
+ }
+
+ return true;
+ }
+
+ private:
+ template <typename T>
+ bool ValidateAndConvertValueGeneric(const JET_COLTYP match_column_type,
+ const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ T* value) {
+ if ((column_type == match_column_type) &&
+ (column_data.size() >= sizeof(T))) {
+ memcpy(value, &column_data[0], sizeof(T));
+ return true;
+ }
+ SetLastError(JET_errInvalidColumnType);
+ return false;
+ }
+
+ bool ValidateAndConvertValue(const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ bool* value) {
+ if ((column_type == JET_coltypBit) && (column_data.size() >= 1)) {
+ *value = (column_data[0] & 1) == 1;
+ return true;
+ }
+ SetLastError(JET_errInvalidColumnType);
+ return false;
+ }
+
+ bool ValidateAndConvertValue(const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ base::string16* value) {
+ if ((column_type == JET_coltypLongText) &&
+ ((column_data.size() % sizeof(base::char16)) == 0)) {
+ base::string16& value_ref = *value;
+ size_t char_length = column_data.size() / sizeof(base::char16);
+ value_ref.resize(char_length);
+ memcpy(&value_ref[0], &column_data[0], column_data.size());
+ // Remove any trailing NUL characters.
+ while (char_length > 0) {
+ if (value_ref[char_length - 1])
+ break;
+ char_length--;
+ }
+ value_ref.resize(char_length);
+ return true;
+ }
+ SetLastError(JET_errInvalidColumnType);
+ return false;
+ }
+
+ bool ValidateAndConvertValue(const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ GUID* value) {
+ return ValidateAndConvertValueGeneric(JET_coltypGUID, column_type,
+ column_data, value);
+ }
+
+ bool ValidateAndConvertValue(const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ int32_t* value) {
+ return ValidateAndConvertValueGeneric(JET_coltypLong, column_type,
+ column_data, value);
+ }
+
+ bool ValidateAndConvertValue(const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ int64_t* value) {
+ return ValidateAndConvertValueGeneric(JET_coltypLongLong, column_type,
+ column_data, value);
+ }
+
+ bool ValidateAndConvertValue(const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ FILETIME* value) {
+ return ValidateAndConvertValueGeneric(JET_coltypLongLong, column_type,
+ column_data, value);
+ }
+
+ bool ValidateAndConvertValue(const JET_COLTYP column_type,
+ const std::vector<uint8_t>& column_data,
+ uint32_t* value) {
+ return ValidateAndConvertValueGeneric(JET_coltypUnsignedLong, column_type,
+ column_data, value);
+ }
+
+ const JET_COLUMNBASE& GetColumnByName(const base::string16& column_name) {
+ auto found_col = columns_by_name_.find(column_name);
+ if (found_col == columns_by_name_.end()) {
+ JET_COLUMNBASE column_base = {};
+ column_base.cbStruct = sizeof(JET_COLUMNBASE);
+ if (!SetLastError(JetGetTableColumnInfo(
+ session_id_, table_id_, column_name.c_str(), &column_base,
+ sizeof(column_base), JET_ColInfoBase))) {
+ // 0 indicates an invalid column.
+ column_base.cbMax = 0;
+ }
+ columns_by_name_[column_name] = column_base;
+ found_col = columns_by_name_.find(column_name);
+ }
+ return found_col->second;
+ }
+
+ std::map<const base::string16, JET_COLUMNBASE> columns_by_name_;
+ JET_TABLEID table_id_;
+ base::string16 table_name_;
+ JET_SESID session_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(EdgeDatabaseTableEnumerator);
+};
+
+class EdgeDatabaseSession : public EdgeErrorObject {
+ public:
+ EdgeDatabaseSession()
+ : db_id_(JET_dbidNil),
+ instance_id_(JET_instanceNil),
+ session_id_(JET_sesidNil) {}
+
+ ~EdgeDatabaseSession() {
+ // We don't need to collect other ID handles, terminating instance
+ // is enough to shut the entire session down.
+ if (instance_id_ != JET_instanceNil)
+ JetTerm(instance_id_);
+ }
+
+ bool OpenDatabase(const base::string16& database_file) {
+ if (IsOpen()) {
+ SetLastError(JET_errOneDatabasePerSession);
+ return false;
+ }
+ if (!SetLastError(JetSetSystemParameter(nullptr, JET_sesidNil,
+ JET_paramDatabasePageSize,
+ kEdgeDatabasePageSize, nullptr)))
+ return false;
+ if (!SetLastError(JetCreateInstance(&instance_id_, L"EdgeDataImporter")))
+ return false;
+ if (!SetLastError(JetSetSystemParameter(&instance_id_, JET_sesidNil,
+ JET_paramRecovery, 0, L"Off")))
+ return false;
+ if (!SetLastError(JetInit(&instance_id_)))
+ return false;
+ if (!SetLastError(
+ JetBeginSession(instance_id_, &session_id_, nullptr, nullptr)))
+ return false;
+ if (!SetLastError(JetAttachDatabase2(session_id_, database_file.c_str(), 0,
+ JET_bitDbReadOnly)))
+ return false;
+ if (!SetLastError(JetOpenDatabase(session_id_, database_file.c_str(),
+ nullptr, &db_id_, JET_bitDbReadOnly)))
+ return false;
+ return true;
+ }
+
+ scoped_ptr<EdgeDatabaseTableEnumerator> OpenTableEnumerator(
+ const base::string16& table_name) {
+ JET_TABLEID table_id;
+
+ if (!IsOpen()) {
+ SetLastError(JET_errDatabaseNotFound);
+ return nullptr;
+ }
+
+ if (!SetLastError(JetOpenTable(session_id_, db_id_, table_name.c_str(),
+ nullptr, 0, JET_bitTableReadOnly,
+ &table_id)))
+ return nullptr;
+
+ return make_scoped_ptr(
+ new EdgeDatabaseTableEnumerator(table_name, session_id_, table_id));
+ }
+
+ private:
+ // This is the page size of the Edge data. It's unlikely to change.
+ static const JET_API_PTR kEdgeDatabasePageSize = 8192;
+
+ bool IsOpen() { return instance_id_ != JET_instanceNil; }
+
+ JET_DBID db_id_;
+ JET_INSTANCE instance_id_;
+ JET_SESID session_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(EdgeDatabaseSession);
+};
+
+struct EdgeFavoriteEntry {
+ EdgeFavoriteEntry()
+ : is_folder(false),
+ order_number(0),
+ item_id(GUID_NULL),
+ parent_id(GUID_NULL) {}
+
+ base::string16 title;
+ GURL url;
+ base::FilePath favicon_file;
+ bool is_folder;
+ int64_t order_number;
+ base::Time date_updated;
+ GUID item_id;
+ GUID parent_id;
+
+ std::vector<const EdgeFavoriteEntry*> children;
+
+ ImportedBookmarkEntry ToBookmarkEntry(
+ bool in_toolbar,
+ const std::vector<base::string16>& path) const {
+ ImportedBookmarkEntry entry;
+ entry.in_toolbar = in_toolbar;
+ entry.is_folder = is_folder;
+ entry.url = url;
+ entry.path = path;
+ entry.title = title;
+ entry.creation_time = date_updated;
+ return entry;
+ }
+};
+
+struct EdgeFavoriteEntryComparator {
+ bool operator()(const EdgeFavoriteEntry* lhs,
+ const EdgeFavoriteEntry* rhs) const {
+ return std::tie(lhs->order_number, lhs->title) <
+ std::tie(rhs->order_number, rhs->title);
+ }
+};
+
+// The name of the database file is spartan.edb, however it isn't clear how
+// the intermediate path between the DataStore and the database is generated.
+// Therefore we just do a simple recursive search until we find a matching name.
+base::FilePath FindSpartanDatabase(const base::FilePath& profile_path) {
+ base::FilePath data_path =
+ profile_path.empty() ? importer::GetEdgeDataFilePath() : profile_path;
+ if (data_path.empty())
+ return base::FilePath();
+
+ base::FileEnumerator enumerator(data_path.Append(L"DataStore\\Data"), true,
+ base::FileEnumerator::FILES);
+ base::FilePath path = enumerator.Next();
+ while (!path.empty()) {
+ if (base::EqualsCaseInsensitiveASCII(path.BaseName().value(),
+ kSpartanDatabaseFile))
+ return path;
+ path = enumerator.Next();
+ }
+ return base::FilePath();
+}
+
+struct GuidComparator {
+ bool operator()(const GUID& a, const GUID& b) const {
+ return memcmp(&a, &b, sizeof(a)) < 0;
+ }
+};
+
+bool ReadFaviconData(const base::FilePath& file,
+ std::vector<unsigned char>* data) {
+ std::string image_data;
+ if (!base::ReadFileToString(file, &image_data))
+ return false;
+
+ const unsigned char* ptr =
+ reinterpret_cast<const unsigned char*>(image_data.c_str());
+ return importer::ReencodeFavicon(ptr, image_data.size(), data);
+}
+
+void BuildBookmarkEntries(const EdgeFavoriteEntry& current_entry,
+ bool is_toolbar,
+ std::vector<ImportedBookmarkEntry>* bookmarks,
+ favicon_base::FaviconUsageDataList* favicons,
+ std::vector<base::string16>* path) {
+ for (const EdgeFavoriteEntry* entry : current_entry.children) {
+ if (entry->is_folder) {
+ // If the favorites bar then load all children as toolbar items.
+ if (base::EqualsCaseInsensitiveASCII(entry->title, kFavoritesBarTitle)) {
+ // Replace name with Links similar to IE.
+ path->push_back(L"Links");
+ BuildBookmarkEntries(*entry, true, bookmarks, favicons, path);
+ path->pop_back();
+ } else {
+ path->push_back(entry->title);
+ BuildBookmarkEntries(*entry, is_toolbar, bookmarks, favicons, path);
+ path->pop_back();
+ }
+ } else {
+ bookmarks->push_back(entry->ToBookmarkEntry(is_toolbar, *path));
+ favicon_base::FaviconUsageData favicon;
+ if (entry->url.is_valid() && !entry->favicon_file.empty() &&
+ ReadFaviconData(entry->favicon_file, &favicon.png_data)) {
+ // As the database doesn't provide us a favicon URL we'll fake one.
+ GURL::Replacements path_replace;
+ path_replace.SetPathStr("/favicon.ico");
+ favicon.favicon_url =
+ entry->url.GetWithEmptyPath().ReplaceComponents(path_replace);
+ favicon.urls.insert(entry->url);
+ favicons->push_back(favicon);
+ }
+ }
+ }
+}
+
+} // namespace
+
+EdgeImporter::EdgeImporter() {}
+
+void EdgeImporter::StartImport(const importer::SourceProfile& source_profile,
+ uint16 items,
+ ImporterBridge* bridge) {
+ bridge_ = bridge;
+ bridge_->NotifyStarted();
+ source_path_ = source_profile.source_path;
+
+ if ((items & importer::FAVORITES) && !cancelled()) {
+ bridge_->NotifyItemStarted(importer::FAVORITES);
+ ImportFavorites();
+ bridge_->NotifyItemEnded(importer::FAVORITES);
+ }
+ bridge_->NotifyEnded();
+}
+
+EdgeImporter::~EdgeImporter() {}
+
+void EdgeImporter::ImportFavorites() {
+ std::vector<ImportedBookmarkEntry> bookmarks;
+ favicon_base::FaviconUsageDataList favicons;
+ ParseFavoritesDatabase(&bookmarks, &favicons);
+
+ if (!bookmarks.empty() && !cancelled()) {
+ const base::string16& first_folder_name =
+ l10n_util::GetStringUTF16(IDS_BOOKMARK_GROUP_FROM_EDGE);
+ bridge_->AddBookmarks(bookmarks, first_folder_name);
+ }
+ if (!favicons.empty() && !cancelled())
+ bridge_->SetFavicons(favicons);
+}
+
+// From Edge 13 (released with Windows 10 TH2), Favorites are stored in a JET
+// database within the Edge local storage. The import uses the ESE library to
+// open and read the data file. The data is stored in a Favorites table with
+// the following schema.
+// Column Name Column Type
+// ------------------------------------------
+// DateUpdated LongLong - FILETIME
+// FaviconFile LongText - Relative path
+// HashedUrl ULong
+// IsDeleted Bit
+// IsFolder Bit
+// ItemId Guid
+// OrderNumber LongLong
+// ParentId Guid
+// RoamDisabled Bit
+// RowId Long
+// Title LongText
+// URL LongText
+void EdgeImporter::ParseFavoritesDatabase(
+ std::vector<ImportedBookmarkEntry>* bookmarks,
+ favicon_base::FaviconUsageDataList* favicons) {
+ base::FilePath database_path = FindSpartanDatabase(source_path_);
+ if (database_path.empty())
+ return;
+
+ EdgeDatabaseSession database;
+ if (!database.OpenDatabase(database_path.value())) {
+ DVLOG(1) << "Error opening database " << database.GetErrorMessage();
+ return;
+ }
+
+ scoped_ptr<EdgeDatabaseTableEnumerator> enumerator =
+ database.OpenTableEnumerator(L"Favorites");
+ if (!enumerator) {
+ DVLOG(1) << "Error opening database table "
+ << database.GetErrorMessage();
+ return;
+ }
+
+ if (!enumerator->Reset())
+ return;
+
+ std::map<GUID, EdgeFavoriteEntry, GuidComparator> database_entries;
+ base::FilePath favicon_base =
+ source_path_.empty() ? importer::GetEdgeDataFilePath() : source_path_;
+ favicon_base = favicon_base.Append(L"DataStore");
+
+ do {
+ EdgeFavoriteEntry entry;
+ bool is_deleted = false;
+ if (!enumerator->RetrieveColumn(L"IsDeleted", &is_deleted))
+ continue;
+ if (is_deleted)
+ continue;
+ if (!enumerator->RetrieveColumn(L"IsFolder", &entry.is_folder))
+ continue;
+ base::string16 url;
+ if (!enumerator->RetrieveColumn(L"URL", &url))
+ continue;
+ entry.url = GURL(url);
+ if (!entry.is_folder && !entry.url.is_valid())
+ continue;
+ if (!enumerator->RetrieveColumn(L"Title", &entry.title))
+ continue;
+ base::string16 favicon_file;
+ if (!enumerator->RetrieveColumn(L"FaviconFile", &favicon_file))
+ continue;
+ if (!favicon_file.empty())
+ entry.favicon_file = favicon_base.Append(favicon_file);
+ if (!enumerator->RetrieveColumn(L"ParentId", &entry.parent_id))
+ continue;
+ if (!enumerator->RetrieveColumn(L"ItemId", &entry.item_id))
+ continue;
+ if (!enumerator->RetrieveColumn(L"OrderNumber", &entry.order_number))
+ continue;
+ FILETIME data_updated;
+ if (!enumerator->RetrieveColumn(L"DateUpdated", &data_updated))
+ continue;
+ entry.date_updated = base::Time::FromFileTime(data_updated);
+ database_entries[entry.item_id] = entry;
+ } while (enumerator->Next() && !cancelled());
+
+ // Build simple tree.
+ EdgeFavoriteEntry root_entry;
+ for (auto& entry : database_entries) {
+ auto found_parent = database_entries.find(entry.second.parent_id);
+ if (found_parent == database_entries.end() ||
+ !found_parent->second.is_folder) {
+ root_entry.children.push_back(&entry.second);
+ } else {
+ found_parent->second.children.push_back(&entry.second);
+ }
+ }
+ // With tree built sort the children of each node including the root.
+ std::sort(root_entry.children.begin(), root_entry.children.end(),
+ EdgeFavoriteEntryComparator());
+ for (auto& entry : database_entries) {
+ std::sort(entry.second.children.begin(), entry.second.children.end(),
+ EdgeFavoriteEntryComparator());
+ }
+ std::vector<base::string16> path;
+ BuildBookmarkEntries(root_entry, false, bookmarks, favicons, &path);
+}

Powered by Google App Engine
This is Rietveld 408576698