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.
5 #include "components/bookmarks/browser/bookmark_codec.h"
9 #include "base/json/json_string_value_serializer.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/string_util.h"
12 #include "base/values.h"
13 #include "components/bookmarks/browser/bookmark_model.h"
14 #include "grit/components_strings.h"
15 #include "ui/base/l10n/l10n_util.h"
22 const char* BookmarkCodec::kRootsKey
= "roots";
23 const char* BookmarkCodec::kRootFolderNameKey
= "bookmark_bar";
24 const char* BookmarkCodec::kOtherBookmarkFolderNameKey
= "other";
25 // The value is left as 'synced' for historical reasons.
26 const char* BookmarkCodec::kMobileBookmarkFolderNameKey
= "synced";
27 const char* BookmarkCodec::kVersionKey
= "version";
28 const char* BookmarkCodec::kChecksumKey
= "checksum";
29 const char* BookmarkCodec::kIdKey
= "id";
30 const char* BookmarkCodec::kTypeKey
= "type";
31 const char* BookmarkCodec::kNameKey
= "name";
32 const char* BookmarkCodec::kDateAddedKey
= "date_added";
33 const char* BookmarkCodec::kURLKey
= "url";
34 const char* BookmarkCodec::kDateModifiedKey
= "date_modified";
35 const char* BookmarkCodec::kChildrenKey
= "children";
36 const char* BookmarkCodec::kMetaInfo
= "meta_info";
37 const char* BookmarkCodec::kSyncTransactionVersion
= "sync_transaction_version";
38 const char* BookmarkCodec::kTypeURL
= "url";
39 const char* BookmarkCodec::kTypeFolder
= "folder";
41 // Current version of the file.
42 static const int kCurrentVersion
= 1;
44 BookmarkCodec::BookmarkCodec()
45 : ids_reassigned_(false),
48 model_sync_transaction_version_(
49 BookmarkNode::kInvalidSyncTransactionVersion
) {
52 BookmarkCodec::~BookmarkCodec() {}
54 base::Value
* BookmarkCodec::Encode(BookmarkModel
* model
) {
55 return Encode(model
->bookmark_bar_node(),
58 model
->root_node()->GetMetaInfoMap(),
59 model
->root_node()->sync_transaction_version());
62 base::Value
* BookmarkCodec::Encode(
63 const BookmarkNode
* bookmark_bar_node
,
64 const BookmarkNode
* other_folder_node
,
65 const BookmarkNode
* mobile_folder_node
,
66 const BookmarkNode::MetaInfoMap
* model_meta_info_map
,
67 int64 sync_transaction_version
) {
68 ids_reassigned_
= false;
70 base::DictionaryValue
* roots
= new base::DictionaryValue();
71 roots
->Set(kRootFolderNameKey
, EncodeNode(bookmark_bar_node
));
72 roots
->Set(kOtherBookmarkFolderNameKey
, EncodeNode(other_folder_node
));
73 roots
->Set(kMobileBookmarkFolderNameKey
, EncodeNode(mobile_folder_node
));
74 if (model_meta_info_map
)
75 roots
->Set(kMetaInfo
, EncodeMetaInfo(*model_meta_info_map
));
76 if (sync_transaction_version
!=
77 BookmarkNode::kInvalidSyncTransactionVersion
) {
78 roots
->SetString(kSyncTransactionVersion
,
79 base::Int64ToString(sync_transaction_version
));
81 base::DictionaryValue
* main
= new base::DictionaryValue();
82 main
->SetInteger(kVersionKey
, kCurrentVersion
);
84 // We are going to store the computed checksum. So set stored checksum to be
85 // the same as computed checksum.
86 stored_checksum_
= computed_checksum_
;
87 main
->Set(kChecksumKey
, new base::StringValue(computed_checksum_
));
88 main
->Set(kRootsKey
, roots
);
92 bool BookmarkCodec::Decode(BookmarkNode
* bb_node
,
93 BookmarkNode
* other_folder_node
,
94 BookmarkNode
* mobile_folder_node
,
96 const base::Value
& value
) {
98 ids_reassigned_
= false;
101 stored_checksum_
.clear();
102 InitializeChecksum();
103 bool success
= DecodeHelper(bb_node
, other_folder_node
, mobile_folder_node
,
106 // If either the checksums differ or some IDs were missing/not unique,
108 if (!ids_valid_
|| computed_checksum() != stored_checksum())
109 ReassignIDs(bb_node
, other_folder_node
, mobile_folder_node
);
110 *max_id
= maximum_id_
+ 1;
114 base::Value
* BookmarkCodec::EncodeNode(const BookmarkNode
* node
) {
115 base::DictionaryValue
* value
= new base::DictionaryValue();
116 std::string id
= base::Int64ToString(node
->id());
117 value
->SetString(kIdKey
, id
);
118 const base::string16
& title
= node
->GetTitle();
119 value
->SetString(kNameKey
, title
);
120 value
->SetString(kDateAddedKey
,
121 base::Int64ToString(node
->date_added().ToInternalValue()));
122 if (node
->is_url()) {
123 value
->SetString(kTypeKey
, kTypeURL
);
124 std::string url
= node
->url().possibly_invalid_spec();
125 value
->SetString(kURLKey
, url
);
126 UpdateChecksumWithUrlNode(id
, title
, url
);
128 value
->SetString(kTypeKey
, kTypeFolder
);
129 value
->SetString(kDateModifiedKey
,
130 base::Int64ToString(node
->date_folder_modified().
132 UpdateChecksumWithFolderNode(id
, title
);
134 base::ListValue
* child_values
= new base::ListValue();
135 value
->Set(kChildrenKey
, child_values
);
136 for (int i
= 0; i
< node
->child_count(); ++i
)
137 child_values
->Append(EncodeNode(node
->GetChild(i
)));
139 const BookmarkNode::MetaInfoMap
* meta_info_map
= node
->GetMetaInfoMap();
141 value
->Set(kMetaInfo
, EncodeMetaInfo(*meta_info_map
));
142 if (node
->sync_transaction_version() !=
143 BookmarkNode::kInvalidSyncTransactionVersion
) {
144 value
->SetString(kSyncTransactionVersion
,
145 base::Int64ToString(node
->sync_transaction_version()));
150 base::Value
* BookmarkCodec::EncodeMetaInfo(
151 const BookmarkNode::MetaInfoMap
& meta_info_map
) {
152 base::DictionaryValue
* meta_info
= new base::DictionaryValue
;
153 for (BookmarkNode::MetaInfoMap::const_iterator it
= meta_info_map
.begin();
154 it
!= meta_info_map
.end(); ++it
) {
155 meta_info
->SetStringWithoutPathExpansion(it
->first
, it
->second
);
160 bool BookmarkCodec::DecodeHelper(BookmarkNode
* bb_node
,
161 BookmarkNode
* other_folder_node
,
162 BookmarkNode
* mobile_folder_node
,
163 const base::Value
& value
) {
164 const base::DictionaryValue
* d_value
= nullptr;
165 if (!value
.GetAsDictionary(&d_value
))
166 return false; // Unexpected type.
169 if (!d_value
->GetInteger(kVersionKey
, &version
) || version
!= kCurrentVersion
)
170 return false; // Unknown version.
172 const base::Value
* checksum_value
;
173 if (d_value
->Get(kChecksumKey
, &checksum_value
)) {
174 if (checksum_value
->GetType() != base::Value::TYPE_STRING
)
176 if (!checksum_value
->GetAsString(&stored_checksum_
))
180 const base::Value
* roots
;
181 if (!d_value
->Get(kRootsKey
, &roots
))
182 return false; // No roots.
184 const base::DictionaryValue
* roots_d_value
= nullptr;
185 if (!roots
->GetAsDictionary(&roots_d_value
))
186 return false; // Invalid type for roots.
187 const base::Value
* root_folder_value
;
188 const base::Value
* other_folder_value
= nullptr;
189 const base::DictionaryValue
* root_folder_d_value
= nullptr;
190 const base::DictionaryValue
* other_folder_d_value
= nullptr;
191 if (!roots_d_value
->Get(kRootFolderNameKey
, &root_folder_value
) ||
192 !root_folder_value
->GetAsDictionary(&root_folder_d_value
) ||
193 !roots_d_value
->Get(kOtherBookmarkFolderNameKey
, &other_folder_value
) ||
194 !other_folder_value
->GetAsDictionary(&other_folder_d_value
)) {
195 return false; // Invalid type for root folder and/or other
198 DecodeNode(*root_folder_d_value
, nullptr, bb_node
);
199 DecodeNode(*other_folder_d_value
, nullptr, other_folder_node
);
201 // Fail silently if we can't deserialize mobile bookmarks. We can't require
202 // them to exist in order to be backwards-compatible with older versions of
204 const base::Value
* mobile_folder_value
;
205 const base::DictionaryValue
* mobile_folder_d_value
= nullptr;
206 if (roots_d_value
->Get(kMobileBookmarkFolderNameKey
, &mobile_folder_value
) &&
207 mobile_folder_value
->GetAsDictionary(&mobile_folder_d_value
)) {
208 DecodeNode(*mobile_folder_d_value
, nullptr, mobile_folder_node
);
210 // If we didn't find the mobile folder, we're almost guaranteed to have a
211 // duplicate id when we add the mobile folder. Consequently, if we don't
212 // intend to reassign ids in the future (ids_valid_ is still true), then at
213 // least reassign the mobile bookmarks to avoid it colliding with anything
216 ReassignIDsHelper(mobile_folder_node
);
219 if (!DecodeMetaInfo(*roots_d_value
, &model_meta_info_map_
,
220 &model_sync_transaction_version_
))
223 std::string sync_transaction_version_str
;
224 if (roots_d_value
->GetString(kSyncTransactionVersion
,
225 &sync_transaction_version_str
) &&
226 !base::StringToInt64(sync_transaction_version_str
,
227 &model_sync_transaction_version_
))
230 // Need to reset the type as decoding resets the type to FOLDER. Similarly
231 // we need to reset the title as the title is persisted and restored from
233 bb_node
->set_type(BookmarkNode::BOOKMARK_BAR
);
234 other_folder_node
->set_type(BookmarkNode::OTHER_NODE
);
235 mobile_folder_node
->set_type(BookmarkNode::MOBILE
);
236 bb_node
->SetTitle(l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_FOLDER_NAME
));
237 other_folder_node
->SetTitle(
238 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OTHER_FOLDER_NAME
));
239 mobile_folder_node
->SetTitle(
240 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_MOBILE_FOLDER_NAME
));
245 bool BookmarkCodec::DecodeChildren(const base::ListValue
& child_value_list
,
246 BookmarkNode
* parent
) {
247 for (size_t i
= 0; i
< child_value_list
.GetSize(); ++i
) {
248 const base::Value
* child_value
;
249 if (!child_value_list
.Get(i
, &child_value
))
252 const base::DictionaryValue
* child_d_value
= nullptr;
253 if (!child_value
->GetAsDictionary(&child_d_value
))
255 DecodeNode(*child_d_value
, parent
, nullptr);
260 bool BookmarkCodec::DecodeNode(const base::DictionaryValue
& value
,
261 BookmarkNode
* parent
,
262 BookmarkNode
* node
) {
263 // If no |node| is specified, we'll create one and add it to the |parent|.
264 // Therefore, in that case, |parent| must be non-NULL.
265 if (!node
&& !parent
) {
270 std::string id_string
;
273 if (!value
.GetString(kIdKey
, &id_string
) ||
274 !base::StringToInt64(id_string
, &id
) ||
275 ids_
.count(id
) != 0) {
282 maximum_id_
= std::max(maximum_id_
, id
);
284 base::string16 title
;
285 value
.GetString(kNameKey
, &title
);
287 std::string date_added_string
;
288 if (!value
.GetString(kDateAddedKey
, &date_added_string
))
289 date_added_string
= base::Int64ToString(Time::Now().ToInternalValue());
291 base::StringToInt64(date_added_string
, &internal_time
);
293 std::string type_string
;
294 if (!value
.GetString(kTypeKey
, &type_string
))
297 if (type_string
!= kTypeURL
&& type_string
!= kTypeFolder
)
298 return false; // Unknown type.
300 if (type_string
== kTypeURL
) {
301 std::string url_string
;
302 if (!value
.GetString(kURLKey
, &url_string
))
305 GURL url
= GURL(url_string
);
306 if (!node
&& url
.is_valid())
307 node
= new BookmarkNode(id
, url
);
309 return false; // Node invalid.
312 parent
->Add(node
, parent
->child_count());
313 node
->set_type(BookmarkNode::URL
);
314 UpdateChecksumWithUrlNode(id_string
, title
, url_string
);
316 std::string last_modified_date
;
317 if (!value
.GetString(kDateModifiedKey
, &last_modified_date
))
318 last_modified_date
= base::Int64ToString(Time::Now().ToInternalValue());
320 const base::Value
* child_values
;
321 if (!value
.Get(kChildrenKey
, &child_values
))
324 if (child_values
->GetType() != base::Value::TYPE_LIST
)
328 node
= new BookmarkNode(id
, GURL());
330 // If a new node is not created, explicitly assign ID to the existing one.
334 node
->set_type(BookmarkNode::FOLDER
);
336 base::StringToInt64(last_modified_date
, &internal_time
);
337 node
->set_date_folder_modified(Time::FromInternalValue(internal_time
));
340 parent
->Add(node
, parent
->child_count());
342 UpdateChecksumWithFolderNode(id_string
, title
);
344 const base::ListValue
* child_l_values
= nullptr;
345 if (!child_values
->GetAsList(&child_l_values
))
347 if (!DecodeChildren(*child_l_values
, node
))
351 node
->SetTitle(title
);
352 node
->set_date_added(Time::FromInternalValue(internal_time
));
354 int64 sync_transaction_version
= node
->sync_transaction_version();
355 BookmarkNode::MetaInfoMap meta_info_map
;
356 if (!DecodeMetaInfo(value
, &meta_info_map
, &sync_transaction_version
))
358 node
->SetMetaInfoMap(meta_info_map
);
360 std::string sync_transaction_version_str
;
361 if (value
.GetString(kSyncTransactionVersion
, &sync_transaction_version_str
) &&
362 !base::StringToInt64(sync_transaction_version_str
,
363 &sync_transaction_version
))
366 node
->set_sync_transaction_version(sync_transaction_version
);
371 bool BookmarkCodec::DecodeMetaInfo(const base::DictionaryValue
& value
,
372 BookmarkNode::MetaInfoMap
* meta_info_map
,
373 int64
* sync_transaction_version
) {
374 DCHECK(meta_info_map
);
375 DCHECK(sync_transaction_version
);
376 meta_info_map
->clear();
378 const base::Value
* meta_info
;
379 if (!value
.Get(kMetaInfo
, &meta_info
))
382 scoped_ptr
<base::Value
> deserialized_holder
;
384 // Meta info used to be stored as a serialized dictionary, so attempt to
385 // parse the value as one.
386 if (meta_info
->IsType(base::Value::TYPE_STRING
)) {
387 std::string meta_info_str
;
388 meta_info
->GetAsString(&meta_info_str
);
389 JSONStringValueSerializer
serializer(meta_info_str
);
390 deserialized_holder
.reset(serializer
.Deserialize(nullptr, nullptr));
391 if (!deserialized_holder
)
393 meta_info
= deserialized_holder
.get();
395 // meta_info is now either the kMetaInfo node, or the deserialized node if it
396 // was stored as a string. Either way it should now be a (possibly nested)
397 // dictionary of meta info values.
398 const base::DictionaryValue
* meta_info_dict
;
399 if (!meta_info
->GetAsDictionary(&meta_info_dict
))
401 DecodeMetaInfoHelper(*meta_info_dict
, std::string(), meta_info_map
);
403 // Previously sync transaction version was stored in the meta info field
404 // using this key. If the key is present when decoding, set the sync
405 // transaction version to its value, then delete the field.
406 if (deserialized_holder
) {
407 const char kBookmarkTransactionVersionKey
[] = "sync.transaction_version";
408 BookmarkNode::MetaInfoMap::iterator it
=
409 meta_info_map
->find(kBookmarkTransactionVersionKey
);
410 if (it
!= meta_info_map
->end()) {
411 base::StringToInt64(it
->second
, sync_transaction_version
);
412 meta_info_map
->erase(it
);
419 void BookmarkCodec::DecodeMetaInfoHelper(
420 const base::DictionaryValue
& dict
,
421 const std::string
& prefix
,
422 BookmarkNode::MetaInfoMap
* meta_info_map
) {
423 for (base::DictionaryValue::Iterator
it(dict
); !it
.IsAtEnd(); it
.Advance()) {
424 if (it
.value().IsType(base::Value::TYPE_DICTIONARY
)) {
425 const base::DictionaryValue
* subdict
;
426 it
.value().GetAsDictionary(&subdict
);
427 DecodeMetaInfoHelper(*subdict
, prefix
+ it
.key() + ".", meta_info_map
);
428 } else if (it
.value().IsType(base::Value::TYPE_STRING
)) {
429 it
.value().GetAsString(&(*meta_info_map
)[prefix
+ it
.key()]);
434 void BookmarkCodec::ReassignIDs(BookmarkNode
* bb_node
,
435 BookmarkNode
* other_node
,
436 BookmarkNode
* mobile_node
) {
438 ReassignIDsHelper(bb_node
);
439 ReassignIDsHelper(other_node
);
440 ReassignIDsHelper(mobile_node
);
441 ids_reassigned_
= true;
444 void BookmarkCodec::ReassignIDsHelper(BookmarkNode
* node
) {
446 node
->set_id(++maximum_id_
);
447 for (int i
= 0; i
< node
->child_count(); ++i
)
448 ReassignIDsHelper(node
->GetChild(i
));
451 void BookmarkCodec::UpdateChecksum(const std::string
& str
) {
452 base::MD5Update(&md5_context_
, str
);
455 void BookmarkCodec::UpdateChecksum(const base::string16
& str
) {
456 base::MD5Update(&md5_context_
,
458 reinterpret_cast<const char*>(str
.data()),
459 str
.length() * sizeof(str
[0])));
462 void BookmarkCodec::UpdateChecksumWithUrlNode(const std::string
& id
,
463 const base::string16
& title
,
464 const std::string
& url
) {
465 DCHECK(base::IsStringUTF8(url
));
467 UpdateChecksum(title
);
468 UpdateChecksum(kTypeURL
);
472 void BookmarkCodec::UpdateChecksumWithFolderNode(const std::string
& id
,
473 const base::string16
& title
) {
475 UpdateChecksum(title
);
476 UpdateChecksum(kTypeFolder
);
479 void BookmarkCodec::InitializeChecksum() {
480 base::MD5Init(&md5_context_
);
483 void BookmarkCodec::FinalizeChecksum() {
484 base::MD5Digest digest
;
485 base::MD5Final(&digest
, &md5_context_
);
486 computed_checksum_
= base::MD5DigestToBase16(digest
);
489 } // namespace bookmarks