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/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