1// Copyright (c) 2011 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/string_number_conversions.h"
10#include "base/string_util.h"
11#include "base/values.h"
12#include "chrome/browser/bookmarks/bookmark_model.h"
13#include "googleurl/src/gurl.h"
14#include "grit/generated_resources.h"
15#include "ui/base/l10n/l10n_util.h"
16
17using base::Time;
18
19const char* BookmarkCodec::kRootsKey = "roots";
20const char* BookmarkCodec::kRootFolderNameKey = "bookmark_bar";
21const char* BookmarkCodec::kOtherBookmarkFolderNameKey = "other";
22const char* BookmarkCodec::kVersionKey = "version";
23const char* BookmarkCodec::kChecksumKey = "checksum";
24const char* BookmarkCodec::kIdKey = "id";
25const char* BookmarkCodec::kTypeKey = "type";
26const char* BookmarkCodec::kNameKey = "name";
27const char* BookmarkCodec::kDateAddedKey = "date_added";
28const char* BookmarkCodec::kURLKey = "url";
29const char* BookmarkCodec::kDateModifiedKey = "date_modified";
30const char* BookmarkCodec::kChildrenKey = "children";
31const char* BookmarkCodec::kTypeURL = "url";
32const char* BookmarkCodec::kTypeFolder = "folder";
33
34// Current version of the file.
35static const int kCurrentVersion = 1;
36
37BookmarkCodec::BookmarkCodec()
38    : ids_reassigned_(false),
39      ids_valid_(true),
40      maximum_id_(0) {
41}
42
43BookmarkCodec::~BookmarkCodec() {}
44
45Value* BookmarkCodec::Encode(BookmarkModel* model) {
46  return Encode(model->GetBookmarkBarNode(), model->other_node());
47}
48
49Value* BookmarkCodec::Encode(const BookmarkNode* bookmark_bar_node,
50                             const BookmarkNode* other_folder_node) {
51  ids_reassigned_ = false;
52  InitializeChecksum();
53  DictionaryValue* roots = new DictionaryValue();
54  roots->Set(kRootFolderNameKey, EncodeNode(bookmark_bar_node));
55  roots->Set(kOtherBookmarkFolderNameKey, EncodeNode(other_folder_node));
56
57  DictionaryValue* main = new DictionaryValue();
58  main->SetInteger(kVersionKey, kCurrentVersion);
59  FinalizeChecksum();
60  // We are going to store the computed checksum. So set stored checksum to be
61  // the same as computed checksum.
62  stored_checksum_ = computed_checksum_;
63  main->Set(kChecksumKey, Value::CreateStringValue(computed_checksum_));
64  main->Set(kRootsKey, roots);
65  return main;
66}
67
68bool BookmarkCodec::Decode(BookmarkNode* bb_node,
69                           BookmarkNode* other_folder_node,
70                           int64* max_id,
71                           const Value& value) {
72  ids_.clear();
73  ids_reassigned_ = false;
74  ids_valid_ = true;
75  maximum_id_ = 0;
76  stored_checksum_.clear();
77  InitializeChecksum();
78  bool success = DecodeHelper(bb_node, other_folder_node, value);
79  FinalizeChecksum();
80  // If either the checksums differ or some IDs were missing/not unique,
81  // reassign IDs.
82  if (!ids_valid_ || computed_checksum() != stored_checksum())
83    ReassignIDs(bb_node, other_folder_node);
84  *max_id = maximum_id_ + 1;
85  return success;
86}
87
88Value* BookmarkCodec::EncodeNode(const BookmarkNode* node) {
89  DictionaryValue* value = new DictionaryValue();
90  std::string id = base::Int64ToString(node->id());
91  value->SetString(kIdKey, id);
92  const string16& title = node->GetTitle();
93  value->SetString(kNameKey, title);
94  value->SetString(kDateAddedKey,
95                   base::Int64ToString(node->date_added().ToInternalValue()));
96  if (node->type() == BookmarkNode::URL) {
97    value->SetString(kTypeKey, kTypeURL);
98    std::string url = node->GetURL().possibly_invalid_spec();
99    value->SetString(kURLKey, url);
100    UpdateChecksumWithUrlNode(id, title, url);
101  } else {
102    value->SetString(kTypeKey, kTypeFolder);
103    value->SetString(kDateModifiedKey,
104                     base::Int64ToString(node->date_folder_modified().
105                                   ToInternalValue()));
106    UpdateChecksumWithFolderNode(id, title);
107
108    ListValue* child_values = new ListValue();
109    value->Set(kChildrenKey, child_values);
110    for (int i = 0; i < node->child_count(); ++i)
111      child_values->Append(EncodeNode(node->GetChild(i)));
112  }
113  return value;
114}
115
116bool BookmarkCodec::DecodeHelper(BookmarkNode* bb_node,
117                                 BookmarkNode* other_folder_node,
118                                 const Value& value) {
119  if (value.GetType() != Value::TYPE_DICTIONARY)
120    return false;  // Unexpected type.
121
122  const DictionaryValue& d_value = static_cast<const DictionaryValue&>(value);
123
124  int version;
125  if (!d_value.GetInteger(kVersionKey, &version) || version != kCurrentVersion)
126    return false;  // Unknown version.
127
128  Value* checksum_value;
129  if (d_value.Get(kChecksumKey, &checksum_value)) {
130    if (checksum_value->GetType() != Value::TYPE_STRING)
131      return false;
132    StringValue* checksum_value_str = static_cast<StringValue*>(checksum_value);
133    if (!checksum_value_str->GetAsString(&stored_checksum_))
134      return false;
135  }
136
137  Value* roots;
138  if (!d_value.Get(kRootsKey, &roots))
139    return false;  // No roots.
140
141  if (roots->GetType() != Value::TYPE_DICTIONARY)
142    return false;  // Invalid type for roots.
143
144  DictionaryValue* roots_d_value = static_cast<DictionaryValue*>(roots);
145  Value* root_folder_value;
146  Value* other_folder_value;
147  if (!roots_d_value->Get(kRootFolderNameKey, &root_folder_value) ||
148      root_folder_value->GetType() != Value::TYPE_DICTIONARY ||
149      !roots_d_value->Get(kOtherBookmarkFolderNameKey, &other_folder_value) ||
150      other_folder_value->GetType() != Value::TYPE_DICTIONARY)
151    return false;  // Invalid type for root folder and/or other folder.
152
153  DecodeNode(*static_cast<DictionaryValue*>(root_folder_value), NULL,
154             bb_node);
155  DecodeNode(*static_cast<DictionaryValue*>(other_folder_value), NULL,
156             other_folder_node);
157  // Need to reset the type as decoding resets the type to FOLDER. Similarly
158  // we need to reset the title as the title is persisted and restored from
159  // the file.
160  bb_node->set_type(BookmarkNode::BOOKMARK_BAR);
161  other_folder_node->set_type(BookmarkNode::OTHER_NODE);
162  bb_node->set_title(l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_FOLDER_NAME));
163  other_folder_node->set_title(
164      l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OTHER_FOLDER_NAME));
165
166  return true;
167}
168
169bool BookmarkCodec::DecodeChildren(const ListValue& child_value_list,
170                                   BookmarkNode* parent) {
171  for (size_t i = 0; i < child_value_list.GetSize(); ++i) {
172    Value* child_value;
173    if (!child_value_list.Get(i, &child_value))
174      return false;
175
176    if (child_value->GetType() != Value::TYPE_DICTIONARY)
177      return false;
178
179    DecodeNode(*static_cast<DictionaryValue*>(child_value), parent, NULL);
180  }
181  return true;
182}
183
184bool BookmarkCodec::DecodeNode(const DictionaryValue& value,
185                               BookmarkNode* parent,
186                               BookmarkNode* node) {
187  // If no |node| is specified, we'll create one and add it to the |parent|.
188  // Therefore, in that case, |parent| must be non-NULL.
189  if (!node && !parent) {
190    NOTREACHED();
191    return false;
192  }
193
194  std::string id_string;
195  int64 id = 0;
196  if (ids_valid_) {
197    if (!value.GetString(kIdKey, &id_string) ||
198        !base::StringToInt64(id_string, &id) ||
199        ids_.count(id) != 0) {
200      ids_valid_ = false;
201    } else {
202      ids_.insert(id);
203    }
204  }
205
206  maximum_id_ = std::max(maximum_id_, id);
207
208  string16 title;
209  value.GetString(kNameKey, &title);
210
211  std::string date_added_string;
212  if (!value.GetString(kDateAddedKey, &date_added_string))
213    date_added_string = base::Int64ToString(Time::Now().ToInternalValue());
214  int64 internal_time;
215  base::StringToInt64(date_added_string, &internal_time);
216  base::Time date_added = base::Time::FromInternalValue(internal_time);
217#if !defined(OS_WIN)
218  // We changed the epoch for dates on Mac & Linux from 1970 to the Windows
219  // one of 1601. We assume any number we encounter from before 1970 is using
220  // the old format, so we need to add the delta to it.
221  //
222  // This code should be removed at some point:
223  // http://code.google.com/p/chromium/issues/detail?id=20264
224  if (date_added.ToInternalValue() <
225      base::Time::kWindowsEpochDeltaMicroseconds) {
226    date_added = base::Time::FromInternalValue(date_added.ToInternalValue() +
227        base::Time::kWindowsEpochDeltaMicroseconds);
228  }
229#endif
230
231  std::string type_string;
232  if (!value.GetString(kTypeKey, &type_string))
233    return false;
234
235  if (type_string != kTypeURL && type_string != kTypeFolder)
236    return false;  // Unknown type.
237
238  if (type_string == kTypeURL) {
239    std::string url_string;
240    if (!value.GetString(kURLKey, &url_string))
241      return false;
242
243    GURL url = GURL(url_string);
244    if (!node && url.is_valid())
245      node = new BookmarkNode(id, url);
246    else
247      return false;  // Node invalid.
248
249    if (parent)
250      parent->Add(node, parent->child_count());
251    node->set_type(BookmarkNode::URL);
252    UpdateChecksumWithUrlNode(id_string, title, url_string);
253  } else {
254    std::string last_modified_date;
255    if (!value.GetString(kDateModifiedKey, &last_modified_date))
256      last_modified_date = base::Int64ToString(Time::Now().ToInternalValue());
257
258    Value* child_values;
259    if (!value.Get(kChildrenKey, &child_values))
260      return false;
261
262    if (child_values->GetType() != Value::TYPE_LIST)
263      return false;
264
265    if (!node) {
266      node = new BookmarkNode(id, GURL());
267    } else {
268      // If a new node is not created, explicitly assign ID to the existing one.
269      node->set_id(id);
270    }
271
272    node->set_type(BookmarkNode::FOLDER);
273    int64 internal_time;
274    base::StringToInt64(last_modified_date, &internal_time);
275    node->set_date_folder_modified(Time::FromInternalValue(internal_time));
276
277    if (parent)
278      parent->Add(node, parent->child_count());
279
280    UpdateChecksumWithFolderNode(id_string, title);
281
282    if (!DecodeChildren(*static_cast<ListValue*>(child_values), node))
283      return false;
284  }
285
286  node->set_title(title);
287  node->set_date_added(date_added);
288  return true;
289}
290
291void BookmarkCodec::ReassignIDs(BookmarkNode* bb_node,
292                                BookmarkNode* other_node) {
293  maximum_id_ = 0;
294  ReassignIDsHelper(bb_node);
295  ReassignIDsHelper(other_node);
296  ids_reassigned_ = true;
297}
298
299void BookmarkCodec::ReassignIDsHelper(BookmarkNode* node) {
300  DCHECK(node);
301  node->set_id(++maximum_id_);
302  for (int i = 0; i < node->child_count(); ++i)
303    ReassignIDsHelper(node->GetChild(i));
304}
305
306void BookmarkCodec::UpdateChecksum(const std::string& str) {
307  MD5Update(&md5_context_, str.data(), str.length() * sizeof(char));
308}
309
310void BookmarkCodec::UpdateChecksum(const string16& str) {
311  MD5Update(&md5_context_, str.data(), str.length() * sizeof(char16));
312}
313
314void BookmarkCodec::UpdateChecksumWithUrlNode(const std::string& id,
315                                              const string16& title,
316                                              const std::string& url) {
317  DCHECK(IsStringUTF8(url));
318  UpdateChecksum(id);
319  UpdateChecksum(title);
320  UpdateChecksum(kTypeURL);
321  UpdateChecksum(url);
322}
323
324void BookmarkCodec::UpdateChecksumWithFolderNode(const std::string& id,
325                                                 const string16& title) {
326  UpdateChecksum(id);
327  UpdateChecksum(title);
328  UpdateChecksum(kTypeFolder);
329}
330
331void BookmarkCodec::InitializeChecksum() {
332  MD5Init(&md5_context_);
333}
334
335void BookmarkCodec::FinalizeChecksum() {
336  MD5Digest digest;
337  MD5Final(&digest, &md5_context_);
338  computed_checksum_ = MD5DigestToBase16(digest);
339}
340