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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 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/edge_importer_win.h"
6
7 #define JET_UNICODE
8 #include <esent.h>
9 #undef JET_UNICODE
10 #include <Shlobj.h>
11
12 #include <algorithm>
13 #include <map>
14 #include <string>
15 #include <tuple>
16 #include <vector>
17
18 #include "base/files/file_enumerator.h"
19 #include "base/files/file_path.h"
20 #include "base/files/file_util.h"
21 #include "base/memory/ref_counted.h"
22 #include "base/memory/scoped_ptr.h"
23 #include "base/strings/string16.h"
24 #include "base/strings/string_util.h"
25 #include "base/time/time.h"
26 #include "base/win/windows_version.h"
27 #include "chrome/common/importer/edge_importer_utils_win.h"
28 #include "chrome/common/importer/imported_bookmark_entry.h"
29 #include "chrome/common/importer/importer_bridge.h"
30 #include "chrome/grit/generated_resources.h"
31 #include "chrome/utility/importer/favicon_reencode.h"
32 #include "ui/base/l10n/l10n_util.h"
33 #include "url/gurl.h"
34
35 namespace {
36
37 // Toolbar favorites are placed under this special folder name.
38 const base::char16 kFavoritesBarTitle[] = L"_Favorites_Bar_";
39 const base::char16 kSpartanDatabaseFile[] = L"spartan.edb";
40 // This is an arbitary size chosen for the database error message buffer.
41 const size_t kErrorMessageSize = 1024;
42
43 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.
44 public:
45 EdgeErrorObject() : last_error_(JET_errSuccess) {}
46
47 base::string16 GetErrorMessage() const {
48 WCHAR error_message[kErrorMessageSize] = {};
49 JET_API_PTR err = last_error_;
50 JET_ERR result = JetGetSystemParameter(
51 JET_instanceNil, JET_sesidNil, JET_paramErrorToString, &err,
52 error_message, sizeof(error_message));
53 if (result != JET_errSuccess)
54 return L"";
55
56 return error_message;
57 }
58
59 protected:
60 JET_ERR last_error() const { return last_error_; }
61
62 // This function returns true if the passed error parameter is equal
63 // to JET_errSuccess
64 bool SetLastError(JET_ERR error) {
65 last_error_ = error;
66 return error == JET_errSuccess;
67 }
68
69 private:
70
71 JET_ERR last_error_;
72
73 DISALLOW_COPY_AND_ASSIGN(EdgeErrorObject);
74 };
75
76 class EdgeDatabaseTableEnumerator : public EdgeErrorObject {
77 public:
78 EdgeDatabaseTableEnumerator(const base::string16& table_name,
79 JET_SESID session_id,
80 JET_TABLEID table_id)
81 : table_id_(table_id), table_name_(table_name), session_id_(session_id) {}
82
83 ~EdgeDatabaseTableEnumerator() {
84 if (table_id_ != JET_tableidNil)
85 JetCloseTable(session_id_, table_id_);
86 }
87
88 const base::string16& table_name() { return table_name_; }
89
90 bool Reset() {
91 return SetLastError(JetMove(session_id_, table_id_, JET_MoveFirst, 0));
92 }
93
94 bool Next() {
95 return SetLastError(JetMove(session_id_, table_id_, JET_MoveNext, 0));
96 }
97
98 template <typename T>
99 bool RetrieveColumn(const base::string16& column_name, T* value) {
100 const JET_COLUMNBASE& column_base = GetColumnByName(column_name);
101 if (column_base.cbMax == 0)
102 return false;
103 std::vector<uint8_t> column_data(column_base.cbMax);
104 unsigned long actual_size = 0;
105 JET_ERR err = JetRetrieveColumn(
106 session_id_, table_id_, column_base.columnid, &column_data[0],
107 column_data.size(), &actual_size, 0, nullptr);
108 SetLastError(err);
109 if (err != JET_errSuccess && err != JET_wrnColumnNull) {
110 return false;
111 }
112
113 if (err == JET_errSuccess) {
114 column_data.resize(actual_size);
115 if (!ValidateAndConvertValue(column_base.coltyp, column_data, value))
116 return false;
117 } else {
118 *value = T();
119 }
120
121 return true;
122 }
123
124 private:
125 template <typename T>
126 bool ValidateAndConvertValueGeneric(const JET_COLTYP match_column_type,
127 const JET_COLTYP column_type,
128 const std::vector<uint8_t>& column_data,
129 T* value) {
130 if ((column_type == match_column_type) &&
131 (column_data.size() >= sizeof(T))) {
132 memcpy(value, &column_data[0], sizeof(T));
133 return true;
134 }
135 SetLastError(JET_errInvalidColumnType);
136 return false;
137 }
138
139 bool ValidateAndConvertValue(const JET_COLTYP column_type,
140 const std::vector<uint8_t>& column_data,
141 bool* value) {
142 if ((column_type == JET_coltypBit) && (column_data.size() >= 1)) {
143 *value = (column_data[0] & 1) == 1;
144 return true;
145 }
146 SetLastError(JET_errInvalidColumnType);
147 return false;
148 }
149
150 bool ValidateAndConvertValue(const JET_COLTYP column_type,
151 const std::vector<uint8_t>& column_data,
152 base::string16* value) {
153 if ((column_type == JET_coltypLongText) &&
154 ((column_data.size() % sizeof(base::char16)) == 0)) {
155 base::string16& value_ref = *value;
156 size_t char_length = column_data.size() / sizeof(base::char16);
157 value_ref.resize(char_length);
158 memcpy(&value_ref[0], &column_data[0], column_data.size());
159 // Remove any trailing NUL characters.
160 while (char_length > 0) {
161 if (value_ref[char_length - 1])
162 break;
163 char_length--;
164 }
165 value_ref.resize(char_length);
166 return true;
167 }
168 SetLastError(JET_errInvalidColumnType);
169 return false;
170 }
171
172 bool ValidateAndConvertValue(const JET_COLTYP column_type,
173 const std::vector<uint8_t>& column_data,
174 GUID* value) {
175 return ValidateAndConvertValueGeneric(JET_coltypGUID, column_type,
176 column_data, value);
177 }
178
179 bool ValidateAndConvertValue(const JET_COLTYP column_type,
180 const std::vector<uint8_t>& column_data,
181 int32_t* value) {
182 return ValidateAndConvertValueGeneric(JET_coltypLong, column_type,
183 column_data, value);
184 }
185
186 bool ValidateAndConvertValue(const JET_COLTYP column_type,
187 const std::vector<uint8_t>& column_data,
188 int64_t* value) {
189 return ValidateAndConvertValueGeneric(JET_coltypLongLong, column_type,
190 column_data, value);
191 }
192
193 bool ValidateAndConvertValue(const JET_COLTYP column_type,
194 const std::vector<uint8_t>& column_data,
195 FILETIME* value) {
196 return ValidateAndConvertValueGeneric(JET_coltypLongLong, column_type,
197 column_data, value);
198 }
199
200 bool ValidateAndConvertValue(const JET_COLTYP column_type,
201 const std::vector<uint8_t>& column_data,
202 uint32_t* value) {
203 return ValidateAndConvertValueGeneric(JET_coltypUnsignedLong, column_type,
204 column_data, value);
205 }
206
207 const JET_COLUMNBASE& GetColumnByName(const base::string16& column_name) {
208 auto found_col = columns_by_name_.find(column_name);
209 if (found_col == columns_by_name_.end()) {
210 JET_COLUMNBASE column_base = {};
211 column_base.cbStruct = sizeof(JET_COLUMNBASE);
212 if (!SetLastError(JetGetTableColumnInfo(
213 session_id_, table_id_, column_name.c_str(), &column_base,
214 sizeof(column_base), JET_ColInfoBase))) {
215 // 0 indicates an invalid column.
216 column_base.cbMax = 0;
217 }
218 columns_by_name_[column_name] = column_base;
219 found_col = columns_by_name_.find(column_name);
220 }
221 return found_col->second;
222 }
223
224 std::map<const base::string16, JET_COLUMNBASE> columns_by_name_;
225 JET_TABLEID table_id_;
226 base::string16 table_name_;
227 JET_SESID session_id_;
228
229 DISALLOW_COPY_AND_ASSIGN(EdgeDatabaseTableEnumerator);
230 };
231
232 class EdgeDatabaseSession : public EdgeErrorObject {
233 public:
234 EdgeDatabaseSession()
235 : db_id_(JET_dbidNil),
236 instance_id_(JET_instanceNil),
237 session_id_(JET_sesidNil) {}
238
239 ~EdgeDatabaseSession() {
240 // We don't need to collect other ID handles, terminating instance
241 // is enough to shut the entire session down.
242 if (instance_id_ != JET_instanceNil)
243 JetTerm(instance_id_);
244 }
245
246 bool OpenDatabase(const base::string16& database_file) {
247 if (IsOpen()) {
248 SetLastError(JET_errOneDatabasePerSession);
249 return false;
250 }
251 if (!SetLastError(JetSetSystemParameter(nullptr, JET_sesidNil,
252 JET_paramDatabasePageSize,
253 kEdgeDatabasePageSize, nullptr)))
254 return false;
255 if (!SetLastError(JetCreateInstance(&instance_id_, L"EdgeDataImporter")))
256 return false;
257 if (!SetLastError(JetSetSystemParameter(&instance_id_, JET_sesidNil,
258 JET_paramRecovery, 0, L"Off")))
259 return false;
260 if (!SetLastError(JetInit(&instance_id_)))
261 return false;
262 if (!SetLastError(
263 JetBeginSession(instance_id_, &session_id_, nullptr, nullptr)))
264 return false;
265 if (!SetLastError(JetAttachDatabase2(session_id_, database_file.c_str(), 0,
266 JET_bitDbReadOnly)))
267 return false;
268 if (!SetLastError(JetOpenDatabase(session_id_, database_file.c_str(),
269 nullptr, &db_id_, JET_bitDbReadOnly)))
270 return false;
271 return true;
272 }
273
274 scoped_ptr<EdgeDatabaseTableEnumerator> OpenTableEnumerator(
275 const base::string16& table_name) {
276 JET_TABLEID table_id;
277
278 if (!IsOpen()) {
279 SetLastError(JET_errDatabaseNotFound);
280 return nullptr;
281 }
282
283 if (!SetLastError(JetOpenTable(session_id_, db_id_, table_name.c_str(),
284 nullptr, 0, JET_bitTableReadOnly,
285 &table_id)))
286 return nullptr;
287
288 return make_scoped_ptr(
289 new EdgeDatabaseTableEnumerator(table_name, session_id_, table_id));
290 }
291
292 private:
293 // This is the page size of the Edge data. It's unlikely to change.
294 static const JET_API_PTR kEdgeDatabasePageSize = 8192;
295
296 bool IsOpen() { return instance_id_ != JET_instanceNil; }
297
298 JET_DBID db_id_;
299 JET_INSTANCE instance_id_;
300 JET_SESID session_id_;
301
302 DISALLOW_COPY_AND_ASSIGN(EdgeDatabaseSession);
303 };
304
305 struct EdgeFavoriteEntry {
306 EdgeFavoriteEntry()
307 : is_folder(false),
308 order_number(0),
309 item_id(GUID_NULL),
310 parent_id(GUID_NULL) {}
311
312 base::string16 title;
313 GURL url;
314 base::FilePath favicon_file;
315 bool is_folder;
316 int64_t order_number;
317 base::Time date_updated;
318 GUID item_id;
319 GUID parent_id;
320
321 std::vector<const EdgeFavoriteEntry*> children;
322
323 ImportedBookmarkEntry ToBookmarkEntry(
324 bool in_toolbar,
325 const std::vector<base::string16>& path) const {
326 ImportedBookmarkEntry entry;
327 entry.in_toolbar = in_toolbar;
328 entry.is_folder = is_folder;
329 entry.url = url;
330 entry.path = path;
331 entry.title = title;
332 entry.creation_time = date_updated;
333 return entry;
334 }
335 };
336
337 struct EdgeFavoriteEntryComparator {
338 bool operator()(const EdgeFavoriteEntry* lhs,
339 const EdgeFavoriteEntry* rhs) const {
340 return std::tie(lhs->order_number, lhs->title) <
341 std::tie(rhs->order_number, rhs->title);
342 }
343 };
344
345 // The name of the database file is spartan.edb, however it isn't clear how
346 // the intermediate path between the DataStore and the database is generated.
347 // Therefore we just do a simple recursive search until we find a matching name.
348 base::FilePath FindSpartanDatabase(const base::FilePath& profile_path) {
349 base::FilePath data_path =
350 profile_path.empty() ? importer::GetEdgeDataFilePath() : profile_path;
351 if (data_path.empty())
352 return base::FilePath();
353
354 base::FileEnumerator enumerator(data_path.Append(L"DataStore\\Data"), true,
355 base::FileEnumerator::FILES);
356 base::FilePath path = enumerator.Next();
357 while (!path.empty()) {
358 if (base::EqualsCaseInsensitiveASCII(path.BaseName().value(),
359 kSpartanDatabaseFile))
360 return path;
361 path = enumerator.Next();
362 }
363 return base::FilePath();
364 }
365
366 struct GuidComparator {
367 bool operator()(const GUID& a, const GUID& b) const {
368 return memcmp(&a, &b, sizeof(a)) < 0;
369 }
370 };
371
372 bool ReadFaviconData(const base::FilePath& file,
373 std::vector<unsigned char>* data) {
374 std::string image_data;
375 if (!base::ReadFileToString(file, &image_data))
376 return false;
377
378 const unsigned char* ptr =
379 reinterpret_cast<const unsigned char*>(image_data.c_str());
380 return importer::ReencodeFavicon(ptr, image_data.size(), data);
381 }
382
383 void BuildBookmarkEntries(const EdgeFavoriteEntry& current_entry,
384 bool is_toolbar,
385 std::vector<ImportedBookmarkEntry>* bookmarks,
386 favicon_base::FaviconUsageDataList* favicons,
387 std::vector<base::string16>* path) {
388 for (const EdgeFavoriteEntry* entry : current_entry.children) {
389 if (entry->is_folder) {
390 // If the favorites bar then load all children as toolbar items.
391 if (base::EqualsCaseInsensitiveASCII(entry->title, kFavoritesBarTitle)) {
392 // Replace name with Links similar to IE.
393 path->push_back(L"Links");
394 BuildBookmarkEntries(*entry, true, bookmarks, favicons, path);
395 path->pop_back();
396 } else {
397 path->push_back(entry->title);
398 BuildBookmarkEntries(*entry, is_toolbar, bookmarks, favicons, path);
399 path->pop_back();
400 }
401 } else {
402 bookmarks->push_back(entry->ToBookmarkEntry(is_toolbar, *path));
403 favicon_base::FaviconUsageData favicon;
404 if (entry->url.is_valid() && !entry->favicon_file.empty() &&
405 ReadFaviconData(entry->favicon_file, &favicon.png_data)) {
406 // As the database doesn't provide us a favicon URL we'll fake one.
407 GURL::Replacements path_replace;
408 path_replace.SetPathStr("/favicon.ico");
409 favicon.favicon_url =
410 entry->url.GetWithEmptyPath().ReplaceComponents(path_replace);
411 favicon.urls.insert(entry->url);
412 favicons->push_back(favicon);
413 }
414 }
415 }
416 }
417
418 } // namespace
419
420 EdgeImporter::EdgeImporter() {}
421
422 void EdgeImporter::StartImport(const importer::SourceProfile& source_profile,
423 uint16 items,
424 ImporterBridge* bridge) {
425 bridge_ = bridge;
426 bridge_->NotifyStarted();
427 source_path_ = source_profile.source_path;
428
429 if ((items & importer::FAVORITES) && !cancelled()) {
430 bridge_->NotifyItemStarted(importer::FAVORITES);
431 ImportFavorites();
432 bridge_->NotifyItemEnded(importer::FAVORITES);
433 }
434 bridge_->NotifyEnded();
435 }
436
437 EdgeImporter::~EdgeImporter() {}
438
439 void EdgeImporter::ImportFavorites() {
440 std::vector<ImportedBookmarkEntry> bookmarks;
441 favicon_base::FaviconUsageDataList favicons;
442 ParseFavoritesDatabase(&bookmarks, &favicons);
443
444 if (!bookmarks.empty() && !cancelled()) {
445 const base::string16& first_folder_name =
446 l10n_util::GetStringUTF16(IDS_BOOKMARK_GROUP_FROM_EDGE);
447 bridge_->AddBookmarks(bookmarks, first_folder_name);
448 }
449 if (!favicons.empty() && !cancelled())
450 bridge_->SetFavicons(favicons);
451 }
452
453 // From Edge 13 (released with Windows 10 TH2), Favorites are stored in a JET
454 // database within the Edge local storage. The import uses the ESE library to
455 // open and read the data file. The data is stored in a Favorites table with
456 // the following schema.
457 // Column Name Column Type
458 // ------------------------------------------
459 // DateUpdated LongLong - FILETIME
460 // FaviconFile LongText - Relative path
461 // HashedUrl ULong
462 // IsDeleted Bit
463 // IsFolder Bit
464 // ItemId Guid
465 // OrderNumber LongLong
466 // ParentId Guid
467 // RoamDisabled Bit
468 // RowId Long
469 // Title LongText
470 // URL LongText
471 void EdgeImporter::ParseFavoritesDatabase(
472 std::vector<ImportedBookmarkEntry>* bookmarks,
473 favicon_base::FaviconUsageDataList* favicons) {
474 base::FilePath database_path = FindSpartanDatabase(source_path_);
475 if (database_path.empty())
476 return;
477
478 EdgeDatabaseSession database;
479 if (!database.OpenDatabase(database_path.value())) {
480 DVLOG(1) << "Error opening database " << database.GetErrorMessage();
481 return;
482 }
483
484 scoped_ptr<EdgeDatabaseTableEnumerator> enumerator =
485 database.OpenTableEnumerator(L"Favorites");
486 if (!enumerator) {
487 DVLOG(1) << "Error opening database table "
488 << database.GetErrorMessage();
489 return;
490 }
491
492 if (!enumerator->Reset())
493 return;
494
495 std::map<GUID, EdgeFavoriteEntry, GuidComparator> database_entries;
496 base::FilePath favicon_base =
497 source_path_.empty() ? importer::GetEdgeDataFilePath() : source_path_;
498 favicon_base = favicon_base.Append(L"DataStore");
499
500 do {
501 EdgeFavoriteEntry entry;
502 bool is_deleted = false;
503 if (!enumerator->RetrieveColumn(L"IsDeleted", &is_deleted))
504 continue;
505 if (is_deleted)
506 continue;
507 if (!enumerator->RetrieveColumn(L"IsFolder", &entry.is_folder))
508 continue;
509 base::string16 url;
510 if (!enumerator->RetrieveColumn(L"URL", &url))
511 continue;
512 entry.url = GURL(url);
513 if (!entry.is_folder && !entry.url.is_valid())
514 continue;
515 if (!enumerator->RetrieveColumn(L"Title", &entry.title))
516 continue;
517 base::string16 favicon_file;
518 if (!enumerator->RetrieveColumn(L"FaviconFile", &favicon_file))
519 continue;
520 if (!favicon_file.empty())
521 entry.favicon_file = favicon_base.Append(favicon_file);
522 if (!enumerator->RetrieveColumn(L"ParentId", &entry.parent_id))
523 continue;
524 if (!enumerator->RetrieveColumn(L"ItemId", &entry.item_id))
525 continue;
526 if (!enumerator->RetrieveColumn(L"OrderNumber", &entry.order_number))
527 continue;
528 FILETIME data_updated;
529 if (!enumerator->RetrieveColumn(L"DateUpdated", &data_updated))
530 continue;
531 entry.date_updated = base::Time::FromFileTime(data_updated);
532 database_entries[entry.item_id] = entry;
533 } while (enumerator->Next() && !cancelled());
534
535 // Build simple tree.
536 EdgeFavoriteEntry root_entry;
537 for (auto& entry : database_entries) {
538 auto found_parent = database_entries.find(entry.second.parent_id);
539 if (found_parent == database_entries.end() ||
540 !found_parent->second.is_folder) {
541 root_entry.children.push_back(&entry.second);
542 } else {
543 found_parent->second.children.push_back(&entry.second);
544 }
545 }
546 // With tree built sort the children of each node including the root.
547 std::sort(root_entry.children.begin(), root_entry.children.end(),
548 EdgeFavoriteEntryComparator());
549 for (auto& entry : database_entries) {
550 std::sort(entry.second.children.begin(), entry.second.children.end(),
551 EdgeFavoriteEntryComparator());
552 }
553 std::vector<base::string16> path;
554 BuildBookmarkEntries(root_entry, false, bookmarks, favicons, &path);
555 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698