bookmark_codec.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. // Copyright 2014 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/bookmarks/browser/bookmark_codec.h"
  5. #include <stddef.h>
  6. #include <algorithm>
  7. #include <memory>
  8. #include <utility>
  9. #include "base/base64.h"
  10. #include "base/containers/contains.h"
  11. #include "base/guid.h"
  12. #include "base/json/json_string_value_serializer.h"
  13. #include "base/memory/ptr_util.h"
  14. #include "base/strings/string_number_conversions.h"
  15. #include "base/strings/string_util.h"
  16. #include "base/strings/utf_string_conversions.h"
  17. #include "base/time/time.h"
  18. #include "base/values.h"
  19. #include "components/bookmarks/browser/bookmark_model.h"
  20. #include "components/strings/grit/components_strings.h"
  21. #include "ui/base/l10n/l10n_util.h"
  22. #include "url/gurl.h"
  23. using base::Time;
  24. namespace bookmarks {
  25. const char BookmarkCodec::kRootsKey[] = "roots";
  26. const char BookmarkCodec::kBookmarkBarFolderNameKey[] = "bookmark_bar";
  27. const char BookmarkCodec::kOtherBookmarkFolderNameKey[] = "other";
  28. // The value is left as 'synced' for historical reasons.
  29. const char BookmarkCodec::kMobileBookmarkFolderNameKey[] = "synced";
  30. const char BookmarkCodec::kVersionKey[] = "version";
  31. const char BookmarkCodec::kChecksumKey[] = "checksum";
  32. const char BookmarkCodec::kIdKey[] = "id";
  33. const char BookmarkCodec::kTypeKey[] = "type";
  34. const char BookmarkCodec::kNameKey[] = "name";
  35. const char BookmarkCodec::kGuidKey[] = "guid";
  36. const char BookmarkCodec::kDateAddedKey[] = "date_added";
  37. const char BookmarkCodec::kURLKey[] = "url";
  38. const char BookmarkCodec::kDateModifiedKey[] = "date_modified";
  39. const char BookmarkCodec::kChildrenKey[] = "children";
  40. const char BookmarkCodec::kMetaInfo[] = "meta_info";
  41. const char BookmarkCodec::kTypeURL[] = "url";
  42. const char BookmarkCodec::kTypeFolder[] = "folder";
  43. const char BookmarkCodec::kSyncMetadata[] = "sync_metadata";
  44. const char BookmarkCodec::kDateLastUsed[] = "date_last_used";
  45. // Current version of the file.
  46. static const int kCurrentVersion = 1;
  47. namespace {
  48. // Encodes Sync metadata and cleans up the input string to decrease peak memory
  49. // usage during encoding.
  50. base::Value EncodeSyncMetadata(std::string sync_metadata_str) {
  51. std::string sync_metadata_str_base64;
  52. base::Base64Encode(sync_metadata_str, &sync_metadata_str_base64);
  53. return base::Value(std::move(sync_metadata_str_base64));
  54. }
  55. } // namespace
  56. BookmarkCodec::BookmarkCodec()
  57. : ids_reassigned_(false),
  58. guids_reassigned_(false),
  59. ids_valid_(true),
  60. maximum_id_(0) {}
  61. BookmarkCodec::~BookmarkCodec() = default;
  62. base::Value BookmarkCodec::Encode(BookmarkModel* model,
  63. std::string sync_metadata_str) {
  64. return Encode(model->bookmark_bar_node(), model->other_node(),
  65. model->mobile_node(), model->root_node()->GetMetaInfoMap(),
  66. std::move(sync_metadata_str));
  67. }
  68. base::Value BookmarkCodec::Encode(
  69. const BookmarkNode* bookmark_bar_node,
  70. const BookmarkNode* other_folder_node,
  71. const BookmarkNode* mobile_folder_node,
  72. const BookmarkNode::MetaInfoMap* model_meta_info_map,
  73. std::string sync_metadata_str) {
  74. ids_reassigned_ = false;
  75. guids_reassigned_ = false;
  76. base::Value main(base::Value::Type::DICTIONARY);
  77. main.SetIntKey(kVersionKey, kCurrentVersion);
  78. // Encode Sync metadata before encoding other fields to reduce peak memory
  79. // usage.
  80. if (!sync_metadata_str.empty()) {
  81. main.SetKey(kSyncMetadata,
  82. EncodeSyncMetadata(std::move(sync_metadata_str)));
  83. sync_metadata_str.clear();
  84. }
  85. InitializeChecksum();
  86. base::Value roots(base::Value::Type::DICTIONARY);
  87. roots.SetKey(kBookmarkBarFolderNameKey, EncodeNode(bookmark_bar_node));
  88. roots.SetKey(kOtherBookmarkFolderNameKey, EncodeNode(other_folder_node));
  89. roots.SetKey(kMobileBookmarkFolderNameKey, EncodeNode(mobile_folder_node));
  90. if (model_meta_info_map)
  91. roots.SetKey(kMetaInfo, EncodeMetaInfo(*model_meta_info_map));
  92. FinalizeChecksum();
  93. // We are going to store the computed checksum. So set stored checksum to be
  94. // the same as computed checksum.
  95. stored_checksum_ = computed_checksum_;
  96. main.SetStringKey(kChecksumKey, computed_checksum_);
  97. main.SetKey(kRootsKey, std::move(roots));
  98. return main;
  99. }
  100. bool BookmarkCodec::Decode(const base::Value& value,
  101. BookmarkNode* bb_node,
  102. BookmarkNode* other_folder_node,
  103. BookmarkNode* mobile_folder_node,
  104. int64_t* max_id,
  105. std::string* sync_metadata_str) {
  106. ids_.clear();
  107. guids_ = {base::GUID::ParseLowercase(BookmarkNode::kRootNodeGuid),
  108. base::GUID::ParseLowercase(BookmarkNode::kBookmarkBarNodeGuid),
  109. base::GUID::ParseLowercase(BookmarkNode::kOtherBookmarksNodeGuid),
  110. base::GUID::ParseLowercase(BookmarkNode::kMobileBookmarksNodeGuid),
  111. base::GUID::ParseLowercase(BookmarkNode::kManagedNodeGuid)};
  112. ids_reassigned_ = false;
  113. guids_reassigned_ = false;
  114. ids_valid_ = true;
  115. maximum_id_ = 0;
  116. stored_checksum_.clear();
  117. InitializeChecksum();
  118. bool success = DecodeHelper(bb_node, other_folder_node, mobile_folder_node,
  119. value, sync_metadata_str);
  120. FinalizeChecksum();
  121. // If either the checksums differ or some IDs were missing/not unique,
  122. // reassign IDs.
  123. if (!ids_valid_ || computed_checksum() != stored_checksum())
  124. ReassignIDs(bb_node, other_folder_node, mobile_folder_node);
  125. *max_id = maximum_id_ + 1;
  126. return success;
  127. }
  128. base::Value BookmarkCodec::EncodeNode(const BookmarkNode* node) {
  129. base::Value value(base::Value::Type::DICTIONARY);
  130. std::string id = base::NumberToString(node->id());
  131. value.SetStringKey(kIdKey, id);
  132. const std::u16string& title = node->GetTitle();
  133. value.SetStringKey(kNameKey, title);
  134. const std::string& guid = node->guid().AsLowercaseString();
  135. value.SetStringKey(kGuidKey, guid);
  136. // TODO(crbug.com/634507): Avoid ToInternalValue().
  137. value.SetStringKey(kDateAddedKey, base::NumberToString(
  138. node->date_added().ToInternalValue()));
  139. value.SetStringKey(
  140. kDateLastUsed,
  141. base::NumberToString(node->date_last_used().ToInternalValue()));
  142. if (node->is_url()) {
  143. value.SetStringKey(kTypeKey, kTypeURL);
  144. std::string url = node->url().possibly_invalid_spec();
  145. value.SetStringKey(kURLKey, url);
  146. UpdateChecksumWithUrlNode(id, title, url);
  147. } else {
  148. value.SetStringKey(kTypeKey, kTypeFolder);
  149. value.SetStringKey(
  150. kDateModifiedKey,
  151. base::NumberToString(node->date_folder_modified().ToInternalValue()));
  152. UpdateChecksumWithFolderNode(id, title);
  153. base::Value::List child_values;
  154. for (const auto& child : node->children())
  155. child_values.Append(EncodeNode(child.get()));
  156. value.SetKey(kChildrenKey, base::Value(std::move(child_values)));
  157. }
  158. const BookmarkNode::MetaInfoMap* meta_info_map = node->GetMetaInfoMap();
  159. if (meta_info_map)
  160. value.SetKey(kMetaInfo, EncodeMetaInfo(*meta_info_map));
  161. return value;
  162. }
  163. base::Value BookmarkCodec::EncodeMetaInfo(
  164. const BookmarkNode::MetaInfoMap& meta_info_map) {
  165. base::Value meta_info(base::Value::Type::DICTIONARY);
  166. for (const auto& item : meta_info_map)
  167. meta_info.SetKey(item.first, base::Value(item.second));
  168. return meta_info;
  169. }
  170. bool BookmarkCodec::DecodeHelper(BookmarkNode* bb_node,
  171. BookmarkNode* other_folder_node,
  172. BookmarkNode* mobile_folder_node,
  173. const base::Value& value,
  174. std::string* sync_metadata_str) {
  175. if (!value.is_dict())
  176. return false; // Unexpected type.
  177. absl::optional<int> version = value.FindIntKey(kVersionKey);
  178. if (!version || *version != kCurrentVersion)
  179. return false; // Unknown version.
  180. const base::Value* checksum_value = value.FindKey(kChecksumKey);
  181. if (checksum_value) {
  182. const std::string* checksum = checksum_value->GetIfString();
  183. if (checksum)
  184. stored_checksum_ = *checksum;
  185. else
  186. return false;
  187. }
  188. const base::Value* roots = value.FindDictKey(kRootsKey);
  189. if (!roots)
  190. return false; // No roots, or invalid type for roots.
  191. const base::Value* bb_value = roots->FindDictKey(kBookmarkBarFolderNameKey);
  192. const base::Value* other_folder_value =
  193. roots->FindDictKey(kOtherBookmarkFolderNameKey);
  194. const base::Value* mobile_folder_value =
  195. roots->FindDictKey(kMobileBookmarkFolderNameKey);
  196. if (!bb_value || !other_folder_value || !mobile_folder_value)
  197. return false;
  198. DecodeNode(*bb_value, nullptr, bb_node);
  199. DecodeNode(*other_folder_value, nullptr, other_folder_node);
  200. DecodeNode(*mobile_folder_value, nullptr, mobile_folder_node);
  201. if (!DecodeMetaInfo(*roots, &model_meta_info_map_))
  202. return false;
  203. if (sync_metadata_str) {
  204. const std::string* sync_metadata_str_base64 =
  205. value.FindStringKey(kSyncMetadata);
  206. if (sync_metadata_str_base64)
  207. base::Base64Decode(*sync_metadata_str_base64, sync_metadata_str);
  208. }
  209. // Need to reset the title as the title is persisted and restored from
  210. // the file.
  211. bb_node->SetTitle(l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_FOLDER_NAME));
  212. other_folder_node->SetTitle(
  213. l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OTHER_FOLDER_NAME));
  214. mobile_folder_node->SetTitle(
  215. l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_MOBILE_FOLDER_NAME));
  216. return true;
  217. }
  218. bool BookmarkCodec::DecodeChildren(const base::Value& child_value_list,
  219. BookmarkNode* parent) {
  220. DCHECK(child_value_list.is_list());
  221. for (const base::Value& child_value : child_value_list.GetList()) {
  222. if (!child_value.is_dict())
  223. return false;
  224. DecodeNode(child_value, parent, nullptr);
  225. }
  226. return true;
  227. }
  228. bool BookmarkCodec::DecodeNode(const base::Value& value,
  229. BookmarkNode* parent,
  230. BookmarkNode* node) {
  231. DCHECK(value.is_dict());
  232. // If no |node| is specified, we'll create one and add it to the |parent|.
  233. // Therefore, in that case, |parent| must be non-NULL.
  234. if (!node && !parent) {
  235. NOTREACHED();
  236. return false;
  237. }
  238. // It's not valid to have both a node and a specified parent.
  239. if (node && parent) {
  240. NOTREACHED();
  241. return false;
  242. }
  243. std::string id_string;
  244. int64_t id = 0;
  245. if (ids_valid_) {
  246. const std::string* string = value.FindStringKey(kIdKey);
  247. if (!string || !base::StringToInt64(*string, &id) || ids_.count(id) != 0) {
  248. ids_valid_ = false;
  249. } else {
  250. ids_.insert(id);
  251. id_string = *string;
  252. }
  253. }
  254. maximum_id_ = std::max(maximum_id_, id);
  255. std::u16string title;
  256. const std::string* string_value = value.FindStringKey(kNameKey);
  257. if (string_value)
  258. title = base::UTF8ToUTF16(*string_value);
  259. base::GUID guid;
  260. // |node| is only passed in for bookmarks of type BookmarkPermanentNode, in
  261. // which case we do not need to check for GUID validity as their GUIDs are
  262. // hard-coded and not read from the persisted file.
  263. if (!node) {
  264. // GUIDs can be empty for bookmarks that were created before GUIDs were
  265. // required. When encountering one such bookmark we thus assign to it a new
  266. // GUID. The same applies if the stored GUID is invalid or a duplicate.
  267. const std::string* guid_str = value.FindStringKey(kGuidKey);
  268. if (guid_str && !guid_str->empty()) {
  269. guid = base::GUID::ParseCaseInsensitive(*guid_str);
  270. }
  271. if (!guid.is_valid()) {
  272. guid = base::GUID::GenerateRandomV4();
  273. guids_reassigned_ = true;
  274. }
  275. if (guid.AsLowercaseString() == BookmarkNode::kBannedGuidDueToPastSyncBug) {
  276. guid = base::GUID::GenerateRandomV4();
  277. guids_reassigned_ = true;
  278. }
  279. // Guard against GUID collisions, which would violate BookmarkModel's
  280. // invariant that each GUID is unique.
  281. if (base::Contains(guids_, guid)) {
  282. guid = base::GUID::GenerateRandomV4();
  283. guids_reassigned_ = true;
  284. }
  285. guids_.insert(guid);
  286. }
  287. std::string date_added_string;
  288. string_value = value.FindStringKey(kDateAddedKey);
  289. if (string_value)
  290. date_added_string = *string_value;
  291. else
  292. date_added_string = base::NumberToString(Time::Now().ToInternalValue());
  293. int64_t date_added_time;
  294. base::StringToInt64(date_added_string, &date_added_time);
  295. std::string date_last_used_string;
  296. string_value = value.FindStringKey(kDateLastUsed);
  297. if (string_value)
  298. date_last_used_string = *string_value;
  299. else
  300. date_last_used_string = base::NumberToString(0);
  301. int64_t date_last_used;
  302. base::StringToInt64(date_last_used_string, &date_last_used);
  303. const std::string* type_string = value.FindStringKey(kTypeKey);
  304. if (!type_string)
  305. return false;
  306. if (*type_string != kTypeURL && *type_string != kTypeFolder)
  307. return false; // Unknown type.
  308. if (*type_string == kTypeURL) {
  309. const std::string* url_string = value.FindStringKey(kURLKey);
  310. if (!url_string)
  311. return false;
  312. GURL url = GURL(*url_string);
  313. if (!node && url.is_valid()) {
  314. DCHECK(guid.is_valid());
  315. node = new BookmarkNode(id, guid, url);
  316. } else {
  317. return false; // Node invalid.
  318. }
  319. if (parent)
  320. parent->Add(base::WrapUnique(node));
  321. UpdateChecksumWithUrlNode(id_string, title, *url_string);
  322. } else {
  323. std::string last_modified_date;
  324. string_value = value.FindStringKey(kDateModifiedKey);
  325. if (string_value)
  326. last_modified_date = *string_value;
  327. else
  328. last_modified_date = base::NumberToString(Time::Now().ToInternalValue());
  329. const base::Value* child_values = value.FindListKey(kChildrenKey);
  330. if (!child_values)
  331. return false;
  332. if (!node) {
  333. DCHECK(guid.is_valid());
  334. node = new BookmarkNode(id, guid, GURL());
  335. } else {
  336. // If a new node is not created, explicitly assign ID to the existing one.
  337. node->set_id(id);
  338. }
  339. int64_t internal_time;
  340. base::StringToInt64(last_modified_date, &internal_time);
  341. node->set_date_folder_modified(Time::FromInternalValue(internal_time));
  342. if (parent)
  343. parent->Add(base::WrapUnique(node));
  344. UpdateChecksumWithFolderNode(id_string, title);
  345. if (!DecodeChildren(*child_values, node))
  346. return false;
  347. }
  348. node->SetTitle(title);
  349. node->set_date_added(Time::FromInternalValue(date_added_time));
  350. node->set_date_last_used(Time::FromInternalValue(date_last_used));
  351. BookmarkNode::MetaInfoMap meta_info_map;
  352. if (!DecodeMetaInfo(value, &meta_info_map))
  353. return false;
  354. node->SetMetaInfoMap(meta_info_map);
  355. return true;
  356. }
  357. bool BookmarkCodec::DecodeMetaInfo(const base::Value& value,
  358. BookmarkNode::MetaInfoMap* meta_info_map) {
  359. DCHECK(value.is_dict());
  360. DCHECK(meta_info_map);
  361. meta_info_map->clear();
  362. const base::Value* meta_info = value.FindKey(kMetaInfo);
  363. if (!meta_info)
  364. return true;
  365. std::unique_ptr<base::Value> deserialized_holder;
  366. // Meta info used to be stored as a serialized dictionary, so attempt to
  367. // parse the value as one.
  368. const std::string* meta_info_str = meta_info->GetIfString();
  369. if (meta_info_str) {
  370. JSONStringValueDeserializer deserializer(*meta_info_str);
  371. deserialized_holder = deserializer.Deserialize(nullptr, nullptr);
  372. if (!deserialized_holder)
  373. return false;
  374. meta_info = deserialized_holder.get();
  375. }
  376. // meta_info is now either the kMetaInfo node, or the deserialized node if it
  377. // was stored as a string. Either way it should now be a (possibly nested)
  378. // dictionary of meta info values.
  379. if (!meta_info->is_dict())
  380. return false;
  381. DecodeMetaInfoHelper(*meta_info, std::string(), meta_info_map);
  382. return true;
  383. }
  384. void BookmarkCodec::DecodeMetaInfoHelper(
  385. const base::Value& dict,
  386. const std::string& prefix,
  387. BookmarkNode::MetaInfoMap* meta_info_map) {
  388. DCHECK(dict.is_dict());
  389. for (const auto it : dict.DictItems()) {
  390. // Deprecated keys should be excluded after removing enhanced bookmarks
  391. // feature crrev.com/1638413003.
  392. if (base::StartsWith(it.first, "stars.", base::CompareCase::SENSITIVE))
  393. continue;
  394. if (it.second.is_dict()) {
  395. DecodeMetaInfoHelper(it.second, prefix + it.first + ".", meta_info_map);
  396. } else {
  397. const std::string* str = it.second.GetIfString();
  398. if (str)
  399. (*meta_info_map)[prefix + it.first] = *str;
  400. }
  401. }
  402. }
  403. void BookmarkCodec::ReassignIDs(BookmarkNode* bb_node,
  404. BookmarkNode* other_node,
  405. BookmarkNode* mobile_node) {
  406. maximum_id_ = 0;
  407. ReassignIDsHelper(bb_node);
  408. ReassignIDsHelper(other_node);
  409. ReassignIDsHelper(mobile_node);
  410. ids_reassigned_ = true;
  411. }
  412. void BookmarkCodec::ReassignIDsHelper(BookmarkNode* node) {
  413. DCHECK(node);
  414. node->set_id(++maximum_id_);
  415. for (const auto& child : node->children())
  416. ReassignIDsHelper(child.get());
  417. }
  418. void BookmarkCodec::UpdateChecksum(const std::string& str) {
  419. base::MD5Update(&md5_context_, str);
  420. }
  421. void BookmarkCodec::UpdateChecksum(const std::u16string& str) {
  422. base::MD5Update(&md5_context_,
  423. base::StringPiece(
  424. reinterpret_cast<const char*>(str.data()),
  425. str.length() * sizeof(str[0])));
  426. }
  427. void BookmarkCodec::UpdateChecksumWithUrlNode(const std::string& id,
  428. const std::u16string& title,
  429. const std::string& url) {
  430. DCHECK(base::IsStringUTF8(url));
  431. UpdateChecksum(id);
  432. UpdateChecksum(title);
  433. UpdateChecksum(kTypeURL);
  434. UpdateChecksum(url);
  435. }
  436. void BookmarkCodec::UpdateChecksumWithFolderNode(const std::string& id,
  437. const std::u16string& title) {
  438. UpdateChecksum(id);
  439. UpdateChecksum(title);
  440. UpdateChecksum(kTypeFolder);
  441. }
  442. void BookmarkCodec::InitializeChecksum() {
  443. base::MD5Init(&md5_context_);
  444. }
  445. void BookmarkCodec::FinalizeChecksum() {
  446. base::MD5Digest digest;
  447. base::MD5Final(&digest, &md5_context_);
  448. computed_checksum_ = base::MD5DigestToBase16(digest);
  449. }
  450. } // namespace bookmarks