Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / bookmarks / bookmark_codec.cc
blob05cd36a5deba4a0f683b75c58f7fa6952a0b3a7b
1 // Copyright (c) 2012 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 "chrome/browser/bookmarks/bookmark_codec.h"
7 #include <algorithm>
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 "chrome/browser/bookmarks/bookmark_model.h"
14 #include "grit/generated_resources.h"
15 #include "ui/base/l10n/l10n_util.h"
16 #include "url/gurl.h"
18 using base::Time;
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";
39 // Current version of the file.
40 static const int kCurrentVersion = 1;
42 BookmarkCodec::BookmarkCodec()
43 : ids_reassigned_(false),
44 ids_valid_(true),
45 maximum_id_(0),
46 model_sync_transaction_version_(
47 BookmarkNode::kInvalidSyncTransactionVersion) {
50 BookmarkCodec::~BookmarkCodec() {}
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());
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));
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;
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;
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);
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)));
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()));
145 return value;
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);
155 return meta_info;
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.
165 const base::DictionaryValue& d_value =
166 static_cast<const base::DictionaryValue&>(value);
168 int version;
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)
175 return false;
176 if (!checksum_value->GetAsString(&stored_checksum_))
177 return false;
180 const base::Value* roots;
181 if (!d_value.Get(kRootsKey, &roots))
182 return false; // No roots.
184 if (roots->GetType() != base::Value::TYPE_DICTIONARY)
185 return false; // Invalid type for roots.
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.
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);
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);
221 if (!DecodeMetaInfo(*roots_d_value, &model_meta_info_map_,
222 &model_sync_transaction_version_))
223 return false;
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;
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));
244 return true;
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;
254 if (child_value->GetType() != base::Value::TYPE_DICTIONARY)
255 return false;
257 DecodeNode(*static_cast<const base::DictionaryValue*>(child_value),
258 parent, NULL);
260 return true;
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;
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);
285 maximum_id_ = std::max(maximum_id_, id);
287 base::string16 title;
288 value.GetString(kNameKey, &title);
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);
296 std::string type_string;
297 if (!value.GetString(kTypeKey, &type_string))
298 return false;
300 if (type_string != kTypeURL && type_string != kTypeFolder)
301 return false; // Unknown type.
303 if (type_string == kTypeURL) {
304 std::string url_string;
305 if (!value.GetString(kURLKey, &url_string))
306 return false;
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.
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());
323 const base::Value* child_values;
324 if (!value.Get(kChildrenKey, &child_values))
325 return false;
327 if (child_values->GetType() != base::Value::TYPE_LIST)
328 return false;
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);
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));
342 if (parent)
343 parent->Add(node, parent->child_count());
345 UpdateChecksumWithFolderNode(id_string, title);
347 if (!DecodeChildren(*static_cast<const base::ListValue*>(child_values),
348 node)) {
349 return false;
353 node->SetTitle(title);
354 node->set_date_added(base::Time::FromInternalValue(internal_time));
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);
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;
368 node->set_sync_transaction_version(sync_transaction_version);
370 return true;
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();
380 const base::Value* meta_info;
381 if (!value.Get(kMetaInfo, &meta_info))
382 return true;
384 scoped_ptr<base::Value> deserialized_holder;
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();
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);
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);
418 return true;
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()]);
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;
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));
453 void BookmarkCodec::UpdateChecksum(const std::string& str) {
454 base::MD5Update(&md5_context_, str);
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])));
464 void BookmarkCodec::UpdateChecksumWithUrlNode(const std::string& id,
465 const base::string16& title,
466 const std::string& url) {
467 DCHECK(IsStringUTF8(url));
468 UpdateChecksum(id);
469 UpdateChecksum(title);
470 UpdateChecksum(kTypeURL);
471 UpdateChecksum(url);
474 void BookmarkCodec::UpdateChecksumWithFolderNode(const std::string& id,
475 const base::string16& title) {
476 UpdateChecksum(id);
477 UpdateChecksum(title);
478 UpdateChecksum(kTypeFolder);
481 void BookmarkCodec::InitializeChecksum() {
482 base::MD5Init(&md5_context_);
485 void BookmarkCodec::FinalizeChecksum() {
486 base::MD5Digest digest;
487 base::MD5Final(&digest, &md5_context_);
488 computed_checksum_ = base::MD5DigestToBase16(digest);