chromium/chrome/utility/importer/edge_importer_win.cc

// Copyright 2015 The Chromium Authors
// 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/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.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 char16_t kFavoritesBarTitle[] = u"_Favorites_Bar_";
const wchar_t kSpartanDatabaseFile[] = L"spartan.edb";

struct EdgeFavoriteEntry {
  EdgeFavoriteEntry()
      : is_folder(false),
        order_number(0),
        item_id(GUID_NULL),
        parent_id(GUID_NULL) {}

  std::u16string 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<raw_ptr<const EdgeFavoriteEntry, VectorExperimental>> children;

  ImportedBookmarkEntry ToBookmarkEntry(
      bool in_toolbar,
      const std::vector<std::u16string>& 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<std::u16string>* 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(u"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 std::u16string& 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;

  base::FilePath log_folder = database_path.DirName().Append(L"LogFiles");

  EdgeDatabaseReader database;

  // If the log file directory does not exist, don't set the log_folder
  // attribute, as the open database operation will fail in such cases.
  // The log folder will usually not be present when running the unit tests.
  if (base::PathExists(log_folder))
    database.set_log_folder(log_folder);
  if (!database.OpenDatabase(database_path)) {
    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;
    std::u16string 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;
    std::u16string favicon_file;
    if (!enumerator->RetrieveColumn(L"FaviconFile", &favicon_file))
      continue;
    if (!favicon_file.empty()) {
      entry.favicon_file =
          favicon_base.Append(base::FilePath::FromUTF16Unsafe(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<std::u16string> path;
  BuildBookmarkEntries(root_entry, false, bookmarks, favicons, &path);
}