OLD | NEW |
| (Empty) |
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 | |
5 #include "components/bookmarks/core/browser/bookmark_codec.h" | |
6 | |
7 #include <algorithm> | |
8 | |
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/core/browser/bookmark_model.h" | |
14 #include "grit/component_strings.h" | |
15 #include "ui/base/l10n/l10n_util.h" | |
16 #include "url/gurl.h" | |
17 | |
18 using base::Time; | |
19 | |
20 const char* BookmarkCodec::kRootsKey = "roots"; | |
21 const char* BookmarkCodec::kRootFolderNameKey = "bookmark_bar"; | |
22 const char* BookmarkCodec::kOtherBookmarkFolderNameKey = "other"; | |
23 // The value is left as 'synced' for historical reasons. | |
24 const char* BookmarkCodec::kMobileBookmarkFolderNameKey = "synced"; | |
25 const char* BookmarkCodec::kVersionKey = "version"; | |
26 const char* BookmarkCodec::kChecksumKey = "checksum"; | |
27 const char* BookmarkCodec::kIdKey = "id"; | |
28 const char* BookmarkCodec::kTypeKey = "type"; | |
29 const char* BookmarkCodec::kNameKey = "name"; | |
30 const char* BookmarkCodec::kDateAddedKey = "date_added"; | |
31 const char* BookmarkCodec::kURLKey = "url"; | |
32 const char* BookmarkCodec::kDateModifiedKey = "date_modified"; | |
33 const char* BookmarkCodec::kChildrenKey = "children"; | |
34 const char* BookmarkCodec::kMetaInfo = "meta_info"; | |
35 const char* BookmarkCodec::kSyncTransactionVersion = "sync_transaction_version"; | |
36 const char* BookmarkCodec::kTypeURL = "url"; | |
37 const char* BookmarkCodec::kTypeFolder = "folder"; | |
38 | |
39 // Current version of the file. | |
40 static const int kCurrentVersion = 1; | |
41 | |
42 BookmarkCodec::BookmarkCodec() | |
43 : ids_reassigned_(false), | |
44 ids_valid_(true), | |
45 maximum_id_(0), | |
46 model_sync_transaction_version_( | |
47 BookmarkNode::kInvalidSyncTransactionVersion) { | |
48 } | |
49 | |
50 BookmarkCodec::~BookmarkCodec() {} | |
51 | |
52 base::Value* BookmarkCodec::Encode(BookmarkModel* model) { | |
53 return Encode(model->bookmark_bar_node(), | |
54 model->other_node(), | |
55 model->mobile_node(), | |
56 model->root_node()->GetMetaInfoMap(), | |
57 model->root_node()->sync_transaction_version()); | |
58 } | |
59 | |
60 base::Value* BookmarkCodec::Encode( | |
61 const BookmarkNode* bookmark_bar_node, | |
62 const BookmarkNode* other_folder_node, | |
63 const BookmarkNode* mobile_folder_node, | |
64 const BookmarkNode::MetaInfoMap* model_meta_info_map, | |
65 int64 sync_transaction_version) { | |
66 ids_reassigned_ = false; | |
67 InitializeChecksum(); | |
68 base::DictionaryValue* roots = new base::DictionaryValue(); | |
69 roots->Set(kRootFolderNameKey, EncodeNode(bookmark_bar_node)); | |
70 roots->Set(kOtherBookmarkFolderNameKey, EncodeNode(other_folder_node)); | |
71 roots->Set(kMobileBookmarkFolderNameKey, EncodeNode(mobile_folder_node)); | |
72 if (model_meta_info_map) | |
73 roots->Set(kMetaInfo, EncodeMetaInfo(*model_meta_info_map)); | |
74 if (sync_transaction_version != | |
75 BookmarkNode::kInvalidSyncTransactionVersion) { | |
76 roots->SetString(kSyncTransactionVersion, | |
77 base::Int64ToString(sync_transaction_version)); | |
78 } | |
79 base::DictionaryValue* main = new base::DictionaryValue(); | |
80 main->SetInteger(kVersionKey, kCurrentVersion); | |
81 FinalizeChecksum(); | |
82 // We are going to store the computed checksum. So set stored checksum to be | |
83 // the same as computed checksum. | |
84 stored_checksum_ = computed_checksum_; | |
85 main->Set(kChecksumKey, new base::StringValue(computed_checksum_)); | |
86 main->Set(kRootsKey, roots); | |
87 return main; | |
88 } | |
89 | |
90 bool BookmarkCodec::Decode(BookmarkNode* bb_node, | |
91 BookmarkNode* other_folder_node, | |
92 BookmarkNode* mobile_folder_node, | |
93 int64* max_id, | |
94 const base::Value& value) { | |
95 ids_.clear(); | |
96 ids_reassigned_ = false; | |
97 ids_valid_ = true; | |
98 maximum_id_ = 0; | |
99 stored_checksum_.clear(); | |
100 InitializeChecksum(); | |
101 bool success = DecodeHelper(bb_node, other_folder_node, mobile_folder_node, | |
102 value); | |
103 FinalizeChecksum(); | |
104 // If either the checksums differ or some IDs were missing/not unique, | |
105 // reassign IDs. | |
106 if (!ids_valid_ || computed_checksum() != stored_checksum()) | |
107 ReassignIDs(bb_node, other_folder_node, mobile_folder_node); | |
108 *max_id = maximum_id_ + 1; | |
109 return success; | |
110 } | |
111 | |
112 base::Value* BookmarkCodec::EncodeNode(const BookmarkNode* node) { | |
113 base::DictionaryValue* value = new base::DictionaryValue(); | |
114 std::string id = base::Int64ToString(node->id()); | |
115 value->SetString(kIdKey, id); | |
116 const base::string16& title = node->GetTitle(); | |
117 value->SetString(kNameKey, title); | |
118 value->SetString(kDateAddedKey, | |
119 base::Int64ToString(node->date_added().ToInternalValue())); | |
120 if (node->is_url()) { | |
121 value->SetString(kTypeKey, kTypeURL); | |
122 std::string url = node->url().possibly_invalid_spec(); | |
123 value->SetString(kURLKey, url); | |
124 UpdateChecksumWithUrlNode(id, title, url); | |
125 } else { | |
126 value->SetString(kTypeKey, kTypeFolder); | |
127 value->SetString(kDateModifiedKey, | |
128 base::Int64ToString(node->date_folder_modified(). | |
129 ToInternalValue())); | |
130 UpdateChecksumWithFolderNode(id, title); | |
131 | |
132 base::ListValue* child_values = new base::ListValue(); | |
133 value->Set(kChildrenKey, child_values); | |
134 for (int i = 0; i < node->child_count(); ++i) | |
135 child_values->Append(EncodeNode(node->GetChild(i))); | |
136 } | |
137 const BookmarkNode::MetaInfoMap* meta_info_map = node->GetMetaInfoMap(); | |
138 if (meta_info_map) | |
139 value->Set(kMetaInfo, EncodeMetaInfo(*meta_info_map)); | |
140 if (node->sync_transaction_version() != | |
141 BookmarkNode::kInvalidSyncTransactionVersion) { | |
142 value->SetString(kSyncTransactionVersion, | |
143 base::Int64ToString(node->sync_transaction_version())); | |
144 } | |
145 return value; | |
146 } | |
147 | |
148 base::Value* BookmarkCodec::EncodeMetaInfo( | |
149 const BookmarkNode::MetaInfoMap& meta_info_map) { | |
150 base::DictionaryValue* meta_info = new base::DictionaryValue; | |
151 for (BookmarkNode::MetaInfoMap::const_iterator it = meta_info_map.begin(); | |
152 it != meta_info_map.end(); ++it) { | |
153 meta_info->SetStringWithoutPathExpansion(it->first, it->second); | |
154 } | |
155 return meta_info; | |
156 } | |
157 | |
158 bool BookmarkCodec::DecodeHelper(BookmarkNode* bb_node, | |
159 BookmarkNode* other_folder_node, | |
160 BookmarkNode* mobile_folder_node, | |
161 const base::Value& value) { | |
162 if (value.GetType() != base::Value::TYPE_DICTIONARY) | |
163 return false; // Unexpected type. | |
164 | |
165 const base::DictionaryValue& d_value = | |
166 static_cast<const base::DictionaryValue&>(value); | |
167 | |
168 int version; | |
169 if (!d_value.GetInteger(kVersionKey, &version) || version != kCurrentVersion) | |
170 return false; // Unknown version. | |
171 | |
172 const base::Value* checksum_value; | |
173 if (d_value.Get(kChecksumKey, &checksum_value)) { | |
174 if (checksum_value->GetType() != base::Value::TYPE_STRING) | |
175 return false; | |
176 if (!checksum_value->GetAsString(&stored_checksum_)) | |
177 return false; | |
178 } | |
179 | |
180 const base::Value* roots; | |
181 if (!d_value.Get(kRootsKey, &roots)) | |
182 return false; // No roots. | |
183 | |
184 if (roots->GetType() != base::Value::TYPE_DICTIONARY) | |
185 return false; // Invalid type for roots. | |
186 | |
187 const base::DictionaryValue* roots_d_value = | |
188 static_cast<const base::DictionaryValue*>(roots); | |
189 const base::Value* root_folder_value; | |
190 const base::Value* other_folder_value = NULL; | |
191 if (!roots_d_value->Get(kRootFolderNameKey, &root_folder_value) || | |
192 root_folder_value->GetType() != base::Value::TYPE_DICTIONARY || | |
193 !roots_d_value->Get(kOtherBookmarkFolderNameKey, &other_folder_value) || | |
194 other_folder_value->GetType() != base::Value::TYPE_DICTIONARY) { | |
195 return false; // Invalid type for root folder and/or other | |
196 // folder. | |
197 } | |
198 DecodeNode(*static_cast<const base::DictionaryValue*>(root_folder_value), | |
199 NULL, bb_node); | |
200 DecodeNode(*static_cast<const base::DictionaryValue*>(other_folder_value), | |
201 NULL, other_folder_node); | |
202 | |
203 // Fail silently if we can't deserialize mobile bookmarks. We can't require | |
204 // them to exist in order to be backwards-compatible with older versions of | |
205 // chrome. | |
206 const base::Value* mobile_folder_value; | |
207 if (roots_d_value->Get(kMobileBookmarkFolderNameKey, &mobile_folder_value) && | |
208 mobile_folder_value->GetType() == base::Value::TYPE_DICTIONARY) { | |
209 DecodeNode(*static_cast<const base::DictionaryValue*>(mobile_folder_value), | |
210 NULL, mobile_folder_node); | |
211 } else { | |
212 // If we didn't find the mobile folder, we're almost guaranteed to have a | |
213 // duplicate id when we add the mobile folder. Consequently, if we don't | |
214 // intend to reassign ids in the future (ids_valid_ is still true), then at | |
215 // least reassign the mobile bookmarks to avoid it colliding with anything | |
216 // else. | |
217 if (ids_valid_) | |
218 ReassignIDsHelper(mobile_folder_node); | |
219 } | |
220 | |
221 if (!DecodeMetaInfo(*roots_d_value, &model_meta_info_map_, | |
222 &model_sync_transaction_version_)) | |
223 return false; | |
224 | |
225 std::string sync_transaction_version_str; | |
226 if (roots_d_value->GetString(kSyncTransactionVersion, | |
227 &sync_transaction_version_str) && | |
228 !base::StringToInt64(sync_transaction_version_str, | |
229 &model_sync_transaction_version_)) | |
230 return false; | |
231 | |
232 // Need to reset the type as decoding resets the type to FOLDER. Similarly | |
233 // we need to reset the title as the title is persisted and restored from | |
234 // the file. | |
235 bb_node->set_type(BookmarkNode::BOOKMARK_BAR); | |
236 other_folder_node->set_type(BookmarkNode::OTHER_NODE); | |
237 mobile_folder_node->set_type(BookmarkNode::MOBILE); | |
238 bb_node->SetTitle(l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_FOLDER_NAME)); | |
239 other_folder_node->SetTitle( | |
240 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OTHER_FOLDER_NAME)); | |
241 mobile_folder_node->SetTitle( | |
242 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_MOBILE_FOLDER_NAME)); | |
243 | |
244 return true; | |
245 } | |
246 | |
247 bool BookmarkCodec::DecodeChildren(const base::ListValue& child_value_list, | |
248 BookmarkNode* parent) { | |
249 for (size_t i = 0; i < child_value_list.GetSize(); ++i) { | |
250 const base::Value* child_value; | |
251 if (!child_value_list.Get(i, &child_value)) | |
252 return false; | |
253 | |
254 if (child_value->GetType() != base::Value::TYPE_DICTIONARY) | |
255 return false; | |
256 | |
257 DecodeNode(*static_cast<const base::DictionaryValue*>(child_value), | |
258 parent, NULL); | |
259 } | |
260 return true; | |
261 } | |
262 | |
263 bool BookmarkCodec::DecodeNode(const base::DictionaryValue& value, | |
264 BookmarkNode* parent, | |
265 BookmarkNode* node) { | |
266 // If no |node| is specified, we'll create one and add it to the |parent|. | |
267 // Therefore, in that case, |parent| must be non-NULL. | |
268 if (!node && !parent) { | |
269 NOTREACHED(); | |
270 return false; | |
271 } | |
272 | |
273 std::string id_string; | |
274 int64 id = 0; | |
275 if (ids_valid_) { | |
276 if (!value.GetString(kIdKey, &id_string) || | |
277 !base::StringToInt64(id_string, &id) || | |
278 ids_.count(id) != 0) { | |
279 ids_valid_ = false; | |
280 } else { | |
281 ids_.insert(id); | |
282 } | |
283 } | |
284 | |
285 maximum_id_ = std::max(maximum_id_, id); | |
286 | |
287 base::string16 title; | |
288 value.GetString(kNameKey, &title); | |
289 | |
290 std::string date_added_string; | |
291 if (!value.GetString(kDateAddedKey, &date_added_string)) | |
292 date_added_string = base::Int64ToString(Time::Now().ToInternalValue()); | |
293 int64 internal_time; | |
294 base::StringToInt64(date_added_string, &internal_time); | |
295 | |
296 std::string type_string; | |
297 if (!value.GetString(kTypeKey, &type_string)) | |
298 return false; | |
299 | |
300 if (type_string != kTypeURL && type_string != kTypeFolder) | |
301 return false; // Unknown type. | |
302 | |
303 if (type_string == kTypeURL) { | |
304 std::string url_string; | |
305 if (!value.GetString(kURLKey, &url_string)) | |
306 return false; | |
307 | |
308 GURL url = GURL(url_string); | |
309 if (!node && url.is_valid()) | |
310 node = new BookmarkNode(id, url); | |
311 else | |
312 return false; // Node invalid. | |
313 | |
314 if (parent) | |
315 parent->Add(node, parent->child_count()); | |
316 node->set_type(BookmarkNode::URL); | |
317 UpdateChecksumWithUrlNode(id_string, title, url_string); | |
318 } else { | |
319 std::string last_modified_date; | |
320 if (!value.GetString(kDateModifiedKey, &last_modified_date)) | |
321 last_modified_date = base::Int64ToString(Time::Now().ToInternalValue()); | |
322 | |
323 const base::Value* child_values; | |
324 if (!value.Get(kChildrenKey, &child_values)) | |
325 return false; | |
326 | |
327 if (child_values->GetType() != base::Value::TYPE_LIST) | |
328 return false; | |
329 | |
330 if (!node) { | |
331 node = new BookmarkNode(id, GURL()); | |
332 } else { | |
333 // If a new node is not created, explicitly assign ID to the existing one. | |
334 node->set_id(id); | |
335 } | |
336 | |
337 node->set_type(BookmarkNode::FOLDER); | |
338 int64 internal_time; | |
339 base::StringToInt64(last_modified_date, &internal_time); | |
340 node->set_date_folder_modified(Time::FromInternalValue(internal_time)); | |
341 | |
342 if (parent) | |
343 parent->Add(node, parent->child_count()); | |
344 | |
345 UpdateChecksumWithFolderNode(id_string, title); | |
346 | |
347 if (!DecodeChildren(*static_cast<const base::ListValue*>(child_values), | |
348 node)) { | |
349 return false; | |
350 } | |
351 } | |
352 | |
353 node->SetTitle(title); | |
354 node->set_date_added(Time::FromInternalValue(internal_time)); | |
355 | |
356 int64 sync_transaction_version = node->sync_transaction_version(); | |
357 BookmarkNode::MetaInfoMap meta_info_map; | |
358 if (!DecodeMetaInfo(value, &meta_info_map, &sync_transaction_version)) | |
359 return false; | |
360 node->SetMetaInfoMap(meta_info_map); | |
361 | |
362 std::string sync_transaction_version_str; | |
363 if (value.GetString(kSyncTransactionVersion, &sync_transaction_version_str) && | |
364 !base::StringToInt64(sync_transaction_version_str, | |
365 &sync_transaction_version)) | |
366 return false; | |
367 | |
368 node->set_sync_transaction_version(sync_transaction_version); | |
369 | |
370 return true; | |
371 } | |
372 | |
373 bool BookmarkCodec::DecodeMetaInfo(const base::DictionaryValue& value, | |
374 BookmarkNode::MetaInfoMap* meta_info_map, | |
375 int64* sync_transaction_version) { | |
376 DCHECK(meta_info_map); | |
377 DCHECK(sync_transaction_version); | |
378 meta_info_map->clear(); | |
379 | |
380 const base::Value* meta_info; | |
381 if (!value.Get(kMetaInfo, &meta_info)) | |
382 return true; | |
383 | |
384 scoped_ptr<base::Value> deserialized_holder; | |
385 | |
386 // Meta info used to be stored as a serialized dictionary, so attempt to | |
387 // parse the value as one. | |
388 if (meta_info->IsType(base::Value::TYPE_STRING)) { | |
389 std::string meta_info_str; | |
390 meta_info->GetAsString(&meta_info_str); | |
391 JSONStringValueSerializer serializer(meta_info_str); | |
392 deserialized_holder.reset(serializer.Deserialize(NULL, NULL)); | |
393 if (!deserialized_holder) | |
394 return false; | |
395 meta_info = deserialized_holder.get(); | |
396 } | |
397 // meta_info is now either the kMetaInfo node, or the deserialized node if it | |
398 // was stored as a string. Either way it should now be a (possibly nested) | |
399 // dictionary of meta info values. | |
400 const base::DictionaryValue* meta_info_dict; | |
401 if (!meta_info->GetAsDictionary(&meta_info_dict)) | |
402 return false; | |
403 DecodeMetaInfoHelper(*meta_info_dict, std::string(), meta_info_map); | |
404 | |
405 // Previously sync transaction version was stored in the meta info field | |
406 // using this key. If the key is present when decoding, set the sync | |
407 // transaction version to its value, then delete the field. | |
408 if (deserialized_holder) { | |
409 const char kBookmarkTransactionVersionKey[] = "sync.transaction_version"; | |
410 BookmarkNode::MetaInfoMap::iterator it = | |
411 meta_info_map->find(kBookmarkTransactionVersionKey); | |
412 if (it != meta_info_map->end()) { | |
413 base::StringToInt64(it->second, sync_transaction_version); | |
414 meta_info_map->erase(it); | |
415 } | |
416 } | |
417 | |
418 return true; | |
419 } | |
420 | |
421 void BookmarkCodec::DecodeMetaInfoHelper( | |
422 const base::DictionaryValue& dict, | |
423 const std::string& prefix, | |
424 BookmarkNode::MetaInfoMap* meta_info_map) { | |
425 for (base::DictionaryValue::Iterator it(dict); !it.IsAtEnd(); it.Advance()) { | |
426 if (it.value().IsType(base::Value::TYPE_DICTIONARY)) { | |
427 const base::DictionaryValue* subdict; | |
428 it.value().GetAsDictionary(&subdict); | |
429 DecodeMetaInfoHelper(*subdict, prefix + it.key() + ".", meta_info_map); | |
430 } else if (it.value().IsType(base::Value::TYPE_STRING)) { | |
431 it.value().GetAsString(&(*meta_info_map)[prefix + it.key()]); | |
432 } | |
433 } | |
434 } | |
435 | |
436 void BookmarkCodec::ReassignIDs(BookmarkNode* bb_node, | |
437 BookmarkNode* other_node, | |
438 BookmarkNode* mobile_node) { | |
439 maximum_id_ = 0; | |
440 ReassignIDsHelper(bb_node); | |
441 ReassignIDsHelper(other_node); | |
442 ReassignIDsHelper(mobile_node); | |
443 ids_reassigned_ = true; | |
444 } | |
445 | |
446 void BookmarkCodec::ReassignIDsHelper(BookmarkNode* node) { | |
447 DCHECK(node); | |
448 node->set_id(++maximum_id_); | |
449 for (int i = 0; i < node->child_count(); ++i) | |
450 ReassignIDsHelper(node->GetChild(i)); | |
451 } | |
452 | |
453 void BookmarkCodec::UpdateChecksum(const std::string& str) { | |
454 base::MD5Update(&md5_context_, str); | |
455 } | |
456 | |
457 void BookmarkCodec::UpdateChecksum(const base::string16& str) { | |
458 base::MD5Update(&md5_context_, | |
459 base::StringPiece( | |
460 reinterpret_cast<const char*>(str.data()), | |
461 str.length() * sizeof(str[0]))); | |
462 } | |
463 | |
464 void BookmarkCodec::UpdateChecksumWithUrlNode(const std::string& id, | |
465 const base::string16& title, | |
466 const std::string& url) { | |
467 DCHECK(base::IsStringUTF8(url)); | |
468 UpdateChecksum(id); | |
469 UpdateChecksum(title); | |
470 UpdateChecksum(kTypeURL); | |
471 UpdateChecksum(url); | |
472 } | |
473 | |
474 void BookmarkCodec::UpdateChecksumWithFolderNode(const std::string& id, | |
475 const base::string16& title) { | |
476 UpdateChecksum(id); | |
477 UpdateChecksum(title); | |
478 UpdateChecksum(kTypeFolder); | |
479 } | |
480 | |
481 void BookmarkCodec::InitializeChecksum() { | |
482 base::MD5Init(&md5_context_); | |
483 } | |
484 | |
485 void BookmarkCodec::FinalizeChecksum() { | |
486 base::MD5Digest digest; | |
487 base::MD5Final(&digest, &md5_context_); | |
488 computed_checksum_ = base::MD5DigestToBase16(digest); | |
489 } | |
OLD | NEW |