Home | History | Annotate | Download | only in bookmarks
      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