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