summaryrefslogtreecommitdiffstats
path: root/sync/internal_api/base_node.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sync/internal_api/base_node.cc')
-rw-r--r--sync/internal_api/base_node.cc346
1 files changed, 346 insertions, 0 deletions
diff --git a/sync/internal_api/base_node.cc b/sync/internal_api/base_node.cc
new file mode 100644
index 0000000..552ee71
--- /dev/null
+++ b/sync/internal_api/base_node.cc
@@ -0,0 +1,346 @@
+// Copyright (c) 2012 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 "sync/internal_api/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/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/syncable.h"
+#include "sync/syncable/syncable_id.h"
+#include "sync/util/time.h"
+
+using syncable::SPECIFICS;
+using sync_pb::AutofillProfileSpecifics;
+
+namespace sync_api {
+
+// Helper function to look up the int64 metahandle of an object given the ID
+// string.
+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() == ' ';
+}
+
+// In the reverse direction, if a server name matches the pattern of a
+// server-illegal name followed by one or more spaces, remove the trailing
+// space.
+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(
+ syncable::ModelType model_type, const std::string& client_tag) {
+ // Blank PB with just the field in it has termination symbol,
+ // handy for delimiter.
+ sync_pb::EntitySpecifics serialized_type;
+ syncable::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; // Ignore unique folders.
+ const sync_pb::EntitySpecifics& specifics =
+ GetEntry()->Get(syncable::SPECIFICS);
+ if (specifics.has_password()) {
+ // Passwords have their own legacy encryption structure.
+ 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;
+ }
+
+ // We assume any node with the encrypted field set has encrypted data and if
+ // not we have no work to do, with the exception of bookmarks. For bookmarks
+ // we must make sure the bookmarks data has the title field supplied. If not,
+ // we fill the unencrypted_data_ with a copy of the bookmark specifics that
+ // follows the new bookmarks format.
+ if (!specifics.has_encrypted()) {
+ if (GetModelType() == syncable::BOOKMARKS &&
+ !specifics.bookmark().has_title() &&
+ !GetTitle().empty()) { // Last check ensures this isn't a new node.
+ // We need to fill in the title.
+ 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 ||
+ !unencrypted_data_.ParseFromString(plaintext_data)) {
+ LOG(ERROR) << "Failed to decrypt encrypted node of type " <<
+ syncable::ModelTypeToString(GetModelType()) << ".";
+ return false;
+ }
+ DVLOG(2) << "Decrypted specifics of type "
+ << syncable::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(syncable::GetModelTypeFromSpecifics(unencrypted_data_),
+ syncable::UNSPECIFIED);
+ return unencrypted_data_;
+ } else {
+ // Due to the change in bookmarks format, we need to check to see if this is
+ // a legacy bookmarks (and has no title field in the proto). If it is, we
+ // return the unencrypted_data_, which was filled in with the title by
+ // DecryptIfNecessary().
+ if (GetModelType() == syncable::BOOKMARKS) {
+ const sync_pb::BookmarkSpecifics& bookmark_specifics =
+ specifics.bookmark();
+ if (bookmark_specifics.has_title() ||
+ GetTitle().empty() || // For the empty node case
+ !GetEntry()->Get(syncable::UNIQUE_SERVER_TAG).empty()) {
+ // It's possible we previously had to convert and set
+ // |unencrypted_data_| but then wrote our own data, so we allow
+ // |unencrypted_data_| to be non-empty.
+ return specifics;
+ } else {
+ DCHECK_EQ(syncable::GetModelTypeFromSpecifics(unencrypted_data_),
+ syncable::BOOKMARKS);
+ return unencrypted_data_;
+ }
+ } else {
+ DCHECK_EQ(syncable::GetModelTypeFromSpecifics(unencrypted_data_),
+ syncable::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;
+ // TODO(zea): refactor bookmarks to not need this functionality.
+ if (syncable::BOOKMARKS == GetModelType() &&
+ GetEntry()->Get(syncable::SPECIFICS).has_encrypted()) {
+ // Special case for legacy bookmarks dealing with encryption.
+ 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;
+ // TODO(akalin): Propagate up the error further (see
+ // http://crbug.com/100907).
+ 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",
+ browser_sync::GetTimeDebugString(GetModificationTime()));
+ node_info->SetString("parentId", base::Int64ToString(GetParentId()));
+ // Specifics are already in the Entry value, so no need to duplicate
+ // it here.
+ 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(syncable::APPS, GetModelType());
+ return GetEntitySpecifics().app();
+}
+
+const sync_pb::AutofillSpecifics& BaseNode::GetAutofillSpecifics() const {
+ DCHECK_EQ(syncable::AUTOFILL, GetModelType());
+ return GetEntitySpecifics().autofill();
+}
+
+const AutofillProfileSpecifics& BaseNode::GetAutofillProfileSpecifics() const {
+ DCHECK_EQ(GetModelType(), syncable::AUTOFILL_PROFILE);
+ return GetEntitySpecifics().autofill_profile();
+}
+
+const sync_pb::BookmarkSpecifics& BaseNode::GetBookmarkSpecifics() const {
+ DCHECK_EQ(syncable::BOOKMARKS, GetModelType());
+ return GetEntitySpecifics().bookmark();
+}
+
+const sync_pb::NigoriSpecifics& BaseNode::GetNigoriSpecifics() const {
+ DCHECK_EQ(syncable::NIGORI, GetModelType());
+ return GetEntitySpecifics().nigori();
+}
+
+const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const {
+ DCHECK_EQ(syncable::PASSWORDS, GetModelType());
+ return *password_data_;
+}
+
+const sync_pb::ThemeSpecifics& BaseNode::GetThemeSpecifics() const {
+ DCHECK_EQ(syncable::THEMES, GetModelType());
+ return GetEntitySpecifics().theme();
+}
+
+const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const {
+ DCHECK_EQ(syncable::TYPED_URLS, GetModelType());
+ return GetEntitySpecifics().typed_url();
+}
+
+const sync_pb::ExtensionSpecifics& BaseNode::GetExtensionSpecifics() const {
+ DCHECK_EQ(syncable::EXTENSIONS, GetModelType());
+ return GetEntitySpecifics().extension();
+}
+
+const sync_pb::SessionSpecifics& BaseNode::GetSessionSpecifics() const {
+ DCHECK_EQ(syncable::SESSIONS, GetModelType());
+ return GetEntitySpecifics().session();
+}
+
+const sync_pb::EntitySpecifics& BaseNode::GetEntitySpecifics() const {
+ return GetUnencryptedSpecifics(GetEntry());
+}
+
+syncable::ModelType BaseNode::GetModelType() const {
+ return GetEntry()->GetModelType();
+}
+
+void BaseNode::SetUnencryptedSpecifics(
+ const sync_pb::EntitySpecifics& specifics) {
+ syncable::ModelType type = syncable::GetModelTypeFromSpecifics(specifics);
+ DCHECK_NE(syncable::UNSPECIFIED, type);
+ if (GetModelType() != syncable::UNSPECIFIED) {
+ DCHECK_EQ(GetModelType(), type);
+ }
+ unencrypted_data_.CopyFrom(specifics);
+}
+
+} // namespace sync_api