• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
BookmarkCodec()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 
~BookmarkCodec()50 BookmarkCodec::~BookmarkCodec() {}
51 
Encode(BookmarkModel * model)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 
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)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 
Decode(BookmarkNode * bb_node,BookmarkNode * other_folder_node,BookmarkNode * mobile_folder_node,int64 * max_id,const Value & value)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 
EncodeNode(const BookmarkNode * node)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 
EncodeMetaInfo(const BookmarkNode::MetaInfoMap & meta_info_map)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 
DecodeHelper(BookmarkNode * bb_node,BookmarkNode * other_folder_node,BookmarkNode * mobile_folder_node,const Value & value)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 
DecodeChildren(const ListValue & child_value_list,BookmarkNode * parent)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 
DecodeNode(const DictionaryValue & value,BookmarkNode * parent,BookmarkNode * node)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 
DecodeMetaInfo(const base::DictionaryValue & value,BookmarkNode::MetaInfoMap * meta_info_map,int64 * sync_transaction_version)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 
DecodeMetaInfoHelper(const base::DictionaryValue & dict,const std::string & prefix,BookmarkNode::MetaInfoMap * meta_info_map)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 
ReassignIDs(BookmarkNode * bb_node,BookmarkNode * other_node,BookmarkNode * mobile_node)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 
ReassignIDsHelper(BookmarkNode * node)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 
UpdateChecksum(const std::string & str)449 void BookmarkCodec::UpdateChecksum(const std::string& str) {
450   base::MD5Update(&md5_context_, str);
451 }
452 
UpdateChecksum(const base::string16 & str)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 
UpdateChecksumWithUrlNode(const std::string & id,const base::string16 & title,const std::string & url)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 
UpdateChecksumWithFolderNode(const std::string & id,const base::string16 & title)470 void BookmarkCodec::UpdateChecksumWithFolderNode(const std::string& id,
471                                                  const base::string16& title) {
472   UpdateChecksum(id);
473   UpdateChecksum(title);
474   UpdateChecksum(kTypeFolder);
475 }
476 
InitializeChecksum()477 void BookmarkCodec::InitializeChecksum() {
478   base::MD5Init(&md5_context_);
479 }
480 
FinalizeChecksum()481 void BookmarkCodec::FinalizeChecksum() {
482   base::MD5Digest digest;
483   base::MD5Final(&digest, &md5_context_);
484   computed_checksum_ = base::MD5DigestToBase16(digest);
485 }
486