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 "chrome/browser/sync/internal_api/write_node.h" | |
6 | |
7 #include "base/utf_string_conversions.h" | |
8 #include "base/values.h" | |
9 #include "chrome/browser/sync/internal_api/syncapi_internal.h" | |
10 #include "chrome/browser/sync/internal_api/base_transaction.h" | |
11 #include "chrome/browser/sync/internal_api/write_transaction.h" | |
12 #include "sync/engine/nigori_util.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/syncable.h" | |
22 #include "sync/util/cryptographer.h" | |
23 | |
24 using browser_sync::Cryptographer; | |
25 using std::string; | |
26 using std::vector; | |
27 using syncable::kEncryptedString; | |
28 using syncable::SPECIFICS; | |
29 | |
30 namespace sync_api { | |
31 | |
32 static const char kDefaultNameForNewNodes[] = " "; | |
33 | |
34 void WriteNode::SetIsFolder(bool folder) { | |
35 if (entry_->Get(syncable::IS_DIR) == folder) | |
36 return; // Skip redundant changes. | |
37 | |
38 entry_->Put(syncable::IS_DIR, folder); | |
39 MarkForSyncing(); | |
40 } | |
41 | |
42 void WriteNode::SetTitle(const std::wstring& title) { | |
43 DCHECK_NE(GetModelType(), syncable::UNSPECIFIED); | |
44 syncable::ModelType type = GetModelType(); | |
45 Cryptographer* cryptographer = GetTransaction()->GetCryptographer(); | |
46 // It's possible the nigori lost the set of encrypted types. If the current | |
47 // specifics are already encrypted, we want to ensure we continue encrypting. | |
48 bool needs_encryption = cryptographer->GetEncryptedTypes().Has(type) || | |
49 entry_->Get(SPECIFICS).has_encrypted(); | |
50 | |
51 // If this datatype is encrypted and is not a bookmark, we disregard the | |
52 // specified title in favor of kEncryptedString. For encrypted bookmarks the | |
53 // NON_UNIQUE_NAME will still be kEncryptedString, but we store the real title | |
54 // into the specifics. All strings compared are server legal strings. | |
55 std::string new_legal_title; | |
56 if (type != syncable::BOOKMARKS && needs_encryption) { | |
57 new_legal_title = kEncryptedString; | |
58 } else { | |
59 SyncAPINameToServerName(WideToUTF8(title), &new_legal_title); | |
60 } | |
61 | |
62 std::string current_legal_title; | |
63 if (syncable::BOOKMARKS == type && | |
64 entry_->Get(syncable::SPECIFICS).has_encrypted()) { | |
65 // Encrypted bookmarks only have their title in the unencrypted specifics. | |
66 current_legal_title = GetBookmarkSpecifics().title(); | |
67 } else { | |
68 // Non-bookmarks and legacy bookmarks (those with no title in their | |
69 // specifics) store their title in NON_UNIQUE_NAME. Non-legacy bookmarks | |
70 // store their title in specifics as well as NON_UNIQUE_NAME. | |
71 current_legal_title = entry_->Get(syncable::NON_UNIQUE_NAME); | |
72 } | |
73 | |
74 bool title_matches = (current_legal_title == new_legal_title); | |
75 bool encrypted_without_overwriting_name = (needs_encryption && | |
76 entry_->Get(syncable::NON_UNIQUE_NAME) != kEncryptedString); | |
77 | |
78 // If the title matches and the NON_UNIQUE_NAME is properly overwritten as | |
79 // necessary, nothing needs to change. | |
80 if (title_matches && !encrypted_without_overwriting_name) { | |
81 DVLOG(2) << "Title matches, dropping change."; | |
82 return; | |
83 } | |
84 | |
85 // For bookmarks, we also set the title field in the specifics. | |
86 // TODO(zea): refactor bookmarks to not need this functionality. | |
87 if (GetModelType() == syncable::BOOKMARKS) { | |
88 sync_pb::EntitySpecifics specifics = GetEntitySpecifics(); | |
89 specifics.mutable_bookmark()->set_title(new_legal_title); | |
90 SetEntitySpecifics(specifics); // Does it's own encryption checking. | |
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_->Put(syncable::NON_UNIQUE_NAME, kEncryptedString); | |
99 else | |
100 entry_->Put(syncable::NON_UNIQUE_NAME, new_legal_title); | |
101 | |
102 DVLOG(1) << "Overwriting title of type " | |
103 << syncable::ModelTypeToString(type) | |
104 << " and marking for syncing."; | |
105 MarkForSyncing(); | |
106 } | |
107 | |
108 void WriteNode::SetURL(const GURL& url) { | |
109 sync_pb::BookmarkSpecifics new_value = GetBookmarkSpecifics(); | |
110 new_value.set_url(url.spec()); | |
111 SetBookmarkSpecifics(new_value); | |
112 } | |
113 | |
114 void WriteNode::SetAppSpecifics( | |
115 const sync_pb::AppSpecifics& new_value) { | |
116 sync_pb::EntitySpecifics entity_specifics; | |
117 entity_specifics.mutable_app()->CopyFrom(new_value); | |
118 SetEntitySpecifics(entity_specifics); | |
119 } | |
120 | |
121 void WriteNode::SetAutofillSpecifics( | |
122 const sync_pb::AutofillSpecifics& new_value) { | |
123 sync_pb::EntitySpecifics entity_specifics; | |
124 entity_specifics.mutable_autofill()->CopyFrom(new_value); | |
125 SetEntitySpecifics(entity_specifics); | |
126 } | |
127 | |
128 void WriteNode::SetAutofillProfileSpecifics( | |
129 const sync_pb::AutofillProfileSpecifics& new_value) { | |
130 sync_pb::EntitySpecifics entity_specifics; | |
131 entity_specifics.mutable_autofill_profile()-> | |
132 CopyFrom(new_value); | |
133 SetEntitySpecifics(entity_specifics); | |
134 } | |
135 | |
136 void WriteNode::SetBookmarkSpecifics( | |
137 const sync_pb::BookmarkSpecifics& new_value) { | |
138 sync_pb::EntitySpecifics entity_specifics; | |
139 entity_specifics.mutable_bookmark()->CopyFrom(new_value); | |
140 SetEntitySpecifics(entity_specifics); | |
141 } | |
142 | |
143 void WriteNode::SetNigoriSpecifics( | |
144 const sync_pb::NigoriSpecifics& new_value) { | |
145 sync_pb::EntitySpecifics entity_specifics; | |
146 entity_specifics.mutable_nigori()->CopyFrom(new_value); | |
147 SetEntitySpecifics(entity_specifics); | |
148 } | |
149 | |
150 void WriteNode::SetPasswordSpecifics( | |
151 const sync_pb::PasswordSpecificsData& data) { | |
152 DCHECK_EQ(syncable::PASSWORDS, GetModelType()); | |
153 | |
154 Cryptographer* cryptographer = GetTransaction()->GetCryptographer(); | |
155 | |
156 // We have to do the idempotency check here (vs in UpdateEntryWithEncryption) | |
157 // because Passwords have their encrypted data within the PasswordSpecifics, | |
158 // vs within the EntitySpecifics like all the other types. | |
159 const sync_pb::EntitySpecifics& old_specifics = GetEntry()->Get(SPECIFICS); | |
160 sync_pb::EntitySpecifics entity_specifics; | |
161 // Copy over the old specifics if they exist. | |
162 if (syncable::GetModelTypeFromSpecifics(old_specifics) == | |
163 syncable::PASSWORDS) { | |
164 entity_specifics.CopyFrom(old_specifics); | |
165 } else { | |
166 syncable::AddDefaultFieldValue(syncable::PASSWORDS, | |
167 &entity_specifics); | |
168 } | |
169 sync_pb::PasswordSpecifics* password_specifics = | |
170 entity_specifics.mutable_password(); | |
171 // This will only update password_specifics if the underlying unencrypted blob | |
172 // was different from |data| or was not encrypted with the proper passphrase. | |
173 if (!cryptographer->Encrypt(data, password_specifics->mutable_encrypted())) { | |
174 NOTREACHED() << "Failed to encrypt password, possibly due to sync node " | |
175 << "corruption"; | |
176 return; | |
177 } | |
178 SetEntitySpecifics(entity_specifics); | |
179 } | |
180 | |
181 void WriteNode::SetThemeSpecifics( | |
182 const sync_pb::ThemeSpecifics& new_value) { | |
183 sync_pb::EntitySpecifics entity_specifics; | |
184 entity_specifics.mutable_theme()->CopyFrom(new_value); | |
185 SetEntitySpecifics(entity_specifics); | |
186 } | |
187 | |
188 void WriteNode::SetSessionSpecifics( | |
189 const sync_pb::SessionSpecifics& new_value) { | |
190 sync_pb::EntitySpecifics entity_specifics; | |
191 entity_specifics.mutable_session()->CopyFrom(new_value); | |
192 SetEntitySpecifics(entity_specifics); | |
193 } | |
194 | |
195 void WriteNode::SetEntitySpecifics( | |
196 const sync_pb::EntitySpecifics& new_value) { | |
197 syncable::ModelType new_specifics_type = | |
198 syncable::GetModelTypeFromSpecifics(new_value); | |
199 DCHECK_NE(new_specifics_type, syncable::UNSPECIFIED); | |
200 DVLOG(1) << "Writing entity specifics of type " | |
201 << syncable::ModelTypeToString(new_specifics_type); | |
202 // GetModelType() can be unspecified if this is the first time this | |
203 // node is being initialized (see PutModelType()). Otherwise, it | |
204 // should match |new_specifics_type|. | |
205 if (GetModelType() != syncable::UNSPECIFIED) { | |
206 DCHECK_EQ(new_specifics_type, GetModelType()); | |
207 } | |
208 browser_sync::Cryptographer* cryptographer = | |
209 GetTransaction()->GetCryptographer(); | |
210 | |
211 // Preserve unknown fields. | |
212 const sync_pb::EntitySpecifics& old_specifics = entry_->Get(SPECIFICS); | |
213 sync_pb::EntitySpecifics new_specifics; | |
214 new_specifics.CopyFrom(new_value); | |
215 new_specifics.mutable_unknown_fields()->MergeFrom( | |
216 old_specifics.unknown_fields()); | |
217 | |
218 // Will update the entry if encryption was necessary. | |
219 if (!UpdateEntryWithEncryption(cryptographer, new_specifics, entry_)) { | |
220 return; | |
221 } | |
222 if (entry_->Get(SPECIFICS).has_encrypted()) { | |
223 // EncryptIfNecessary already updated the entry for us and marked for | |
224 // syncing if it was needed. Now we just make a copy of the unencrypted | |
225 // specifics so that if this node is updated, we do not have to decrypt the | |
226 // old data. Note that this only modifies the node's local data, not the | |
227 // entry itself. | |
228 SetUnencryptedSpecifics(new_value); | |
229 } | |
230 | |
231 DCHECK_EQ(new_specifics_type, GetModelType()); | |
232 } | |
233 | |
234 void WriteNode::ResetFromSpecifics() { | |
235 SetEntitySpecifics(GetEntitySpecifics()); | |
236 } | |
237 | |
238 void WriteNode::SetTypedUrlSpecifics( | |
239 const sync_pb::TypedUrlSpecifics& new_value) { | |
240 sync_pb::EntitySpecifics entity_specifics; | |
241 entity_specifics.mutable_typed_url()->CopyFrom(new_value); | |
242 SetEntitySpecifics(entity_specifics); | |
243 } | |
244 | |
245 void WriteNode::SetExtensionSpecifics( | |
246 const sync_pb::ExtensionSpecifics& new_value) { | |
247 sync_pb::EntitySpecifics entity_specifics; | |
248 entity_specifics.mutable_extension()->CopyFrom(new_value); | |
249 SetEntitySpecifics(entity_specifics); | |
250 } | |
251 | |
252 void WriteNode::SetExternalId(int64 id) { | |
253 if (GetExternalId() != id) | |
254 entry_->Put(syncable::LOCAL_EXTERNAL_ID, id); | |
255 } | |
256 | |
257 WriteNode::WriteNode(WriteTransaction* transaction) | |
258 : entry_(NULL), transaction_(transaction) { | |
259 DCHECK(transaction); | |
260 } | |
261 | |
262 WriteNode::~WriteNode() { | |
263 delete entry_; | |
264 } | |
265 | |
266 // Find an existing node matching the ID |id|, and bind this WriteNode to it. | |
267 // Return true on success. | |
268 BaseNode::InitByLookupResult WriteNode::InitByIdLookup(int64 id) { | |
269 DCHECK(!entry_) << "Init called twice"; | |
270 DCHECK_NE(id, kInvalidId); | |
271 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | |
272 syncable::GET_BY_HANDLE, id); | |
273 if (!entry_->good()) | |
274 return INIT_FAILED_ENTRY_NOT_GOOD; | |
275 if (entry_->Get(syncable::IS_DEL)) | |
276 return INIT_FAILED_ENTRY_IS_DEL; | |
277 return DecryptIfNecessary() ? INIT_OK : INIT_FAILED_DECRYPT_IF_NECESSARY; | |
278 } | |
279 | |
280 // Find a node by client tag, and bind this WriteNode to it. | |
281 // Return true if the write node was found, and was not deleted. | |
282 // Undeleting a deleted node is possible by ClientTag. | |
283 BaseNode::InitByLookupResult WriteNode::InitByClientTagLookup( | |
284 syncable::ModelType model_type, | |
285 const std::string& tag) { | |
286 DCHECK(!entry_) << "Init called twice"; | |
287 if (tag.empty()) | |
288 return INIT_FAILED_PRECONDITION; | |
289 | |
290 const std::string hash = GenerateSyncableHash(model_type, tag); | |
291 | |
292 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | |
293 syncable::GET_BY_CLIENT_TAG, hash); | |
294 if (!entry_->good()) | |
295 return INIT_FAILED_ENTRY_NOT_GOOD; | |
296 if (entry_->Get(syncable::IS_DEL)) | |
297 return INIT_FAILED_ENTRY_IS_DEL; | |
298 return DecryptIfNecessary() ? INIT_OK : INIT_FAILED_DECRYPT_IF_NECESSARY; | |
299 } | |
300 | |
301 BaseNode::InitByLookupResult WriteNode::InitByTagLookup( | |
302 const std::string& tag) { | |
303 DCHECK(!entry_) << "Init called twice"; | |
304 if (tag.empty()) | |
305 return INIT_FAILED_PRECONDITION; | |
306 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | |
307 syncable::GET_BY_SERVER_TAG, tag); | |
308 if (!entry_->good()) | |
309 return INIT_FAILED_ENTRY_NOT_GOOD; | |
310 if (entry_->Get(syncable::IS_DEL)) | |
311 return INIT_FAILED_ENTRY_IS_DEL; | |
312 syncable::ModelType model_type = GetModelType(); | |
313 DCHECK_EQ(syncable::NIGORI, model_type); | |
314 return INIT_OK; | |
315 } | |
316 | |
317 void WriteNode::PutModelType(syncable::ModelType model_type) { | |
318 // Set an empty specifics of the appropriate datatype. The presence | |
319 // of the specific field will identify the model type. | |
320 DCHECK(GetModelType() == model_type || | |
321 GetModelType() == syncable::UNSPECIFIED); // Immutable once set. | |
322 | |
323 sync_pb::EntitySpecifics specifics; | |
324 syncable::AddDefaultFieldValue(model_type, &specifics); | |
325 SetEntitySpecifics(specifics); | |
326 } | |
327 | |
328 // Create a new node with default properties, and bind this WriteNode to it. | |
329 // Return true on success. | |
330 bool WriteNode::InitByCreation(syncable::ModelType model_type, | |
331 const BaseNode& parent, | |
332 const BaseNode* predecessor) { | |
333 DCHECK(!entry_) << "Init called twice"; | |
334 // |predecessor| must be a child of |parent| or NULL. | |
335 if (predecessor && predecessor->GetParentId() != parent.GetId()) { | |
336 DCHECK(false); | |
337 return false; | |
338 } | |
339 | |
340 syncable::Id parent_id = parent.GetEntry()->Get(syncable::ID); | |
341 | |
342 // Start out with a dummy name. We expect | |
343 // the caller to set a meaningful name after creation. | |
344 string dummy(kDefaultNameForNewNodes); | |
345 | |
346 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | |
347 syncable::CREATE, parent_id, dummy); | |
348 | |
349 if (!entry_->good()) | |
350 return false; | |
351 | |
352 // Entries are untitled folders by default. | |
353 entry_->Put(syncable::IS_DIR, true); | |
354 | |
355 PutModelType(model_type); | |
356 | |
357 // Now set the predecessor, which sets IS_UNSYNCED as necessary. | |
358 return PutPredecessor(predecessor); | |
359 } | |
360 | |
361 // Create a new node with default properties and a client defined unique tag, | |
362 // and bind this WriteNode to it. | |
363 // Return true on success. If the tag exists in the database, then | |
364 // we will attempt to undelete the node. | |
365 // TODO(chron): Code datatype into hash tag. | |
366 // TODO(chron): Is model type ever lost? | |
367 bool WriteNode::InitUniqueByCreation(syncable::ModelType model_type, | |
368 const BaseNode& parent, | |
369 const std::string& tag) { | |
370 DCHECK(!entry_) << "Init called twice"; | |
371 if (tag.empty()) { | |
372 LOG(WARNING) << "InitUniqueByCreation failed due to empty tag."; | |
373 return false; | |
374 } | |
375 | |
376 const std::string hash = GenerateSyncableHash(model_type, tag); | |
377 | |
378 syncable::Id parent_id = parent.GetEntry()->Get(syncable::ID); | |
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->Get(syncable::IS_DEL)) { | |
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->Put(syncable::IS_DEL, 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->Put(syncable::NON_UNIQUE_NAME, dummy); | |
415 existing_entry->Put(syncable::PARENT_ID, parent_id); | |
416 entry_ = existing_entry.release(); | |
417 } else { | |
418 return false; | |
419 } | |
420 } else { | |
421 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), | |
422 syncable::CREATE, parent_id, dummy); | |
423 if (!entry_->good()) { | |
424 return false; | |
425 } | |
426 | |
427 // Only set IS_DIR for new entries. Don't bitflip undeleted ones. | |
428 entry_->Put(syncable::UNIQUE_CLIENT_TAG, hash); | |
429 } | |
430 | |
431 // We don't support directory and tag combinations. | |
432 entry_->Put(syncable::IS_DIR, false); | |
433 | |
434 // Will clear specifics data. | |
435 PutModelType(model_type); | |
436 | |
437 // Now set the predecessor, which sets IS_UNSYNCED as necessary. | |
438 return PutPredecessor(NULL); | |
439 } | |
440 | |
441 bool WriteNode::SetPosition(const BaseNode& new_parent, | |
442 const BaseNode* predecessor) { | |
443 // |predecessor| must be a child of |new_parent| or NULL. | |
444 if (predecessor && predecessor->GetParentId() != new_parent.GetId()) { | |
445 DCHECK(false); | |
446 return false; | |
447 } | |
448 | |
449 syncable::Id new_parent_id = new_parent.GetEntry()->Get(syncable::ID); | |
450 | |
451 // Filter out redundant changes if both the parent and the predecessor match. | |
452 if (new_parent_id == entry_->Get(syncable::PARENT_ID)) { | |
453 const syncable::Id& old = entry_->Get(syncable::PREV_ID); | |
454 if ((!predecessor && old.IsRoot()) || | |
455 (predecessor && (old == predecessor->GetEntry()->Get(syncable::ID)))) { | |
456 return true; | |
457 } | |
458 } | |
459 | |
460 // Atomically change the parent. This will fail if it would | |
461 // introduce a cycle in the hierarchy. | |
462 if (!entry_->Put(syncable::PARENT_ID, new_parent_id)) | |
463 return false; | |
464 | |
465 // Now set the predecessor, which sets IS_UNSYNCED as necessary. | |
466 return PutPredecessor(predecessor); | |
467 } | |
468 | |
469 const syncable::Entry* WriteNode::GetEntry() const { | |
470 return entry_; | |
471 } | |
472 | |
473 const BaseTransaction* WriteNode::GetTransaction() const { | |
474 return transaction_; | |
475 } | |
476 | |
477 void WriteNode::Remove() { | |
478 entry_->Put(syncable::IS_DEL, true); | |
479 MarkForSyncing(); | |
480 } | |
481 | |
482 bool WriteNode::PutPredecessor(const BaseNode* predecessor) { | |
483 syncable::Id predecessor_id = predecessor ? | |
484 predecessor->GetEntry()->Get(syncable::ID) : syncable::Id(); | |
485 if (!entry_->PutPredecessor(predecessor_id)) | |
486 return false; | |
487 // Mark this entry as unsynced, to wake up the syncer. | |
488 MarkForSyncing(); | |
489 | |
490 return true; | |
491 } | |
492 | |
493 void WriteNode::SetFaviconBytes(const vector<unsigned char>& bytes) { | |
494 sync_pb::BookmarkSpecifics new_value = GetBookmarkSpecifics(); | |
495 new_value.set_favicon(bytes.empty() ? NULL : &bytes[0], bytes.size()); | |
496 SetBookmarkSpecifics(new_value); | |
497 } | |
498 | |
499 void WriteNode::MarkForSyncing() { | |
500 syncable::MarkForSyncing(entry_); | |
501 } | |
502 | |
503 } // namespace sync_api | |
OLD | NEW |