| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <Cocoa/Cocoa.h> |
| |
| #include "chrome/utility/importer/safari_importer.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/apple/foundation_util.h" |
| #include "base/files/file_util.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/time/time.h" |
| #include "chrome/common/importer/imported_bookmark_entry.h" |
| #include "chrome/common/importer/importer_bridge.h" |
| #include "chrome/common/url_constants.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "net/base/data_url.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| |
| // Function to recursively read Bookmarks out of Safari plist. |
| // |
| // - `bookmark_folder`: The dictionary containing a folder to parse. |
| // - `parent_path_elements`: Path elements up to this point. |
| // - `is_in_toolbar`: True if this folder is in the toolbar. |
| // - `out_bookmarks`: The Bookmark element array to write into. |
| void RecursiveReadBookmarksFolder( |
| NSDictionary* bookmark_folder, |
| const std::vector<std::u16string>& parent_path_elements, |
| bool is_in_toolbar, |
| const std::u16string& toolbar_name, |
| std::vector<ImportedBookmarkEntry>* out_bookmarks) { |
| DCHECK(bookmark_folder); |
| |
| NSString* type = bookmark_folder[@"WebBookmarkType"]; |
| NSString* title = bookmark_folder[@"Title"]; |
| |
| // Are we the dictionary that contains all other bookmarks? |
| // We need to know this so we don't add it to the path. |
| bool is_top_level_bookmarks_container = |
| bookmark_folder[@"WebBookmarkFileVersion"] != nil; |
| |
| // We're expecting a list of bookmarks here, if that isn't what we got, fail. |
| if (!is_top_level_bookmarks_container) { |
| // Top level containers sometimes don't have title attributes. |
| if (![type isEqualToString:@"WebBookmarkTypeList"] || !title) { |
| NOTREACHED() << "Type=(" |
| << (type ? base::SysNSStringToUTF8(type) : "Null type") |
| << ") Title=(" |
| << (title ? base::SysNSStringToUTF8(title) : "Null title") |
| << ")"; |
| return; |
| } |
| } |
| |
| NSArray* elements = bookmark_folder[@"Children"]; |
| if (!elements && (!parent_path_elements.empty() || !is_in_toolbar) && |
| ![title isEqualToString:@"BookmarksMenu"]) { |
| // This is an empty folder, so add it explicitly. Note that the condition |
| // above prevents either the toolbar folder or the bookmarks menu from being |
| // added if either is empty. Note also that all non-empty folders are added |
| // implicitly when their children are added. |
| ImportedBookmarkEntry entry; |
| // Safari doesn't specify a creation time for the folder. |
| entry.creation_time = base::Time::Now(); |
| entry.title = base::SysNSStringToUTF16(title); |
| entry.path = parent_path_elements; |
| entry.in_toolbar = is_in_toolbar; |
| entry.is_folder = true; |
| |
| out_bookmarks->push_back(entry); |
| return; |
| } |
| |
| std::vector<std::u16string> path_elements(parent_path_elements); |
| // Create a folder for the toolbar, but not for the bookmarks menu. |
| if (path_elements.empty() && [title isEqualToString:@"BookmarksBar"]) { |
| is_in_toolbar = true; |
| path_elements.push_back(toolbar_name); |
| } else if (!is_top_level_bookmarks_container && |
| !(path_elements.empty() && |
| [title isEqualToString:@"BookmarksMenu"])) { |
| if (title) { |
| path_elements.push_back(base::SysNSStringToUTF16(title)); |
| } |
| } |
| |
| // Iterate over individual bookmarks. |
| for (NSDictionary* bookmark in elements) { |
| NSString* element_type = bookmark[@"WebBookmarkType"]; |
| if (!element_type) { |
| continue; |
| } |
| |
| // If this is a folder, recurse. |
| if ([element_type isEqualToString:@"WebBookmarkTypeList"]) { |
| RecursiveReadBookmarksFolder(bookmark, path_elements, is_in_toolbar, |
| toolbar_name, out_bookmarks); |
| } |
| |
| // If we didn't see a bookmark folder, then we're expecting a bookmark |
| // item. If that's not what we got then ignore it. |
| if (![element_type isEqualToString:@"WebBookmarkTypeLeaf"]) { |
| continue; |
| } |
| |
| NSString* element_url = bookmark[@"URLString"]; |
| NSString* element_title = bookmark[@"URIDictionary"][@"title"]; |
| |
| if (!element_url || !element_title) { |
| continue; |
| } |
| |
| // Output Bookmark. |
| ImportedBookmarkEntry entry; |
| // Safari doesn't specify a creation time for the bookmark. |
| entry.creation_time = base::Time::Now(); |
| entry.title = base::SysNSStringToUTF16(element_title); |
| entry.url = GURL(base::SysNSStringToUTF8(element_url)); |
| entry.path = path_elements; |
| entry.in_toolbar = is_in_toolbar; |
| |
| out_bookmarks->push_back(entry); |
| } |
| } |
| |
| } // namespace |
| |
| SafariImporter::SafariImporter(const base::FilePath& library_dir) |
| : library_dir_(library_dir) { |
| } |
| |
| SafariImporter::~SafariImporter() = default; |
| |
| void SafariImporter::StartImport(const importer::SourceProfile& source_profile, |
| uint16_t items, |
| ImporterBridge* bridge) { |
| bridge_ = bridge; |
| // The order here is important! |
| bridge_->NotifyStarted(); |
| |
| // In keeping with import on other platforms (and for other browsers), we |
| // don't import the home page (since it may lead to a useless homepage); see |
| // crbug.com/25603. |
| if ((items & importer::FAVORITES) && !cancelled()) { |
| bridge_->NotifyItemStarted(importer::FAVORITES); |
| ImportBookmarks(); |
| bridge_->NotifyItemEnded(importer::FAVORITES); |
| } |
| |
| bridge_->NotifyEnded(); |
| } |
| |
| void SafariImporter::ImportBookmarks() { |
| std::u16string toolbar_name = |
| bridge_->GetLocalizedString(IDS_BOOKMARK_BAR_FOLDER_NAME); |
| std::vector<ImportedBookmarkEntry> bookmarks; |
| ParseBookmarks(toolbar_name, &bookmarks); |
| |
| // Write bookmarks into profile. |
| if (!bookmarks.empty() && !cancelled()) { |
| const std::u16string& first_folder_name = |
| bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP_FROM_SAFARI); |
| bridge_->AddBookmarks(bookmarks, first_folder_name); |
| } |
| } |
| |
| void SafariImporter::ParseBookmarks( |
| const std::u16string& toolbar_name, |
| std::vector<ImportedBookmarkEntry>* bookmarks) { |
| DCHECK(bookmarks); |
| |
| // Construct ~/Library/Safari/Bookmarks.plist path |
| NSURL* library_dir = base::apple::FilePathToNSURL(library_dir_); |
| NSURL* safari_dir = [library_dir URLByAppendingPathComponent:@"Safari"]; |
| NSURL* bookmarks_plist = |
| [safari_dir URLByAppendingPathComponent:@"Bookmarks.plist"]; |
| |
| // Load the plist file. |
| NSDictionary* bookmarks_dict = |
| [NSDictionary dictionaryWithContentsOfURL:bookmarks_plist error:nil]; |
| if (!bookmarks_dict) { |
| return; |
| } |
| |
| // Recursively read in bookmarks. |
| std::vector<std::u16string> parent_path_elements; |
| RecursiveReadBookmarksFolder(bookmarks_dict, parent_path_elements, false, |
| toolbar_name, bookmarks); |
| } |