OLD | NEW |
---|---|
(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 } | |
OLD | NEW |