OLD | NEW |
| (Empty) |
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 "components/sync/core/write_node.h" | |
6 | |
7 #include <memory> | |
8 | |
9 #include "base/strings/string_util.h" | |
10 #include "base/strings/utf_string_conversions.h" | |
11 #include "base/values.h" | |
12 #include "components/sync/base/cryptographer.h" | |
13 #include "components/sync/core/base_transaction.h" | |
14 #include "components/sync/core/write_transaction.h" | |
15 #include "components/sync/core_impl/syncapi_internal.h" | |
16 #include "components/sync/protocol/bookmark_specifics.pb.h" | |
17 #include "components/sync/protocol/typed_url_specifics.pb.h" | |
18 #include "components/sync/syncable/mutable_entry.h" | |
19 #include "components/sync/syncable/nigori_util.h" | |
20 #include "components/sync/syncable/syncable_util.h" | |
21 | |
22 using std::string; | |
23 using std::vector; | |
24 | |
25 namespace syncer { | |
26 | |
27 using syncable::kEncryptedString; | |
28 using syncable::SPECIFICS; | |
29 | |
30 static const char kDefaultNameForNewNodes[] = " "; | |
31 | |
32 void WriteNode::SetIsFolder(bool folder) { | |
33 if (entry_->GetIsDir() == folder) | |
34 return; // Skip redundant changes. | |
35 | |
36 entry_->PutIsDir(folder); | |
37 MarkForSyncing(); | |
38 } | |
39 | |
40 void WriteNode::SetTitle(const std::string& title) { | |
41 DCHECK_NE(GetModelType(), UNSPECIFIED); | |
42 ModelType type = GetModelType(); | |
43 // It's possible the nigori lost the set of encrypted types. If the current | |
44 // specifics are already encrypted, we want to ensure we continue encrypting. | |
45 bool needs_encryption = GetTransaction()->GetEncryptedTypes().Has(type) || | |
46 entry_->GetSpecifics().has_encrypted(); | |
47 | |
48 // If this datatype is encrypted and is not a bookmark, we disregard the | |
49 // specified title in favor of kEncryptedString. For encrypted bookmarks the | |
50 // NON_UNIQUE_NAME will still be kEncryptedString, but we store the real title | |
51 // into the specifics. All strings compared are server legal strings. | |
52 std::string new_legal_title; | |
53 if (type != BOOKMARKS && needs_encryption) { | |
54 new_legal_title = kEncryptedString; | |
55 } else { | |
56 DCHECK(base::IsStringUTF8(title)); | |
57 SyncAPINameToServerName(title, &new_legal_title); | |
58 base::TruncateUTF8ToByteSize(new_legal_title, 255, &new_legal_title); | |
59 } | |
60 | |
61 std::string current_legal_title; | |
62 if (BOOKMARKS == type && entry_->GetSpecifics().has_encrypted()) { | |
63 // Encrypted bookmarks only have their title in the unencrypted specifics. | |
64 current_legal_title = GetBookmarkSpecifics().title(); | |
65 } else { | |
66 // Non-bookmarks and legacy bookmarks (those with no title in their | |
67 // specifics) store their title in NON_UNIQUE_NAME. Non-legacy bookmarks | |
68 // store their title in specifics as well as NON_UNIQUE_NAME. | |
69 current_legal_title = entry_->GetNonUniqueName(); | |
70 } | |
71 | |
72 bool title_matches = (current_legal_title == new_legal_title); | |
73 bool encrypted_without_overwriting_name = | |
74 (needs_encryption && entry_->GetNonUniqueName() != kEncryptedString); | |
75 | |
76 // For bookmarks, we also set the title field in the specifics. | |
77 // TODO(zea): refactor bookmarks to not need this functionality. | |
78 sync_pb::EntitySpecifics specifics = GetEntitySpecifics(); | |
79 if (GetModelType() == BOOKMARKS && | |
80 specifics.bookmark().title() != new_legal_title) { | |
81 specifics.mutable_bookmark()->set_title(new_legal_title); | |
82 SetEntitySpecifics(specifics); // Does it's own encryption checking. | |
83 title_matches = false; | |
84 } | |
85 | |
86 // If the title matches and the NON_UNIQUE_NAME is properly overwritten as | |
87 // necessary, nothing needs to change. | |
88 if (title_matches && !encrypted_without_overwriting_name) { | |
89 DVLOG(2) << "Title matches, dropping change."; | |
90 return; | |
91 } | |
92 | |
93 // For bookmarks, this has to happen after we set the title in the specifics, | |
94 // because the presence of a title in the NON_UNIQUE_NAME is what controls | |
95 // the logic deciding whether this is an empty node or a legacy bookmark. | |
96 // See BaseNode::GetUnencryptedSpecific(..). | |
97 if (needs_encryption) | |
98 entry_->PutNonUniqueName(kEncryptedString); | |
99 else | |
100 entry_->PutNonUniqueName(new_legal_title); | |
101 | |
102 DVLOG(1) << "Overwriting title of type " << ModelTypeToString(type) | |
103 << " and marking for syncing."; | |
104 MarkForSyncing(); | |
105 } | |
106 | |
107 void WriteNode::SetBookmarkSpecifics( | |
108 const sync_pb::BookmarkSpecifics& new_value) { | |
109 sync_pb::EntitySpecifics entity_specifics; | |
110 entity_specifics.mutable_bookmark()->CopyFrom(new_value); | |
111 SetEntitySpecifics(entity_specifics); | |
112 } | |
113 | |
114 void WriteNode::SetNigoriSpecifics(const sync_pb::NigoriSpecifics& new_value) { | |
115 sync_pb::EntitySpecifics entity_specifics; | |
116 entity_specifics.mutable_nigori()->CopyFrom(new_value); | |
117 SetEntitySpecifics(entity_specifics); | |
118 } | |
119 | |
120 void WriteNode::SetPasswordSpecifics( | |
121 const sync_pb::PasswordSpecificsData& data) { | |
122 DCHECK_EQ(GetModelType(), PASSWORDS); | |
123 | |
124 Cryptographer* cryptographer = GetTransaction()->GetCryptographer(); | |
125 | |
126 // We have to do the idempotency check here (vs in UpdateEntryWithEncryption) | |
127 // because Passwords have their encrypted data within the PasswordSpecifics, | |
128 // vs within the EntitySpecifics like all the other types. | |
129 const sync_pb::EntitySpecifics& old_specifics = GetEntitySpecifics(); | |
130 sync_pb::EntitySpecifics entity_specifics; | |
131 // Copy over the old specifics if they exist. | |
132 if (GetModelTypeFromSpecifics(old_specifics) == PASSWORDS) { | |
133 entity_specifics.CopyFrom(old_specifics); | |
134 } else { | |
135 AddDefaultFieldValue(PASSWORDS, &entity_specifics); | |
136 } | |
137 sync_pb::PasswordSpecifics* password_specifics = | |
138 entity_specifics.mutable_password(); | |
139 // This will only update password_specifics if the underlying unencrypted blob | |
140 // was different from |data| or was not encrypted with the proper passphrase. | |
141 if (!cryptographer->Encrypt(data, password_specifics->mutable_encrypted())) { | |
142 LOG(ERROR) << "Failed to encrypt password, possibly due to sync node " | |
143 << "corruption"; | |
144 return; | |
145 } | |
146 SetEntitySpecifics(entity_specifics); | |
147 } | |
148 | |
149 void WriteNode::SetEntitySpecifics(const sync_pb::EntitySpecifics& new_value) { | |
150 ModelType new_specifics_type = GetModelTypeFromSpecifics(new_value); | |
151 CHECK(!new_value.password().has_client_only_encrypted_data()); | |
152 DCHECK_NE(new_specifics_type, UNSPECIFIED); | |
153 DVLOG(1) << "Writing entity specifics of type " | |
154 << ModelTypeToString(new_specifics_type); | |
155 DCHECK_EQ(new_specifics_type, GetModelType()); | |
156 | |
157 // Preserve unknown fields. | |
158 const sync_pb::EntitySpecifics& old_specifics = entry_->GetSpecifics(); | |
159 sync_pb::EntitySpecifics new_specifics; | |
160 new_specifics.CopyFrom(new_value); | |
161 new_specifics.mutable_unknown_fields()->append( | |
162 old_specifics.unknown_fields()); | |
163 | |
164 // Will update the entry if encryption was necessary. | |
165 if (!UpdateEntryWithEncryption(GetTransaction()->GetWrappedTrans(), | |
166 new_specifics, entry_)) { | |
167 return; | |
168 } | |
169 if (entry_->GetSpecifics().has_encrypted()) { | |
170 // EncryptIfNecessary already updated the entry for us and marked for | |
171 // syncing if it was needed. Now we just make a copy of the unencrypted | |
172 // specifics so that if this node is updated, we do not have to decrypt the | |
173 // old data. Note that this only modifies the node's local data, not the | |
174 // entry itself. | |
175 SetUnencryptedSpecifics(new_value); | |
176 } | |
177 | |
178 DCHECK_EQ(new_specifics_type, GetModelType()); | |
179 } | |
180 | |
181 void WriteNode::ResetFromSpecifics() { | |
182 SetEntitySpecifics(GetEntitySpecifics()); | |
183 } | |
184 | |
185 void WriteNode::SetTypedUrlSpecifics( | |
186 const sync_pb::TypedUrlSpecifics& new_value) { | |
187 sync_pb::EntitySpecifics entity_specifics; | |
188 entity_specifics.mutable_typed_url()->CopyFrom(new_value); | |
189 SetEntitySpecifics(entity_specifics); | |
190 } | |
191 | |
192 void WriteNode::SetExternalId(int64_t id) { | |
193 if (GetExternalId() != id) | |
194 entry_->PutLocalExternalId(id); | |
195 } | |
196 | |
197 WriteNode::WriteNode(WriteTransaction* transaction) | |
198 : entry_(NULL), transaction_(transaction) { | |
199 DCHECK(transaction); | |
200 } | |
201 | |
202 WriteNode::~WriteNode() { | |
203 delete entry_; | |
204 } | |
205 | |
206 // Find an existing node matching the ID |id|, and bind this WriteNode to it. | |
207 // Return true on success. | |
208 BaseNode::InitByLookupResult WriteNode::InitByIdLookup(int64_t id) { | |
209 DCHECK(!entry_) << "Init called twice"; | |
210 DCHECK_NE(id, kInvalidId); | |
211 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | |
212 syncable::GET_BY_HANDLE, id); | |
213 if (!entry_->good()) | |
214 return INIT_FAILED_ENTRY_NOT_GOOD; | |
215 if (entry_->GetIsDel()) | |
216 return INIT_FAILED_ENTRY_IS_DEL; | |
217 return DecryptIfNecessary() ? INIT_OK : INIT_FAILED_DECRYPT_IF_NECESSARY; | |
218 } | |
219 | |
220 // Find a node by client tag, and bind this WriteNode to it. | |
221 // Return true if the write node was found, and was not deleted. | |
222 // Undeleting a deleted node is possible by ClientTag. | |
223 BaseNode::InitByLookupResult WriteNode::InitByClientTagLookup( | |
224 ModelType model_type, | |
225 const std::string& tag) { | |
226 DCHECK(!entry_) << "Init called twice"; | |
227 if (tag.empty()) | |
228 return INIT_FAILED_PRECONDITION; | |
229 | |
230 const std::string hash = syncable::GenerateSyncableHash(model_type, tag); | |
231 | |
232 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | |
233 syncable::GET_BY_CLIENT_TAG, hash); | |
234 if (!entry_->good()) | |
235 return INIT_FAILED_ENTRY_NOT_GOOD; | |
236 if (entry_->GetIsDel()) | |
237 return INIT_FAILED_ENTRY_IS_DEL; | |
238 return DecryptIfNecessary() ? INIT_OK : INIT_FAILED_DECRYPT_IF_NECESSARY; | |
239 } | |
240 | |
241 BaseNode::InitByLookupResult WriteNode::InitTypeRoot(ModelType type) { | |
242 DCHECK(!entry_) << "Init called twice"; | |
243 if (!IsRealDataType(type)) | |
244 return INIT_FAILED_PRECONDITION; | |
245 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | |
246 syncable::GET_TYPE_ROOT, type); | |
247 if (!entry_->good()) | |
248 return INIT_FAILED_ENTRY_NOT_GOOD; | |
249 if (entry_->GetIsDel()) | |
250 return INIT_FAILED_ENTRY_IS_DEL; | |
251 ModelType model_type = GetModelType(); | |
252 DCHECK_EQ(model_type, NIGORI); | |
253 return INIT_OK; | |
254 } | |
255 | |
256 // Create a new node with default properties, and bind this WriteNode to it. | |
257 // Return true on success. | |
258 bool WriteNode::InitBookmarkByCreation(const BaseNode& parent, | |
259 const BaseNode* predecessor) { | |
260 DCHECK(!entry_) << "Init called twice"; | |
261 // |predecessor| must be a child of |parent| or NULL. | |
262 if (predecessor && predecessor->GetParentId() != parent.GetId()) { | |
263 DCHECK(false); | |
264 return false; | |
265 } | |
266 | |
267 syncable::Id parent_id = parent.GetSyncId(); | |
268 DCHECK(!parent_id.IsNull()); | |
269 | |
270 // Start out with a dummy name. We expect | |
271 // the caller to set a meaningful name after creation. | |
272 string dummy(kDefaultNameForNewNodes); | |
273 | |
274 entry_ = | |
275 new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | |
276 syncable::CREATE, BOOKMARKS, parent_id, dummy); | |
277 | |
278 if (!entry_->good()) | |
279 return false; | |
280 | |
281 // Entries are untitled folders by default. | |
282 entry_->PutIsDir(true); | |
283 | |
284 if (!PutPredecessor(predecessor)) { | |
285 return false; | |
286 } | |
287 | |
288 // Mark this entry as unsynced, to wake up the syncer. | |
289 MarkForSyncing(); | |
290 return true; | |
291 } | |
292 | |
293 WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreation( | |
294 ModelType model_type, | |
295 const BaseNode& parent, | |
296 const std::string& tag) { | |
297 return InitUniqueByCreationImpl(model_type, parent.GetSyncId(), tag); | |
298 } | |
299 | |
300 WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreation( | |
301 ModelType model_type, | |
302 const std::string& tag) { | |
303 return InitUniqueByCreationImpl(model_type, syncable::Id(), tag); | |
304 } | |
305 | |
306 // Create a new node with default properties and a client defined unique tag, | |
307 // and bind this WriteNode to it. | |
308 // Return true on success. If the tag exists in the database, then | |
309 // we will attempt to undelete the node. | |
310 WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreationImpl( | |
311 ModelType model_type, | |
312 const syncable::Id& parent_id, | |
313 const std::string& tag) { | |
314 // This DCHECK will only fail if init is called twice. | |
315 DCHECK(!entry_); | |
316 if (tag.empty()) { | |
317 LOG(WARNING) << "InitUniqueByCreation failed due to empty tag."; | |
318 return INIT_FAILED_EMPTY_TAG; | |
319 } | |
320 | |
321 const std::string hash = syncable::GenerateSyncableHash(model_type, tag); | |
322 | |
323 // Start out with a dummy name. We expect | |
324 // the caller to set a meaningful name after creation. | |
325 string dummy(kDefaultNameForNewNodes); | |
326 | |
327 // Check if we have this locally and need to undelete it. | |
328 std::unique_ptr<syncable::MutableEntry> existing_entry( | |
329 new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | |
330 syncable::GET_BY_CLIENT_TAG, hash)); | |
331 | |
332 if (existing_entry->good()) { | |
333 bool entry_undeleted = false; | |
334 if (existing_entry->GetIsDel()) { | |
335 // Rules for undelete: | |
336 // BASE_VERSION: Must keep the same. | |
337 // ID: Essential to keep the same. | |
338 // META_HANDLE: Must be the same, so we can't "split" the entry. | |
339 // IS_DEL: Must be set to false, will cause reindexing. | |
340 // This one is weird because IS_DEL is true for "update only" | |
341 // items. It should be OK to undelete an update only. | |
342 // MTIME/CTIME: Seems reasonable to just leave them alone. | |
343 // IS_UNSYNCED: Must set this to true or face database insurrection. | |
344 // We do this below this block. | |
345 // IS_UNAPPLIED_UPDATE: Either keep it the same or also set BASE_VERSION | |
346 // to SERVER_VERSION. We keep it the same here. | |
347 // IS_DIR: We'll leave it the same. | |
348 // SPECIFICS: Reset it. | |
349 | |
350 // Put specifics to define the entry's model type to handle the case | |
351 // where this is not actually an undeletion, but instead a collision | |
352 // with a newly downloaded, processed, and unapplied server update. | |
353 // This should be done first before inserting the entry into the | |
354 // directory's ParentChildIndex by clearing its "deleted" flag below. | |
355 // This is a fix for http://crbug.com/505761. | |
356 sync_pb::EntitySpecifics specifics; | |
357 AddDefaultFieldValue(model_type, &specifics); | |
358 existing_entry->PutSpecifics(specifics); | |
359 | |
360 existing_entry->PutIsDel(false); | |
361 | |
362 // Client tags are immutable and must be paired with the ID. | |
363 // If a server update comes down with an ID and client tag combo, | |
364 // and it already exists, always overwrite it and store only one copy. | |
365 // We have to undelete entries because we can't disassociate IDs from | |
366 // tags and updates. | |
367 | |
368 existing_entry->PutNonUniqueName(dummy); | |
369 existing_entry->PutParentId(parent_id); | |
370 entry_undeleted = true; | |
371 } // Else just reuse the existing entry. | |
372 entry_ = existing_entry.release(); | |
373 // If entry is undeleted, its specifics are reset to default, unencrypted | |
374 // value, and therefore no decryption is necessary. Moreover trying to | |
375 // decrypt the password entry will fail because passwords are expected to be | |
376 // encrypted. | |
377 if (!entry_undeleted && !DecryptIfNecessary()) | |
378 return INIT_FAILED_DECRYPT_EXISTING_ENTRY; | |
379 } else { | |
380 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | |
381 syncable::CREATE, model_type, parent_id, | |
382 dummy); | |
383 } | |
384 | |
385 if (!entry_->good()) | |
386 return INIT_FAILED_COULD_NOT_CREATE_ENTRY; | |
387 | |
388 // Has no impact if the client tag is already set. | |
389 entry_->PutUniqueClientTag(hash); | |
390 | |
391 // We don't support directory and tag combinations. | |
392 entry_->PutIsDir(false); | |
393 | |
394 if (entry_->ShouldMaintainPosition()) { | |
395 if (!entry_->PutPredecessor(syncable::Id())) | |
396 return INIT_FAILED_SET_PREDECESSOR; | |
397 } | |
398 | |
399 // Mark this entry as unsynced, to wake up the syncer. | |
400 MarkForSyncing(); | |
401 | |
402 return INIT_SUCCESS; | |
403 } | |
404 | |
405 bool WriteNode::SetPosition(const BaseNode& new_parent, | |
406 const BaseNode* predecessor) { | |
407 // |predecessor| must be a child of |new_parent| or NULL. | |
408 if (predecessor && predecessor->GetParentId() != new_parent.GetId()) { | |
409 DCHECK(false); | |
410 return false; | |
411 } | |
412 | |
413 syncable::Id new_parent_id = new_parent.GetSyncId(); | |
414 DCHECK(!new_parent_id.IsNull()); | |
415 | |
416 // Filter out redundant changes if both the parent and the predecessor match. | |
417 if (new_parent_id == entry_->GetParentId()) { | |
418 const syncable::Id& old = entry_->GetPredecessorId(); | |
419 if ((!predecessor && old.IsNull()) || | |
420 (predecessor && (old == predecessor->GetSyncId()))) { | |
421 return true; | |
422 } | |
423 } | |
424 | |
425 entry_->PutParentId(new_parent_id); | |
426 | |
427 if (!PutPredecessor(predecessor)) { | |
428 return false; | |
429 } | |
430 | |
431 // Mark this entry as unsynced, to wake up the syncer. | |
432 MarkForSyncing(); | |
433 return true; | |
434 } | |
435 | |
436 void WriteNode::SetAttachmentMetadata( | |
437 const sync_pb::AttachmentMetadata& attachment_metadata) { | |
438 entry_->PutAttachmentMetadata(attachment_metadata); | |
439 } | |
440 | |
441 const syncable::Entry* WriteNode::GetEntry() const { | |
442 return entry_; | |
443 } | |
444 | |
445 const BaseTransaction* WriteNode::GetTransaction() const { | |
446 return transaction_; | |
447 } | |
448 | |
449 syncable::MutableEntry* WriteNode::GetMutableEntryForTest() { | |
450 return entry_; | |
451 } | |
452 | |
453 void WriteNode::Tombstone() { | |
454 // These lines must be in this order. The call to Put(IS_DEL) might choose to | |
455 // unset the IS_UNSYNCED bit if the item was not known to the server at the | |
456 // time of deletion. It's important that the bit not be reset in that case. | |
457 MarkForSyncing(); | |
458 entry_->PutIsDel(true); | |
459 } | |
460 | |
461 void WriteNode::Drop() { | |
462 if (entry_->GetId().ServerKnows()) { | |
463 entry_->PutIsDel(true); | |
464 } | |
465 } | |
466 | |
467 bool WriteNode::PutPredecessor(const BaseNode* predecessor) { | |
468 DCHECK(!entry_->GetParentId().IsNull()); | |
469 syncable::Id predecessor_id = | |
470 predecessor ? predecessor->GetSyncId() : syncable::Id(); | |
471 return entry_->PutPredecessor(predecessor_id); | |
472 } | |
473 | |
474 void WriteNode::MarkForSyncing() { | |
475 syncable::MarkForSyncing(entry_); | |
476 } | |
477 | |
478 } // namespace syncer | |
OLD | NEW |