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/app_specifics.pb.h"
14 #include "sync/protocol/autofill_specifics.pb.h"
15 #include "sync/protocol/bookmark_specifics.pb.h"
16 #include "sync/protocol/extension_specifics.pb.h"
17 #include "sync/protocol/password_specifics.pb.h"
18 #include "sync/protocol/session_specifics.pb.h"
19 #include "sync/protocol/theme_specifics.pb.h"
20 #include "sync/protocol/typed_url_specifics.pb.h"
21 #include "sync/syncable/mutable_entry.h"
22 #include "sync/syncable/nigori_util.h"
23 #include "sync/syncable/syncable_util.h"
24 #include "sync/util/cryptographer.h"
31 using syncable::kEncryptedString
;
32 using syncable::SPECIFICS
;
34 static const char kDefaultNameForNewNodes
[] = " ";
36 void WriteNode::SetIsFolder(bool folder
) {
37 if (entry_
->GetIsDir() == folder
)
38 return; // Skip redundant changes.
40 entry_
->PutIsDir(folder
);
44 void WriteNode::SetTitle(const std::string
& title
) {
45 DCHECK_NE(GetModelType(), UNSPECIFIED
);
46 ModelType type
= GetModelType();
47 // It's possible the nigori lost the set of encrypted types. If the current
48 // specifics are already encrypted, we want to ensure we continue encrypting.
49 bool needs_encryption
= GetTransaction()->GetEncryptedTypes().Has(type
) ||
50 entry_
->GetSpecifics().has_encrypted();
52 // If this datatype is encrypted and is not a bookmark, we disregard the
53 // specified title in favor of kEncryptedString. For encrypted bookmarks the
54 // NON_UNIQUE_NAME will still be kEncryptedString, but we store the real title
55 // into the specifics. All strings compared are server legal strings.
56 std::string new_legal_title
;
57 if (type
!= BOOKMARKS
&& needs_encryption
) {
58 new_legal_title
= kEncryptedString
;
60 DCHECK(base::IsStringUTF8(title
));
61 SyncAPINameToServerName(title
, &new_legal_title
);
62 base::TruncateUTF8ToByteSize(new_legal_title
, 255, &new_legal_title
);
65 std::string current_legal_title
;
66 if (BOOKMARKS
== type
&&
67 entry_
->GetSpecifics().has_encrypted()) {
68 // Encrypted bookmarks only have their title in the unencrypted specifics.
69 current_legal_title
= GetBookmarkSpecifics().title();
71 // Non-bookmarks and legacy bookmarks (those with no title in their
72 // specifics) store their title in NON_UNIQUE_NAME. Non-legacy bookmarks
73 // store their title in specifics as well as NON_UNIQUE_NAME.
74 current_legal_title
= entry_
->GetNonUniqueName();
77 bool title_matches
= (current_legal_title
== new_legal_title
);
78 bool encrypted_without_overwriting_name
= (needs_encryption
&&
79 entry_
->GetNonUniqueName() != kEncryptedString
);
81 // If the title matches and the NON_UNIQUE_NAME is properly overwritten as
82 // necessary, nothing needs to change.
83 if (title_matches
&& !encrypted_without_overwriting_name
) {
84 DVLOG(2) << "Title matches, dropping change.";
88 // For bookmarks, we also set the title field in the specifics.
89 // TODO(zea): refactor bookmarks to not need this functionality.
90 if (GetModelType() == BOOKMARKS
) {
91 sync_pb::EntitySpecifics specifics
= GetEntitySpecifics();
92 specifics
.mutable_bookmark()->set_title(new_legal_title
);
93 SetEntitySpecifics(specifics
); // Does it's own encryption checking.
96 // For bookmarks, this has to happen after we set the title in the specifics,
97 // because the presence of a title in the NON_UNIQUE_NAME is what controls
98 // the logic deciding whether this is an empty node or a legacy bookmark.
99 // See BaseNode::GetUnencryptedSpecific(..).
100 if (needs_encryption
)
101 entry_
->PutNonUniqueName(kEncryptedString
);
103 entry_
->PutNonUniqueName(new_legal_title
);
105 DVLOG(1) << "Overwriting title of type "
106 << ModelTypeToString(type
)
107 << " and marking for syncing.";
111 void WriteNode::SetAppSpecifics(
112 const sync_pb::AppSpecifics
& new_value
) {
113 sync_pb::EntitySpecifics entity_specifics
;
114 entity_specifics
.mutable_app()->CopyFrom(new_value
);
115 SetEntitySpecifics(entity_specifics
);
118 void WriteNode::SetAutofillSpecifics(
119 const sync_pb::AutofillSpecifics
& new_value
) {
120 sync_pb::EntitySpecifics entity_specifics
;
121 entity_specifics
.mutable_autofill()->CopyFrom(new_value
);
122 SetEntitySpecifics(entity_specifics
);
125 void WriteNode::SetAutofillProfileSpecifics(
126 const sync_pb::AutofillProfileSpecifics
& new_value
) {
127 sync_pb::EntitySpecifics entity_specifics
;
128 entity_specifics
.mutable_autofill_profile()->
130 SetEntitySpecifics(entity_specifics
);
133 void WriteNode::SetBookmarkSpecifics(
134 const sync_pb::BookmarkSpecifics
& new_value
) {
135 sync_pb::EntitySpecifics entity_specifics
;
136 entity_specifics
.mutable_bookmark()->CopyFrom(new_value
);
137 SetEntitySpecifics(entity_specifics
);
140 void WriteNode::SetNigoriSpecifics(
141 const sync_pb::NigoriSpecifics
& new_value
) {
142 sync_pb::EntitySpecifics entity_specifics
;
143 entity_specifics
.mutable_nigori()->CopyFrom(new_value
);
144 SetEntitySpecifics(entity_specifics
);
147 void WriteNode::SetPasswordSpecifics(
148 const sync_pb::PasswordSpecificsData
& data
) {
149 DCHECK_EQ(GetModelType(), PASSWORDS
);
151 Cryptographer
* cryptographer
= GetTransaction()->GetCryptographer();
153 // We have to do the idempotency check here (vs in UpdateEntryWithEncryption)
154 // because Passwords have their encrypted data within the PasswordSpecifics,
155 // vs within the EntitySpecifics like all the other types.
156 const sync_pb::EntitySpecifics
& old_specifics
= GetEntry()->GetSpecifics();
157 sync_pb::EntitySpecifics entity_specifics
;
158 // Copy over the old specifics if they exist.
159 if (GetModelTypeFromSpecifics(old_specifics
) == PASSWORDS
) {
160 entity_specifics
.CopyFrom(old_specifics
);
162 AddDefaultFieldValue(PASSWORDS
, &entity_specifics
);
164 sync_pb::PasswordSpecifics
* password_specifics
=
165 entity_specifics
.mutable_password();
166 // This will only update password_specifics if the underlying unencrypted blob
167 // was different from |data| or was not encrypted with the proper passphrase.
168 if (!cryptographer
->Encrypt(data
, password_specifics
->mutable_encrypted())) {
169 NOTREACHED() << "Failed to encrypt password, possibly due to sync node "
173 SetEntitySpecifics(entity_specifics
);
176 void WriteNode::SetThemeSpecifics(
177 const sync_pb::ThemeSpecifics
& new_value
) {
178 sync_pb::EntitySpecifics entity_specifics
;
179 entity_specifics
.mutable_theme()->CopyFrom(new_value
);
180 SetEntitySpecifics(entity_specifics
);
183 void WriteNode::SetSessionSpecifics(
184 const sync_pb::SessionSpecifics
& new_value
) {
185 sync_pb::EntitySpecifics entity_specifics
;
186 entity_specifics
.mutable_session()->CopyFrom(new_value
);
187 SetEntitySpecifics(entity_specifics
);
190 void WriteNode::SetDeviceInfoSpecifics(
191 const sync_pb::DeviceInfoSpecifics
& new_value
) {
192 sync_pb::EntitySpecifics entity_specifics
;
193 entity_specifics
.mutable_device_info()->CopyFrom(new_value
);
194 SetEntitySpecifics(entity_specifics
);
197 void WriteNode::SetExperimentsSpecifics(
198 const sync_pb::ExperimentsSpecifics
& new_value
) {
199 sync_pb::EntitySpecifics entity_specifics
;
200 entity_specifics
.mutable_experiments()->CopyFrom(new_value
);
201 SetEntitySpecifics(entity_specifics
);
204 void WriteNode::SetPriorityPreferenceSpecifics(
205 const sync_pb::PriorityPreferenceSpecifics
& new_value
) {
206 sync_pb::EntitySpecifics entity_specifics
;
207 entity_specifics
.mutable_priority_preference()->CopyFrom(new_value
);
208 SetEntitySpecifics(entity_specifics
);
211 void WriteNode::SetEntitySpecifics(
212 const sync_pb::EntitySpecifics
& new_value
) {
213 ModelType new_specifics_type
=
214 GetModelTypeFromSpecifics(new_value
);
215 CHECK(!new_value
.password().has_client_only_encrypted_data());
216 DCHECK_NE(new_specifics_type
, UNSPECIFIED
);
217 DVLOG(1) << "Writing entity specifics of type "
218 << ModelTypeToString(new_specifics_type
);
219 DCHECK_EQ(new_specifics_type
, GetModelType());
221 // Preserve unknown fields.
222 const sync_pb::EntitySpecifics
& old_specifics
= entry_
->GetSpecifics();
223 sync_pb::EntitySpecifics new_specifics
;
224 new_specifics
.CopyFrom(new_value
);
225 new_specifics
.mutable_unknown_fields()->MergeFrom(
226 old_specifics
.unknown_fields());
228 // Will update the entry if encryption was necessary.
229 if (!UpdateEntryWithEncryption(GetTransaction()->GetWrappedTrans(),
234 if (entry_
->GetSpecifics().has_encrypted()) {
235 // EncryptIfNecessary already updated the entry for us and marked for
236 // syncing if it was needed. Now we just make a copy of the unencrypted
237 // specifics so that if this node is updated, we do not have to decrypt the
238 // old data. Note that this only modifies the node's local data, not the
240 SetUnencryptedSpecifics(new_value
);
243 DCHECK_EQ(new_specifics_type
, GetModelType());
246 void WriteNode::ResetFromSpecifics() {
247 SetEntitySpecifics(GetEntitySpecifics());
250 void WriteNode::SetTypedUrlSpecifics(
251 const sync_pb::TypedUrlSpecifics
& new_value
) {
252 sync_pb::EntitySpecifics entity_specifics
;
253 entity_specifics
.mutable_typed_url()->CopyFrom(new_value
);
254 SetEntitySpecifics(entity_specifics
);
257 void WriteNode::SetExtensionSpecifics(
258 const sync_pb::ExtensionSpecifics
& new_value
) {
259 sync_pb::EntitySpecifics entity_specifics
;
260 entity_specifics
.mutable_extension()->CopyFrom(new_value
);
261 SetEntitySpecifics(entity_specifics
);
264 void WriteNode::SetExternalId(int64 id
) {
265 if (GetExternalId() != id
)
266 entry_
->PutLocalExternalId(id
);
269 WriteNode::WriteNode(WriteTransaction
* transaction
)
270 : entry_(NULL
), transaction_(transaction
) {
274 WriteNode::~WriteNode() {
278 // Find an existing node matching the ID |id|, and bind this WriteNode to it.
279 // Return true on success.
280 BaseNode::InitByLookupResult
WriteNode::InitByIdLookup(int64 id
) {
281 DCHECK(!entry_
) << "Init called twice";
282 DCHECK_NE(id
, kInvalidId
);
283 entry_
= new syncable::MutableEntry(transaction_
->GetWrappedWriteTrans(),
284 syncable::GET_BY_HANDLE
, id
);
286 return INIT_FAILED_ENTRY_NOT_GOOD
;
287 if (entry_
->GetIsDel())
288 return INIT_FAILED_ENTRY_IS_DEL
;
289 return DecryptIfNecessary() ? INIT_OK
: INIT_FAILED_DECRYPT_IF_NECESSARY
;
292 // Find a node by client tag, and bind this WriteNode to it.
293 // Return true if the write node was found, and was not deleted.
294 // Undeleting a deleted node is possible by ClientTag.
295 BaseNode::InitByLookupResult
WriteNode::InitByClientTagLookup(
296 ModelType model_type
,
297 const std::string
& tag
) {
298 DCHECK(!entry_
) << "Init called twice";
300 return INIT_FAILED_PRECONDITION
;
302 const std::string hash
= syncable::GenerateSyncableHash(model_type
, tag
);
304 entry_
= new syncable::MutableEntry(transaction_
->GetWrappedWriteTrans(),
305 syncable::GET_BY_CLIENT_TAG
, hash
);
307 return INIT_FAILED_ENTRY_NOT_GOOD
;
308 if (entry_
->GetIsDel())
309 return INIT_FAILED_ENTRY_IS_DEL
;
310 return DecryptIfNecessary() ? INIT_OK
: INIT_FAILED_DECRYPT_IF_NECESSARY
;
313 BaseNode::InitByLookupResult
WriteNode::InitTypeRoot(ModelType type
) {
314 DCHECK(!entry_
) << "Init called twice";
315 if (!IsRealDataType(type
))
316 return INIT_FAILED_PRECONDITION
;
317 entry_
= new syncable::MutableEntry(transaction_
->GetWrappedWriteTrans(),
318 syncable::GET_TYPE_ROOT
, type
);
320 return INIT_FAILED_ENTRY_NOT_GOOD
;
321 if (entry_
->GetIsDel())
322 return INIT_FAILED_ENTRY_IS_DEL
;
323 ModelType model_type
= GetModelType();
324 DCHECK_EQ(model_type
, NIGORI
);
328 // Create a new node with default properties, and bind this WriteNode to it.
329 // Return true on success.
330 bool WriteNode::InitBookmarkByCreation(const BaseNode
& parent
,
331 const BaseNode
* predecessor
) {
332 DCHECK(!entry_
) << "Init called twice";
333 // |predecessor| must be a child of |parent| or NULL.
334 if (predecessor
&& predecessor
->GetParentId() != parent
.GetId()) {
339 syncable::Id parent_id
= parent
.GetEntry()->GetId();
340 DCHECK(!parent_id
.IsNull());
342 // Start out with a dummy name. We expect
343 // the caller to set a meaningful name after creation.
344 string
dummy(kDefaultNameForNewNodes
);
346 entry_
= new syncable::MutableEntry(transaction_
->GetWrappedWriteTrans(),
347 syncable::CREATE
, BOOKMARKS
,
353 // Entries are untitled folders by default.
354 entry_
->PutIsDir(true);
356 if (!PutPredecessor(predecessor
)) {
360 // Mark this entry as unsynced, to wake up the syncer.
365 WriteNode::InitUniqueByCreationResult
WriteNode::InitUniqueByCreation(
366 ModelType model_type
,
367 const BaseNode
& parent
,
368 const std::string
& tag
) {
369 return InitUniqueByCreationImpl(model_type
, parent
.GetEntry()->GetId(), tag
);
372 WriteNode::InitUniqueByCreationResult
WriteNode::InitUniqueByCreation(
373 ModelType model_type
,
374 const std::string
& tag
) {
375 return InitUniqueByCreationImpl(model_type
, syncable::Id(), tag
);
378 // Create a new node with default properties and a client defined unique tag,
379 // and bind this WriteNode to it.
380 // Return true on success. If the tag exists in the database, then
381 // we will attempt to undelete the node.
382 // TODO(chron): Code datatype into hash tag.
383 // TODO(chron): Is model type ever lost?
384 WriteNode::InitUniqueByCreationResult
WriteNode::InitUniqueByCreationImpl(
385 ModelType model_type
,
386 const syncable::Id
& parent_id
,
387 const std::string
& tag
) {
388 // This DCHECK will only fail if init is called twice.
391 LOG(WARNING
) << "InitUniqueByCreation failed due to empty tag.";
392 return INIT_FAILED_EMPTY_TAG
;
395 const std::string hash
= syncable::GenerateSyncableHash(model_type
, tag
);
397 // Start out with a dummy name. We expect
398 // the caller to set a meaningful name after creation.
399 string
dummy(kDefaultNameForNewNodes
);
401 // Check if we have this locally and need to undelete it.
402 scoped_ptr
<syncable::MutableEntry
> existing_entry(
403 new syncable::MutableEntry(transaction_
->GetWrappedWriteTrans(),
404 syncable::GET_BY_CLIENT_TAG
, hash
));
406 if (existing_entry
->good()) {
407 if (existing_entry
->GetIsDel()) {
408 // Rules for undelete:
409 // BASE_VERSION: Must keep the same.
410 // ID: Essential to keep the same.
411 // META_HANDLE: Must be the same, so we can't "split" the entry.
412 // IS_DEL: Must be set to false, will cause reindexing.
413 // This one is weird because IS_DEL is true for "update only"
414 // items. It should be OK to undelete an update only.
415 // MTIME/CTIME: Seems reasonable to just leave them alone.
416 // IS_UNSYNCED: Must set this to true or face database insurrection.
417 // We do this below this block.
418 // IS_UNAPPLIED_UPDATE: Either keep it the same or also set BASE_VERSION
419 // to SERVER_VERSION. We keep it the same here.
420 // IS_DIR: We'll leave it the same.
421 // SPECIFICS: Reset it.
423 existing_entry
->PutIsDel(false);
425 // Client tags are immutable and must be paired with the ID.
426 // If a server update comes down with an ID and client tag combo,
427 // and it already exists, always overwrite it and store only one copy.
428 // We have to undelete entries because we can't disassociate IDs from
431 existing_entry
->PutNonUniqueName(dummy
);
432 existing_entry
->PutParentId(parent_id
);
434 // Put specifics to handle the case where this is not actually an
435 // undeletion, but instead a collision with a newly downloaded,
436 // processed, and unapplied server update. This is a fix for
437 // http://crbug.com/397766.
438 sync_pb::EntitySpecifics specifics
;
439 AddDefaultFieldValue(model_type
, &specifics
);
440 existing_entry
->PutSpecifics(specifics
);
442 entry_
= existing_entry
.release();
444 return INIT_FAILED_ENTRY_ALREADY_EXISTS
;
447 entry_
= new syncable::MutableEntry(transaction_
->GetWrappedWriteTrans(),
449 model_type
, parent_id
, dummy
);
451 return INIT_FAILED_COULD_NOT_CREATE_ENTRY
;
453 // Only set IS_DIR for new entries. Don't bitflip undeleted ones.
454 entry_
->PutUniqueClientTag(hash
);
457 // We don't support directory and tag combinations.
458 entry_
->PutIsDir(false);
460 if (!parent_id
.IsNull()) {
461 if (!PutPredecessor(NULL
))
462 return INIT_FAILED_SET_PREDECESSOR
;
465 // Mark this entry as unsynced, to wake up the syncer.
471 bool WriteNode::SetPosition(const BaseNode
& new_parent
,
472 const BaseNode
* predecessor
) {
473 // |predecessor| must be a child of |new_parent| or NULL.
474 if (predecessor
&& predecessor
->GetParentId() != new_parent
.GetId()) {
479 syncable::Id new_parent_id
= new_parent
.GetEntry()->GetId();
480 DCHECK(!new_parent_id
.IsNull());
482 // Filter out redundant changes if both the parent and the predecessor match.
483 if (new_parent_id
== entry_
->GetParentId()) {
484 const syncable::Id
& old
= entry_
->GetPredecessorId();
485 if ((!predecessor
&& old
.IsNull()) ||
486 (predecessor
&& (old
== predecessor
->GetEntry()->GetId()))) {
491 entry_
->PutParentId(new_parent_id
);
493 if (!PutPredecessor(predecessor
)) {
497 // Mark this entry as unsynced, to wake up the syncer.
502 void WriteNode::SetAttachmentMetadata(
503 const sync_pb::AttachmentMetadata
& attachment_metadata
) {
504 entry_
->PutAttachmentMetadata(attachment_metadata
);
507 const syncable::Entry
* WriteNode::GetEntry() const {
511 const BaseTransaction
* WriteNode::GetTransaction() const {
515 syncable::MutableEntry
* WriteNode::GetMutableEntryForTest() {
519 void WriteNode::Tombstone() {
520 // These lines must be in this order. The call to Put(IS_DEL) might choose to
521 // unset the IS_UNSYNCED bit if the item was not known to the server at the
522 // time of deletion. It's important that the bit not be reset in that case.
524 entry_
->PutIsDel(true);
527 void WriteNode::Drop() {
528 if (entry_
->GetId().ServerKnows()) {
529 entry_
->PutIsDel(true);
533 bool WriteNode::PutPredecessor(const BaseNode
* predecessor
) {
534 DCHECK(!entry_
->GetParentId().IsNull());
535 syncable::Id predecessor_id
= predecessor
?
536 predecessor
->GetEntry()->GetId() : syncable::Id();
537 return entry_
->PutPredecessor(predecessor_id
);
540 void WriteNode::MarkForSyncing() {
541 syncable::MarkForSyncing(entry_
);
544 } // namespace syncer