// 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_IN_MIGRATION()
<< "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);
}