#include "components/sync/engine/bookmark_update_preprocessing.h"
#include <array>
#include "base/containers/span.h"
#include "base/hash/sha1.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/uuid.h"
#include "components/sync/base/hash_util.h"
#include "components/sync/base/unique_position.h"
#include "components/sync/protocol/bookmark_specifics.pb.h"
#include "components/sync/protocol/entity_specifics.pb.h"
#include "components/sync/protocol/sync_entity.pb.h"
namespace syncer {
namespace {
enum class BookmarkGuidSource {
kSpecifics = 0,
kValidOCII = 1,
kDeprecatedLeftEmpty = 2,
kInferred = 3,
kLeftEmptyPossiblyForClientTag = 4,
kMaxValue = kLeftEmptyPossiblyForClientTag,
};
inline void LogGuidSource(BookmarkGuidSource source) {
base::UmaHistogramEnumeration("Sync.BookmarkGUIDSource2", source);
}
std::string ComputeUuidFromBytes(base::span<const uint8_t> bytes) {
DCHECK_GE(bytes.size(), 16U);
const uint8_t byte6 = (bytes[6] & 0x0fU) | 0xf0U;
const uint8_t byte8 = (bytes[8] & 0x3fU) | 0x80U;
return base::StringPrintf(
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], byte6,
bytes[7], byte8, bytes[9], bytes[10], bytes[11], bytes[12], bytes[13],
bytes[14], bytes[15]);
}
std::string InferGuidForLegacyBookmark(
const std::string& originator_cache_guid,
const std::string& originator_client_item_id) {
DCHECK(
!base::Uuid::ParseCaseInsensitive(originator_client_item_id).is_valid());
const std::string unique_tag =
base::StrCat({originator_cache_guid, originator_client_item_id});
const base::SHA1Digest hash =
base::SHA1HashSpan(base::as_bytes(base::make_span(unique_tag)));
static_assert(base::kSHA1Length >= 16, "16 bytes needed to infer UUID");
const std::string guid = ComputeUuidFromBytes(base::make_span(hash));
DCHECK(base::Uuid::ParseLowercase(guid).is_valid());
return guid;
}
sync_pb::UniquePosition GetUniquePositionFromSyncEntity(
const sync_pb::SyncEntity& update_entity) {
if (update_entity.has_unique_position()) {
return update_entity.unique_position();
}
std::string suffix;
if (update_entity.has_originator_cache_guid() &&
update_entity.has_originator_client_item_id()) {
suffix =
GenerateSyncableBookmarkHash(update_entity.originator_cache_guid(),
update_entity.originator_client_item_id());
} else {
suffix = UniquePosition::RandomSuffix();
}
if (update_entity.has_position_in_parent()) {
return UniquePosition::FromInt64(update_entity.position_in_parent(), suffix)
.ToProto();
}
if (update_entity.has_insert_after_item_id()) {
return UniquePosition::FromInt64(0, suffix).ToProto();
}
return UniquePosition::InitialPosition(suffix).ToProto();
}
}
bool AdaptUniquePositionForBookmark(const sync_pb::SyncEntity& update_entity,
sync_pb::EntitySpecifics* specifics) {
DCHECK(specifics);
if (specifics->bookmark().has_unique_position() || update_entity.deleted()) {
return false;
}
if (update_entity.folder() &&
!update_entity.server_defined_unique_tag().empty()) {
return false;
}
*specifics->mutable_bookmark()->mutable_unique_position() =
GetUniquePositionFromSyncEntity(update_entity);
return true;
}
void AdaptTypeForBookmark(const sync_pb::SyncEntity& update_entity,
sync_pb::EntitySpecifics* specifics) {
DCHECK(specifics);
if (specifics->bookmark().has_type() || update_entity.deleted()) {
return;
}
DCHECK(specifics->has_bookmark());
if (update_entity.has_folder()) {
specifics->mutable_bookmark()->set_type(
update_entity.folder() ? sync_pb::BookmarkSpecifics::FOLDER
: sync_pb::BookmarkSpecifics::URL);
return;
}
specifics->mutable_bookmark()->set_type(
specifics->bookmark().has_url() ? sync_pb::BookmarkSpecifics::URL
: sync_pb::BookmarkSpecifics::FOLDER);
}
void AdaptTitleForBookmark(const sync_pb::SyncEntity& update_entity,
sync_pb::EntitySpecifics* specifics,
bool specifics_were_encrypted) {
DCHECK(specifics);
if (specifics_were_encrypted || update_entity.deleted()) {
return;
}
DCHECK(specifics->has_bookmark());
if (!specifics->bookmark().has_legacy_canonicalized_title() &&
!update_entity.name().empty()) {
specifics->mutable_bookmark()->set_legacy_canonicalized_title(
update_entity.name());
}
}
void AdaptGuidForBookmark(const sync_pb::SyncEntity& update_entity,
sync_pb::EntitySpecifics* specifics) {
DCHECK(specifics);
if (update_entity.deleted() ||
!update_entity.server_defined_unique_tag().empty()) {
return;
}
DCHECK(specifics->has_bookmark());
if (specifics->bookmark().has_guid()) {
LogGuidSource(BookmarkGuidSource::kSpecifics);
return;
}
if (base::Uuid::ParseCaseInsensitive(
update_entity.originator_client_item_id())
.is_valid()) {
specifics->mutable_bookmark()->set_guid(
base::ToLowerASCII(update_entity.originator_client_item_id()));
DCHECK(base::Uuid::ParseLowercase(specifics->bookmark().guid()).is_valid());
LogGuidSource(BookmarkGuidSource::kValidOCII);
} else if (update_entity.originator_cache_guid().empty() &&
update_entity.originator_client_item_id().empty()) {
LogGuidSource(BookmarkGuidSource::kLeftEmptyPossiblyForClientTag);
} else {
specifics->mutable_bookmark()->set_guid(
InferGuidForLegacyBookmark(update_entity.originator_cache_guid(),
update_entity.originator_client_item_id()));
DCHECK(base::Uuid::ParseLowercase(specifics->bookmark().guid()).is_valid());
LogGuidSource(BookmarkGuidSource::kInferred);
}
}
std::string InferGuidForLegacyBookmarkForTesting(
const std::string& originator_cache_guid,
const std::string& originator_client_item_id) {
return InferGuidForLegacyBookmark(originator_cache_guid,
originator_client_item_id);
}
}