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.
4
5 #include "sync/internal_api/public/write_node.h"
6
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"
25
26 using std::string;
27 using std::vector;
28
29 namespace syncer {
30
31 using syncable::kEncryptedString;
32 using syncable::SPECIFICS;
33
34 static const char kDefaultNameForNewNodes[] = " ";
35
SetIsFolder(bool folder)36 void WriteNode::SetIsFolder(bool folder) {
37 if (entry_->GetIsDir() == folder)
38 return; // Skip redundant changes.
39
40 entry_->PutIsDir(folder);
41 MarkForSyncing();
42 }
43
SetTitle(const std::string & title)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();
51
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;
59 } else {
60 DCHECK(base::IsStringUTF8(title));
61 SyncAPINameToServerName(title, &new_legal_title);
62 base::TruncateUTF8ToByteSize(new_legal_title, 255, &new_legal_title);
63 }
64
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();
70 } else {
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();
75 }
76
77 bool title_matches = (current_legal_title == new_legal_title);
78 bool encrypted_without_overwriting_name = (needs_encryption &&
79 entry_->GetNonUniqueName() != kEncryptedString);
80
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.";
85 return;
86 }
87
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.
94 }
95
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);
102 else
103 entry_->PutNonUniqueName(new_legal_title);
104
105 DVLOG(1) << "Overwriting title of type "
106 << ModelTypeToString(type)
107 << " and marking for syncing.";
108 MarkForSyncing();
109 }
110
SetAppSpecifics(const sync_pb::AppSpecifics & new_value)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);
116 }
117
SetAutofillSpecifics(const sync_pb::AutofillSpecifics & new_value)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);
123 }
124
SetAutofillProfileSpecifics(const sync_pb::AutofillProfileSpecifics & new_value)125 void WriteNode::SetAutofillProfileSpecifics(
126 const sync_pb::AutofillProfileSpecifics& new_value) {
127 sync_pb::EntitySpecifics entity_specifics;
128 entity_specifics.mutable_autofill_profile()->
129 CopyFrom(new_value);
130 SetEntitySpecifics(entity_specifics);
131 }
132
SetBookmarkSpecifics(const sync_pb::BookmarkSpecifics & new_value)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);
138 }
139
SetNigoriSpecifics(const sync_pb::NigoriSpecifics & new_value)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);
145 }
146
SetPasswordSpecifics(const sync_pb::PasswordSpecificsData & data)147 void WriteNode::SetPasswordSpecifics(
148 const sync_pb::PasswordSpecificsData& data) {
149 DCHECK_EQ(GetModelType(), PASSWORDS);
150
151 Cryptographer* cryptographer = GetTransaction()->GetCryptographer();
152
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);
161 } else {
162 AddDefaultFieldValue(PASSWORDS, &entity_specifics);
163 }
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 "
170 << "corruption";
171 return;
172 }
173 SetEntitySpecifics(entity_specifics);
174 }
175
SetThemeSpecifics(const sync_pb::ThemeSpecifics & new_value)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);
181 }
182
SetSessionSpecifics(const sync_pb::SessionSpecifics & new_value)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);
188 }
189
SetDeviceInfoSpecifics(const sync_pb::DeviceInfoSpecifics & new_value)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);
195 }
196
SetExperimentsSpecifics(const sync_pb::ExperimentsSpecifics & new_value)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);
202 }
203
SetPriorityPreferenceSpecifics(const sync_pb::PriorityPreferenceSpecifics & new_value)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);
209 }
210
SetEntitySpecifics(const sync_pb::EntitySpecifics & new_value)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());
220
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());
227
228 // Will update the entry if encryption was necessary.
229 if (!UpdateEntryWithEncryption(GetTransaction()->GetWrappedTrans(),
230 new_specifics,
231 entry_)) {
232 return;
233 }
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
239 // entry itself.
240 SetUnencryptedSpecifics(new_value);
241 }
242
243 DCHECK_EQ(new_specifics_type, GetModelType());
244 }
245
ResetFromSpecifics()246 void WriteNode::ResetFromSpecifics() {
247 SetEntitySpecifics(GetEntitySpecifics());
248 }
249
SetTypedUrlSpecifics(const sync_pb::TypedUrlSpecifics & new_value)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);
255 }
256
SetExtensionSpecifics(const sync_pb::ExtensionSpecifics & new_value)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);
262 }
263
SetExternalId(int64 id)264 void WriteNode::SetExternalId(int64 id) {
265 if (GetExternalId() != id)
266 entry_->PutLocalExternalId(id);
267 }
268
WriteNode(WriteTransaction * transaction)269 WriteNode::WriteNode(WriteTransaction* transaction)
270 : entry_(NULL), transaction_(transaction) {
271 DCHECK(transaction);
272 }
273
~WriteNode()274 WriteNode::~WriteNode() {
275 delete entry_;
276 }
277
278 // Find an existing node matching the ID |id|, and bind this WriteNode to it.
279 // Return true on success.
InitByIdLookup(int64 id)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);
285 if (!entry_->good())
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;
290 }
291
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.
InitByClientTagLookup(ModelType model_type,const std::string & tag)295 BaseNode::InitByLookupResult WriteNode::InitByClientTagLookup(
296 ModelType model_type,
297 const std::string& tag) {
298 DCHECK(!entry_) << "Init called twice";
299 if (tag.empty())
300 return INIT_FAILED_PRECONDITION;
301
302 const std::string hash = syncable::GenerateSyncableHash(model_type, tag);
303
304 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
305 syncable::GET_BY_CLIENT_TAG, hash);
306 if (!entry_->good())
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;
311 }
312
InitTypeRoot(ModelType type)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);
319 if (!entry_->good())
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);
325 return INIT_OK;
326 }
327
328 // Create a new node with default properties, and bind this WriteNode to it.
329 // Return true on success.
InitBookmarkByCreation(const BaseNode & parent,const BaseNode * predecessor)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()) {
335 DCHECK(false);
336 return false;
337 }
338
339 syncable::Id parent_id = parent.GetEntry()->GetId();
340
341 // Start out with a dummy name. We expect
342 // the caller to set a meaningful name after creation.
343 string dummy(kDefaultNameForNewNodes);
344
345 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
346 syncable::CREATE, BOOKMARKS,
347 parent_id, dummy);
348
349 if (!entry_->good())
350 return false;
351
352 // Entries are untitled folders by default.
353 entry_->PutIsDir(true);
354
355 // Now set the predecessor, which sets IS_UNSYNCED as necessary.
356 return PutPredecessor(predecessor);
357 }
358
359 // Create a new node with default properties and a client defined unique tag,
360 // and bind this WriteNode to it.
361 // Return true on success. If the tag exists in the database, then
362 // we will attempt to undelete the node.
363 // TODO(chron): Code datatype into hash tag.
364 // TODO(chron): Is model type ever lost?
InitUniqueByCreation(ModelType model_type,const BaseNode & parent,const std::string & tag)365 WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreation(
366 ModelType model_type,
367 const BaseNode& parent,
368 const std::string& tag) {
369 // This DCHECK will only fail if init is called twice.
370 DCHECK(!entry_);
371 if (tag.empty()) {
372 LOG(WARNING) << "InitUniqueByCreation failed due to empty tag.";
373 return INIT_FAILED_EMPTY_TAG;
374 }
375
376 const std::string hash = syncable::GenerateSyncableHash(model_type, tag);
377
378 syncable::Id parent_id = parent.GetEntry()->GetId();
379
380 // Start out with a dummy name. We expect
381 // the caller to set a meaningful name after creation.
382 string dummy(kDefaultNameForNewNodes);
383
384 // Check if we have this locally and need to undelete it.
385 scoped_ptr<syncable::MutableEntry> existing_entry(
386 new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
387 syncable::GET_BY_CLIENT_TAG, hash));
388
389 if (existing_entry->good()) {
390 if (existing_entry->GetIsDel()) {
391 // Rules for undelete:
392 // BASE_VERSION: Must keep the same.
393 // ID: Essential to keep the same.
394 // META_HANDLE: Must be the same, so we can't "split" the entry.
395 // IS_DEL: Must be set to false, will cause reindexing.
396 // This one is weird because IS_DEL is true for "update only"
397 // items. It should be OK to undelete an update only.
398 // MTIME/CTIME: Seems reasonable to just leave them alone.
399 // IS_UNSYNCED: Must set this to true or face database insurrection.
400 // We do this below this block.
401 // IS_UNAPPLIED_UPDATE: Either keep it the same or also set BASE_VERSION
402 // to SERVER_VERSION. We keep it the same here.
403 // IS_DIR: We'll leave it the same.
404 // SPECIFICS: Reset it.
405
406 existing_entry->PutIsDel(false);
407
408 // Client tags are immutable and must be paired with the ID.
409 // If a server update comes down with an ID and client tag combo,
410 // and it already exists, always overwrite it and store only one copy.
411 // We have to undelete entries because we can't disassociate IDs from
412 // tags and updates.
413
414 existing_entry->PutNonUniqueName(dummy);
415 existing_entry->PutParentId(parent_id);
416
417 // Put specifics to handle the case where this is not actually an
418 // undeletion, but instead a collision with a newly downloaded,
419 // processed, and unapplied server update. This is a fix for
420 // http://crbug.com/397766.
421 sync_pb::EntitySpecifics specifics;
422 AddDefaultFieldValue(model_type, &specifics);
423 existing_entry->PutSpecifics(specifics);
424
425 entry_ = existing_entry.release();
426 } else {
427 return INIT_FAILED_ENTRY_ALREADY_EXISTS;
428 }
429 } else {
430 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
431 syncable::CREATE,
432 model_type, parent_id, dummy);
433 if (!entry_->good())
434 return INIT_FAILED_COULD_NOT_CREATE_ENTRY;
435
436 // Only set IS_DIR for new entries. Don't bitflip undeleted ones.
437 entry_->PutUniqueClientTag(hash);
438 }
439
440 // We don't support directory and tag combinations.
441 entry_->PutIsDir(false);
442
443 // Now set the predecessor, which sets IS_UNSYNCED as necessary.
444 bool success = PutPredecessor(NULL);
445 if (!success)
446 return INIT_FAILED_SET_PREDECESSOR;
447
448 return INIT_SUCCESS;
449 }
450
SetPosition(const BaseNode & new_parent,const BaseNode * predecessor)451 bool WriteNode::SetPosition(const BaseNode& new_parent,
452 const BaseNode* predecessor) {
453 // |predecessor| must be a child of |new_parent| or NULL.
454 if (predecessor && predecessor->GetParentId() != new_parent.GetId()) {
455 DCHECK(false);
456 return false;
457 }
458
459 syncable::Id new_parent_id = new_parent.GetEntry()->GetId();
460
461 // Filter out redundant changes if both the parent and the predecessor match.
462 if (new_parent_id == entry_->GetParentId()) {
463 const syncable::Id& old = entry_->GetPredecessorId();
464 if ((!predecessor && old.IsRoot()) ||
465 (predecessor && (old == predecessor->GetEntry()->GetId()))) {
466 return true;
467 }
468 }
469
470 entry_->PutParentId(new_parent_id);
471
472 // Now set the predecessor, which sets IS_UNSYNCED as necessary.
473 return PutPredecessor(predecessor);
474 }
475
SetAttachmentMetadata(const sync_pb::AttachmentMetadata & attachment_metadata)476 void WriteNode::SetAttachmentMetadata(
477 const sync_pb::AttachmentMetadata& attachment_metadata) {
478 entry_->PutAttachmentMetadata(attachment_metadata);
479 }
480
GetEntry() const481 const syncable::Entry* WriteNode::GetEntry() const {
482 return entry_;
483 }
484
GetTransaction() const485 const BaseTransaction* WriteNode::GetTransaction() const {
486 return transaction_;
487 }
488
GetMutableEntryForTest()489 syncable::MutableEntry* WriteNode::GetMutableEntryForTest() {
490 return entry_;
491 }
492
Tombstone()493 void WriteNode::Tombstone() {
494 // These lines must be in this order. The call to Put(IS_DEL) might choose to
495 // unset the IS_UNSYNCED bit if the item was not known to the server at the
496 // time of deletion. It's important that the bit not be reset in that case.
497 MarkForSyncing();
498 entry_->PutIsDel(true);
499 }
500
Drop()501 void WriteNode::Drop() {
502 if (entry_->GetId().ServerKnows()) {
503 entry_->PutIsDel(true);
504 }
505 }
506
PutPredecessor(const BaseNode * predecessor)507 bool WriteNode::PutPredecessor(const BaseNode* predecessor) {
508 syncable::Id predecessor_id = predecessor ?
509 predecessor->GetEntry()->GetId() : syncable::Id();
510 if (!entry_->PutPredecessor(predecessor_id))
511 return false;
512 // Mark this entry as unsynced, to wake up the syncer.
513 MarkForSyncing();
514
515 return true;
516 }
517
MarkForSyncing()518 void WriteNode::MarkForSyncing() {
519 syncable::MarkForSyncing(entry_);
520 }
521
522 } // namespace syncer
523