blob: 2be0e484eb349e9684536971681a76f1ca6fb8e5 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync_bookmarks/bookmark_specifics_conversions.h"
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/strings/utf_string_conversions.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/favicon/core/favicon_service.h"
#include "components/sync/engine/engine_util.h"
#include "components/sync/protocol/sync.pb.h"
#include "ui/gfx/favicon_size.h"
#include "url/gurl.h"
namespace sync_bookmarks {
namespace {
// Maximum number of bytes to allow in a title (must match sync's internal
// limits; see write_node.cc).
const int kTitleLimitBytes = 255;
base::string16 NodeTitleFromSpecificsTitle(const std::string& specifics_title) {
// Adjust the title for backward compatibility with legacy clients.
std::string node_title;
syncer::ServerNameToSyncAPIName(specifics_title, &node_title);
return base::UTF8ToUTF16(node_title);
}
std::string SpecificsTitleFromNodeTitle(const base::string16& node_title) {
// Adjust the title for backward compatibility with legacy clients.
std::string specifics_title;
syncer::SyncAPINameToServerName(base::UTF16ToUTF8(node_title),
&specifics_title);
base::TruncateUTF8ToByteSize(specifics_title, kTitleLimitBytes,
&specifics_title);
return specifics_title;
}
void UpdateBookmarkSpecificsMetaInfo(
const bookmarks::BookmarkNode::MetaInfoMap* metainfo_map,
sync_pb::BookmarkSpecifics* bm_specifics) {
for (const std::pair<std::string, std::string>& pair : *metainfo_map) {
sync_pb::MetaInfo* meta_info = bm_specifics->add_meta_info();
meta_info->set_key(pair.first);
meta_info->set_value(pair.second);
}
}
// Metainfo entries in |specifics| must have unique keys.
bookmarks::BookmarkNode::MetaInfoMap GetBookmarkMetaInfo(
const sync_pb::BookmarkSpecifics& specifics) {
bookmarks::BookmarkNode::MetaInfoMap meta_info_map;
for (const sync_pb::MetaInfo& meta_info : specifics.meta_info()) {
meta_info_map[meta_info.key()] = meta_info.value();
}
DCHECK_EQ(static_cast<size_t>(specifics.meta_info_size()),
meta_info_map.size());
return meta_info_map;
}
// Sets the favicon of the given bookmark node from the given specifics.
void SetBookmarkFaviconFromSpecifics(
const sync_pb::BookmarkSpecifics& specifics,
const bookmarks::BookmarkNode* bookmark_node,
favicon::FaviconService* favicon_service) {
DCHECK(bookmark_node);
DCHECK(favicon_service);
favicon_service->AddPageNoVisitForBookmark(bookmark_node->url(),
bookmark_node->GetTitle());
const std::string& icon_bytes_str = specifics.favicon();
scoped_refptr<base::RefCountedString> icon_bytes(
new base::RefCountedString());
icon_bytes->data().assign(icon_bytes_str);
GURL icon_url(specifics.icon_url());
if (icon_bytes->size() == 0 && icon_url.is_empty()) {
// Empty icon URL and no bitmap data means no icon mapping.
favicon_service->DeleteFaviconMappings({bookmark_node->url()},
favicon_base::IconType::kFavicon);
return;
}
if (icon_url.is_empty()) {
// WebUI pages such as "chrome://bookmarks/" are missing a favicon URL but
// they have a favicon. In addition, ancient clients (prior to M25) may not
// be syncing the favicon URL. If the icon URL is not synced, use the page
// URL as a fake icon URL as it is guaranteed to be unique.
icon_url = GURL(bookmark_node->url());
}
// The client may have cached the favicon at 2x. Use MergeFavicon() as not to
// overwrite the cached 2x favicon bitmap. Sync favicons are always
// gfx::kFaviconSize in width and height. Store the favicon into history
// as such.
gfx::Size pixel_size(gfx::kFaviconSize, gfx::kFaviconSize);
favicon_service->MergeFavicon(bookmark_node->url(), icon_url,
favicon_base::IconType::kFavicon, icon_bytes,
pixel_size);
}
} // namespace
sync_pb::EntitySpecifics CreateSpecificsFromBookmarkNode(
const bookmarks::BookmarkNode* node,
bookmarks::BookmarkModel* model,
bool force_favicon_load) {
sync_pb::EntitySpecifics specifics;
sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark();
if (!node->is_folder()) {
bm_specifics->set_url(node->url().spec());
}
bm_specifics->set_title(SpecificsTitleFromNodeTitle(node->GetTitle()));
bm_specifics->set_creation_time_us(
node->date_added().ToDeltaSinceWindowsEpoch().InMicroseconds());
if (node->GetMetaInfoMap()) {
UpdateBookmarkSpecificsMetaInfo(node->GetMetaInfoMap(), bm_specifics);
}
if (!force_favicon_load && !node->is_favicon_loaded()) {
return specifics;
}
// Encodes a bookmark's favicon into raw PNG data.
scoped_refptr<base::RefCountedMemory> favicon_bytes(nullptr);
const gfx::Image& favicon = model->GetFavicon(node);
// Check for empty images. This can happen if the favicon is still being
// loaded. Also avoid syncing touch icons.
if (!favicon.IsEmpty() &&
model->GetFaviconType(node) == favicon_base::IconType::kFavicon) {
// TODO(crbug.com/516866): Verify that this isn't problematic for bookmarks
// created on iOS devices.
// Re-encode the BookmarkNode's favicon as a PNG.
favicon_bytes = favicon.As1xPNGBytes();
}
if (favicon_bytes.get() && favicon_bytes->size() != 0) {
bm_specifics->set_favicon(favicon_bytes->front(), favicon_bytes->size());
bm_specifics->set_icon_url(node->icon_url() ? node->icon_url()->spec()
: std::string());
} else {
bm_specifics->clear_favicon();
bm_specifics->clear_icon_url();
}
return specifics;
}
const bookmarks::BookmarkNode* CreateBookmarkNodeFromSpecifics(
const sync_pb::BookmarkSpecifics& specifics,
const bookmarks::BookmarkNode* parent,
size_t index,
bool is_folder,
bookmarks::BookmarkModel* model,
favicon::FaviconService* favicon_service) {
DCHECK(parent);
DCHECK(model);
DCHECK(favicon_service);
bookmarks::BookmarkNode::MetaInfoMap metainfo =
GetBookmarkMetaInfo(specifics);
const bookmarks::BookmarkNode* node;
if (is_folder) {
node = model->AddFolderWithMetaInfo(
parent, index, NodeTitleFromSpecificsTitle(specifics.title()),
&metainfo);
} else {
const int64_t create_time_us = specifics.creation_time_us();
base::Time create_time = base::Time::FromDeltaSinceWindowsEpoch(
// Use FromDeltaSinceWindowsEpoch because create_time_us has
// always used the Windows epoch.
base::TimeDelta::FromMicroseconds(create_time_us));
node = model->AddURLWithCreationTimeAndMetaInfo(
parent, index, NodeTitleFromSpecificsTitle(specifics.title()),
GURL(specifics.url()), create_time, &metainfo);
}
if (node) {
SetBookmarkFaviconFromSpecifics(specifics, node, favicon_service);
}
return node;
}
void UpdateBookmarkNodeFromSpecifics(
const sync_pb::BookmarkSpecifics& specifics,
const bookmarks::BookmarkNode* node,
bookmarks::BookmarkModel* model,
favicon::FaviconService* favicon_service) {
DCHECK(node);
DCHECK(model);
DCHECK(favicon_service);
if (!node->is_folder()) {
model->SetURL(node, GURL(specifics.url()));
}
model->SetTitle(node, NodeTitleFromSpecificsTitle(specifics.title()));
model->SetNodeMetaInfoMap(node, GetBookmarkMetaInfo(specifics));
SetBookmarkFaviconFromSpecifics(specifics, node, favicon_service);
}
bool IsValidBookmarkSpecifics(const sync_pb::BookmarkSpecifics& specifics,
bool is_folder) {
if (specifics.ByteSize() == 0) {
DLOG(ERROR) << "Invalid bookmark: empty specifics.";
return false;
}
if (!is_folder) {
if (!GURL(specifics.url()).is_valid()) {
DLOG(ERROR) << "Invalid bookmark: invalid url in the specifics.";
return false;
}
if (specifics.favicon().empty() && !specifics.icon_url().empty()) {
DLOG(ERROR) << "Invalid bookmark: specifics cannot have an icon_url "
"without having a favicon.";
return false;
}
if (!specifics.icon_url().empty() &&
!GURL(specifics.icon_url()).is_valid()) {
DLOG(ERROR) << "Invalid bookmark: invalid icon_url in specifics.";
return false;
}
}
// Verify all keys in meta_info are unique.
std::unordered_set<base::StringPiece, base::StringPieceHash> keys;
for (const sync_pb::MetaInfo& meta_info : specifics.meta_info()) {
if (!keys.insert(meta_info.key()).second) {
DLOG(ERROR) << "Invalid bookmark: keys in meta_info aren't unique.";
return false;
}
}
return true;
}
} // namespace sync_bookmarks