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/write_node.h"
7 #include "base/strings/string_util.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "base/values.h"
10 #include "sync/internal_api/public/base_transaction.h"
11 #include "sync/internal_api/public/write_transaction.h"
12 #include "sync/internal_api/syncapi_internal.h"
13 #include "sync/protocol/bookmark_specifics.pb.h"
14 #include "sync/protocol/typed_url_specifics.pb.h"
15 #include "sync/syncable/mutable_entry.h"
16 #include "sync/syncable/nigori_util.h"
17 #include "sync/syncable/syncable_util.h"
18 #include "sync/util/cryptographer.h"
25 using syncable::kEncryptedString
;
26 using syncable::SPECIFICS
;
28 static const char kDefaultNameForNewNodes
[] = " ";
30 void WriteNode::SetIsFolder(bool folder
) {
31 if (entry_
->GetIsDir() == folder
)
32 return; // Skip redundant changes.
34 entry_
->PutIsDir(folder
);
38 void WriteNode::SetTitle(const std::string
& title
) {
39 DCHECK_NE(GetModelType(), UNSPECIFIED
);
40 ModelType type
= GetModelType();
41 // It's possible the nigori lost the set of encrypted types. If the current
42 // specifics are already encrypted, we want to ensure we continue encrypting.
43 bool needs_encryption
= GetTransaction()->GetEncryptedTypes().Has(type
) ||
44 entry_
->GetSpecifics().has_encrypted();
46 // If this datatype is encrypted and is not a bookmark, we disregard the
47 // specified title in favor of kEncryptedString. For encrypted bookmarks the
48 // NON_UNIQUE_NAME will still be kEncryptedString, but we store the real title
49 // into the specifics. All strings compared are server legal strings.
50 std::string new_legal_title
;
51 if (type
!= BOOKMARKS
&& needs_encryption
) {
52 new_legal_title
= kEncryptedString
;
54 DCHECK(base::IsStringUTF8(title
));
55 SyncAPINameToServerName(title
, &new_legal_title
);
56 base::TruncateUTF8ToByteSize(new_legal_title
, 255, &new_legal_title
);
59 std::string current_legal_title
;
60 if (BOOKMARKS
== type
&&
61 entry_
->GetSpecifics().has_encrypted()) {
62 // Encrypted bookmarks only have their title in the unencrypted specifics.
63 current_legal_title
= GetBookmarkSpecifics().title();
65 // Non-bookmarks and legacy bookmarks (those with no title in their
66 // specifics) store their title in NON_UNIQUE_NAME. Non-legacy bookmarks
67 // store their title in specifics as well as NON_UNIQUE_NAME.
68 current_legal_title
= entry_
->GetNonUniqueName();
71 bool title_matches
= (current_legal_title
== new_legal_title
);
72 bool encrypted_without_overwriting_name
= (needs_encryption
&&
73 entry_
->GetNonUniqueName() != kEncryptedString
);
75 // For bookmarks, we also set the title field in the specifics.
76 // TODO(zea): refactor bookmarks to not need this functionality.
77 sync_pb::EntitySpecifics specifics
= GetEntitySpecifics();
78 if (GetModelType() == BOOKMARKS
&&
79 specifics
.bookmark().title() != new_legal_title
) {
80 specifics
.mutable_bookmark()->set_title(new_legal_title
);
81 SetEntitySpecifics(specifics
); // Does it's own encryption checking.
82 title_matches
= false;
85 // If the title matches and the NON_UNIQUE_NAME is properly overwritten as
86 // necessary, nothing needs to change.
87 if (title_matches
&& !encrypted_without_overwriting_name
) {
88 DVLOG(2) << "Title matches, dropping change.";
92 // For bookmarks, this has to happen after we set the title in the specifics,
93 // because the presence of a title in the NON_UNIQUE_NAME is what controls
94 // the logic deciding whether this is an empty node or a legacy bookmark.
95 // See BaseNode::GetUnencryptedSpecific(..).
97 entry_
->PutNonUniqueName(kEncryptedString
);
99 entry_
->PutNonUniqueName(new_legal_title
);
101 DVLOG(1) << "Overwriting title of type "
102 << ModelTypeToString(type
)
103 << " and marking for syncing.";
107 void WriteNode::SetBookmarkSpecifics(
108 const sync_pb::BookmarkSpecifics
& new_value
) {
109 sync_pb::EntitySpecifics entity_specifics
;
110 entity_specifics
.mutable_bookmark()->CopyFrom(new_value
);
111 SetEntitySpecifics(entity_specifics
);
114 void WriteNode::SetNigoriSpecifics(
115 const sync_pb::NigoriSpecifics
& new_value
) {
116 sync_pb::EntitySpecifics entity_specifics
;
117 entity_specifics
.mutable_nigori()->CopyFrom(new_value
);
118 SetEntitySpecifics(entity_specifics
);
121 void WriteNode::SetPasswordSpecifics(
122 const sync_pb::PasswordSpecificsData
& data
) {
123 DCHECK_EQ(GetModelType(), PASSWORDS
);
125 Cryptographer
* cryptographer
= GetTransaction()->GetCryptographer();
127 // We have to do the idempotency check here (vs in UpdateEntryWithEncryption)
128 // because Passwords have their encrypted data within the PasswordSpecifics,
129 // vs within the EntitySpecifics like all the other types.
130 const sync_pb::EntitySpecifics
& old_specifics
= GetEntry()->GetSpecifics();
131 sync_pb::EntitySpecifics entity_specifics
;
132 // Copy over the old specifics if they exist.
133 if (GetModelTypeFromSpecifics(old_specifics
) == PASSWORDS
) {
134 entity_specifics
.CopyFrom(old_specifics
);
136 AddDefaultFieldValue(PASSWORDS
, &entity_specifics
);
138 sync_pb::PasswordSpecifics
* password_specifics
=
139 entity_specifics
.mutable_password();
140 // This will only update password_specifics if the underlying unencrypted blob
141 // was different from |data| or was not encrypted with the proper passphrase.
142 if (!cryptographer
->Encrypt(data
, password_specifics
->mutable_encrypted())) {
143 LOG(ERROR
) << "Failed to encrypt password, possibly due to sync node "
147 SetEntitySpecifics(entity_specifics
);
150 void WriteNode::SetEntitySpecifics(
151 const sync_pb::EntitySpecifics
& new_value
) {
152 ModelType new_specifics_type
=
153 GetModelTypeFromSpecifics(new_value
);
154 CHECK(!new_value
.password().has_client_only_encrypted_data());
155 DCHECK_NE(new_specifics_type
, UNSPECIFIED
);
156 DVLOG(1) << "Writing entity specifics of type "
157 << ModelTypeToString(new_specifics_type
);
158 DCHECK_EQ(new_specifics_type
, GetModelType());
160 // Preserve unknown fields.
161 const sync_pb::EntitySpecifics
& old_specifics
= entry_
->GetSpecifics();
162 sync_pb::EntitySpecifics new_specifics
;
163 new_specifics
.CopyFrom(new_value
);
164 new_specifics
.mutable_unknown_fields()->MergeFrom(
165 old_specifics
.unknown_fields());
167 // Will update the entry if encryption was necessary.
168 if (!UpdateEntryWithEncryption(GetTransaction()->GetWrappedTrans(),
173 if (entry_
->GetSpecifics().has_encrypted()) {
174 // EncryptIfNecessary already updated the entry for us and marked for
175 // syncing if it was needed. Now we just make a copy of the unencrypted
176 // specifics so that if this node is updated, we do not have to decrypt the
177 // old data. Note that this only modifies the node's local data, not the
179 SetUnencryptedSpecifics(new_value
);
182 DCHECK_EQ(new_specifics_type
, GetModelType());
185 void WriteNode::ResetFromSpecifics() {
186 SetEntitySpecifics(GetEntitySpecifics());
189 void WriteNode::SetTypedUrlSpecifics(
190 const sync_pb::TypedUrlSpecifics
& new_value
) {
191 sync_pb::EntitySpecifics entity_specifics
;
192 entity_specifics
.mutable_typed_url()->CopyFrom(new_value
);
193 SetEntitySpecifics(entity_specifics
);
196 void WriteNode::SetExternalId(int64 id
) {
197 if (GetExternalId() != id
)
198 entry_
->PutLocalExternalId(id
);
201 WriteNode::WriteNode(WriteTransaction
* transaction
)
202 : entry_(NULL
), transaction_(transaction
) {
206 WriteNode::~WriteNode() {
210 // Find an existing node matching the ID |id|, and bind this WriteNode to it.
211 // Return true on success.
212 BaseNode::InitByLookupResult
WriteNode::InitByIdLookup(int64 id
) {
213 DCHECK(!entry_
) << "Init called twice";
214 DCHECK_NE(id
, kInvalidId
);
215 entry_
= new syncable::MutableEntry(transaction_
->GetWrappedWriteTrans(),
216 syncable::GET_BY_HANDLE
, id
);
218 return INIT_FAILED_ENTRY_NOT_GOOD
;
219 if (entry_
->GetIsDel())
220 return INIT_FAILED_ENTRY_IS_DEL
;
221 return DecryptIfNecessary() ? INIT_OK
: INIT_FAILED_DECRYPT_IF_NECESSARY
;
224 // Find a node by client tag, and bind this WriteNode to it.
225 // Return true if the write node was found, and was not deleted.
226 // Undeleting a deleted node is possible by ClientTag.
227 BaseNode::InitByLookupResult
WriteNode::InitByClientTagLookup(
228 ModelType model_type
,
229 const std::string
& tag
) {
230 DCHECK(!entry_
) << "Init called twice";
232 return INIT_FAILED_PRECONDITION
;
234 const std::string hash
= syncable::GenerateSyncableHash(model_type
, tag
);
236 entry_
= new syncable::MutableEntry(transaction_
->GetWrappedWriteTrans(),
237 syncable::GET_BY_CLIENT_TAG
, hash
);
239 return INIT_FAILED_ENTRY_NOT_GOOD
;
240 if (entry_
->GetIsDel())
241 return INIT_FAILED_ENTRY_IS_DEL
;
242 return DecryptIfNecessary() ? INIT_OK
: INIT_FAILED_DECRYPT_IF_NECESSARY
;
245 BaseNode::InitByLookupResult
WriteNode::InitTypeRoot(ModelType type
) {
246 DCHECK(!entry_
) << "Init called twice";
247 if (!IsRealDataType(type
))
248 return INIT_FAILED_PRECONDITION
;
249 entry_
= new syncable::MutableEntry(transaction_
->GetWrappedWriteTrans(),
250 syncable::GET_TYPE_ROOT
, type
);
252 return INIT_FAILED_ENTRY_NOT_GOOD
;
253 if (entry_
->GetIsDel())
254 return INIT_FAILED_ENTRY_IS_DEL
;
255 ModelType model_type
= GetModelType();
256 DCHECK_EQ(model_type
, NIGORI
);
260 // Create a new node with default properties, and bind this WriteNode to it.
261 // Return true on success.
262 bool WriteNode::InitBookmarkByCreation(const BaseNode
& parent
,
263 const BaseNode
* predecessor
) {
264 DCHECK(!entry_
) << "Init called twice";
265 // |predecessor| must be a child of |parent| or NULL.
266 if (predecessor
&& predecessor
->GetParentId() != parent
.GetId()) {
271 syncable::Id parent_id
= parent
.GetEntry()->GetId();
272 DCHECK(!parent_id
.IsNull());
274 // Start out with a dummy name. We expect
275 // the caller to set a meaningful name after creation.
276 string
dummy(kDefaultNameForNewNodes
);
278 entry_
= new syncable::MutableEntry(transaction_
->GetWrappedWriteTrans(),
279 syncable::CREATE
, BOOKMARKS
,
285 // Entries are untitled folders by default.
286 entry_
->PutIsDir(true);
288 if (!PutPredecessor(predecessor
)) {
292 // Mark this entry as unsynced, to wake up the syncer.
297 WriteNode::InitUniqueByCreationResult
WriteNode::InitUniqueByCreation(
298 ModelType model_type
,
299 const BaseNode
& parent
,
300 const std::string
& tag
) {
301 return InitUniqueByCreationImpl(model_type
, parent
.GetEntry()->GetId(), tag
);
304 WriteNode::InitUniqueByCreationResult
WriteNode::InitUniqueByCreation(
305 ModelType model_type
,
306 const std::string
& tag
) {
307 return InitUniqueByCreationImpl(model_type
, syncable::Id(), tag
);
310 // Create a new node with default properties and a client defined unique tag,
311 // and bind this WriteNode to it.
312 // Return true on success. If the tag exists in the database, then
313 // we will attempt to undelete the node.
314 WriteNode::InitUniqueByCreationResult
WriteNode::InitUniqueByCreationImpl(
315 ModelType model_type
,
316 const syncable::Id
& parent_id
,
317 const std::string
& tag
) {
318 // This DCHECK will only fail if init is called twice.
321 LOG(WARNING
) << "InitUniqueByCreation failed due to empty tag.";
322 return INIT_FAILED_EMPTY_TAG
;
325 const std::string hash
= syncable::GenerateSyncableHash(model_type
, tag
);
327 // Start out with a dummy name. We expect
328 // the caller to set a meaningful name after creation.
329 string
dummy(kDefaultNameForNewNodes
);
331 // Check if we have this locally and need to undelete it.
332 scoped_ptr
<syncable::MutableEntry
> existing_entry(
333 new syncable::MutableEntry(transaction_
->GetWrappedWriteTrans(),
334 syncable::GET_BY_CLIENT_TAG
, hash
));
336 if (existing_entry
->good()) {
337 if (existing_entry
->GetIsDel()) {
338 // Rules for undelete:
339 // BASE_VERSION: Must keep the same.
340 // ID: Essential to keep the same.
341 // META_HANDLE: Must be the same, so we can't "split" the entry.
342 // IS_DEL: Must be set to false, will cause reindexing.
343 // This one is weird because IS_DEL is true for "update only"
344 // items. It should be OK to undelete an update only.
345 // MTIME/CTIME: Seems reasonable to just leave them alone.
346 // IS_UNSYNCED: Must set this to true or face database insurrection.
347 // We do this below this block.
348 // IS_UNAPPLIED_UPDATE: Either keep it the same or also set BASE_VERSION
349 // to SERVER_VERSION. We keep it the same here.
350 // IS_DIR: We'll leave it the same.
351 // SPECIFICS: Reset it.
353 existing_entry
->PutIsDel(false);
355 // Client tags are immutable and must be paired with the ID.
356 // If a server update comes down with an ID and client tag combo,
357 // and it already exists, always overwrite it and store only one copy.
358 // We have to undelete entries because we can't disassociate IDs from
361 existing_entry
->PutNonUniqueName(dummy
);
362 existing_entry
->PutParentId(parent_id
);
364 // Put specifics to handle the case where this is not actually an
365 // undeletion, but instead a collision with a newly downloaded,
366 // processed, and unapplied server update. This is a fix for
367 // http://crbug.com/397766.
368 sync_pb::EntitySpecifics specifics
;
369 AddDefaultFieldValue(model_type
, &specifics
);
370 existing_entry
->PutSpecifics(specifics
);
371 } // Else just reuse the existing entry.
372 entry_
= existing_entry
.release();
374 entry_
= new syncable::MutableEntry(transaction_
->GetWrappedWriteTrans(),
376 model_type
, parent_id
, dummy
);
380 return INIT_FAILED_COULD_NOT_CREATE_ENTRY
;
382 // Has no impact if the client tag is already set.
383 entry_
->PutUniqueClientTag(hash
);
385 // We don't support directory and tag combinations.
386 entry_
->PutIsDir(false);
388 if (!parent_id
.IsNull()) {
389 if (!PutPredecessor(NULL
))
390 return INIT_FAILED_SET_PREDECESSOR
;
393 // Mark this entry as unsynced, to wake up the syncer.
399 bool WriteNode::SetPosition(const BaseNode
& new_parent
,
400 const BaseNode
* predecessor
) {
401 // |predecessor| must be a child of |new_parent| or NULL.
402 if (predecessor
&& predecessor
->GetParentId() != new_parent
.GetId()) {
407 syncable::Id new_parent_id
= new_parent
.GetEntry()->GetId();
408 DCHECK(!new_parent_id
.IsNull());
410 // Filter out redundant changes if both the parent and the predecessor match.
411 if (new_parent_id
== entry_
->GetParentId()) {
412 const syncable::Id
& old
= entry_
->GetPredecessorId();
413 if ((!predecessor
&& old
.IsNull()) ||
414 (predecessor
&& (old
== predecessor
->GetEntry()->GetId()))) {
419 entry_
->PutParentId(new_parent_id
);
421 if (!PutPredecessor(predecessor
)) {
425 // Mark this entry as unsynced, to wake up the syncer.
430 void WriteNode::SetAttachmentMetadata(
431 const sync_pb::AttachmentMetadata
& attachment_metadata
) {
432 entry_
->PutAttachmentMetadata(attachment_metadata
);
435 const syncable::Entry
* WriteNode::GetEntry() const {
439 const BaseTransaction
* WriteNode::GetTransaction() const {
443 syncable::MutableEntry
* WriteNode::GetMutableEntryForTest() {
447 void WriteNode::Tombstone() {
448 // These lines must be in this order. The call to Put(IS_DEL) might choose to
449 // unset the IS_UNSYNCED bit if the item was not known to the server at the
450 // time of deletion. It's important that the bit not be reset in that case.
452 entry_
->PutIsDel(true);
455 void WriteNode::Drop() {
456 if (entry_
->GetId().ServerKnows()) {
457 entry_
->PutIsDel(true);
461 bool WriteNode::PutPredecessor(const BaseNode
* predecessor
) {
462 DCHECK(!entry_
->GetParentId().IsNull());
463 syncable::Id predecessor_id
= predecessor
?
464 predecessor
->GetEntry()->GetId() : syncable::Id();
465 return entry_
->PutPredecessor(predecessor_id
);
468 void WriteNode::MarkForSyncing() {
469 syncable::MarkForSyncing(entry_
);
472 } // namespace syncer