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::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 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 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 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 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 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 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 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 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 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 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 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 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 246 void WriteNode::ResetFromSpecifics() { 247 SetEntitySpecifics(GetEntitySpecifics()); 248 } 249 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 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 264 void WriteNode::SetExternalId(int64 id) { 265 if (GetExternalId() != id) 266 entry_->PutLocalExternalId(id); 267 } 268 269 WriteNode::WriteNode(WriteTransaction* transaction) 270 : entry_(NULL), transaction_(transaction) { 271 DCHECK(transaction); 272 } 273 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. 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. 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 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. 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? 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 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 476 void WriteNode::SetAttachmentMetadata( 477 const sync_pb::AttachmentMetadata& attachment_metadata) { 478 entry_->PutAttachmentMetadata(attachment_metadata); 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