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
26using std::string;
27using std::vector;
28
29namespace syncer {
30
31using syncable::kEncryptedString;
32using syncable::SPECIFICS;
33
34static const char kDefaultNameForNewNodes[] = " ";
35
36void WriteNode::SetIsFolder(bool folder) {
37  if (entry_->GetIsDir() == folder)
38    return;  // Skip redundant changes.
39
40  entry_->PutIsDir(folder);
41  MarkForSyncing();
42}
43
44void WriteNode::SetTitle(const std::wstring& 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    SyncAPINameToServerName(WideToUTF8(title), &new_legal_title);
61    base::TruncateUTF8ToByteSize(new_legal_title, 255, &new_legal_title);
62  }
63
64  std::string current_legal_title;
65  if (BOOKMARKS == type &&
66      entry_->GetSpecifics().has_encrypted()) {
67    // Encrypted bookmarks only have their title in the unencrypted specifics.
68    current_legal_title = GetBookmarkSpecifics().title();
69  } else {
70    // Non-bookmarks and legacy bookmarks (those with no title in their
71    // specifics) store their title in NON_UNIQUE_NAME. Non-legacy bookmarks
72    // store their title in specifics as well as NON_UNIQUE_NAME.
73    current_legal_title = entry_->GetNonUniqueName();
74  }
75
76  bool title_matches = (current_legal_title == new_legal_title);
77  bool encrypted_without_overwriting_name = (needs_encryption &&
78      entry_->GetNonUniqueName() != kEncryptedString);
79
80  // If the title matches and the NON_UNIQUE_NAME is properly overwritten as
81  // necessary, nothing needs to change.
82  if (title_matches && !encrypted_without_overwriting_name) {
83    DVLOG(2) << "Title matches, dropping change.";
84    return;
85  }
86
87  // For bookmarks, we also set the title field in the specifics.
88  // TODO(zea): refactor bookmarks to not need this functionality.
89  if (GetModelType() == BOOKMARKS) {
90    sync_pb::EntitySpecifics specifics = GetEntitySpecifics();
91    specifics.mutable_bookmark()->set_title(new_legal_title);
92    SetEntitySpecifics(specifics);  // Does it's own encryption checking.
93  }
94
95  // For bookmarks, this has to happen after we set the title in the specifics,
96  // because the presence of a title in the NON_UNIQUE_NAME is what controls
97  // the logic deciding whether this is an empty node or a legacy bookmark.
98  // See BaseNode::GetUnencryptedSpecific(..).
99  if (needs_encryption)
100    entry_->PutNonUniqueName(kEncryptedString);
101  else
102    entry_->PutNonUniqueName(new_legal_title);
103
104  DVLOG(1) << "Overwriting title of type "
105           << ModelTypeToString(type)
106           << " and marking for syncing.";
107  MarkForSyncing();
108}
109
110void WriteNode::SetAppSpecifics(
111    const sync_pb::AppSpecifics& new_value) {
112  sync_pb::EntitySpecifics entity_specifics;
113  entity_specifics.mutable_app()->CopyFrom(new_value);
114  SetEntitySpecifics(entity_specifics);
115}
116
117void WriteNode::SetAutofillSpecifics(
118    const sync_pb::AutofillSpecifics& new_value) {
119  sync_pb::EntitySpecifics entity_specifics;
120  entity_specifics.mutable_autofill()->CopyFrom(new_value);
121  SetEntitySpecifics(entity_specifics);
122}
123
124void WriteNode::SetAutofillProfileSpecifics(
125    const sync_pb::AutofillProfileSpecifics& new_value) {
126  sync_pb::EntitySpecifics entity_specifics;
127  entity_specifics.mutable_autofill_profile()->
128      CopyFrom(new_value);
129  SetEntitySpecifics(entity_specifics);
130}
131
132void WriteNode::SetBookmarkSpecifics(
133    const sync_pb::BookmarkSpecifics& new_value) {
134  sync_pb::EntitySpecifics entity_specifics;
135  entity_specifics.mutable_bookmark()->CopyFrom(new_value);
136  SetEntitySpecifics(entity_specifics);
137}
138
139void WriteNode::SetNigoriSpecifics(
140    const sync_pb::NigoriSpecifics& new_value) {
141  sync_pb::EntitySpecifics entity_specifics;
142  entity_specifics.mutable_nigori()->CopyFrom(new_value);
143  SetEntitySpecifics(entity_specifics);
144}
145
146void WriteNode::SetPasswordSpecifics(
147    const sync_pb::PasswordSpecificsData& data) {
148  DCHECK_EQ(GetModelType(), PASSWORDS);
149
150  Cryptographer* cryptographer = GetTransaction()->GetCryptographer();
151
152  // We have to do the idempotency check here (vs in UpdateEntryWithEncryption)
153  // because Passwords have their encrypted data within the PasswordSpecifics,
154  // vs within the EntitySpecifics like all the other types.
155  const sync_pb::EntitySpecifics& old_specifics = GetEntry()->GetSpecifics();
156  sync_pb::EntitySpecifics entity_specifics;
157  // Copy over the old specifics if they exist.
158  if (GetModelTypeFromSpecifics(old_specifics) == PASSWORDS) {
159    entity_specifics.CopyFrom(old_specifics);
160  } else {
161    AddDefaultFieldValue(PASSWORDS, &entity_specifics);
162  }
163  sync_pb::PasswordSpecifics* password_specifics =
164      entity_specifics.mutable_password();
165  // This will only update password_specifics if the underlying unencrypted blob
166  // was different from |data| or was not encrypted with the proper passphrase.
167  if (!cryptographer->Encrypt(data, password_specifics->mutable_encrypted())) {
168    NOTREACHED() << "Failed to encrypt password, possibly due to sync node "
169                 << "corruption";
170    return;
171  }
172  SetEntitySpecifics(entity_specifics);
173}
174
175void WriteNode::SetThemeSpecifics(
176    const sync_pb::ThemeSpecifics& new_value) {
177  sync_pb::EntitySpecifics entity_specifics;
178  entity_specifics.mutable_theme()->CopyFrom(new_value);
179  SetEntitySpecifics(entity_specifics);
180}
181
182void WriteNode::SetSessionSpecifics(
183    const sync_pb::SessionSpecifics& new_value) {
184  sync_pb::EntitySpecifics entity_specifics;
185  entity_specifics.mutable_session()->CopyFrom(new_value);
186  SetEntitySpecifics(entity_specifics);
187}
188
189void WriteNode::SetManagedUserSettingSpecifics(
190    const sync_pb::ManagedUserSettingSpecifics& new_value) {
191  sync_pb::EntitySpecifics entity_specifics;
192  entity_specifics.mutable_managed_user_setting()->CopyFrom(new_value);
193  SetEntitySpecifics(entity_specifics);
194}
195
196void WriteNode::SetManagedUserSpecifics(
197    const sync_pb::ManagedUserSpecifics& new_value) {
198  sync_pb::EntitySpecifics entity_specifics;
199  entity_specifics.mutable_managed_user()->CopyFrom(new_value);
200  SetEntitySpecifics(entity_specifics);
201}
202
203void WriteNode::SetDeviceInfoSpecifics(
204    const sync_pb::DeviceInfoSpecifics& new_value) {
205  sync_pb::EntitySpecifics entity_specifics;
206  entity_specifics.mutable_device_info()->CopyFrom(new_value);
207  SetEntitySpecifics(entity_specifics);
208}
209
210void WriteNode::SetExperimentsSpecifics(
211    const sync_pb::ExperimentsSpecifics& new_value) {
212  sync_pb::EntitySpecifics entity_specifics;
213  entity_specifics.mutable_experiments()->CopyFrom(new_value);
214  SetEntitySpecifics(entity_specifics);
215}
216
217void WriteNode::SetPriorityPreferenceSpecifics(
218    const sync_pb::PriorityPreferenceSpecifics& new_value) {
219  sync_pb::EntitySpecifics entity_specifics;
220  entity_specifics.mutable_priority_preference()->CopyFrom(new_value);
221  SetEntitySpecifics(entity_specifics);
222}
223
224void WriteNode::SetEntitySpecifics(
225    const sync_pb::EntitySpecifics& new_value) {
226  ModelType new_specifics_type =
227      GetModelTypeFromSpecifics(new_value);
228  CHECK(!new_value.password().has_client_only_encrypted_data());
229  DCHECK_NE(new_specifics_type, UNSPECIFIED);
230  DVLOG(1) << "Writing entity specifics of type "
231           << ModelTypeToString(new_specifics_type);
232  DCHECK_EQ(new_specifics_type, GetModelType());
233
234  // Preserve unknown fields.
235  const sync_pb::EntitySpecifics& old_specifics = entry_->GetSpecifics();
236  sync_pb::EntitySpecifics new_specifics;
237  new_specifics.CopyFrom(new_value);
238  new_specifics.mutable_unknown_fields()->MergeFrom(
239      old_specifics.unknown_fields());
240
241  // Will update the entry if encryption was necessary.
242  if (!UpdateEntryWithEncryption(GetTransaction()->GetWrappedTrans(),
243                                 new_specifics,
244                                 entry_)) {
245    return;
246  }
247  if (entry_->GetSpecifics().has_encrypted()) {
248    // EncryptIfNecessary already updated the entry for us and marked for
249    // syncing if it was needed. Now we just make a copy of the unencrypted
250    // specifics so that if this node is updated, we do not have to decrypt the
251    // old data. Note that this only modifies the node's local data, not the
252    // entry itself.
253    SetUnencryptedSpecifics(new_value);
254  }
255
256  DCHECK_EQ(new_specifics_type, GetModelType());
257}
258
259void WriteNode::ResetFromSpecifics() {
260  SetEntitySpecifics(GetEntitySpecifics());
261}
262
263void WriteNode::SetTypedUrlSpecifics(
264    const sync_pb::TypedUrlSpecifics& new_value) {
265  sync_pb::EntitySpecifics entity_specifics;
266  entity_specifics.mutable_typed_url()->CopyFrom(new_value);
267  SetEntitySpecifics(entity_specifics);
268}
269
270void WriteNode::SetExtensionSpecifics(
271    const sync_pb::ExtensionSpecifics& new_value) {
272  sync_pb::EntitySpecifics entity_specifics;
273  entity_specifics.mutable_extension()->CopyFrom(new_value);
274  SetEntitySpecifics(entity_specifics);
275}
276
277void WriteNode::SetExternalId(int64 id) {
278  if (GetExternalId() != id)
279    entry_->PutLocalExternalId(id);
280}
281
282WriteNode::WriteNode(WriteTransaction* transaction)
283    : entry_(NULL), transaction_(transaction) {
284  DCHECK(transaction);
285}
286
287WriteNode::~WriteNode() {
288  delete entry_;
289}
290
291// Find an existing node matching the ID |id|, and bind this WriteNode to it.
292// Return true on success.
293BaseNode::InitByLookupResult WriteNode::InitByIdLookup(int64 id) {
294  DCHECK(!entry_) << "Init called twice";
295  DCHECK_NE(id, kInvalidId);
296  entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
297                                      syncable::GET_BY_HANDLE, id);
298  if (!entry_->good())
299    return INIT_FAILED_ENTRY_NOT_GOOD;
300  if (entry_->GetIsDel())
301    return INIT_FAILED_ENTRY_IS_DEL;
302  return DecryptIfNecessary() ? INIT_OK : INIT_FAILED_DECRYPT_IF_NECESSARY;
303}
304
305// Find a node by client tag, and bind this WriteNode to it.
306// Return true if the write node was found, and was not deleted.
307// Undeleting a deleted node is possible by ClientTag.
308BaseNode::InitByLookupResult WriteNode::InitByClientTagLookup(
309    ModelType model_type,
310    const std::string& tag) {
311  DCHECK(!entry_) << "Init called twice";
312  if (tag.empty())
313    return INIT_FAILED_PRECONDITION;
314
315  const std::string hash = syncable::GenerateSyncableHash(model_type, tag);
316
317  entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
318                                      syncable::GET_BY_CLIENT_TAG, hash);
319  if (!entry_->good())
320    return INIT_FAILED_ENTRY_NOT_GOOD;
321  if (entry_->GetIsDel())
322    return INIT_FAILED_ENTRY_IS_DEL;
323  return DecryptIfNecessary() ? INIT_OK : INIT_FAILED_DECRYPT_IF_NECESSARY;
324}
325
326BaseNode::InitByLookupResult WriteNode::InitByTagLookup(
327    const std::string& tag) {
328  DCHECK(!entry_) << "Init called twice";
329  if (tag.empty())
330    return INIT_FAILED_PRECONDITION;
331  entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
332                                      syncable::GET_BY_SERVER_TAG, tag);
333  if (!entry_->good())
334    return INIT_FAILED_ENTRY_NOT_GOOD;
335  if (entry_->GetIsDel())
336    return INIT_FAILED_ENTRY_IS_DEL;
337  ModelType model_type = GetModelType();
338  DCHECK_EQ(model_type, NIGORI);
339  return INIT_OK;
340}
341
342// Create a new node with default properties, and bind this WriteNode to it.
343// Return true on success.
344bool WriteNode::InitBookmarkByCreation(const BaseNode& parent,
345                                       const BaseNode* predecessor) {
346  DCHECK(!entry_) << "Init called twice";
347  // |predecessor| must be a child of |parent| or NULL.
348  if (predecessor && predecessor->GetParentId() != parent.GetId()) {
349    DCHECK(false);
350    return false;
351  }
352
353  syncable::Id parent_id = parent.GetEntry()->GetId();
354
355  // Start out with a dummy name.  We expect
356  // the caller to set a meaningful name after creation.
357  string dummy(kDefaultNameForNewNodes);
358
359  entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
360                                      syncable::CREATE, BOOKMARKS,
361                                      parent_id, dummy);
362
363  if (!entry_->good())
364    return false;
365
366  // Entries are untitled folders by default.
367  entry_->PutIsDir(true);
368
369  // Now set the predecessor, which sets IS_UNSYNCED as necessary.
370  return PutPredecessor(predecessor);
371}
372
373// Create a new node with default properties and a client defined unique tag,
374// and bind this WriteNode to it.
375// Return true on success. If the tag exists in the database, then
376// we will attempt to undelete the node.
377// TODO(chron): Code datatype into hash tag.
378// TODO(chron): Is model type ever lost?
379WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreation(
380    ModelType model_type,
381    const BaseNode& parent,
382    const std::string& tag) {
383  // This DCHECK will only fail if init is called twice.
384  DCHECK(!entry_);
385  if (tag.empty()) {
386    LOG(WARNING) << "InitUniqueByCreation failed due to empty tag.";
387    return INIT_FAILED_EMPTY_TAG;
388  }
389
390  const std::string hash = syncable::GenerateSyncableHash(model_type, tag);
391
392  syncable::Id parent_id = parent.GetEntry()->GetId();
393
394  // Start out with a dummy name.  We expect
395  // the caller to set a meaningful name after creation.
396  string dummy(kDefaultNameForNewNodes);
397
398  // Check if we have this locally and need to undelete it.
399  scoped_ptr<syncable::MutableEntry> existing_entry(
400      new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
401                                 syncable::GET_BY_CLIENT_TAG, hash));
402
403  if (existing_entry->good()) {
404    if (existing_entry->GetIsDel()) {
405      // Rules for undelete:
406      // BASE_VERSION: Must keep the same.
407      // ID: Essential to keep the same.
408      // META_HANDLE: Must be the same, so we can't "split" the entry.
409      // IS_DEL: Must be set to false, will cause reindexing.
410      //         This one is weird because IS_DEL is true for "update only"
411      //         items. It should be OK to undelete an update only.
412      // MTIME/CTIME: Seems reasonable to just leave them alone.
413      // IS_UNSYNCED: Must set this to true or face database insurrection.
414      //              We do this below this block.
415      // IS_UNAPPLIED_UPDATE: Either keep it the same or also set BASE_VERSION
416      //                      to SERVER_VERSION. We keep it the same here.
417      // IS_DIR: We'll leave it the same.
418      // SPECIFICS: Reset it.
419
420      existing_entry->PutIsDel(false);
421
422      // Client tags are immutable and must be paired with the ID.
423      // If a server update comes down with an ID and client tag combo,
424      // and it already exists, always overwrite it and store only one copy.
425      // We have to undelete entries because we can't disassociate IDs from
426      // tags and updates.
427
428      existing_entry->PutNonUniqueName(dummy);
429      existing_entry->PutParentId(parent_id);
430      entry_ = existing_entry.release();
431    } else {
432      return INIT_FAILED_ENTRY_ALREADY_EXISTS;
433    }
434  } else {
435    entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
436                                        syncable::CREATE,
437                                        model_type, parent_id, dummy);
438    if (!entry_->good())
439      return INIT_FAILED_COULD_NOT_CREATE_ENTRY;
440
441    // Only set IS_DIR for new entries. Don't bitflip undeleted ones.
442    entry_->PutUniqueClientTag(hash);
443  }
444
445  // We don't support directory and tag combinations.
446  entry_->PutIsDir(false);
447
448  // Now set the predecessor, which sets IS_UNSYNCED as necessary.
449  bool success = PutPredecessor(NULL);
450  if (!success)
451    return INIT_FAILED_SET_PREDECESSOR;
452
453  return INIT_SUCCESS;
454}
455
456bool WriteNode::SetPosition(const BaseNode& new_parent,
457                            const BaseNode* predecessor) {
458  // |predecessor| must be a child of |new_parent| or NULL.
459  if (predecessor && predecessor->GetParentId() != new_parent.GetId()) {
460    DCHECK(false);
461    return false;
462  }
463
464  syncable::Id new_parent_id = new_parent.GetEntry()->GetId();
465
466  // Filter out redundant changes if both the parent and the predecessor match.
467  if (new_parent_id == entry_->GetParentId()) {
468    const syncable::Id& old = entry_->GetPredecessorId();
469    if ((!predecessor && old.IsRoot()) ||
470        (predecessor && (old == predecessor->GetEntry()->GetId()))) {
471      return true;
472    }
473  }
474
475  entry_->PutParentId(new_parent_id);
476
477  // Now set the predecessor, which sets IS_UNSYNCED as necessary.
478  return PutPredecessor(predecessor);
479}
480
481const syncable::Entry* WriteNode::GetEntry() const {
482  return entry_;
483}
484
485const BaseTransaction* WriteNode::GetTransaction() const {
486  return transaction_;
487}
488
489syncable::MutableEntry* WriteNode::GetMutableEntryForTest() {
490  return entry_;
491}
492
493void 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
501void WriteNode::Drop() {
502  if (entry_->GetId().ServerKnows()) {
503    entry_->PutIsDel(true);
504  }
505}
506
507bool 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
518void WriteNode::MarkForSyncing() {
519  syncable::MarkForSyncing(entry_);
520}
521
522}  // namespace syncer
523