Home | History | Annotate | Download | only in internal_api
      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 
     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 
     44 void 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 
    110 void 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 
    117 void 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 
    124 void 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 
    132 void 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 
    139 void 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 
    146 void 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 
    175 void 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 
    182 void 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 
    189 void 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 
    196 void 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 
    203 void 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 
    210 void 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 
    217 void 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 
    224 void 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 
    259 void WriteNode::ResetFromSpecifics() {
    260   SetEntitySpecifics(GetEntitySpecifics());
    261 }
    262 
    263 void 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 
    270 void 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 
    277 void WriteNode::SetExternalId(int64 id) {
    278   if (GetExternalId() != id)
    279     entry_->PutLocalExternalId(id);
    280 }
    281 
    282 WriteNode::WriteNode(WriteTransaction* transaction)
    283     : entry_(NULL), transaction_(transaction) {
    284   DCHECK(transaction);
    285 }
    286 
    287 WriteNode::~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.
    293 BaseNode::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.
    308 BaseNode::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 
    326 BaseNode::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.
    344 bool 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?
    379 WriteNode::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 
    456 bool 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 
    481 const syncable::Entry* WriteNode::GetEntry() const {
    482   return entry_;
    483 }
    484 
    485 const BaseTransaction* WriteNode::GetTransaction() const {
    486   return transaction_;
    487 }
    488 
    489 syncable::MutableEntry* WriteNode::GetMutableEntryForTest() {
    490   return entry_;
    491 }
    492 
    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 
    501 void WriteNode::Drop() {
    502   if (entry_->GetId().ServerKnows()) {
    503     entry_->PutIsDel(true);
    504   }
    505 }
    506 
    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 
    518 void WriteNode::MarkForSyncing() {
    519   syncable::MarkForSyncing(entry_);
    520 }
    521 
    522 }  // namespace syncer
    523