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_->Get(syncable::IS_DIR) == folder) 38 return; // Skip redundant changes. 39 40 entry_->Put(syncable::IS_DIR, 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_->Get(SPECIFICS).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 TruncateUTF8ToByteSize(new_legal_title, 255, &new_legal_title); 62 } 63 64 std::string current_legal_title; 65 if (BOOKMARKS == type && 66 entry_->Get(syncable::SPECIFICS).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_->Get(syncable::NON_UNIQUE_NAME); 74 } 75 76 bool title_matches = (current_legal_title == new_legal_title); 77 bool encrypted_without_overwriting_name = (needs_encryption && 78 entry_->Get(syncable::NON_UNIQUE_NAME) != 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_->Put(syncable::NON_UNIQUE_NAME, kEncryptedString); 101 else 102 entry_->Put(syncable::NON_UNIQUE_NAME, 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()->Get(SPECIFICS); 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_->Get(SPECIFICS); 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_->Get(SPECIFICS).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_->Put(syncable::LOCAL_EXTERNAL_ID, 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_->Get(syncable::IS_DEL)) 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_->Get(syncable::IS_DEL)) 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_->Get(syncable::IS_DEL)) 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()->Get(syncable::ID); 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_->Put(syncable::IS_DIR, 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()->Get(syncable::ID); 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->Get(syncable::IS_DEL)) { 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->Put(syncable::IS_DEL, 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->Put(syncable::NON_UNIQUE_NAME, dummy); 429 existing_entry->Put(syncable::PARENT_ID, 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_->Put(syncable::UNIQUE_CLIENT_TAG, hash); 443 } 444 445 // We don't support directory and tag combinations. 446 entry_->Put(syncable::IS_DIR, 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()->Get(syncable::ID); 465 466 // Filter out redundant changes if both the parent and the predecessor match. 467 if (new_parent_id == entry_->Get(syncable::PARENT_ID)) { 468 const syncable::Id& old = entry_->GetPredecessorId(); 469 if ((!predecessor && old.IsRoot()) || 470 (predecessor && (old == predecessor->GetEntry()->Get(syncable::ID)))) { 471 return true; 472 } 473 } 474 475 // Atomically change the parent. This will fail if it would 476 // introduce a cycle in the hierarchy. 477 if (!entry_->Put(syncable::PARENT_ID, new_parent_id)) 478 return false; 479 480 // Now set the predecessor, which sets IS_UNSYNCED as necessary. 481 return PutPredecessor(predecessor); 482 } 483 484 const syncable::Entry* WriteNode::GetEntry() const { 485 return entry_; 486 } 487 488 const BaseTransaction* WriteNode::GetTransaction() const { 489 return transaction_; 490 } 491 492 syncable::MutableEntry* WriteNode::GetMutableEntryForTest() { 493 return entry_; 494 } 495 496 void WriteNode::Tombstone() { 497 // These lines must be in this order. The call to Put(IS_DEL) might choose to 498 // unset the IS_UNSYNCED bit if the item was not known to the server at the 499 // time of deletion. It's important that the bit not be reset in that case. 500 MarkForSyncing(); 501 entry_->Put(syncable::IS_DEL, true); 502 } 503 504 void WriteNode::Drop() { 505 if (entry_->Get(syncable::ID).ServerKnows()) { 506 entry_->Put(syncable::IS_DEL, true); 507 } 508 } 509 510 bool WriteNode::PutPredecessor(const BaseNode* predecessor) { 511 syncable::Id predecessor_id = predecessor ? 512 predecessor->GetEntry()->Get(syncable::ID) : syncable::Id(); 513 if (!entry_->PutPredecessor(predecessor_id)) 514 return false; 515 // Mark this entry as unsynced, to wake up the syncer. 516 MarkForSyncing(); 517 518 return true; 519 } 520 521 void WriteNode::MarkForSyncing() { 522 syncable::MarkForSyncing(entry_); 523 } 524 525 } // namespace syncer 526