bookmark_codec.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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
18using base::Time;
19
20const char* BookmarkCodec::kRootsKey = "roots";
21const char* BookmarkCodec::kRootFolderNameKey = "bookmark_bar";
22const char* BookmarkCodec::kOtherBookmarkFolderNameKey = "other";
23// The value is left as 'synced' for historical reasons.
24const char* BookmarkCodec::kMobileBookmarkFolderNameKey = "synced";
25const char* BookmarkCodec::kVersionKey = "version";
26const char* BookmarkCodec::kChecksumKey = "checksum";
27const char* BookmarkCodec::kIdKey = "id";
28const char* BookmarkCodec::kTypeKey = "type";
29const char* BookmarkCodec::kNameKey = "name";
30const char* BookmarkCodec::kDateAddedKey = "date_added";
31const char* BookmarkCodec::kURLKey = "url";
32const char* BookmarkCodec::kDateModifiedKey = "date_modified";
33const char* BookmarkCodec::kChildrenKey = "children";
34const char* BookmarkCodec::kMetaInfo = "meta_info";
35const char* BookmarkCodec::kSyncTransactionVersion = "sync_transaction_version";
36const char* BookmarkCodec::kTypeURL = "url";
37const char* BookmarkCodec::kTypeFolder = "folder";
38
39// Current version of the file.
40static const int kCurrentVersion = 1;
41
42BookmarkCodec::BookmarkCodec()
43    : ids_reassigned_(false),
44      ids_valid_(true),
45      maximum_id_(0),
46      model_sync_transaction_version_(
47          BookmarkNode::kInvalidSyncTransactionVersion) {
48}
49
50BookmarkCodec::~BookmarkCodec() {}
51
52Value* 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
60Value* 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
90bool 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
112Value* 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
148base::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
158bool 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
246bool 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
261bool 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
369bool 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
417void 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
432void 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
442void 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
449void BookmarkCodec::UpdateChecksum(const std::string& str) {
450  base::MD5Update(&md5_context_, str);
451}
452
453void 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
460void 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
470void BookmarkCodec::UpdateChecksumWithFolderNode(const std::string& id,
471                                                 const base::string16& title) {
472  UpdateChecksum(id);
473  UpdateChecksum(title);
474  UpdateChecksum(kTypeFolder);
475}
476
477void BookmarkCodec::InitializeChecksum() {
478  base::MD5Init(&md5_context_);
479}
480
481void BookmarkCodec::FinalizeChecksum() {
482  base::MD5Digest digest;
483  base::MD5Final(&digest, &md5_context_);
484  computed_checksum_ = base::MD5DigestToBase16(digest);
485}
486