| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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 <Cocoa/Cocoa.h> | |
| 6 | |
| 7 #include "chrome/browser/importer/safari_importer.h" | |
| 8 | |
| 9 #include <map> | |
| 10 #include <vector> | |
| 11 | |
| 12 #include "base/file_util.h" | |
| 13 #include "base/mac/mac_util.h" | |
| 14 #include "base/strings/string16.h" | |
| 15 #include "base/strings/sys_string_conversions.h" | |
| 16 #include "base/strings/utf_string_conversions.h" | |
| 17 #include "base/time/time.h" | |
| 18 #include "chrome/browser/favicon/favicon_util.h" | |
| 19 #include "chrome/browser/importer/importer_bridge.h" | |
| 20 #include "chrome/common/importer/imported_bookmark_entry.h" | |
| 21 #include "chrome/common/importer/imported_favicon_usage.h" | |
| 22 #include "chrome/common/url_constants.h" | |
| 23 #include "grit/generated_resources.h" | |
| 24 #include "net/base/data_url.h" | |
| 25 #include "sql/statement.h" | |
| 26 #include "url/gurl.h" | |
| 27 | |
| 28 namespace { | |
| 29 | |
| 30 // A function like this is used by other importers in order to filter out | |
| 31 // URLS we don't want to import. | |
| 32 // For now it's pretty basic, but I've split it out so it's easy to slot | |
| 33 // in necessary logic for filtering URLS, should we need it. | |
| 34 bool CanImportSafariURL(const GURL& url) { | |
| 35 // The URL is not valid. | |
| 36 if (!url.is_valid()) | |
| 37 return false; | |
| 38 | |
| 39 return true; | |
| 40 } | |
| 41 | |
| 42 } // namespace | |
| 43 | |
| 44 SafariImporter::SafariImporter(const base::FilePath& library_dir) | |
| 45 : library_dir_(library_dir) { | |
| 46 } | |
| 47 | |
| 48 SafariImporter::~SafariImporter() { | |
| 49 } | |
| 50 | |
| 51 // static | |
| 52 bool SafariImporter::CanImport(const base::FilePath& library_dir, | |
| 53 uint16* services_supported) { | |
| 54 DCHECK(services_supported); | |
| 55 *services_supported = importer::NONE; | |
| 56 | |
| 57 // Import features are toggled by the following: | |
| 58 // bookmarks import: existence of ~/Library/Safari/Bookmarks.plist file. | |
| 59 // history import: existence of ~/Library/Safari/History.plist file. | |
| 60 base::FilePath safari_dir = library_dir.Append("Safari"); | |
| 61 base::FilePath bookmarks_path = safari_dir.Append("Bookmarks.plist"); | |
| 62 base::FilePath history_path = safari_dir.Append("History.plist"); | |
| 63 | |
| 64 if (file_util::PathExists(bookmarks_path)) | |
| 65 *services_supported |= importer::FAVORITES; | |
| 66 if (file_util::PathExists(history_path)) | |
| 67 *services_supported |= importer::HISTORY; | |
| 68 | |
| 69 return *services_supported != importer::NONE; | |
| 70 } | |
| 71 | |
| 72 void SafariImporter::StartImport(const importer::SourceProfile& source_profile, | |
| 73 uint16 items, | |
| 74 ImporterBridge* bridge) { | |
| 75 bridge_ = bridge; | |
| 76 // The order here is important! | |
| 77 bridge_->NotifyStarted(); | |
| 78 | |
| 79 // In keeping with import on other platforms (and for other browsers), we | |
| 80 // don't import the home page (since it may lead to a useless homepage); see | |
| 81 // crbug.com/25603. | |
| 82 if ((items & importer::HISTORY) && !cancelled()) { | |
| 83 bridge_->NotifyItemStarted(importer::HISTORY); | |
| 84 ImportHistory(); | |
| 85 bridge_->NotifyItemEnded(importer::HISTORY); | |
| 86 } | |
| 87 if ((items & importer::FAVORITES) && !cancelled()) { | |
| 88 bridge_->NotifyItemStarted(importer::FAVORITES); | |
| 89 ImportBookmarks(); | |
| 90 bridge_->NotifyItemEnded(importer::FAVORITES); | |
| 91 } | |
| 92 if ((items & importer::PASSWORDS) && !cancelled()) { | |
| 93 bridge_->NotifyItemStarted(importer::PASSWORDS); | |
| 94 ImportPasswords(); | |
| 95 bridge_->NotifyItemEnded(importer::PASSWORDS); | |
| 96 } | |
| 97 bridge_->NotifyEnded(); | |
| 98 } | |
| 99 | |
| 100 void SafariImporter::ImportBookmarks() { | |
| 101 string16 toolbar_name = | |
| 102 bridge_->GetLocalizedString(IDS_BOOKMARK_BAR_FOLDER_NAME); | |
| 103 std::vector<ImportedBookmarkEntry> bookmarks; | |
| 104 ParseBookmarks(toolbar_name, &bookmarks); | |
| 105 | |
| 106 // Write bookmarks into profile. | |
| 107 if (!bookmarks.empty() && !cancelled()) { | |
| 108 const string16& first_folder_name = | |
| 109 bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP_FROM_SAFARI); | |
| 110 bridge_->AddBookmarks(bookmarks, first_folder_name); | |
| 111 } | |
| 112 | |
| 113 // Import favicons. | |
| 114 sql::Connection db; | |
| 115 if (!OpenDatabase(&db)) | |
| 116 return; | |
| 117 | |
| 118 FaviconMap favicon_map; | |
| 119 ImportFaviconURLs(&db, &favicon_map); | |
| 120 // Write favicons into profile. | |
| 121 if (!favicon_map.empty() && !cancelled()) { | |
| 122 std::vector<ImportedFaviconUsage> favicons; | |
| 123 LoadFaviconData(&db, favicon_map, &favicons); | |
| 124 bridge_->SetFavicons(favicons); | |
| 125 } | |
| 126 } | |
| 127 | |
| 128 bool SafariImporter::OpenDatabase(sql::Connection* db) { | |
| 129 // Construct ~/Library/Safari/WebIcons.db path. | |
| 130 NSString* library_dir = [NSString | |
| 131 stringWithUTF8String:library_dir_.value().c_str()]; | |
| 132 NSString* safari_dir = [library_dir | |
| 133 stringByAppendingPathComponent:@"Safari"]; | |
| 134 NSString* favicons_db_path = [safari_dir | |
| 135 stringByAppendingPathComponent:@"WebpageIcons.db"]; | |
| 136 | |
| 137 const char* db_path = [favicons_db_path fileSystemRepresentation]; | |
| 138 return db->Open(base::FilePath(db_path)); | |
| 139 } | |
| 140 | |
| 141 void SafariImporter::ImportFaviconURLs(sql::Connection* db, | |
| 142 FaviconMap* favicon_map) { | |
| 143 const char* query = "SELECT iconID, url FROM PageURL;"; | |
| 144 sql::Statement s(db->GetUniqueStatement(query)); | |
| 145 | |
| 146 while (s.Step() && !cancelled()) { | |
| 147 int64 icon_id = s.ColumnInt64(0); | |
| 148 GURL url = GURL(s.ColumnString(1)); | |
| 149 (*favicon_map)[icon_id].insert(url); | |
| 150 } | |
| 151 } | |
| 152 | |
| 153 void SafariImporter::LoadFaviconData( | |
| 154 sql::Connection* db, | |
| 155 const FaviconMap& favicon_map, | |
| 156 std::vector<ImportedFaviconUsage>* favicons) { | |
| 157 const char* query = "SELECT i.url, d.data " | |
| 158 "FROM IconInfo i JOIN IconData d " | |
| 159 "ON i.iconID = d.iconID " | |
| 160 "WHERE i.iconID = ?;"; | |
| 161 sql::Statement s(db->GetUniqueStatement(query)); | |
| 162 | |
| 163 for (FaviconMap::const_iterator i = favicon_map.begin(); | |
| 164 i != favicon_map.end(); ++i) { | |
| 165 s.Reset(true); | |
| 166 s.BindInt64(0, i->first); | |
| 167 if (s.Step()) { | |
| 168 ImportedFaviconUsage usage; | |
| 169 | |
| 170 usage.favicon_url = GURL(s.ColumnString(0)); | |
| 171 if (!usage.favicon_url.is_valid()) | |
| 172 continue; // Don't bother importing favicons with invalid URLs. | |
| 173 | |
| 174 std::vector<unsigned char> data; | |
| 175 s.ColumnBlobAsVector(1, &data); | |
| 176 if (data.empty()) | |
| 177 continue; // Data definitely invalid. | |
| 178 | |
| 179 if (!FaviconUtil::ReencodeFavicon(&data[0], data.size(), &usage.png_data)) | |
| 180 continue; // Unable to decode. | |
| 181 | |
| 182 usage.urls = i->second; | |
| 183 favicons->push_back(usage); | |
| 184 } | |
| 185 } | |
| 186 } | |
| 187 | |
| 188 void SafariImporter::RecursiveReadBookmarksFolder( | |
| 189 NSDictionary* bookmark_folder, | |
| 190 const std::vector<string16>& parent_path_elements, | |
| 191 bool is_in_toolbar, | |
| 192 const string16& toolbar_name, | |
| 193 std::vector<ImportedBookmarkEntry>* out_bookmarks) { | |
| 194 DCHECK(bookmark_folder); | |
| 195 | |
| 196 NSString* type = [bookmark_folder objectForKey:@"WebBookmarkType"]; | |
| 197 NSString* title = [bookmark_folder objectForKey:@"Title"]; | |
| 198 | |
| 199 // Are we the dictionary that contains all other bookmarks? | |
| 200 // We need to know this so we don't add it to the path. | |
| 201 bool is_top_level_bookmarks_container = [bookmark_folder | |
| 202 objectForKey:@"WebBookmarkFileVersion"] != nil; | |
| 203 | |
| 204 // We're expecting a list of bookmarks here, if that isn't what we got, fail. | |
| 205 if (!is_top_level_bookmarks_container) { | |
| 206 // Top level containers sometimes don't have title attributes. | |
| 207 if (![type isEqualToString:@"WebBookmarkTypeList"] || !title) { | |
| 208 NOTREACHED() << "Type=(" | |
| 209 << (type ? base::SysNSStringToUTF8(type) : "Null type") | |
| 210 << ") Title=(" | |
| 211 << (title ? base::SysNSStringToUTF8(title) : "Null title") | |
| 212 << ")"; | |
| 213 return; | |
| 214 } | |
| 215 } | |
| 216 | |
| 217 NSArray* elements = [bookmark_folder objectForKey:@"Children"]; | |
| 218 if (!elements && (!parent_path_elements.empty() || !is_in_toolbar)) { | |
| 219 // This is an empty folder, so add it explicitly; but don't add the toolbar | |
| 220 // folder if it is empty. Note that all non-empty folders are added | |
| 221 // implicitly when their children are added. | |
| 222 ImportedBookmarkEntry entry; | |
| 223 // Safari doesn't specify a creation time for the folder. | |
| 224 entry.creation_time = base::Time::Now(); | |
| 225 entry.title = base::SysNSStringToUTF16(title); | |
| 226 entry.path = parent_path_elements; | |
| 227 entry.in_toolbar = is_in_toolbar; | |
| 228 entry.is_folder = true; | |
| 229 | |
| 230 out_bookmarks->push_back(entry); | |
| 231 return; | |
| 232 } | |
| 233 | |
| 234 std::vector<string16> path_elements(parent_path_elements); | |
| 235 // Create a folder for the toolbar, but not for the bookmarks menu. | |
| 236 if (path_elements.empty() && [title isEqualToString:@"BookmarksBar"]) { | |
| 237 is_in_toolbar = true; | |
| 238 path_elements.push_back(toolbar_name); | |
| 239 } else if (!is_top_level_bookmarks_container && | |
| 240 !(path_elements.empty() && | |
| 241 [title isEqualToString:@"BookmarksMenu"])) { | |
| 242 if (title) | |
| 243 path_elements.push_back(base::SysNSStringToUTF16(title)); | |
| 244 } | |
| 245 | |
| 246 // Iterate over individual bookmarks. | |
| 247 for (NSDictionary* bookmark in elements) { | |
| 248 NSString* type = [bookmark objectForKey:@"WebBookmarkType"]; | |
| 249 if (!type) | |
| 250 continue; | |
| 251 | |
| 252 // If this is a folder, recurse. | |
| 253 if ([type isEqualToString:@"WebBookmarkTypeList"]) { | |
| 254 RecursiveReadBookmarksFolder(bookmark, | |
| 255 path_elements, | |
| 256 is_in_toolbar, | |
| 257 toolbar_name, | |
| 258 out_bookmarks); | |
| 259 } | |
| 260 | |
| 261 // If we didn't see a bookmark folder, then we're expecting a bookmark | |
| 262 // item. If that's not what we got then ignore it. | |
| 263 if (![type isEqualToString:@"WebBookmarkTypeLeaf"]) | |
| 264 continue; | |
| 265 | |
| 266 NSString* url = [bookmark objectForKey:@"URLString"]; | |
| 267 NSString* title = [[bookmark objectForKey:@"URIDictionary"] | |
| 268 objectForKey:@"title"]; | |
| 269 | |
| 270 if (!url || !title) | |
| 271 continue; | |
| 272 | |
| 273 // Output Bookmark. | |
| 274 ImportedBookmarkEntry entry; | |
| 275 // Safari doesn't specify a creation time for the bookmark. | |
| 276 entry.creation_time = base::Time::Now(); | |
| 277 entry.title = base::SysNSStringToUTF16(title); | |
| 278 entry.url = GURL(base::SysNSStringToUTF8(url)); | |
| 279 entry.path = path_elements; | |
| 280 entry.in_toolbar = is_in_toolbar; | |
| 281 | |
| 282 out_bookmarks->push_back(entry); | |
| 283 } | |
| 284 } | |
| 285 | |
| 286 void SafariImporter::ParseBookmarks( | |
| 287 const string16& toolbar_name, | |
| 288 std::vector<ImportedBookmarkEntry>* bookmarks) { | |
| 289 DCHECK(bookmarks); | |
| 290 | |
| 291 // Construct ~/Library/Safari/Bookmarks.plist path | |
| 292 NSString* library_dir = [NSString | |
| 293 stringWithUTF8String:library_dir_.value().c_str()]; | |
| 294 NSString* safari_dir = [library_dir | |
| 295 stringByAppendingPathComponent:@"Safari"]; | |
| 296 NSString* bookmarks_plist = [safari_dir | |
| 297 stringByAppendingPathComponent:@"Bookmarks.plist"]; | |
| 298 | |
| 299 // Load the plist file. | |
| 300 NSDictionary* bookmarks_dict = [NSDictionary | |
| 301 dictionaryWithContentsOfFile:bookmarks_plist]; | |
| 302 if (!bookmarks_dict) | |
| 303 return; | |
| 304 | |
| 305 // Recursively read in bookmarks. | |
| 306 std::vector<string16> parent_path_elements; | |
| 307 RecursiveReadBookmarksFolder(bookmarks_dict, parent_path_elements, false, | |
| 308 toolbar_name, bookmarks); | |
| 309 } | |
| 310 | |
| 311 void SafariImporter::ImportPasswords() { | |
| 312 // Safari stores it's passwords in the Keychain, same as us so we don't need | |
| 313 // to import them. | |
| 314 // Note: that we don't automatically pick them up, there is some logic around | |
| 315 // the user needing to explicitly input his username in a page and blurring | |
| 316 // the field before we pick it up, but the details of that are beyond the | |
| 317 // scope of this comment. | |
| 318 } | |
| 319 | |
| 320 void SafariImporter::ImportHistory() { | |
| 321 std::vector<ImporterURLRow> rows; | |
| 322 ParseHistoryItems(&rows); | |
| 323 | |
| 324 if (!rows.empty() && !cancelled()) { | |
| 325 bridge_->SetHistoryItems(rows, importer::VISIT_SOURCE_SAFARI_IMPORTED); | |
| 326 } | |
| 327 } | |
| 328 | |
| 329 double SafariImporter::HistoryTimeToEpochTime(NSString* history_time) { | |
| 330 DCHECK(history_time); | |
| 331 // Add Difference between Unix epoch and CFAbsoluteTime epoch in seconds. | |
| 332 // Unix epoch is 1970-01-01 00:00:00.0 UTC, | |
| 333 // CF epoch is 2001-01-01 00:00:00.0 UTC. | |
| 334 return CFStringGetDoubleValue(base::mac::NSToCFCast(history_time)) + | |
| 335 kCFAbsoluteTimeIntervalSince1970; | |
| 336 } | |
| 337 | |
| 338 void SafariImporter::ParseHistoryItems( | |
| 339 std::vector<ImporterURLRow>* history_items) { | |
| 340 DCHECK(history_items); | |
| 341 | |
| 342 // Construct ~/Library/Safari/History.plist path | |
| 343 NSString* library_dir = [NSString | |
| 344 stringWithUTF8String:library_dir_.value().c_str()]; | |
| 345 NSString* safari_dir = [library_dir | |
| 346 stringByAppendingPathComponent:@"Safari"]; | |
| 347 NSString* history_plist = [safari_dir | |
| 348 stringByAppendingPathComponent:@"History.plist"]; | |
| 349 | |
| 350 // Load the plist file. | |
| 351 NSDictionary* history_dict = [NSDictionary | |
| 352 dictionaryWithContentsOfFile:history_plist]; | |
| 353 if (!history_dict) | |
| 354 return; | |
| 355 | |
| 356 NSArray* safari_history_items = [history_dict | |
| 357 objectForKey:@"WebHistoryDates"]; | |
| 358 | |
| 359 for (NSDictionary* history_item in safari_history_items) { | |
| 360 NSString* url_ns = [history_item objectForKey:@""]; | |
| 361 if (!url_ns) | |
| 362 continue; | |
| 363 | |
| 364 GURL url(base::SysNSStringToUTF8(url_ns)); | |
| 365 | |
| 366 if (!CanImportSafariURL(url)) | |
| 367 continue; | |
| 368 | |
| 369 ImporterURLRow row(url); | |
| 370 NSString* title_ns = [history_item objectForKey:@"title"]; | |
| 371 | |
| 372 // Sometimes items don't have a title, in which case we just substitue | |
| 373 // the url. | |
| 374 if (!title_ns) | |
| 375 title_ns = url_ns; | |
| 376 | |
| 377 row.title = base::SysNSStringToUTF16(title_ns); | |
| 378 int visit_count = [[history_item objectForKey:@"visitCount"] | |
| 379 intValue]; | |
| 380 row.visit_count = visit_count; | |
| 381 // Include imported URLs in autocompletion - don't hide them. | |
| 382 row.hidden = 0; | |
| 383 // Item was never typed before in the omnibox. | |
| 384 row.typed_count = 0; | |
| 385 | |
| 386 NSString* last_visit_str = [history_item objectForKey:@"lastVisitedDate"]; | |
| 387 // The last visit time should always be in the history item, but if not | |
| 388 /// just continue without this item. | |
| 389 DCHECK(last_visit_str); | |
| 390 if (!last_visit_str) | |
| 391 continue; | |
| 392 | |
| 393 // Convert Safari's last visit time to Unix Epoch time. | |
| 394 double seconds_since_unix_epoch = HistoryTimeToEpochTime(last_visit_str); | |
| 395 row.last_visit = base::Time::FromDoubleT(seconds_since_unix_epoch); | |
| 396 | |
| 397 history_items->push_back(row); | |
| 398 } | |
| 399 } | |
| OLD | NEW |