• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
BookmarkCodec()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 
~BookmarkCodec()52 BookmarkCodec::~BookmarkCodec() {}
53 
Encode(BookmarkModel * model)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 
Encode(const BookmarkNode * bookmark_bar_node,const BookmarkNode * other_folder_node,const BookmarkNode * mobile_folder_node,const BookmarkNode::MetaInfoMap * model_meta_info_map,int64 sync_transaction_version)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 
Decode(BookmarkNode * bb_node,BookmarkNode * other_folder_node,BookmarkNode * mobile_folder_node,int64 * max_id,const base::Value & value)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 
EncodeNode(const BookmarkNode * node)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 
EncodeMetaInfo(const BookmarkNode::MetaInfoMap & meta_info_map)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 
DecodeHelper(BookmarkNode * bb_node,BookmarkNode * other_folder_node,BookmarkNode * mobile_folder_node,const base::Value & value)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 
DecodeChildren(const base::ListValue & child_value_list,BookmarkNode * parent)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 
DecodeNode(const base::DictionaryValue & value,BookmarkNode * parent,BookmarkNode * node)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 
DecodeMetaInfo(const base::DictionaryValue & value,BookmarkNode::MetaInfoMap * meta_info_map,int64 * sync_transaction_version)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 
DecodeMetaInfoHelper(const base::DictionaryValue & dict,const std::string & prefix,BookmarkNode::MetaInfoMap * meta_info_map)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 
ReassignIDs(BookmarkNode * bb_node,BookmarkNode * other_node,BookmarkNode * mobile_node)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 
ReassignIDsHelper(BookmarkNode * node)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 
UpdateChecksum(const std::string & str)455 void BookmarkCodec::UpdateChecksum(const std::string& str) {
456   base::MD5Update(&md5_context_, str);
457 }
458 
UpdateChecksum(const base::string16 & str)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 
UpdateChecksumWithUrlNode(const std::string & id,const base::string16 & title,const std::string & url)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 
UpdateChecksumWithFolderNode(const std::string & id,const base::string16 & title)476 void BookmarkCodec::UpdateChecksumWithFolderNode(const std::string& id,
477                                                  const base::string16& title) {
478   UpdateChecksum(id);
479   UpdateChecksum(title);
480   UpdateChecksum(kTypeFolder);
481 }
482 
InitializeChecksum()483 void BookmarkCodec::InitializeChecksum() {
484   base::MD5Init(&md5_context_);
485 }
486 
FinalizeChecksum()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