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