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