| // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "sync/internal_api/public/write_node.h" | 
 |  | 
 | #include "base/strings/string_util.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "base/values.h" | 
 | #include "sync/internal_api/public/base_transaction.h" | 
 | #include "sync/internal_api/public/write_transaction.h" | 
 | #include "sync/internal_api/syncapi_internal.h" | 
 | #include "sync/protocol/bookmark_specifics.pb.h" | 
 | #include "sync/protocol/typed_url_specifics.pb.h" | 
 | #include "sync/syncable/mutable_entry.h" | 
 | #include "sync/syncable/nigori_util.h" | 
 | #include "sync/syncable/syncable_util.h" | 
 | #include "sync/util/cryptographer.h" | 
 |  | 
 | using std::string; | 
 | using std::vector; | 
 |  | 
 | namespace syncer { | 
 |  | 
 | using syncable::kEncryptedString; | 
 | using syncable::SPECIFICS; | 
 |  | 
 | static const char kDefaultNameForNewNodes[] = " "; | 
 |  | 
 | void WriteNode::SetIsFolder(bool folder) { | 
 |   if (entry_->GetIsDir() == folder) | 
 |     return;  // Skip redundant changes. | 
 |  | 
 |   entry_->PutIsDir(folder); | 
 |   MarkForSyncing(); | 
 | } | 
 |  | 
 | void WriteNode::SetTitle(const std::string& title) { | 
 |   DCHECK_NE(GetModelType(), UNSPECIFIED); | 
 |   ModelType type = GetModelType(); | 
 |   // It's possible the nigori lost the set of encrypted types. If the current | 
 |   // specifics are already encrypted, we want to ensure we continue encrypting. | 
 |   bool needs_encryption = GetTransaction()->GetEncryptedTypes().Has(type) || | 
 |                           entry_->GetSpecifics().has_encrypted(); | 
 |  | 
 |   // If this datatype is encrypted and is not a bookmark, we disregard the | 
 |   // specified title in favor of kEncryptedString. For encrypted bookmarks the | 
 |   // NON_UNIQUE_NAME will still be kEncryptedString, but we store the real title | 
 |   // into the specifics. All strings compared are server legal strings. | 
 |   std::string new_legal_title; | 
 |   if (type != BOOKMARKS && needs_encryption) { | 
 |     new_legal_title = kEncryptedString; | 
 |   } else { | 
 |     DCHECK(base::IsStringUTF8(title)); | 
 |     SyncAPINameToServerName(title, &new_legal_title); | 
 |     base::TruncateUTF8ToByteSize(new_legal_title, 255, &new_legal_title); | 
 |   } | 
 |  | 
 |   std::string current_legal_title; | 
 |   if (BOOKMARKS == type && | 
 |       entry_->GetSpecifics().has_encrypted()) { | 
 |     // Encrypted bookmarks only have their title in the unencrypted specifics. | 
 |     current_legal_title = GetBookmarkSpecifics().title(); | 
 |   } else { | 
 |     // Non-bookmarks and legacy bookmarks (those with no title in their | 
 |     // specifics) store their title in NON_UNIQUE_NAME. Non-legacy bookmarks | 
 |     // store their title in specifics as well as NON_UNIQUE_NAME. | 
 |     current_legal_title = entry_->GetNonUniqueName(); | 
 |   } | 
 |  | 
 |   bool title_matches = (current_legal_title == new_legal_title); | 
 |   bool encrypted_without_overwriting_name = (needs_encryption && | 
 |       entry_->GetNonUniqueName() != kEncryptedString); | 
 |  | 
 |   // For bookmarks, we also set the title field in the specifics. | 
 |   // TODO(zea): refactor bookmarks to not need this functionality. | 
 |   sync_pb::EntitySpecifics specifics = GetEntitySpecifics(); | 
 |   if (GetModelType() == BOOKMARKS && | 
 |       specifics.bookmark().title() != new_legal_title) { | 
 |     specifics.mutable_bookmark()->set_title(new_legal_title); | 
 |     SetEntitySpecifics(specifics);  // Does it's own encryption checking. | 
 |     title_matches = false; | 
 |   } | 
 |  | 
 |   // If the title matches and the NON_UNIQUE_NAME is properly overwritten as | 
 |   // necessary, nothing needs to change. | 
 |   if (title_matches && !encrypted_without_overwriting_name) { | 
 |     DVLOG(2) << "Title matches, dropping change."; | 
 |     return; | 
 |   } | 
 |  | 
 |   // For bookmarks, this has to happen after we set the title in the specifics, | 
 |   // because the presence of a title in the NON_UNIQUE_NAME is what controls | 
 |   // the logic deciding whether this is an empty node or a legacy bookmark. | 
 |   // See BaseNode::GetUnencryptedSpecific(..). | 
 |   if (needs_encryption) | 
 |     entry_->PutNonUniqueName(kEncryptedString); | 
 |   else | 
 |     entry_->PutNonUniqueName(new_legal_title); | 
 |  | 
 |   DVLOG(1) << "Overwriting title of type " | 
 |            << ModelTypeToString(type) | 
 |            << " and marking for syncing."; | 
 |   MarkForSyncing(); | 
 | } | 
 |  | 
 | void WriteNode::SetBookmarkSpecifics( | 
 |     const sync_pb::BookmarkSpecifics& new_value) { | 
 |   sync_pb::EntitySpecifics entity_specifics; | 
 |   entity_specifics.mutable_bookmark()->CopyFrom(new_value); | 
 |   SetEntitySpecifics(entity_specifics); | 
 | } | 
 |  | 
 | void WriteNode::SetNigoriSpecifics( | 
 |     const sync_pb::NigoriSpecifics& new_value) { | 
 |   sync_pb::EntitySpecifics entity_specifics; | 
 |   entity_specifics.mutable_nigori()->CopyFrom(new_value); | 
 |   SetEntitySpecifics(entity_specifics); | 
 | } | 
 |  | 
 | void WriteNode::SetPasswordSpecifics( | 
 |     const sync_pb::PasswordSpecificsData& data) { | 
 |   DCHECK_EQ(GetModelType(), PASSWORDS); | 
 |  | 
 |   Cryptographer* cryptographer = GetTransaction()->GetCryptographer(); | 
 |  | 
 |   // We have to do the idempotency check here (vs in UpdateEntryWithEncryption) | 
 |   // because Passwords have their encrypted data within the PasswordSpecifics, | 
 |   // vs within the EntitySpecifics like all the other types. | 
 |   const sync_pb::EntitySpecifics& old_specifics = GetEntry()->GetSpecifics(); | 
 |   sync_pb::EntitySpecifics entity_specifics; | 
 |   // Copy over the old specifics if they exist. | 
 |   if (GetModelTypeFromSpecifics(old_specifics) == PASSWORDS) { | 
 |     entity_specifics.CopyFrom(old_specifics); | 
 |   } else { | 
 |     AddDefaultFieldValue(PASSWORDS, &entity_specifics); | 
 |   } | 
 |   sync_pb::PasswordSpecifics* password_specifics = | 
 |       entity_specifics.mutable_password(); | 
 |   // This will only update password_specifics if the underlying unencrypted blob | 
 |   // was different from |data| or was not encrypted with the proper passphrase. | 
 |   if (!cryptographer->Encrypt(data, password_specifics->mutable_encrypted())) { | 
 |     LOG(ERROR) << "Failed to encrypt password, possibly due to sync node " | 
 |                << "corruption"; | 
 |     return; | 
 |   } | 
 |   SetEntitySpecifics(entity_specifics); | 
 | } | 
 |  | 
 | void WriteNode::SetEntitySpecifics( | 
 |     const sync_pb::EntitySpecifics& new_value) { | 
 |   ModelType new_specifics_type = | 
 |       GetModelTypeFromSpecifics(new_value); | 
 |   CHECK(!new_value.password().has_client_only_encrypted_data()); | 
 |   DCHECK_NE(new_specifics_type, UNSPECIFIED); | 
 |   DVLOG(1) << "Writing entity specifics of type " | 
 |            << ModelTypeToString(new_specifics_type); | 
 |   DCHECK_EQ(new_specifics_type, GetModelType()); | 
 |  | 
 |   // Preserve unknown fields. | 
 |   const sync_pb::EntitySpecifics& old_specifics = entry_->GetSpecifics(); | 
 |   sync_pb::EntitySpecifics new_specifics; | 
 |   new_specifics.CopyFrom(new_value); | 
 |   new_specifics.mutable_unknown_fields()->MergeFrom( | 
 |       old_specifics.unknown_fields()); | 
 |  | 
 |   // Will update the entry if encryption was necessary. | 
 |   if (!UpdateEntryWithEncryption(GetTransaction()->GetWrappedTrans(), | 
 |                                  new_specifics, | 
 |                                  entry_)) { | 
 |     return; | 
 |   } | 
 |   if (entry_->GetSpecifics().has_encrypted()) { | 
 |     // EncryptIfNecessary already updated the entry for us and marked for | 
 |     // syncing if it was needed. Now we just make a copy of the unencrypted | 
 |     // specifics so that if this node is updated, we do not have to decrypt the | 
 |     // old data. Note that this only modifies the node's local data, not the | 
 |     // entry itself. | 
 |     SetUnencryptedSpecifics(new_value); | 
 |   } | 
 |  | 
 |   DCHECK_EQ(new_specifics_type, GetModelType()); | 
 | } | 
 |  | 
 | void WriteNode::ResetFromSpecifics() { | 
 |   SetEntitySpecifics(GetEntitySpecifics()); | 
 | } | 
 |  | 
 | void WriteNode::SetTypedUrlSpecifics( | 
 |     const sync_pb::TypedUrlSpecifics& new_value) { | 
 |   sync_pb::EntitySpecifics entity_specifics; | 
 |   entity_specifics.mutable_typed_url()->CopyFrom(new_value); | 
 |   SetEntitySpecifics(entity_specifics); | 
 | } | 
 |  | 
 | void WriteNode::SetExternalId(int64 id) { | 
 |   if (GetExternalId() != id) | 
 |     entry_->PutLocalExternalId(id); | 
 | } | 
 |  | 
 | WriteNode::WriteNode(WriteTransaction* transaction) | 
 |     : entry_(NULL), transaction_(transaction) { | 
 |   DCHECK(transaction); | 
 | } | 
 |  | 
 | WriteNode::~WriteNode() { | 
 |   delete entry_; | 
 | } | 
 |  | 
 | // Find an existing node matching the ID |id|, and bind this WriteNode to it. | 
 | // Return true on success. | 
 | BaseNode::InitByLookupResult WriteNode::InitByIdLookup(int64 id) { | 
 |   DCHECK(!entry_) << "Init called twice"; | 
 |   DCHECK_NE(id, kInvalidId); | 
 |   entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | 
 |                                       syncable::GET_BY_HANDLE, id); | 
 |   if (!entry_->good()) | 
 |     return INIT_FAILED_ENTRY_NOT_GOOD; | 
 |   if (entry_->GetIsDel()) | 
 |     return INIT_FAILED_ENTRY_IS_DEL; | 
 |   return DecryptIfNecessary() ? INIT_OK : INIT_FAILED_DECRYPT_IF_NECESSARY; | 
 | } | 
 |  | 
 | // Find a node by client tag, and bind this WriteNode to it. | 
 | // Return true if the write node was found, and was not deleted. | 
 | // Undeleting a deleted node is possible by ClientTag. | 
 | BaseNode::InitByLookupResult WriteNode::InitByClientTagLookup( | 
 |     ModelType model_type, | 
 |     const std::string& tag) { | 
 |   DCHECK(!entry_) << "Init called twice"; | 
 |   if (tag.empty()) | 
 |     return INIT_FAILED_PRECONDITION; | 
 |  | 
 |   const std::string hash = syncable::GenerateSyncableHash(model_type, tag); | 
 |  | 
 |   entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | 
 |                                       syncable::GET_BY_CLIENT_TAG, hash); | 
 |   if (!entry_->good()) | 
 |     return INIT_FAILED_ENTRY_NOT_GOOD; | 
 |   if (entry_->GetIsDel()) | 
 |     return INIT_FAILED_ENTRY_IS_DEL; | 
 |   return DecryptIfNecessary() ? INIT_OK : INIT_FAILED_DECRYPT_IF_NECESSARY; | 
 | } | 
 |  | 
 | BaseNode::InitByLookupResult WriteNode::InitTypeRoot(ModelType type) { | 
 |   DCHECK(!entry_) << "Init called twice"; | 
 |   if (!IsRealDataType(type)) | 
 |     return INIT_FAILED_PRECONDITION; | 
 |   entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | 
 |                                       syncable::GET_TYPE_ROOT, type); | 
 |   if (!entry_->good()) | 
 |     return INIT_FAILED_ENTRY_NOT_GOOD; | 
 |   if (entry_->GetIsDel()) | 
 |     return INIT_FAILED_ENTRY_IS_DEL; | 
 |   ModelType model_type = GetModelType(); | 
 |   DCHECK_EQ(model_type, NIGORI); | 
 |   return INIT_OK; | 
 | } | 
 |  | 
 | // Create a new node with default properties, and bind this WriteNode to it. | 
 | // Return true on success. | 
 | bool WriteNode::InitBookmarkByCreation(const BaseNode& parent, | 
 |                                        const BaseNode* predecessor) { | 
 |   DCHECK(!entry_) << "Init called twice"; | 
 |   // |predecessor| must be a child of |parent| or NULL. | 
 |   if (predecessor && predecessor->GetParentId() != parent.GetId()) { | 
 |     DCHECK(false); | 
 |     return false; | 
 |   } | 
 |  | 
 |   syncable::Id parent_id = parent.GetEntry()->GetId(); | 
 |   DCHECK(!parent_id.IsNull()); | 
 |  | 
 |   // Start out with a dummy name.  We expect | 
 |   // the caller to set a meaningful name after creation. | 
 |   string dummy(kDefaultNameForNewNodes); | 
 |  | 
 |   entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | 
 |                                       syncable::CREATE, BOOKMARKS, | 
 |                                       parent_id, dummy); | 
 |  | 
 |   if (!entry_->good()) | 
 |     return false; | 
 |  | 
 |   // Entries are untitled folders by default. | 
 |   entry_->PutIsDir(true); | 
 |  | 
 |   if (!PutPredecessor(predecessor)) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   // Mark this entry as unsynced, to wake up the syncer. | 
 |   MarkForSyncing(); | 
 |   return true; | 
 | } | 
 |  | 
 | WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreation( | 
 |     ModelType model_type, | 
 |     const BaseNode& parent, | 
 |     const std::string& tag) { | 
 |   return InitUniqueByCreationImpl(model_type, parent.GetEntry()->GetId(), tag); | 
 | } | 
 |  | 
 | WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreation( | 
 |     ModelType model_type, | 
 |     const std::string& tag) { | 
 |   return InitUniqueByCreationImpl(model_type, syncable::Id(), tag); | 
 | } | 
 |  | 
 | // Create a new node with default properties and a client defined unique tag, | 
 | // and bind this WriteNode to it. | 
 | // Return true on success. If the tag exists in the database, then | 
 | // we will attempt to undelete the node. | 
 | WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreationImpl( | 
 |     ModelType model_type, | 
 |     const syncable::Id& parent_id, | 
 |     const std::string& tag) { | 
 |   // This DCHECK will only fail if init is called twice. | 
 |   DCHECK(!entry_); | 
 |   if (tag.empty()) { | 
 |     LOG(WARNING) << "InitUniqueByCreation failed due to empty tag."; | 
 |     return INIT_FAILED_EMPTY_TAG; | 
 |   } | 
 |  | 
 |   const std::string hash = syncable::GenerateSyncableHash(model_type, tag); | 
 |  | 
 |   // Start out with a dummy name.  We expect | 
 |   // the caller to set a meaningful name after creation. | 
 |   string dummy(kDefaultNameForNewNodes); | 
 |  | 
 |   // Check if we have this locally and need to undelete it. | 
 |   scoped_ptr<syncable::MutableEntry> existing_entry( | 
 |       new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | 
 |                                  syncable::GET_BY_CLIENT_TAG, hash)); | 
 |  | 
 |   if (existing_entry->good()) { | 
 |     if (existing_entry->GetIsDel()) { | 
 |       // Rules for undelete: | 
 |       // BASE_VERSION: Must keep the same. | 
 |       // ID: Essential to keep the same. | 
 |       // META_HANDLE: Must be the same, so we can't "split" the entry. | 
 |       // IS_DEL: Must be set to false, will cause reindexing. | 
 |       //         This one is weird because IS_DEL is true for "update only" | 
 |       //         items. It should be OK to undelete an update only. | 
 |       // MTIME/CTIME: Seems reasonable to just leave them alone. | 
 |       // IS_UNSYNCED: Must set this to true or face database insurrection. | 
 |       //              We do this below this block. | 
 |       // IS_UNAPPLIED_UPDATE: Either keep it the same or also set BASE_VERSION | 
 |       //                      to SERVER_VERSION. We keep it the same here. | 
 |       // IS_DIR: We'll leave it the same. | 
 |       // SPECIFICS: Reset it. | 
 |  | 
 |       // Put specifics to define the entry's model type to handle the case | 
 |       // where this is not actually an undeletion, but instead a collision | 
 |       // with a newly downloaded, processed, and unapplied server update. | 
 |       // This should be done first before inserting the entry into the | 
 |       // directory's ParentChildIndex by clearing its "deleted" flag below. | 
 |       // This is a fix for http://crbug.com/505761. | 
 |       sync_pb::EntitySpecifics specifics; | 
 |       AddDefaultFieldValue(model_type, &specifics); | 
 |       existing_entry->PutSpecifics(specifics); | 
 |  | 
 |       existing_entry->PutIsDel(false); | 
 |  | 
 |       // Client tags are immutable and must be paired with the ID. | 
 |       // If a server update comes down with an ID and client tag combo, | 
 |       // and it already exists, always overwrite it and store only one copy. | 
 |       // We have to undelete entries because we can't disassociate IDs from | 
 |       // tags and updates. | 
 |  | 
 |       existing_entry->PutNonUniqueName(dummy); | 
 |       existing_entry->PutParentId(parent_id); | 
 |     }  // Else just reuse the existing entry. | 
 |     entry_ = existing_entry.release(); | 
 |   } else { | 
 |     entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | 
 |                                         syncable::CREATE, | 
 |                                         model_type, parent_id, dummy); | 
 |   } | 
 |  | 
 |   if (!entry_->good()) | 
 |     return INIT_FAILED_COULD_NOT_CREATE_ENTRY; | 
 |  | 
 |   // Has no impact if the client tag is already set. | 
 |   entry_->PutUniqueClientTag(hash); | 
 |  | 
 |   // We don't support directory and tag combinations. | 
 |   entry_->PutIsDir(false); | 
 |  | 
 |   if (entry_->ShouldMaintainPosition()) { | 
 |     if (!entry_->PutPredecessor(syncable::Id())) | 
 |       return INIT_FAILED_SET_PREDECESSOR; | 
 |   } | 
 |  | 
 |   // Mark this entry as unsynced, to wake up the syncer. | 
 |   MarkForSyncing(); | 
 |  | 
 |   return INIT_SUCCESS; | 
 | } | 
 |  | 
 | bool WriteNode::SetPosition(const BaseNode& new_parent, | 
 |                             const BaseNode* predecessor) { | 
 |   // |predecessor| must be a child of |new_parent| or NULL. | 
 |   if (predecessor && predecessor->GetParentId() != new_parent.GetId()) { | 
 |     DCHECK(false); | 
 |     return false; | 
 |   } | 
 |  | 
 |   syncable::Id new_parent_id = new_parent.GetEntry()->GetId(); | 
 |   DCHECK(!new_parent_id.IsNull()); | 
 |  | 
 |   // Filter out redundant changes if both the parent and the predecessor match. | 
 |   if (new_parent_id == entry_->GetParentId()) { | 
 |     const syncable::Id& old = entry_->GetPredecessorId(); | 
 |     if ((!predecessor && old.IsNull()) || | 
 |         (predecessor && (old == predecessor->GetEntry()->GetId()))) { | 
 |       return true; | 
 |     } | 
 |   } | 
 |  | 
 |   entry_->PutParentId(new_parent_id); | 
 |  | 
 |   if (!PutPredecessor(predecessor)) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   // Mark this entry as unsynced, to wake up the syncer. | 
 |   MarkForSyncing(); | 
 |   return true; | 
 | } | 
 |  | 
 | void WriteNode::SetAttachmentMetadata( | 
 |     const sync_pb::AttachmentMetadata& attachment_metadata) { | 
 |   entry_->PutAttachmentMetadata(attachment_metadata); | 
 | } | 
 |  | 
 | const syncable::Entry* WriteNode::GetEntry() const { | 
 |   return entry_; | 
 | } | 
 |  | 
 | const BaseTransaction* WriteNode::GetTransaction() const { | 
 |   return transaction_; | 
 | } | 
 |  | 
 | syncable::MutableEntry* WriteNode::GetMutableEntryForTest() { | 
 |   return entry_; | 
 | } | 
 |  | 
 | void WriteNode::Tombstone() { | 
 |   // These lines must be in this order.  The call to Put(IS_DEL) might choose to | 
 |   // unset the IS_UNSYNCED bit if the item was not known to the server at the | 
 |   // time of deletion.  It's important that the bit not be reset in that case. | 
 |   MarkForSyncing(); | 
 |   entry_->PutIsDel(true); | 
 | } | 
 |  | 
 | void WriteNode::Drop() { | 
 |   if (entry_->GetId().ServerKnows()) { | 
 |     entry_->PutIsDel(true); | 
 |   } | 
 | } | 
 |  | 
 | bool WriteNode::PutPredecessor(const BaseNode* predecessor) { | 
 |   DCHECK(!entry_->GetParentId().IsNull()); | 
 |   syncable::Id predecessor_id = predecessor ? | 
 |       predecessor->GetEntry()->GetId() : syncable::Id(); | 
 |   return entry_->PutPredecessor(predecessor_id); | 
 | } | 
 |  | 
 | void WriteNode::MarkForSyncing() { | 
 |   syncable::MarkForSyncing(entry_); | 
 | } | 
 |  | 
 | }  // namespace syncer |