| // 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" |
| |
| #include <Shlobj.h> |
| |
| #include <algorithm> |
| #include <map> |
| #include <memory> |
| #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/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/edge_database_reader_win.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"; |
| |
| 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_t 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; |
| |
| EdgeDatabaseReader database; |
| if (!database.OpenDatabase(database_path.value())) { |
| DVLOG(1) << "Error opening database " << database.GetErrorMessage(); |
| return; |
| } |
| |
| std::unique_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); |
| } |