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.
5 #include "sync/internal_api/public/base_node.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/values.h"
12 #include "sync/internal_api/public/base_transaction.h"
13 #include "sync/internal_api/syncapi_internal.h"
14 #include "sync/protocol/app_specifics.pb.h"
15 #include "sync/protocol/autofill_specifics.pb.h"
16 #include "sync/protocol/bookmark_specifics.pb.h"
17 #include "sync/protocol/extension_specifics.pb.h"
18 #include "sync/protocol/nigori_specifics.pb.h"
19 #include "sync/protocol/password_specifics.pb.h"
20 #include "sync/protocol/session_specifics.pb.h"
21 #include "sync/protocol/theme_specifics.pb.h"
22 #include "sync/protocol/typed_url_specifics.pb.h"
23 #include "sync/syncable/directory.h"
24 #include "sync/syncable/entry.h"
25 #include "sync/syncable/syncable_id.h"
26 #include "sync/util/time.h"
28 using sync_pb::AutofillProfileSpecifics;
32 using syncable::SPECIFICS;
34 // Helper function to look up the int64 metahandle of an object given the ID
36 static int64 IdToMetahandle(syncable::BaseTransaction* trans,
37 const syncable::Id& id) {
38 syncable::Entry entry(trans, syncable::GET_BY_ID, id);
41 return entry.GetMetahandle();
44 static bool EndsWithSpace(const std::string& string) {
45 return !string.empty() && *string.rbegin() == ' ';
48 // In the reverse direction, if a server name matches the pattern of a
49 // server-illegal name followed by one or more spaces, remove the trailing
51 static void ServerNameToSyncAPIName(const std::string& server_name,
54 int length_to_copy = server_name.length();
55 if (IsNameServerIllegalAfterTrimming(server_name) &&
56 EndsWithSpace(server_name)) {
59 *out = std::string(server_name.c_str(), length_to_copy);
62 BaseNode::BaseNode() : password_data_(new sync_pb::PasswordSpecificsData) {}
64 BaseNode::~BaseNode() {}
66 bool BaseNode::DecryptIfNecessary() {
67 if (!GetEntry()->GetUniqueServerTag().empty())
68 return true; // Ignore unique folders.
69 const sync_pb::EntitySpecifics& specifics =
70 GetEntry()->GetSpecifics();
71 if (specifics.has_password()) {
72 // Passwords have their own legacy encryption structure.
73 scoped_ptr<sync_pb::PasswordSpecificsData> data(DecryptPasswordSpecifics(
74 specifics, GetTransaction()->GetCryptographer()));
76 LOG(ERROR) << "Failed to decrypt password specifics.";
79 password_data_.swap(data);
83 // We assume any node with the encrypted field set has encrypted data and if
84 // not we have no work to do, with the exception of bookmarks. For bookmarks
85 // we must make sure the bookmarks data has the title field supplied. If not,
86 // we fill the unencrypted_data_ with a copy of the bookmark specifics that
87 // follows the new bookmarks format.
88 if (!specifics.has_encrypted()) {
89 if (GetModelType() == BOOKMARKS &&
90 !specifics.bookmark().has_title() &&
91 !GetTitle().empty()) { // Last check ensures this isn't a new node.
92 // We need to fill in the title.
93 std::string title = GetTitle();
94 std::string server_legal_title;
95 SyncAPINameToServerName(title, &server_legal_title);
96 DVLOG(1) << "Reading from legacy bookmark, manually returning title "
98 unencrypted_data_.CopyFrom(specifics);
99 unencrypted_data_.mutable_bookmark()->set_title(
105 const sync_pb::EncryptedData& encrypted = specifics.encrypted();
106 std::string plaintext_data = GetTransaction()->GetCryptographer()->
107 DecryptToString(encrypted);
108 if (plaintext_data.length() == 0) {
109 LOG(ERROR) << "Failed to decrypt encrypted node of type "
110 << ModelTypeToString(GetModelType()) << ".";
111 // Debugging for crbug.com/123223. We failed to decrypt the data, which
112 // means we applied an update without having the key or lost the key at a
116 } else if (!unencrypted_data_.ParseFromString(plaintext_data)) {
117 // Debugging for crbug.com/123223. We should never succeed in decrypting
118 // but fail to parse into a protobuf.
122 DVLOG(2) << "Decrypted specifics of type "
123 << ModelTypeToString(GetModelType())
124 << " with content: " << plaintext_data;
128 const sync_pb::EntitySpecifics& BaseNode::GetUnencryptedSpecifics(
129 const syncable::Entry* entry) const {
130 const sync_pb::EntitySpecifics& specifics = entry->GetSpecifics();
131 if (specifics.has_encrypted()) {
132 DCHECK_NE(GetModelTypeFromSpecifics(unencrypted_data_), UNSPECIFIED);
133 return unencrypted_data_;
135 // Due to the change in bookmarks format, we need to check to see if this is
136 // a legacy bookmarks (and has no title field in the proto). If it is, we
137 // return the unencrypted_data_, which was filled in with the title by
138 // DecryptIfNecessary().
139 if (GetModelType() == BOOKMARKS) {
140 const sync_pb::BookmarkSpecifics& bookmark_specifics =
141 specifics.bookmark();
142 if (bookmark_specifics.has_title() ||
143 GetTitle().empty() || // For the empty node case
144 !GetEntry()->GetUniqueServerTag().empty()) {
145 // It's possible we previously had to convert and set
146 // |unencrypted_data_| but then wrote our own data, so we allow
147 // |unencrypted_data_| to be non-empty.
150 DCHECK_EQ(GetModelTypeFromSpecifics(unencrypted_data_), BOOKMARKS);
151 return unencrypted_data_;
154 DCHECK_EQ(GetModelTypeFromSpecifics(unencrypted_data_), UNSPECIFIED);
160 int64 BaseNode::GetParentId() const {
161 return IdToMetahandle(GetTransaction()->GetWrappedTrans(),
162 GetEntry()->GetParentId());
165 int64 BaseNode::GetId() const {
166 return GetEntry()->GetMetahandle();
169 base::Time BaseNode::GetModificationTime() const {
170 return GetEntry()->GetMtime();
173 bool BaseNode::GetIsFolder() const {
174 return GetEntry()->GetIsDir();
177 std::string BaseNode::GetTitle() const {
179 // TODO(zea): refactor bookmarks to not need this functionality.
180 if (BOOKMARKS == GetModelType() &&
181 GetEntry()->GetSpecifics().has_encrypted()) {
182 // Special case for legacy bookmarks dealing with encryption.
183 ServerNameToSyncAPIName(GetBookmarkSpecifics().title(), &result);
185 ServerNameToSyncAPIName(GetEntry()->GetNonUniqueName(),
191 bool BaseNode::HasChildren() const {
192 syncable::Directory* dir = GetTransaction()->GetDirectory();
193 syncable::BaseTransaction* trans = GetTransaction()->GetWrappedTrans();
194 return dir->HasChildren(trans, GetEntry()->GetId());
197 int64 BaseNode::GetPredecessorId() const {
198 syncable::Id id_string = GetEntry()->GetPredecessorId();
199 if (id_string.IsRoot())
201 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
204 int64 BaseNode::GetSuccessorId() const {
205 syncable::Id id_string = GetEntry()->GetSuccessorId();
206 if (id_string.IsRoot())
208 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
211 int64 BaseNode::GetFirstChildId() const {
212 syncable::Id id_string = GetEntry()->GetFirstChildId();
213 if (id_string.IsRoot())
215 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
218 void BaseNode::GetChildIds(std::vector<int64>* result) const {
219 GetEntry()->GetChildHandles(result);
222 int BaseNode::GetTotalNodeCount() const {
223 return GetEntry()->GetTotalNodeCount();
226 int BaseNode::GetPositionIndex() const {
227 return GetEntry()->GetPositionIndex();
230 base::DictionaryValue* BaseNode::GetSummaryAsValue() const {
231 base::DictionaryValue* node_info = new base::DictionaryValue();
232 node_info->SetString("id", base::Int64ToString(GetId()));
233 node_info->SetBoolean("isFolder", GetIsFolder());
234 node_info->SetString("title", GetTitle());
235 node_info->Set("type", ModelTypeToValue(GetModelType()));
239 base::DictionaryValue* BaseNode::GetDetailsAsValue() const {
240 base::DictionaryValue* node_info = GetSummaryAsValue();
241 node_info->SetString(
242 "modificationTime", GetTimeDebugString(GetModificationTime()));
243 node_info->SetString("parentId", base::Int64ToString(GetParentId()));
244 // Specifics are already in the Entry value, so no need to duplicate
246 node_info->SetString("externalId", base::Int64ToString(GetExternalId()));
247 if (GetEntry()->ShouldMaintainPosition() &&
248 !GetEntry()->GetIsDel()) {
249 node_info->SetString("successorId", base::Int64ToString(GetSuccessorId()));
250 node_info->SetString(
251 "predecessorId", base::Int64ToString(GetPredecessorId()));
253 if (GetEntry()->GetIsDir()) {
254 node_info->SetString(
255 "firstChildId", base::Int64ToString(GetFirstChildId()));
258 "entry", GetEntry()->ToValue(GetTransaction()->GetCryptographer()));
262 int64 BaseNode::GetExternalId() const {
263 return GetEntry()->GetLocalExternalId();
266 const sync_pb::AppSpecifics& BaseNode::GetAppSpecifics() const {
267 DCHECK_EQ(GetModelType(), APPS);
268 return GetEntitySpecifics().app();
271 const sync_pb::AutofillSpecifics& BaseNode::GetAutofillSpecifics() const {
272 DCHECK_EQ(GetModelType(), AUTOFILL);
273 return GetEntitySpecifics().autofill();
276 const AutofillProfileSpecifics& BaseNode::GetAutofillProfileSpecifics() const {
277 DCHECK_EQ(GetModelType(), AUTOFILL_PROFILE);
278 return GetEntitySpecifics().autofill_profile();
281 const sync_pb::BookmarkSpecifics& BaseNode::GetBookmarkSpecifics() const {
282 DCHECK_EQ(GetModelType(), BOOKMARKS);
283 return GetEntitySpecifics().bookmark();
286 const sync_pb::NigoriSpecifics& BaseNode::GetNigoriSpecifics() const {
287 DCHECK_EQ(GetModelType(), NIGORI);
288 return GetEntitySpecifics().nigori();
291 const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const {
292 DCHECK_EQ(GetModelType(), PASSWORDS);
293 return *password_data_;
296 const sync_pb::ThemeSpecifics& BaseNode::GetThemeSpecifics() const {
297 DCHECK_EQ(GetModelType(), THEMES);
298 return GetEntitySpecifics().theme();
301 const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const {
302 DCHECK_EQ(GetModelType(), TYPED_URLS);
303 return GetEntitySpecifics().typed_url();
306 const sync_pb::ExtensionSpecifics& BaseNode::GetExtensionSpecifics() const {
307 DCHECK_EQ(GetModelType(), EXTENSIONS);
308 return GetEntitySpecifics().extension();
311 const sync_pb::SessionSpecifics& BaseNode::GetSessionSpecifics() const {
312 DCHECK_EQ(GetModelType(), SESSIONS);
313 return GetEntitySpecifics().session();
316 const sync_pb::ManagedUserSettingSpecifics&
317 BaseNode::GetManagedUserSettingSpecifics() const {
318 DCHECK_EQ(GetModelType(), MANAGED_USER_SETTINGS);
319 return GetEntitySpecifics().managed_user_setting();
322 const sync_pb::ManagedUserSpecifics& BaseNode::GetManagedUserSpecifics() const {
323 DCHECK_EQ(GetModelType(), MANAGED_USERS);
324 return GetEntitySpecifics().managed_user();
327 const sync_pb::DeviceInfoSpecifics& BaseNode::GetDeviceInfoSpecifics() const {
328 DCHECK_EQ(GetModelType(), DEVICE_INFO);
329 return GetEntitySpecifics().device_info();
332 const sync_pb::ExperimentsSpecifics& BaseNode::GetExperimentsSpecifics() const {
333 DCHECK_EQ(GetModelType(), EXPERIMENTS);
334 return GetEntitySpecifics().experiments();
337 const sync_pb::PriorityPreferenceSpecifics&
338 BaseNode::GetPriorityPreferenceSpecifics() const {
339 DCHECK_EQ(GetModelType(), PRIORITY_PREFERENCES);
340 return GetEntitySpecifics().priority_preference();
343 const sync_pb::EntitySpecifics& BaseNode::GetEntitySpecifics() const {
344 return GetUnencryptedSpecifics(GetEntry());
347 ModelType BaseNode::GetModelType() const {
348 return GetEntry()->GetModelType();
351 void BaseNode::SetUnencryptedSpecifics(
352 const sync_pb::EntitySpecifics& specifics) {
353 ModelType type = GetModelTypeFromSpecifics(specifics);
354 DCHECK_NE(UNSPECIFIED, type);
355 if (GetModelType() != UNSPECIFIED) {
356 DCHECK_EQ(GetModelType(), type);
358 unencrypted_data_.CopyFrom(specifics);
361 } // namespace syncer