bookmark_update_preprocessing.cc 9.5 KB

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