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/strings/string_number_conversions.h" 10 #include "base/strings/string_util.h" 11 #include "base/values.h" 12 #include "chrome/browser/bookmarks/bookmark_model.h" 13 #include "grit/generated_resources.h" 14 #include "ui/base/l10n/l10n_util.h" 15 #include "url/gurl.h" 16 17 using base::Time; 18 19 const char* BookmarkCodec::kRootsKey = "roots"; 20 const char* BookmarkCodec::kRootFolderNameKey = "bookmark_bar"; 21 const char* BookmarkCodec::kOtherBookmarkFolderNameKey = "other"; 22 // The value is left as 'synced' for historical reasons. 23 const char* BookmarkCodec::kMobileBookmarkFolderNameKey = "synced"; 24 const char* BookmarkCodec::kVersionKey = "version"; 25 const char* BookmarkCodec::kChecksumKey = "checksum"; 26 const char* BookmarkCodec::kIdKey = "id"; 27 const char* BookmarkCodec::kTypeKey = "type"; 28 const char* BookmarkCodec::kNameKey = "name"; 29 const char* BookmarkCodec::kDateAddedKey = "date_added"; 30 const char* BookmarkCodec::kURLKey = "url"; 31 const char* BookmarkCodec::kDateModifiedKey = "date_modified"; 32 const char* BookmarkCodec::kChildrenKey = "children"; 33 const char* BookmarkCodec::kMetaInfo = "meta_info"; 34 const char* BookmarkCodec::kTypeURL = "url"; 35 const char* BookmarkCodec::kTypeFolder = "folder"; 36 37 // Current version of the file. 38 static const int kCurrentVersion = 1; 39 40 BookmarkCodec::BookmarkCodec() 41 : ids_reassigned_(false), 42 ids_valid_(true), 43 maximum_id_(0) { 44 } 45 46 BookmarkCodec::~BookmarkCodec() {} 47 48 Value* BookmarkCodec::Encode(BookmarkModel* model) { 49 return Encode(model->bookmark_bar_node(), 50 model->other_node(), 51 model->mobile_node(), 52 model->root_node()->meta_info_str()); 53 } 54 55 Value* BookmarkCodec::Encode(const BookmarkNode* bookmark_bar_node, 56 const BookmarkNode* other_folder_node, 57 const BookmarkNode* mobile_folder_node, 58 const std::string& model_meta_info) { 59 ids_reassigned_ = false; 60 InitializeChecksum(); 61 DictionaryValue* roots = new DictionaryValue(); 62 roots->Set(kRootFolderNameKey, EncodeNode(bookmark_bar_node)); 63 roots->Set(kOtherBookmarkFolderNameKey, EncodeNode(other_folder_node)); 64 roots->Set(kMobileBookmarkFolderNameKey, EncodeNode(mobile_folder_node)); 65 if (!model_meta_info.empty()) 66 roots->SetString(kMetaInfo, model_meta_info); 67 DictionaryValue* main = new DictionaryValue(); 68 main->SetInteger(kVersionKey, kCurrentVersion); 69 FinalizeChecksum(); 70 // We are going to store the computed checksum. So set stored checksum to be 71 // the same as computed checksum. 72 stored_checksum_ = computed_checksum_; 73 main->Set(kChecksumKey, new base::StringValue(computed_checksum_)); 74 main->Set(kRootsKey, roots); 75 return main; 76 } 77 78 bool BookmarkCodec::Decode(BookmarkNode* bb_node, 79 BookmarkNode* other_folder_node, 80 BookmarkNode* mobile_folder_node, 81 int64* max_id, 82 const Value& value) { 83 ids_.clear(); 84 ids_reassigned_ = false; 85 ids_valid_ = true; 86 maximum_id_ = 0; 87 stored_checksum_.clear(); 88 InitializeChecksum(); 89 bool success = DecodeHelper(bb_node, other_folder_node, mobile_folder_node, 90 value); 91 FinalizeChecksum(); 92 // If either the checksums differ or some IDs were missing/not unique, 93 // reassign IDs. 94 if (!ids_valid_ || computed_checksum() != stored_checksum()) 95 ReassignIDs(bb_node, other_folder_node, mobile_folder_node); 96 *max_id = maximum_id_ + 1; 97 return success; 98 } 99 100 Value* BookmarkCodec::EncodeNode(const BookmarkNode* node) { 101 DictionaryValue* value = new DictionaryValue(); 102 std::string id = base::Int64ToString(node->id()); 103 value->SetString(kIdKey, id); 104 const string16& title = node->GetTitle(); 105 value->SetString(kNameKey, title); 106 value->SetString(kDateAddedKey, 107 base::Int64ToString(node->date_added().ToInternalValue())); 108 if (node->is_url()) { 109 value->SetString(kTypeKey, kTypeURL); 110 std::string url = node->url().possibly_invalid_spec(); 111 value->SetString(kURLKey, url); 112 UpdateChecksumWithUrlNode(id, title, url); 113 } else { 114 value->SetString(kTypeKey, kTypeFolder); 115 value->SetString(kDateModifiedKey, 116 base::Int64ToString(node->date_folder_modified(). 117 ToInternalValue())); 118 UpdateChecksumWithFolderNode(id, title); 119 120 ListValue* child_values = new ListValue(); 121 value->Set(kChildrenKey, child_values); 122 for (int i = 0; i < node->child_count(); ++i) 123 child_values->Append(EncodeNode(node->GetChild(i))); 124 } 125 if (!node->meta_info_str().empty()) 126 value->SetString(kMetaInfo, node->meta_info_str()); 127 return value; 128 } 129 130 bool BookmarkCodec::DecodeHelper(BookmarkNode* bb_node, 131 BookmarkNode* other_folder_node, 132 BookmarkNode* mobile_folder_node, 133 const Value& value) { 134 if (value.GetType() != Value::TYPE_DICTIONARY) 135 return false; // Unexpected type. 136 137 const DictionaryValue& d_value = static_cast<const DictionaryValue&>(value); 138 139 int version; 140 if (!d_value.GetInteger(kVersionKey, &version) || version != kCurrentVersion) 141 return false; // Unknown version. 142 143 const Value* checksum_value; 144 if (d_value.Get(kChecksumKey, &checksum_value)) { 145 if (checksum_value->GetType() != Value::TYPE_STRING) 146 return false; 147 if (!checksum_value->GetAsString(&stored_checksum_)) 148 return false; 149 } 150 151 const Value* roots; 152 if (!d_value.Get(kRootsKey, &roots)) 153 return false; // No roots. 154 155 if (roots->GetType() != Value::TYPE_DICTIONARY) 156 return false; // Invalid type for roots. 157 158 const DictionaryValue* roots_d_value = 159 static_cast<const DictionaryValue*>(roots); 160 const Value* root_folder_value; 161 const Value* other_folder_value = NULL; 162 if (!roots_d_value->Get(kRootFolderNameKey, &root_folder_value) || 163 root_folder_value->GetType() != Value::TYPE_DICTIONARY || 164 !roots_d_value->Get(kOtherBookmarkFolderNameKey, &other_folder_value) || 165 other_folder_value->GetType() != Value::TYPE_DICTIONARY) { 166 return false; // Invalid type for root folder and/or other 167 // folder. 168 } 169 DecodeNode(*static_cast<const DictionaryValue*>(root_folder_value), NULL, 170 bb_node); 171 DecodeNode(*static_cast<const DictionaryValue*>(other_folder_value), NULL, 172 other_folder_node); 173 174 // Fail silently if we can't deserialize mobile bookmarks. We can't require 175 // them to exist in order to be backwards-compatible with older versions of 176 // chrome. 177 const Value* mobile_folder_value; 178 if (roots_d_value->Get(kMobileBookmarkFolderNameKey, &mobile_folder_value) && 179 mobile_folder_value->GetType() == Value::TYPE_DICTIONARY) { 180 DecodeNode(*static_cast<const DictionaryValue*>(mobile_folder_value), NULL, 181 mobile_folder_node); 182 } else { 183 // If we didn't find the mobile folder, we're almost guaranteed to have a 184 // duplicate id when we add the mobile folder. Consequently, if we don't 185 // intend to reassign ids in the future (ids_valid_ is still true), then at 186 // least reassign the mobile bookmarks to avoid it colliding with anything 187 // else. 188 if (ids_valid_) 189 ReassignIDsHelper(mobile_folder_node); 190 } 191 192 roots_d_value->GetString(kMetaInfo, &model_meta_info_); 193 194 // Need to reset the type as decoding resets the type to FOLDER. Similarly 195 // we need to reset the title as the title is persisted and restored from 196 // the file. 197 bb_node->set_type(BookmarkNode::BOOKMARK_BAR); 198 other_folder_node->set_type(BookmarkNode::OTHER_NODE); 199 mobile_folder_node->set_type(BookmarkNode::MOBILE); 200 bb_node->SetTitle(l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_FOLDER_NAME)); 201 other_folder_node->SetTitle( 202 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_OTHER_FOLDER_NAME)); 203 mobile_folder_node->SetTitle( 204 l10n_util::GetStringUTF16(IDS_BOOKMARK_BAR_MOBILE_FOLDER_NAME)); 205 206 return true; 207 } 208 209 bool BookmarkCodec::DecodeChildren(const ListValue& child_value_list, 210 BookmarkNode* parent) { 211 for (size_t i = 0; i < child_value_list.GetSize(); ++i) { 212 const Value* child_value; 213 if (!child_value_list.Get(i, &child_value)) 214 return false; 215 216 if (child_value->GetType() != Value::TYPE_DICTIONARY) 217 return false; 218 219 DecodeNode(*static_cast<const DictionaryValue*>(child_value), parent, NULL); 220 } 221 return true; 222 } 223 224 bool BookmarkCodec::DecodeNode(const DictionaryValue& value, 225 BookmarkNode* parent, 226 BookmarkNode* node) { 227 // If no |node| is specified, we'll create one and add it to the |parent|. 228 // Therefore, in that case, |parent| must be non-NULL. 229 if (!node && !parent) { 230 NOTREACHED(); 231 return false; 232 } 233 234 std::string id_string; 235 int64 id = 0; 236 if (ids_valid_) { 237 if (!value.GetString(kIdKey, &id_string) || 238 !base::StringToInt64(id_string, &id) || 239 ids_.count(id) != 0) { 240 ids_valid_ = false; 241 } else { 242 ids_.insert(id); 243 } 244 } 245 246 maximum_id_ = std::max(maximum_id_, id); 247 248 string16 title; 249 value.GetString(kNameKey, &title); 250 251 std::string date_added_string; 252 if (!value.GetString(kDateAddedKey, &date_added_string)) 253 date_added_string = base::Int64ToString(Time::Now().ToInternalValue()); 254 int64 internal_time; 255 base::StringToInt64(date_added_string, &internal_time); 256 257 std::string type_string; 258 if (!value.GetString(kTypeKey, &type_string)) 259 return false; 260 261 if (type_string != kTypeURL && type_string != kTypeFolder) 262 return false; // Unknown type. 263 264 if (type_string == kTypeURL) { 265 std::string url_string; 266 if (!value.GetString(kURLKey, &url_string)) 267 return false; 268 269 GURL url = GURL(url_string); 270 if (!node && url.is_valid()) 271 node = new BookmarkNode(id, url); 272 else 273 return false; // Node invalid. 274 275 if (parent) 276 parent->Add(node, parent->child_count()); 277 node->set_type(BookmarkNode::URL); 278 UpdateChecksumWithUrlNode(id_string, title, url_string); 279 } else { 280 std::string last_modified_date; 281 if (!value.GetString(kDateModifiedKey, &last_modified_date)) 282 last_modified_date = base::Int64ToString(Time::Now().ToInternalValue()); 283 284 const Value* child_values; 285 if (!value.Get(kChildrenKey, &child_values)) 286 return false; 287 288 if (child_values->GetType() != Value::TYPE_LIST) 289 return false; 290 291 if (!node) { 292 node = new BookmarkNode(id, GURL()); 293 } else { 294 // If a new node is not created, explicitly assign ID to the existing one. 295 node->set_id(id); 296 } 297 298 node->set_type(BookmarkNode::FOLDER); 299 int64 internal_time; 300 base::StringToInt64(last_modified_date, &internal_time); 301 node->set_date_folder_modified(Time::FromInternalValue(internal_time)); 302 303 if (parent) 304 parent->Add(node, parent->child_count()); 305 306 UpdateChecksumWithFolderNode(id_string, title); 307 308 if (!DecodeChildren(*static_cast<const ListValue*>(child_values), node)) 309 return false; 310 } 311 312 node->SetTitle(title); 313 node->set_date_added(base::Time::FromInternalValue(internal_time)); 314 315 std::string meta_info; 316 if (value.GetString(kMetaInfo, &meta_info)) 317 node->set_meta_info_str(meta_info); 318 319 return true; 320 } 321 322 void BookmarkCodec::ReassignIDs(BookmarkNode* bb_node, 323 BookmarkNode* other_node, 324 BookmarkNode* mobile_node) { 325 maximum_id_ = 0; 326 ReassignIDsHelper(bb_node); 327 ReassignIDsHelper(other_node); 328 ReassignIDsHelper(mobile_node); 329 ids_reassigned_ = true; 330 } 331 332 void BookmarkCodec::ReassignIDsHelper(BookmarkNode* node) { 333 DCHECK(node); 334 node->set_id(++maximum_id_); 335 for (int i = 0; i < node->child_count(); ++i) 336 ReassignIDsHelper(node->GetChild(i)); 337 } 338 339 void BookmarkCodec::UpdateChecksum(const std::string& str) { 340 base::MD5Update(&md5_context_, str); 341 } 342 343 void BookmarkCodec::UpdateChecksum(const string16& str) { 344 base::MD5Update(&md5_context_, 345 base::StringPiece( 346 reinterpret_cast<const char*>(str.data()), 347 str.length() * sizeof(str[0]))); 348 } 349 350 void BookmarkCodec::UpdateChecksumWithUrlNode(const std::string& id, 351 const string16& title, 352 const std::string& url) { 353 DCHECK(IsStringUTF8(url)); 354 UpdateChecksum(id); 355 UpdateChecksum(title); 356 UpdateChecksum(kTypeURL); 357 UpdateChecksum(url); 358 } 359 360 void BookmarkCodec::UpdateChecksumWithFolderNode(const std::string& id, 361 const string16& title) { 362 UpdateChecksum(id); 363 UpdateChecksum(title); 364 UpdateChecksum(kTypeFolder); 365 } 366 367 void BookmarkCodec::InitializeChecksum() { 368 base::MD5Init(&md5_context_); 369 } 370 371 void BookmarkCodec::FinalizeChecksum() { 372 base::MD5Digest digest; 373 base::MD5Final(&digest, &md5_context_); 374 computed_checksum_ = base::MD5DigestToBase16(digest); 375 } 376