1 // Copyright (c) 2011 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/string_number_conversions.h" 10 #include "base/string_util.h" 11 #include "base/values.h" 12 #include "chrome/browser/bookmarks/bookmark_model.h" 13 #include "googleurl/src/gurl.h" 14 #include "grit/generated_resources.h" 15 #include "ui/base/l10n/l10n_util.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 const char* BookmarkCodec::kVersionKey = "version"; 23 const char* BookmarkCodec::kChecksumKey = "checksum"; 24 const char* BookmarkCodec::kIdKey = "id"; 25 const char* BookmarkCodec::kTypeKey = "type"; 26 const char* BookmarkCodec::kNameKey = "name"; 27 const char* BookmarkCodec::kDateAddedKey = "date_added"; 28 const char* BookmarkCodec::kURLKey = "url"; 29 const char* BookmarkCodec::kDateModifiedKey = "date_modified"; 30 const char* BookmarkCodec::kChildrenKey = "children"; 31 const char* BookmarkCodec::kTypeURL = "url"; 32 const char* BookmarkCodec::kTypeFolder = "folder"; 33 34 // Current version of the file. 35 static const int kCurrentVersion = 1; 36 37 BookmarkCodec::BookmarkCodec() 38 : ids_reassigned_(false), 39 ids_valid_(true), 40 maximum_id_(0) { 41 } 42 43 BookmarkCodec::~BookmarkCodec() {} 44 45 Value* BookmarkCodec::Encode(BookmarkModel* model) { 46 return Encode(model->GetBookmarkBarNode(), model->other_node()); 47 } 48 49 Value* BookmarkCodec::Encode(const BookmarkNode* bookmark_bar_node, 50 const BookmarkNode* other_folder_node) { 51 ids_reassigned_ = false; 52 InitializeChecksum(); 53 DictionaryValue* roots = new DictionaryValue(); 54 roots->Set(kRootFolderNameKey, EncodeNode(bookmark_bar_node)); 55 roots->Set(kOtherBookmarkFolderNameKey, EncodeNode(other_folder_node)); 56 57 DictionaryValue* main = new DictionaryValue(); 58 main->SetInteger(kVersionKey, kCurrentVersion); 59 FinalizeChecksum(); 60 // We are going to store the computed checksum. So set stored checksum to be 61 // the same as computed checksum. 62 stored_checksum_ = computed_checksum_; 63 main->Set(kChecksumKey, Value::CreateStringValue(computed_checksum_)); 64 main->Set(kRootsKey, roots); 65 return main; 66 } 67 68 bool BookmarkCodec::Decode(BookmarkNode* bb_node, 69 BookmarkNode* other_folder_node, 70 int64* max_id, 71 const Value& value) { 72 ids_.clear(); 73 ids_reassigned_ = false; 74 ids_valid_ = true; 75 maximum_id_ = 0; 76 stored_checksum_.clear(); 77 InitializeChecksum(); 78 bool success = DecodeHelper(bb_node, other_folder_node, value); 79 FinalizeChecksum(); 80 // If either the checksums differ or some IDs were missing/not unique, 81 // reassign IDs. 82 if (!ids_valid_ || computed_checksum() != stored_checksum()) 83 ReassignIDs(bb_node, other_folder_node); 84 *max_id = maximum_id_ + 1; 85 return success; 86 } 87 88 Value* BookmarkCodec::EncodeNode(const BookmarkNode* node) { 89 DictionaryValue* value = new DictionaryValue(); 90 std::string id = base::Int64ToString(node->id()); 91 value->SetString(kIdKey, id); 92 const string16& title = node->GetTitle(); 93 value->SetString(kNameKey, title); 94 value->SetString(kDateAddedKey, 95 base::Int64ToString(node->date_added().ToInternalValue())); 96 if (node->type() == BookmarkNode::URL) { 97 value->SetString(kTypeKey, kTypeURL); 98 std::string url = node->GetURL().possibly_invalid_spec(); 99 value->SetString(kURLKey, url); 100 UpdateChecksumWithUrlNode(id, title, url); 101 } else { 102 value->SetString(kTypeKey, kTypeFolder); 103 value->SetString(kDateModifiedKey, 104 base::Int64ToString(node->date_folder_modified(). 105 ToInternalValue())); 106 UpdateChecksumWithFolderNode(id, title); 107 108 ListValue* child_values = new ListValue(); 109 value->Set(kChildrenKey, child_values); 110 for (int i = 0; i < node->child_count(); ++i) 111 child_values->Append(EncodeNode(node->GetChild(i))); 112 } 113 return value; 114 } 115 116 bool BookmarkCodec::DecodeHelper(BookmarkNode* bb_node, 117 BookmarkNode* other_folder_node, 118 const Value& value) { 119 if (value.GetType() != Value::TYPE_DICTIONARY) 120 return false; // Unexpected type. 121 122 const DictionaryValue& d_value = static_cast<const DictionaryValue&>(value); 123 124 int version; 125 if (!d_value.GetInteger(kVersionKey, &version) || version != kCurrentVersion) 126 return false; // Unknown version. 127 128 Value* checksum_value; 129 if (d_value.Get(kChecksumKey, &checksum_value)) { 130 if (checksum_value->GetType() != Value::TYPE_STRING) 131 return false; 132 StringValue* checksum_value_str = static_cast<StringValue*>(checksum_value); 133 if (!checksum_value_str->GetAsString(&stored_checksum_)) 134 return false; 135 } 136 137 Value* roots; 138 if (!d_value.Get(kRootsKey, &roots)) 139 return false; // No roots. 140 141 if (roots->GetType() != Value::TYPE_DICTIONARY) 142 return false; // Invalid type for roots. 143 144 DictionaryValue* roots_d_value = static_cast<DictionaryValue*>(roots); 145 Value* root_folder_value; 146 Value* other_folder_value; 147 if (!roots_d_value->Get(kRootFolderNameKey, &root_folder_value) || 148 root_folder_value->GetType() != Value::TYPE_DICTIONARY || 149 !roots_d_value->Get(kOtherBookmarkFolderNameKey, &other_folder_value) || 150 other_folder_value->GetType() != Value::TYPE_DICTIONARY) 151 return false; // Invalid type for root folder and/or other folder. 152 153 DecodeNode(*static_cast<DictionaryValue*>(root_folder_value), NULL, 154 bb_node); 155 DecodeNode(*static_cast<DictionaryValue*>(other_folder_value), NULL, 156 other_folder_node); 157 // Need to reset the type as decoding resets the type to FOLDER. Similarly 158 // we need to reset the title as the title is persisted and restored from 159 // the file. 160 bb_node->set_type(BookmarkNode::BOOKMARK_BAR); 161 other_folder_node->set_type(BookmarkNode::OTHER_NODE); 162 bb_node->set_title(l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_FOLDER_NAME)); 163 other_folder_node->set_title( 164 l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OTHER_FOLDER_NAME)); 165 166 return true; 167 } 168 169 bool BookmarkCodec::DecodeChildren(const ListValue& child_value_list, 170 BookmarkNode* parent) { 171 for (size_t i = 0; i < child_value_list.GetSize(); ++i) { 172 Value* child_value; 173 if (!child_value_list.Get(i, &child_value)) 174 return false; 175 176 if (child_value->GetType() != Value::TYPE_DICTIONARY) 177 return false; 178 179 DecodeNode(*static_cast<DictionaryValue*>(child_value), parent, NULL); 180 } 181 return true; 182 } 183 184 bool BookmarkCodec::DecodeNode(const DictionaryValue& value, 185 BookmarkNode* parent, 186 BookmarkNode* node) { 187 // If no |node| is specified, we'll create one and add it to the |parent|. 188 // Therefore, in that case, |parent| must be non-NULL. 189 if (!node && !parent) { 190 NOTREACHED(); 191 return false; 192 } 193 194 std::string id_string; 195 int64 id = 0; 196 if (ids_valid_) { 197 if (!value.GetString(kIdKey, &id_string) || 198 !base::StringToInt64(id_string, &id) || 199 ids_.count(id) != 0) { 200 ids_valid_ = false; 201 } else { 202 ids_.insert(id); 203 } 204 } 205 206 maximum_id_ = std::max(maximum_id_, id); 207 208 string16 title; 209 value.GetString(kNameKey, &title); 210 211 std::string date_added_string; 212 if (!value.GetString(kDateAddedKey, &date_added_string)) 213 date_added_string = base::Int64ToString(Time::Now().ToInternalValue()); 214 int64 internal_time; 215 base::StringToInt64(date_added_string, &internal_time); 216 base::Time date_added = base::Time::FromInternalValue(internal_time); 217 #if !defined(OS_WIN) 218 // We changed the epoch for dates on Mac & Linux from 1970 to the Windows 219 // one of 1601. We assume any number we encounter from before 1970 is using 220 // the old format, so we need to add the delta to it. 221 // 222 // This code should be removed at some point: 223 // http://code.google.com/p/chromium/issues/detail?id=20264 224 if (date_added.ToInternalValue() < 225 base::Time::kWindowsEpochDeltaMicroseconds) { 226 date_added = base::Time::FromInternalValue(date_added.ToInternalValue() + 227 base::Time::kWindowsEpochDeltaMicroseconds); 228 } 229 #endif 230 231 std::string type_string; 232 if (!value.GetString(kTypeKey, &type_string)) 233 return false; 234 235 if (type_string != kTypeURL && type_string != kTypeFolder) 236 return false; // Unknown type. 237 238 if (type_string == kTypeURL) { 239 std::string url_string; 240 if (!value.GetString(kURLKey, &url_string)) 241 return false; 242 243 GURL url = GURL(url_string); 244 if (!node && url.is_valid()) 245 node = new BookmarkNode(id, url); 246 else 247 return false; // Node invalid. 248 249 if (parent) 250 parent->Add(node, parent->child_count()); 251 node->set_type(BookmarkNode::URL); 252 UpdateChecksumWithUrlNode(id_string, title, url_string); 253 } else { 254 std::string last_modified_date; 255 if (!value.GetString(kDateModifiedKey, &last_modified_date)) 256 last_modified_date = base::Int64ToString(Time::Now().ToInternalValue()); 257 258 Value* child_values; 259 if (!value.Get(kChildrenKey, &child_values)) 260 return false; 261 262 if (child_values->GetType() != Value::TYPE_LIST) 263 return false; 264 265 if (!node) { 266 node = new BookmarkNode(id, GURL()); 267 } else { 268 // If a new node is not created, explicitly assign ID to the existing one. 269 node->set_id(id); 270 } 271 272 node->set_type(BookmarkNode::FOLDER); 273 int64 internal_time; 274 base::StringToInt64(last_modified_date, &internal_time); 275 node->set_date_folder_modified(Time::FromInternalValue(internal_time)); 276 277 if (parent) 278 parent->Add(node, parent->child_count()); 279 280 UpdateChecksumWithFolderNode(id_string, title); 281 282 if (!DecodeChildren(*static_cast<ListValue*>(child_values), node)) 283 return false; 284 } 285 286 node->set_title(title); 287 node->set_date_added(date_added); 288 return true; 289 } 290 291 void BookmarkCodec::ReassignIDs(BookmarkNode* bb_node, 292 BookmarkNode* other_node) { 293 maximum_id_ = 0; 294 ReassignIDsHelper(bb_node); 295 ReassignIDsHelper(other_node); 296 ids_reassigned_ = true; 297 } 298 299 void BookmarkCodec::ReassignIDsHelper(BookmarkNode* node) { 300 DCHECK(node); 301 node->set_id(++maximum_id_); 302 for (int i = 0; i < node->child_count(); ++i) 303 ReassignIDsHelper(node->GetChild(i)); 304 } 305 306 void BookmarkCodec::UpdateChecksum(const std::string& str) { 307 MD5Update(&md5_context_, str.data(), str.length() * sizeof(char)); 308 } 309 310 void BookmarkCodec::UpdateChecksum(const string16& str) { 311 MD5Update(&md5_context_, str.data(), str.length() * sizeof(char16)); 312 } 313 314 void BookmarkCodec::UpdateChecksumWithUrlNode(const std::string& id, 315 const string16& title, 316 const std::string& url) { 317 DCHECK(IsStringUTF8(url)); 318 UpdateChecksum(id); 319 UpdateChecksum(title); 320 UpdateChecksum(kTypeURL); 321 UpdateChecksum(url); 322 } 323 324 void BookmarkCodec::UpdateChecksumWithFolderNode(const std::string& id, 325 const string16& title) { 326 UpdateChecksum(id); 327 UpdateChecksum(title); 328 UpdateChecksum(kTypeFolder); 329 } 330 331 void BookmarkCodec::InitializeChecksum() { 332 MD5Init(&md5_context_); 333 } 334 335 void BookmarkCodec::FinalizeChecksum() { 336 MD5Digest digest; 337 MD5Final(&digest, &md5_context_); 338 computed_checksum_ = MD5DigestToBase16(digest); 339 } 340