#include "sync/internal_api/public/base_node.h"
#include "base/base64.h"
#include "base/sha1.h"
#include "base/string_number_conversions.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "sync/internal_api/public/base_transaction.h"
#include "sync/internal_api/syncapi_internal.h"
#include "sync/protocol/app_specifics.pb.h"
#include "sync/protocol/autofill_specifics.pb.h"
#include "sync/protocol/bookmark_specifics.pb.h"
#include "sync/protocol/extension_specifics.pb.h"
#include "sync/protocol/nigori_specifics.pb.h"
#include "sync/protocol/password_specifics.pb.h"
#include "sync/protocol/session_specifics.pb.h"
#include "sync/protocol/theme_specifics.pb.h"
#include "sync/protocol/typed_url_specifics.pb.h"
#include "sync/syncable/directory.h"
#include "sync/syncable/entry.h"
#include "sync/syncable/syncable_id.h"
#include "sync/util/time.h"
using sync_pb::AutofillProfileSpecifics;
namespace syncer {
using syncable::SPECIFICS;
static int64 IdToMetahandle(syncable::BaseTransaction* trans,
const syncable::Id& id) {
syncable::Entry entry(trans, syncable::GET_BY_ID, id);
if (!entry.good())
return kInvalidId;
return entry.Get(syncable::META_HANDLE);
}
static bool EndsWithSpace(const std::string& string) {
return !string.empty() && *string.rbegin() == ' ';
}
static void ServerNameToSyncAPIName(const std::string& server_name,
std::string* out) {
CHECK(out);
int length_to_copy = server_name.length();
if (IsNameServerIllegalAfterTrimming(server_name) &&
EndsWithSpace(server_name)) {
--length_to_copy;
}
*out = std::string(server_name.c_str(), length_to_copy);
}
BaseNode::BaseNode() : password_data_(new sync_pb::PasswordSpecificsData) {}
BaseNode::~BaseNode() {}
std::string BaseNode::GenerateSyncableHash(
ModelType model_type, const std::string& client_tag) {
sync_pb::EntitySpecifics serialized_type;
AddDefaultFieldValue(model_type, &serialized_type);
std::string hash_input;
serialized_type.AppendToString(&hash_input);
hash_input.append(client_tag);
std::string encode_output;
CHECK(base::Base64Encode(base::SHA1HashString(hash_input), &encode_output));
return encode_output;
}
bool BaseNode::DecryptIfNecessary() {
if (!GetEntry()->Get(syncable::UNIQUE_SERVER_TAG).empty())
return true;
const sync_pb::EntitySpecifics& specifics =
GetEntry()->Get(syncable::SPECIFICS);
if (specifics.has_password()) {
scoped_ptr<sync_pb::PasswordSpecificsData> data(DecryptPasswordSpecifics(
specifics, GetTransaction()->GetCryptographer()));
if (!data.get()) {
LOG(ERROR) << "Failed to decrypt password specifics.";
return false;
}
password_data_.swap(data);
return true;
}
if (!specifics.has_encrypted()) {
if (GetModelType() == BOOKMARKS &&
!specifics.bookmark().has_title() &&
!GetTitle().empty()) {
std::string title = GetTitle();
std::string server_legal_title;
SyncAPINameToServerName(title, &server_legal_title);
DVLOG(1) << "Reading from legacy bookmark, manually returning title "
<< title;
unencrypted_data_.CopyFrom(specifics);
unencrypted_data_.mutable_bookmark()->set_title(
server_legal_title);
}
return true;
}
const sync_pb::EncryptedData& encrypted = specifics.encrypted();
std::string plaintext_data = GetTransaction()->GetCryptographer()->
DecryptToString(encrypted);
if (plaintext_data.length() == 0) {
LOG(ERROR) << "Failed to decrypt encrypted node of type "
<< ModelTypeToString(GetModelType()) << ".";
CHECK(false);
return false;
} else if (!unencrypted_data_.ParseFromString(plaintext_data)) {
CHECK(false);
return false;
}
DVLOG(2) << "Decrypted specifics of type "
<< ModelTypeToString(GetModelType())
<< " with content: " << plaintext_data;
return true;
}
const sync_pb::EntitySpecifics& BaseNode::GetUnencryptedSpecifics(
const syncable::Entry* entry) const {
const sync_pb::EntitySpecifics& specifics = entry->Get(SPECIFICS);
if (specifics.has_encrypted()) {
DCHECK_NE(GetModelTypeFromSpecifics(unencrypted_data_), UNSPECIFIED);
return unencrypted_data_;
} else {
if (GetModelType() == BOOKMARKS) {
const sync_pb::BookmarkSpecifics& bookmark_specifics =
specifics.bookmark();
if (bookmark_specifics.has_title() ||
GetTitle().empty() ||
!GetEntry()->Get(syncable::UNIQUE_SERVER_TAG).empty()) {
return specifics;
} else {
DCHECK_EQ(GetModelTypeFromSpecifics(unencrypted_data_), BOOKMARKS);
return unencrypted_data_;
}
} else {
DCHECK_EQ(GetModelTypeFromSpecifics(unencrypted_data_), UNSPECIFIED);
return specifics;
}
}
}
int64 BaseNode::GetParentId() const {
return IdToMetahandle(GetTransaction()->GetWrappedTrans(),
GetEntry()->Get(syncable::PARENT_ID));
}
int64 BaseNode::GetId() const {
return GetEntry()->Get(syncable::META_HANDLE);
}
const base::Time& BaseNode::GetModificationTime() const {
return GetEntry()->Get(syncable::MTIME);
}
bool BaseNode::GetIsFolder() const {
return GetEntry()->Get(syncable::IS_DIR);
}
std::string BaseNode::GetTitle() const {
std::string result;
if (BOOKMARKS == GetModelType() &&
GetEntry()->Get(syncable::SPECIFICS).has_encrypted()) {
ServerNameToSyncAPIName(GetBookmarkSpecifics().title(), &result);
} else {
ServerNameToSyncAPIName(GetEntry()->Get(syncable::NON_UNIQUE_NAME),
&result);
}
return result;
}
GURL BaseNode::GetURL() const {
return GURL(GetBookmarkSpecifics().url());
}
bool BaseNode::HasChildren() const {
syncable::Directory* dir = GetTransaction()->GetDirectory();
syncable::BaseTransaction* trans = GetTransaction()->GetWrappedTrans();
return dir->HasChildren(trans, GetEntry()->Get(syncable::ID));
}
int64 BaseNode::GetPredecessorId() const {
syncable::Id id_string = GetEntry()->Get(syncable::PREV_ID);
if (id_string.IsRoot())
return kInvalidId;
return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
}
int64 BaseNode::GetSuccessorId() const {
syncable::Id id_string = GetEntry()->Get(syncable::NEXT_ID);
if (id_string.IsRoot())
return kInvalidId;
return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
}
int64 BaseNode::GetFirstChildId() const {
syncable::Directory* dir = GetTransaction()->GetDirectory();
syncable::BaseTransaction* trans = GetTransaction()->GetWrappedTrans();
syncable::Id id_string;
CHECK(dir->GetFirstChildId(trans,
GetEntry()->Get(syncable::ID), &id_string));
if (id_string.IsRoot())
return kInvalidId;
return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
}
DictionaryValue* BaseNode::GetSummaryAsValue() const {
DictionaryValue* node_info = new DictionaryValue();
node_info->SetString("id", base::Int64ToString(GetId()));
node_info->SetBoolean("isFolder", GetIsFolder());
node_info->SetString("title", GetTitle());
node_info->Set("type", ModelTypeToValue(GetModelType()));
return node_info;
}
DictionaryValue* BaseNode::GetDetailsAsValue() const {
DictionaryValue* node_info = GetSummaryAsValue();
node_info->SetString(
"modificationTime",
GetTimeDebugString(GetModificationTime()));
node_info->SetString("parentId", base::Int64ToString(GetParentId()));
node_info->SetString("externalId",
base::Int64ToString(GetExternalId()));
node_info->SetString("predecessorId",
base::Int64ToString(GetPredecessorId()));
node_info->SetString("successorId",
base::Int64ToString(GetSuccessorId()));
node_info->SetString("firstChildId",
base::Int64ToString(GetFirstChildId()));
node_info->Set("entry", GetEntry()->ToValue());
return node_info;
}
void BaseNode::GetFaviconBytes(std::vector<unsigned char>* output) const {
if (!output)
return;
const std::string& favicon = GetBookmarkSpecifics().favicon();
output->assign(reinterpret_cast<const unsigned char*>(favicon.data()),
reinterpret_cast<const unsigned char*>(favicon.data() +
favicon.length()));
}
int64 BaseNode::GetExternalId() const {
return GetEntry()->Get(syncable::LOCAL_EXTERNAL_ID);
}
const sync_pb::AppSpecifics& BaseNode::GetAppSpecifics() const {
DCHECK_EQ(GetModelType(), APPS);
return GetEntitySpecifics().app();
}
const sync_pb::AutofillSpecifics& BaseNode::GetAutofillSpecifics() const {
DCHECK_EQ(GetModelType(), AUTOFILL);
return GetEntitySpecifics().autofill();
}
const AutofillProfileSpecifics& BaseNode::GetAutofillProfileSpecifics() const {
DCHECK_EQ(GetModelType(), AUTOFILL_PROFILE);
return GetEntitySpecifics().autofill_profile();
}
const sync_pb::BookmarkSpecifics& BaseNode::GetBookmarkSpecifics() const {
DCHECK_EQ(GetModelType(), BOOKMARKS);
return GetEntitySpecifics().bookmark();
}
const sync_pb::NigoriSpecifics& BaseNode::GetNigoriSpecifics() const {
DCHECK_EQ(GetModelType(), NIGORI);
return GetEntitySpecifics().nigori();
}
const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const {
DCHECK_EQ(GetModelType(), PASSWORDS);
return *password_data_;
}
const sync_pb::ThemeSpecifics& BaseNode::GetThemeSpecifics() const {
DCHECK_EQ(GetModelType(), THEMES);
return GetEntitySpecifics().theme();
}
const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const {
DCHECK_EQ(GetModelType(), TYPED_URLS);
return GetEntitySpecifics().typed_url();
}
const sync_pb::ExtensionSpecifics& BaseNode::GetExtensionSpecifics() const {
DCHECK_EQ(GetModelType(), EXTENSIONS);
return GetEntitySpecifics().extension();
}
const sync_pb::SessionSpecifics& BaseNode::GetSessionSpecifics() const {
DCHECK_EQ(GetModelType(), SESSIONS);
return GetEntitySpecifics().session();
}
const sync_pb::EntitySpecifics& BaseNode::GetEntitySpecifics() const {
return GetUnencryptedSpecifics(GetEntry());
}
ModelType BaseNode::GetModelType() const {
return GetEntry()->GetModelType();
}
void BaseNode::SetUnencryptedSpecifics(
const sync_pb::EntitySpecifics& specifics) {
ModelType type = GetModelTypeFromSpecifics(specifics);
DCHECK_NE(UNSPECIFIED, type);
if (GetModelType() != UNSPECIFIED) {
DCHECK_EQ(GetModelType(), type);
}
unencrypted_data_.CopyFrom(specifics);
}
}