1 // Copyright 2019 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 "components/sync/engine_impl/bookmark_update_preprocessing.h"
6
7 #include <array>
8
9 #include "base/containers/span.h"
10 #include "base/guid.h"
11 #include "base/hash/sha1.h"
12 #include "base/logging.h"
13 #include "base/metrics/histogram_functions.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/strings/strcat.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "components/sync/base/hash_util.h"
19 #include "components/sync/base/unique_position.h"
20 #include "components/sync/model/entity_data.h"
21 #include "components/sync/protocol/sync.pb.h"
22
23 namespace syncer {
24
25 namespace {
26
27 // Used in metric "Sync.BookmarkGUIDSource2". These values are persisted to
28 // logs. Entries should not be renumbered and numeric values should never be
29 // reused.
30 enum class BookmarkGuidSource {
31 // GUID came from specifics.
32 kSpecifics = 0,
33 // GUID came from originator_client_item_id and is valid.
34 kValidOCII = 1,
35 // GUID not found in the specifics and originator_client_item_id is invalid,
36 // so field left empty (currently unused).
37 kDeprecatedLeftEmpty = 2,
38 // GUID not found in the specifics and originator_client_item_id is invalid,
39 // so the GUID is inferred from combining originator_client_item_id and
40 // originator_cache_guid.
41 kInferred = 3,
42
43 kMaxValue = kInferred,
44 };
45
LogGuidSource(BookmarkGuidSource source)46 inline void LogGuidSource(BookmarkGuidSource source) {
47 base::UmaHistogramEnumeration("Sync.BookmarkGUIDSource2", source);
48 }
49
ComputeGuidFromBytes(base::span<const uint8_t> bytes)50 std::string ComputeGuidFromBytes(base::span<const uint8_t> bytes) {
51 DCHECK_GE(bytes.size(), 16U);
52
53 // This implementation is based on the equivalent logic in base/guid.cc.
54
55 // Set the GUID to version 4 as described in RFC 4122, section 4.4.
56 // The format of GUID version 4 must be xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx,
57 // where y is one of [8, 9, A, B].
58
59 // Clear the version bits and set the version to 4:
60 const uint8_t byte6 = (bytes[6] & 0x0fU) | 0xf0U;
61
62 // Set the two most significant bits (bits 6 and 7) of the
63 // clock_seq_hi_and_reserved to zero and one, respectively:
64 const uint8_t byte8 = (bytes[8] & 0x3fU) | 0x80U;
65
66 return base::StringPrintf(
67 "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
68 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], byte6,
69 bytes[7], byte8, bytes[9], bytes[10], bytes[11], bytes[12], bytes[13],
70 bytes[14], bytes[15]);
71 }
72
73 // Bookmarks created before 2015 (https://codereview.chromium.org/1136953013)
74 // have an originator client item ID that is NOT a GUID. Hence, an alternative
75 // method must be used to infer a GUID deterministically from a combination of
76 // sync fields that is known to be a) immutable and b) unique per synced
77 // bookmark.
InferGuidForLegacyBookmark(const std::string & originator_cache_guid,const std::string & originator_client_item_id)78 std::string InferGuidForLegacyBookmark(
79 const std::string& originator_cache_guid,
80 const std::string& originator_client_item_id) {
81 DCHECK(!base::IsValidGUID(originator_client_item_id));
82
83 const std::string unique_tag =
84 base::StrCat({originator_cache_guid, originator_client_item_id});
85 const base::SHA1Digest hash =
86 base::SHA1HashSpan(base::as_bytes(base::make_span(unique_tag)));
87
88 static_assert(base::kSHA1Length >= 16, "16 bytes needed to infer GUID");
89
90 const std::string guid = ComputeGuidFromBytes(base::make_span(hash));
91 DCHECK(base::IsValidGUIDOutputString(guid));
92 return guid;
93 }
94
95 } // namespace
96
AdaptUniquePositionForBookmark(const sync_pb::SyncEntity & update_entity,EntityData * data)97 void AdaptUniquePositionForBookmark(const sync_pb::SyncEntity& update_entity,
98 EntityData* data) {
99 DCHECK(data);
100
101 // Tombstones don't need positioning information.
102 if (update_entity.deleted()) {
103 return;
104 }
105
106 // Permanent folders don't need positioning information.
107 if (update_entity.folder() &&
108 !update_entity.server_defined_unique_tag().empty()) {
109 return;
110 }
111
112 if (update_entity.has_unique_position()) {
113 data->unique_position = update_entity.unique_position();
114 } else if (update_entity.has_position_in_parent() ||
115 update_entity.has_insert_after_item_id()) {
116 bool missing_originator_fields = false;
117 if (!update_entity.has_originator_cache_guid() ||
118 !update_entity.has_originator_client_item_id()) {
119 DLOG(ERROR) << "Update is missing requirements for bookmark position.";
120 missing_originator_fields = true;
121 }
122
123 std::string suffix = missing_originator_fields
124 ? UniquePosition::RandomSuffix()
125 : GenerateSyncableBookmarkHash(
126 update_entity.originator_cache_guid(),
127 update_entity.originator_client_item_id());
128
129 if (update_entity.has_position_in_parent()) {
130 data->unique_position =
131 UniquePosition::FromInt64(update_entity.position_in_parent(), suffix)
132 .ToProto();
133 } else {
134 // If update_entity has insert_after_item_id, use 0 index.
135 DCHECK(update_entity.has_insert_after_item_id());
136 data->unique_position = UniquePosition::FromInt64(0, suffix).ToProto();
137 }
138 } else {
139 DLOG(ERROR) << "Missing required position information in update: "
140 << update_entity.id_string();
141 }
142 }
143
AdaptTitleForBookmark(const sync_pb::SyncEntity & update_entity,sync_pb::EntitySpecifics * specifics,bool specifics_were_encrypted)144 void AdaptTitleForBookmark(const sync_pb::SyncEntity& update_entity,
145 sync_pb::EntitySpecifics* specifics,
146 bool specifics_were_encrypted) {
147 DCHECK(specifics);
148 if (specifics_were_encrypted || update_entity.deleted()) {
149 // If encrypted, the name field is never populated (unencrypted) for privacy
150 // reasons. Encryption was also introduced after moving the name out of
151 // SyncEntity so this hack is not needed at all.
152 return;
153 }
154 // Legacy clients populate the name field in the SyncEntity instead of the
155 // title field in the BookmarkSpecifics.
156 if (!specifics->bookmark().has_legacy_canonicalized_title() &&
157 !update_entity.name().empty()) {
158 specifics->mutable_bookmark()->set_legacy_canonicalized_title(
159 update_entity.name());
160 }
161 }
162
AdaptGuidForBookmark(const sync_pb::SyncEntity & update_entity,sync_pb::EntitySpecifics * specifics)163 bool AdaptGuidForBookmark(const sync_pb::SyncEntity& update_entity,
164 sync_pb::EntitySpecifics* specifics) {
165 DCHECK(specifics);
166 // Tombstones and permanent entities don't have a GUID.
167 if (update_entity.deleted() ||
168 !update_entity.server_defined_unique_tag().empty()) {
169 return false;
170 }
171 // Legacy clients don't populate the guid field in the BookmarkSpecifics, so
172 // we use the originator_client_item_id instead, if it is a valid GUID.
173 // Otherwise, we leave the field empty.
174 if (specifics->bookmark().has_guid()) {
175 LogGuidSource(BookmarkGuidSource::kSpecifics);
176 return false;
177 }
178 if (base::IsValidGUID(update_entity.originator_client_item_id())) {
179 // Bookmarks created around 2016, between [M44..M52) use an uppercase GUID
180 // as originator client item ID, so it needs to be lowercased to adhere to
181 // the invariant that GUIDs in specifics are canonicalized.
182 specifics->mutable_bookmark()->set_guid(
183 base::ToLowerASCII(update_entity.originator_client_item_id()));
184 DCHECK(base::IsValidGUIDOutputString(specifics->bookmark().guid()));
185 LogGuidSource(BookmarkGuidSource::kValidOCII);
186 } else {
187 specifics->mutable_bookmark()->set_guid(
188 InferGuidForLegacyBookmark(update_entity.originator_cache_guid(),
189 update_entity.originator_client_item_id()));
190 DCHECK(base::IsValidGUIDOutputString(specifics->bookmark().guid()));
191 LogGuidSource(BookmarkGuidSource::kInferred);
192 }
193 return true;
194 }
195
InferGuidForLegacyBookmarkForTesting(const std::string & originator_cache_guid,const std::string & originator_client_item_id)196 std::string InferGuidForLegacyBookmarkForTesting(
197 const std::string& originator_cache_guid,
198 const std::string& originator_client_item_id) {
199 return InferGuidForLegacyBookmark(originator_cache_guid,
200 originator_client_item_id);
201 }
202
203 } // namespace syncer
204