// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/bookmarks/bookmark_codec.h"
#include <algorithm>
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "base/values.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "googleurl/src/gurl.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
using base::Time;
const char* BookmarkCodec::kRootsKey = "roots";
const char* BookmarkCodec::kRootFolderNameKey = "bookmark_bar";
const char* BookmarkCodec::kOtherBookmarkFolderNameKey = "other";
const char* BookmarkCodec::kVersionKey = "version";
const char* BookmarkCodec::kChecksumKey = "checksum";
const char* BookmarkCodec::kIdKey = "id";
const char* BookmarkCodec::kTypeKey = "type";
const char* BookmarkCodec::kNameKey = "name";
const char* BookmarkCodec::kDateAddedKey = "date_added";
const char* BookmarkCodec::kURLKey = "url";
const char* BookmarkCodec::kDateModifiedKey = "date_modified";
const char* BookmarkCodec::kChildrenKey = "children";
const char* BookmarkCodec::kTypeURL = "url";
const char* BookmarkCodec::kTypeFolder = "folder";
// Current version of the file.
static const int kCurrentVersion = 1;
BookmarkCodec::BookmarkCodec()
: ids_reassigned_(false),
ids_valid_(true),
maximum_id_(0) {
}
BookmarkCodec::~BookmarkCodec() {}
Value* BookmarkCodec::Encode(BookmarkModel* model) {
return Encode(model->GetBookmarkBarNode(), model->other_node());
}
Value* BookmarkCodec::Encode(const BookmarkNode* bookmark_bar_node,
const BookmarkNode* other_folder_node) {
ids_reassigned_ = false;
InitializeChecksum();
DictionaryValue* roots = new DictionaryValue();
roots->Set(kRootFolderNameKey, EncodeNode(bookmark_bar_node));
roots->Set(kOtherBookmarkFolderNameKey, EncodeNode(other_folder_node));
DictionaryValue* main = new DictionaryValue();
main->SetInteger(kVersionKey, kCurrentVersion);
FinalizeChecksum();
// We are going to store the computed checksum. So set stored checksum to be
// the same as computed checksum.
stored_checksum_ = computed_checksum_;
main->Set(kChecksumKey, Value::CreateStringValue(computed_checksum_));
main->Set(kRootsKey, roots);
return main;
}
bool BookmarkCodec::Decode(BookmarkNode* bb_node,
BookmarkNode* other_folder_node,
int64* max_id,
const Value& value) {
ids_.clear();
ids_reassigned_ = false;
ids_valid_ = true;
maximum_id_ = 0;
stored_checksum_.clear();
InitializeChecksum();
bool success = DecodeHelper(bb_node, other_folder_node, value);
FinalizeChecksum();
// If either the checksums differ or some IDs were missing/not unique,
// reassign IDs.
if (!ids_valid_ || computed_checksum() != stored_checksum())
ReassignIDs(bb_node, other_folder_node);
*max_id = maximum_id_ + 1;
return success;
}
Value* BookmarkCodec::EncodeNode(const BookmarkNode* node) {
DictionaryValue* value = new DictionaryValue();
std::string id = base::Int64ToString(node->id());
value->SetString(kIdKey, id);
const string16& title = node->GetTitle();
value->SetString(kNameKey, title);
value->SetString(kDateAddedKey,
base::Int64ToString(node->date_added().ToInternalValue()));
if (node->type() == BookmarkNode::URL) {
value->SetString(kTypeKey, kTypeURL);
std::string url = node->GetURL().possibly_invalid_spec();
value->SetString(kURLKey, url);
UpdateChecksumWithUrlNode(id, title, url);
} else {
value->SetString(kTypeKey, kTypeFolder);
value->SetString(kDateModifiedKey,
base::Int64ToString(node->date_folder_modified().
ToInternalValue()));
UpdateChecksumWithFolderNode(id, title);
ListValue* child_values = new ListValue();
value->Set(kChildrenKey, child_values);
for (int i = 0; i < node->child_count(); ++i)
child_values->Append(EncodeNode(node->GetChild(i)));
}
return value;
}
bool BookmarkCodec::DecodeHelper(BookmarkNode* bb_node,
BookmarkNode* other_folder_node,
const Value& value) {
if (value.GetType() != Value::TYPE_DICTIONARY)
return false; // Unexpected type.
const DictionaryValue& d_value = static_cast<const DictionaryValue&>(value);
int version;
if (!d_value.GetInteger(kVersionKey, &version) || version != kCurrentVersion)
return false; // Unknown version.
Value* checksum_value;
if (d_value.Get(kChecksumKey, &checksum_value)) {
if (checksum_value->GetType() != Value::TYPE_STRING)
return false;
StringValue* checksum_value_str = static_cast<StringValue*>(checksum_value);
if (!checksum_value_str->GetAsString(&stored_checksum_))
return false;
}
Value* roots;
if (!d_value.Get(kRootsKey, &roots))
return false; // No roots.
if (roots->GetType() != Value::TYPE_DICTIONARY)
return false; // Invalid type for roots.
DictionaryValue* roots_d_value = static_cast<DictionaryValue*>(roots);
Value* root_folder_value;
Value* other_folder_value;
if (!roots_d_value->Get(kRootFolderNameKey, &root_folder_value) ||
root_folder_value->GetType() != Value::TYPE_DICTIONARY ||
!roots_d_value->Get(kOtherBookmarkFolderNameKey, &other_folder_value) ||
other_folder_value->GetType() != Value::TYPE_DICTIONARY)
return false; // Invalid type for root folder and/or other folder.
DecodeNode(*static_cast<DictionaryValue*>(root_folder_value), NULL,
bb_node);
DecodeNode(*static_cast<DictionaryValue*>(other_folder_value), NULL,
other_folder_node);
// Need to reset the type as decoding resets the type to FOLDER. Similarly
// we need to reset the title as the title is persisted and restored from
// the file.
bb_node->set_type(BookmarkNode::BOOKMARK_BAR);
other_folder_node->set_type(BookmarkNode::OTHER_NODE);
bb_node->set_title(l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_FOLDER_NAME));
other_folder_node->set_title(
l10n_util::GetStringUTF16(IDS_BOOMARK_BAR_OTHER_FOLDER_NAME));
return true;
}
bool BookmarkCodec::DecodeChildren(const ListValue& child_value_list,
BookmarkNode* parent) {
for (size_t i = 0; i < child_value_list.GetSize(); ++i) {
Value* child_value;
if (!child_value_list.Get(i, &child_value))
return false;
if (child_value->GetType() != Value::TYPE_DICTIONARY)
return false;
DecodeNode(*static_cast<DictionaryValue*>(child_value), parent, NULL);
}
return true;
}
bool BookmarkCodec::DecodeNode(const DictionaryValue& value,
BookmarkNode* parent,
BookmarkNode* node) {
// If no |node| is specified, we'll create one and add it to the |parent|.
// Therefore, in that case, |parent| must be non-NULL.
if (!node && !parent) {
NOTREACHED();
return false;
}
std::string id_string;
int64 id = 0;
if (ids_valid_) {
if (!value.GetString(kIdKey, &id_string) ||
!base::StringToInt64(id_string, &id) ||
ids_.count(id) != 0) {
ids_valid_ = false;
} else {
ids_.insert(id);
}
}
maximum_id_ = std::max(maximum_id_, id);
string16 title;
value.GetString(kNameKey, &title);
std::string date_added_string;
if (!value.GetString(kDateAddedKey, &date_added_string))
date_added_string = base::Int64ToString(Time::Now().ToInternalValue());
int64 internal_time;
base::StringToInt64(date_added_string, &internal_time);
base::Time date_added = base::Time::FromInternalValue(internal_time);
#if !defined(OS_WIN)
// We changed the epoch for dates on Mac & Linux from 1970 to the Windows
// one of 1601. We assume any number we encounter from before 1970 is using
// the old format, so we need to add the delta to it.
//
// This code should be removed at some point:
// http://code.google.com/p/chromium/issues/detail?id=20264
if (date_added.ToInternalValue() <
base::Time::kWindowsEpochDeltaMicroseconds) {
date_added = base::Time::FromInternalValue(date_added.ToInternalValue() +
base::Time::kWindowsEpochDeltaMicroseconds);
}
#endif
std::string type_string;
if (!value.GetString(kTypeKey, &type_string))
return false;
if (type_string != kTypeURL && type_string != kTypeFolder)
return false; // Unknown type.
if (type_string == kTypeURL) {
std::string url_string;
if (!value.GetString(kURLKey, &url_string))
return false;
GURL url = GURL(url_string);
if (!node && url.is_valid())
node = new BookmarkNode(id, url);
else
return false; // Node invalid.
if (parent)
parent->Add(node, parent->child_count());
node->set_type(BookmarkNode::URL);
UpdateChecksumWithUrlNode(id_string, title, url_string);
} else {
std::string last_modified_date;
if (!value.GetString(kDateModifiedKey, &last_modified_date))
last_modified_date = base::Int64ToString(Time::Now().ToInternalValue());
Value* child_values;
if (!value.Get(kChildrenKey, &child_values))
return false;
if (child_values->GetType() != Value::TYPE_LIST)
return false;
if (!node) {
node = new BookmarkNode(id, GURL());
} else {
// If a new node is not created, explicitly assign ID to the existing one.
node->set_id(id);
}
node->set_type(BookmarkNode::FOLDER);
int64 internal_time;
base::StringToInt64(last_modified_date, &internal_time);
node->set_date_folder_modified(Time::FromInternalValue(internal_time));
if (parent)
parent->Add(node, parent->child_count());
UpdateChecksumWithFolderNode(id_string, title);
if (!DecodeChildren(*static_cast<ListValue*>(child_values), node))
return false;
}
node->set_title(title);
node->set_date_added(date_added);
return true;
}
void BookmarkCodec::ReassignIDs(BookmarkNode* bb_node,
BookmarkNode* other_node) {
maximum_id_ = 0;
ReassignIDsHelper(bb_node);
ReassignIDsHelper(other_node);
ids_reassigned_ = true;
}
void BookmarkCodec::ReassignIDsHelper(BookmarkNode* node) {
DCHECK(node);
node->set_id(++maximum_id_);
for (int i = 0; i < node->child_count(); ++i)
ReassignIDsHelper(node->GetChild(i));
}
void BookmarkCodec::UpdateChecksum(const std::string& str) {
MD5Update(&md5_context_, str.data(), str.length() * sizeof(char));
}
void BookmarkCodec::UpdateChecksum(const string16& str) {
MD5Update(&md5_context_, str.data(), str.length() * sizeof(char16));
}
void BookmarkCodec::UpdateChecksumWithUrlNode(const std::string& id,
const string16& title,
const std::string& url) {
DCHECK(IsStringUTF8(url));
UpdateChecksum(id);
UpdateChecksum(title);
UpdateChecksum(kTypeURL);
UpdateChecksum(url);
}
void BookmarkCodec::UpdateChecksumWithFolderNode(const std::string& id,
const string16& title) {
UpdateChecksum(id);
UpdateChecksum(title);
UpdateChecksum(kTypeFolder);
}
void BookmarkCodec::InitializeChecksum() {
MD5Init(&md5_context_);
}
void BookmarkCodec::FinalizeChecksum() {
MD5Digest digest;
MD5Final(&digest, &md5_context_);
computed_checksum_ = MD5DigestToBase16(digest);
}