Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(211)

Side by Side Diff: chrome/browser/sync/internal_api/write_node.cc

Issue 7624009: Original patch by rlarocque@chromium.org at http://codereview.chromium.org/7633077/ (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix windows build pt5 Created 9 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 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/json/json_writer.h"
8 #include "base/utf_string_conversions.h"
9 #include "base/values.h"
10 #include "chrome/browser/sync/engine/nigori_util.h"
11 #include "chrome/browser/sync/engine/syncapi_internal.h"
12 #include "chrome/browser/sync/internal_api/base_transaction.h"
13 #include "chrome/browser/sync/internal_api/write_transaction.h"
14 #include "chrome/browser/sync/protocol/app_specifics.pb.h"
15 #include "chrome/browser/sync/protocol/autofill_specifics.pb.h"
16 #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h"
17 #include "chrome/browser/sync/protocol/extension_specifics.pb.h"
18 #include "chrome/browser/sync/protocol/password_specifics.pb.h"
19 #include "chrome/browser/sync/protocol/session_specifics.pb.h"
20 #include "chrome/browser/sync/protocol/theme_specifics.pb.h"
21 #include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
22 #include "chrome/browser/sync/syncable/syncable.h"
23 #include "chrome/browser/sync/util/cryptographer.h"
24
25 using browser_sync::Cryptographer;
26 using std::string;
27 using std::vector;
28 using syncable::kEncryptedString;
29 using syncable::SPECIFICS;
30
31 namespace sync_api {
32
33 static const char kDefaultNameForNewNodes[] = " ";
34
35 //////////////////////////////////////////////////////////////////////////
36 // Static helper functions.
37
38 // When taking a name from the syncapi, append a space if it matches the
39 // pattern of a server-illegal name followed by zero or more spaces.
40 static void SyncAPINameToServerName(const std::wstring& sync_api_name,
41 std::string* out) {
42 *out = WideToUTF8(sync_api_name);
43 if (IsNameServerIllegalAfterTrimming(*out))
44 out->append(" ");
45 }
46
47 bool WriteNode::UpdateEntryWithEncryption(
48 browser_sync::Cryptographer* cryptographer,
49 const sync_pb::EntitySpecifics& new_specifics,
50 syncable::MutableEntry* entry) {
51 syncable::ModelType type = syncable::GetModelTypeFromSpecifics(new_specifics);
52 DCHECK_GE(type, syncable::FIRST_REAL_MODEL_TYPE);
53 syncable::ModelTypeSet encrypted_types = cryptographer->GetEncryptedTypes();
54
55 sync_pb::EntitySpecifics generated_specifics;
56 if (type == syncable::PASSWORDS || // Has own encryption scheme.
57 type == syncable::NIGORI || // Encrypted separately.
58 encrypted_types.count(type) == 0 ||
59 new_specifics.has_encrypted()) {
60 // No encryption required.
61 generated_specifics.CopyFrom(new_specifics);
62 } else {
63 // Encrypt new_specifics into generated_specifics.
64 if (VLOG_IS_ON(2)) {
65 scoped_ptr<DictionaryValue> value(entry->ToValue());
66 std::string info;
67 base::JSONWriter::Write(value.get(), true, &info);
68 VLOG(2) << "Encrypting specifics of type "
69 << syncable::ModelTypeToString(type)
70 << " with content: "
71 << info;
72 }
73 if (!cryptographer->is_initialized())
74 return false;
75 syncable::AddDefaultExtensionValue(type, &generated_specifics);
76 if (!cryptographer->Encrypt(new_specifics,
77 generated_specifics.mutable_encrypted())) {
78 NOTREACHED() << "Could not encrypt data for node of type "
79 << syncable::ModelTypeToString(type);
80 return false;
81 }
82 }
83
84 const sync_pb::EntitySpecifics& old_specifics = entry->Get(SPECIFICS);
85 if (AreSpecificsEqual(cryptographer, old_specifics, generated_specifics)) {
86 // Even if the data is the same but the old specifics are encrypted with an
87 // old key, we should go ahead and re-encrypt with the new key.
88 if ((!old_specifics.has_encrypted() &&
89 !generated_specifics.has_encrypted()) ||
90 cryptographer->CanDecryptUsingDefaultKey(old_specifics.encrypted())) {
91 VLOG(2) << "Specifics of type " << syncable::ModelTypeToString(type)
92 << " already match, dropping change.";
93 return true;
94 }
95 // TODO(zea): Add some way to keep track of how often we're reencrypting
96 // because of a passphrase change.
97 }
98
99 if (generated_specifics.has_encrypted()) {
100 // Overwrite the possibly sensitive non-specifics data.
101 entry->Put(syncable::NON_UNIQUE_NAME, kEncryptedString);
102 // For bookmarks we actually put bogus data into the unencrypted specifics,
103 // else the server will try to do it for us.
104 if (type == syncable::BOOKMARKS) {
105 sync_pb::BookmarkSpecifics* bookmark_specifics =
106 generated_specifics.MutableExtension(sync_pb::bookmark);
107 if (!entry->Get(syncable::IS_DIR))
108 bookmark_specifics->set_url(kEncryptedString);
109 bookmark_specifics->set_title(kEncryptedString);
110 }
111 }
112 entry->Put(syncable::SPECIFICS, generated_specifics);
113 syncable::MarkForSyncing(entry);
114 return true;
115 }
116
117 void WriteNode::SetIsFolder(bool folder) {
118 if (entry_->Get(syncable::IS_DIR) == folder)
119 return; // Skip redundant changes.
120
121 entry_->Put(syncable::IS_DIR, folder);
122 MarkForSyncing();
123 }
124
125 void WriteNode::SetTitle(const std::wstring& title) {
126 std::string server_legal_name;
127 SyncAPINameToServerName(title, &server_legal_name);
128
129 string old_name = entry_->Get(syncable::NON_UNIQUE_NAME);
130
131 if (server_legal_name == old_name)
132 return; // Skip redundant changes.
133
134 // Only set NON_UNIQUE_NAME to the title if we're not encrypted.
135 if (GetEntitySpecifics().has_encrypted())
136 entry_->Put(syncable::NON_UNIQUE_NAME, kEncryptedString);
137 else
138 entry_->Put(syncable::NON_UNIQUE_NAME, server_legal_name);
139
140 // For bookmarks, we also set the title field in the specifics.
141 // TODO(zea): refactor bookmarks to not need this functionality.
142 if (GetModelType() == syncable::BOOKMARKS) {
143 sync_pb::BookmarkSpecifics new_value = GetBookmarkSpecifics();
144 new_value.set_title(server_legal_name);
145 SetBookmarkSpecifics(new_value); // Does it's own encryption checking.
146 }
147
148 MarkForSyncing();
149 }
150
151 void WriteNode::SetURL(const GURL& url) {
152 sync_pb::BookmarkSpecifics new_value = GetBookmarkSpecifics();
153 new_value.set_url(url.spec());
154 SetBookmarkSpecifics(new_value);
155 }
156
157 void WriteNode::SetAppSpecifics(
158 const sync_pb::AppSpecifics& new_value) {
159 sync_pb::EntitySpecifics entity_specifics;
160 entity_specifics.MutableExtension(sync_pb::app)->CopyFrom(new_value);
161 SetEntitySpecifics(entity_specifics);
162 }
163
164 void WriteNode::SetAutofillSpecifics(
165 const sync_pb::AutofillSpecifics& new_value) {
166 sync_pb::EntitySpecifics entity_specifics;
167 entity_specifics.MutableExtension(sync_pb::autofill)->CopyFrom(new_value);
168 SetEntitySpecifics(entity_specifics);
169 }
170
171 void WriteNode::SetAutofillProfileSpecifics(
172 const sync_pb::AutofillProfileSpecifics& new_value) {
173 sync_pb::EntitySpecifics entity_specifics;
174 entity_specifics.MutableExtension(sync_pb::autofill_profile)->
175 CopyFrom(new_value);
176 SetEntitySpecifics(entity_specifics);
177 }
178
179 void WriteNode::SetBookmarkSpecifics(
180 const sync_pb::BookmarkSpecifics& new_value) {
181 sync_pb::EntitySpecifics entity_specifics;
182 entity_specifics.MutableExtension(sync_pb::bookmark)->CopyFrom(new_value);
183 SetEntitySpecifics(entity_specifics);
184 }
185
186 void WriteNode::SetNigoriSpecifics(
187 const sync_pb::NigoriSpecifics& new_value) {
188 sync_pb::EntitySpecifics entity_specifics;
189 entity_specifics.MutableExtension(sync_pb::nigori)->CopyFrom(new_value);
190 SetEntitySpecifics(entity_specifics);
191 }
192
193 void WriteNode::SetPasswordSpecifics(
194 const sync_pb::PasswordSpecificsData& data) {
195 DCHECK_EQ(syncable::PASSWORDS, GetModelType());
196
197 Cryptographer* cryptographer = GetTransaction()->GetCryptographer();
198
199 // Idempotency check to prevent unnecessary syncing: if the plaintexts match
200 // and the old ciphertext is encrypted with the most current key, there's
201 // nothing to do here. Because each encryption is seeded with a different
202 // random value, checking for equivalence post-encryption doesn't suffice.
203 const sync_pb::EncryptedData& old_ciphertext =
204 GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::password).encrypted();
205 scoped_ptr<sync_pb::PasswordSpecificsData> old_plaintext(
206 DecryptPasswordSpecifics(GetEntry()->Get(SPECIFICS), cryptographer));
207 if (old_plaintext.get() &&
208 old_plaintext->SerializeAsString() == data.SerializeAsString() &&
209 cryptographer->CanDecryptUsingDefaultKey(old_ciphertext)) {
210 return;
211 }
212
213 sync_pb::PasswordSpecifics new_value;
214 if (!cryptographer->Encrypt(data, new_value.mutable_encrypted())) {
215 NOTREACHED() << "Failed to encrypt password, possibly due to sync node "
216 << "corruption";
217 return;
218 }
219
220 sync_pb::EntitySpecifics entity_specifics;
221 entity_specifics.MutableExtension(sync_pb::password)->CopyFrom(new_value);
222 SetEntitySpecifics(entity_specifics);
223 }
224
225 void WriteNode::SetThemeSpecifics(
226 const sync_pb::ThemeSpecifics& new_value) {
227 sync_pb::EntitySpecifics entity_specifics;
228 entity_specifics.MutableExtension(sync_pb::theme)->CopyFrom(new_value);
229 SetEntitySpecifics(entity_specifics);
230 }
231
232 void WriteNode::SetSessionSpecifics(
233 const sync_pb::SessionSpecifics& new_value) {
234 sync_pb::EntitySpecifics entity_specifics;
235 entity_specifics.MutableExtension(sync_pb::session)->CopyFrom(new_value);
236 SetEntitySpecifics(entity_specifics);
237 }
238
239 void WriteNode::SetEntitySpecifics(
240 const sync_pb::EntitySpecifics& new_value) {
241 syncable::ModelType new_specifics_type =
242 syncable::GetModelTypeFromSpecifics(new_value);
243 DCHECK_NE(new_specifics_type, syncable::UNSPECIFIED);
244 VLOG(1) << "Writing entity specifics of type "
245 << syncable::ModelTypeToString(new_specifics_type);
246 // GetModelType() can be unspecified if this is the first time this
247 // node is being initialized (see PutModelType()). Otherwise, it
248 // should match |new_specifics_type|.
249 if (GetModelType() != syncable::UNSPECIFIED) {
250 DCHECK_EQ(new_specifics_type, GetModelType());
251 }
252 browser_sync::Cryptographer* cryptographer =
253 GetTransaction()->GetCryptographer();
254
255 // Preserve unknown fields.
256 const sync_pb::EntitySpecifics& old_specifics = entry_->Get(SPECIFICS);
257 sync_pb::EntitySpecifics new_specifics;
258 new_specifics.CopyFrom(new_value);
259 new_specifics.mutable_unknown_fields()->MergeFrom(
260 old_specifics.unknown_fields());
261
262 // Will update the entry if encryption was necessary.
263 if (!UpdateEntryWithEncryption(cryptographer, new_specifics, entry_)) {
264 return;
265 }
266 if (entry_->Get(SPECIFICS).has_encrypted()) {
267 // EncryptIfNecessary already updated the entry for us and marked for
268 // syncing if it was needed. Now we just make a copy of the unencrypted
269 // specifics so that if this node is updated, we do not have to decrypt the
270 // old data. Note that this only modifies the node's local data, not the
271 // entry itself.
272 SetUnencryptedSpecifics(new_value);
273 }
274
275 DCHECK_EQ(new_specifics_type, GetModelType());
276 }
277
278 void WriteNode::ResetFromSpecifics() {
279 SetEntitySpecifics(GetEntitySpecifics());
280 }
281
282 void WriteNode::SetTypedUrlSpecifics(
283 const sync_pb::TypedUrlSpecifics& new_value) {
284 sync_pb::EntitySpecifics entity_specifics;
285 entity_specifics.MutableExtension(sync_pb::typed_url)->CopyFrom(new_value);
286 SetEntitySpecifics(entity_specifics);
287 }
288
289 void WriteNode::SetExtensionSpecifics(
290 const sync_pb::ExtensionSpecifics& new_value) {
291 sync_pb::EntitySpecifics entity_specifics;
292 entity_specifics.MutableExtension(sync_pb::extension)->CopyFrom(new_value);
293 SetEntitySpecifics(entity_specifics);
294 }
295
296 void WriteNode::SetExternalId(int64 id) {
297 if (GetExternalId() != id)
298 entry_->Put(syncable::LOCAL_EXTERNAL_ID, id);
299 }
300
301 WriteNode::WriteNode(WriteTransaction* transaction)
302 : entry_(NULL), transaction_(transaction) {
303 DCHECK(transaction);
304 }
305
306 WriteNode::~WriteNode() {
307 delete entry_;
308 }
309
310 // Find an existing node matching the ID |id|, and bind this WriteNode to it.
311 // Return true on success.
312 bool WriteNode::InitByIdLookup(int64 id) {
313 DCHECK(!entry_) << "Init called twice";
314 DCHECK_NE(id, kInvalidId);
315 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
316 syncable::GET_BY_HANDLE, id);
317 return (entry_->good() && !entry_->Get(syncable::IS_DEL) &&
318 DecryptIfNecessary());
319 }
320
321 // Find a node by client tag, and bind this WriteNode to it.
322 // Return true if the write node was found, and was not deleted.
323 // Undeleting a deleted node is possible by ClientTag.
324 bool WriteNode::InitByClientTagLookup(syncable::ModelType model_type,
325 const std::string& tag) {
326 DCHECK(!entry_) << "Init called twice";
327 if (tag.empty())
328 return false;
329
330 const std::string hash = GenerateSyncableHash(model_type, tag);
331
332 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
333 syncable::GET_BY_CLIENT_TAG, hash);
334 return (entry_->good() && !entry_->Get(syncable::IS_DEL) &&
335 DecryptIfNecessary());
336 }
337
338 bool WriteNode::InitByTagLookup(const std::string& tag) {
339 DCHECK(!entry_) << "Init called twice";
340 if (tag.empty())
341 return false;
342 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
343 syncable::GET_BY_SERVER_TAG, tag);
344 if (!entry_->good())
345 return false;
346 if (entry_->Get(syncable::IS_DEL))
347 return false;
348 syncable::ModelType model_type = GetModelType();
349 DCHECK_EQ(syncable::NIGORI, model_type);
350 return true;
351 }
352
353 void WriteNode::PutModelType(syncable::ModelType model_type) {
354 // Set an empty specifics of the appropriate datatype. The presence
355 // of the specific extension will identify the model type.
356 DCHECK(GetModelType() == model_type ||
357 GetModelType() == syncable::UNSPECIFIED); // Immutable once set.
358
359 sync_pb::EntitySpecifics specifics;
360 syncable::AddDefaultExtensionValue(model_type, &specifics);
361 SetEntitySpecifics(specifics);
362 }
363
364 // Create a new node with default properties, and bind this WriteNode to it.
365 // Return true on success.
366 bool WriteNode::InitByCreation(syncable::ModelType model_type,
367 const BaseNode& parent,
368 const BaseNode* predecessor) {
369 DCHECK(!entry_) << "Init called twice";
370 // |predecessor| must be a child of |parent| or NULL.
371 if (predecessor && predecessor->GetParentId() != parent.GetId()) {
372 DCHECK(false);
373 return false;
374 }
375
376 syncable::Id parent_id = parent.GetEntry()->Get(syncable::ID);
377
378 // Start out with a dummy name. We expect
379 // the caller to set a meaningful name after creation.
380 string dummy(kDefaultNameForNewNodes);
381
382 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
383 syncable::CREATE, parent_id, dummy);
384
385 if (!entry_->good())
386 return false;
387
388 // Entries are untitled folders by default.
389 entry_->Put(syncable::IS_DIR, true);
390
391 PutModelType(model_type);
392
393 // Now set the predecessor, which sets IS_UNSYNCED as necessary.
394 PutPredecessor(predecessor);
395
396 return true;
397 }
398
399 // Create a new node with default properties and a client defined unique tag,
400 // and bind this WriteNode to it.
401 // Return true on success. If the tag exists in the database, then
402 // we will attempt to undelete the node.
403 // TODO(chron): Code datatype into hash tag.
404 // TODO(chron): Is model type ever lost?
405 bool WriteNode::InitUniqueByCreation(syncable::ModelType model_type,
406 const BaseNode& parent,
407 const std::string& tag) {
408 DCHECK(!entry_) << "Init called twice";
409
410 const std::string hash = GenerateSyncableHash(model_type, tag);
411
412 syncable::Id parent_id = parent.GetEntry()->Get(syncable::ID);
413
414 // Start out with a dummy name. We expect
415 // the caller to set a meaningful name after creation.
416 string dummy(kDefaultNameForNewNodes);
417
418 // Check if we have this locally and need to undelete it.
419 scoped_ptr<syncable::MutableEntry> existing_entry(
420 new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
421 syncable::GET_BY_CLIENT_TAG, hash));
422
423 if (existing_entry->good()) {
424 if (existing_entry->Get(syncable::IS_DEL)) {
425 // Rules for undelete:
426 // BASE_VERSION: Must keep the same.
427 // ID: Essential to keep the same.
428 // META_HANDLE: Must be the same, so we can't "split" the entry.
429 // IS_DEL: Must be set to false, will cause reindexing.
430 // This one is weird because IS_DEL is true for "update only"
431 // items. It should be OK to undelete an update only.
432 // MTIME/CTIME: Seems reasonable to just leave them alone.
433 // IS_UNSYNCED: Must set this to true or face database insurrection.
434 // We do this below this block.
435 // IS_UNAPPLIED_UPDATE: Either keep it the same or also set BASE_VERSION
436 // to SERVER_VERSION. We keep it the same here.
437 // IS_DIR: We'll leave it the same.
438 // SPECIFICS: Reset it.
439
440 existing_entry->Put(syncable::IS_DEL, false);
441
442 // Client tags are immutable and must be paired with the ID.
443 // If a server update comes down with an ID and client tag combo,
444 // and it already exists, always overwrite it and store only one copy.
445 // We have to undelete entries because we can't disassociate IDs from
446 // tags and updates.
447
448 existing_entry->Put(syncable::NON_UNIQUE_NAME, dummy);
449 existing_entry->Put(syncable::PARENT_ID, parent_id);
450 entry_ = existing_entry.release();
451 } else {
452 return false;
453 }
454 } else {
455 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
456 syncable::CREATE, parent_id, dummy);
457 if (!entry_->good()) {
458 return false;
459 }
460
461 // Only set IS_DIR for new entries. Don't bitflip undeleted ones.
462 entry_->Put(syncable::UNIQUE_CLIENT_TAG, hash);
463 }
464
465 // We don't support directory and tag combinations.
466 entry_->Put(syncable::IS_DIR, false);
467
468 // Will clear specifics data.
469 PutModelType(model_type);
470
471 // Now set the predecessor, which sets IS_UNSYNCED as necessary.
472 PutPredecessor(NULL);
473
474 return true;
475 }
476
477 bool WriteNode::SetPosition(const BaseNode& new_parent,
478 const BaseNode* predecessor) {
479 // |predecessor| must be a child of |new_parent| or NULL.
480 if (predecessor && predecessor->GetParentId() != new_parent.GetId()) {
481 DCHECK(false);
482 return false;
483 }
484
485 syncable::Id new_parent_id = new_parent.GetEntry()->Get(syncable::ID);
486
487 // Filter out redundant changes if both the parent and the predecessor match.
488 if (new_parent_id == entry_->Get(syncable::PARENT_ID)) {
489 const syncable::Id& old = entry_->Get(syncable::PREV_ID);
490 if ((!predecessor && old.IsRoot()) ||
491 (predecessor && (old == predecessor->GetEntry()->Get(syncable::ID)))) {
492 return true;
493 }
494 }
495
496 // Atomically change the parent. This will fail if it would
497 // introduce a cycle in the hierarchy.
498 if (!entry_->Put(syncable::PARENT_ID, new_parent_id))
499 return false;
500
501 // Now set the predecessor, which sets IS_UNSYNCED as necessary.
502 PutPredecessor(predecessor);
503
504 return true;
505 }
506
507 const syncable::Entry* WriteNode::GetEntry() const {
508 return entry_;
509 }
510
511 const BaseTransaction* WriteNode::GetTransaction() const {
512 return transaction_;
513 }
514
515 void WriteNode::Remove() {
516 entry_->Put(syncable::IS_DEL, true);
517 MarkForSyncing();
518 }
519
520 void WriteNode::PutPredecessor(const BaseNode* predecessor) {
521 syncable::Id predecessor_id = predecessor ?
522 predecessor->GetEntry()->Get(syncable::ID) : syncable::Id();
523 entry_->PutPredecessor(predecessor_id);
524 // Mark this entry as unsynced, to wake up the syncer.
525 MarkForSyncing();
526 }
527
528 void WriteNode::SetFaviconBytes(const vector<unsigned char>& bytes) {
529 sync_pb::BookmarkSpecifics new_value = GetBookmarkSpecifics();
530 new_value.set_favicon(bytes.empty() ? NULL : &bytes[0], bytes.size());
531 SetBookmarkSpecifics(new_value);
532 }
533
534 void WriteNode::MarkForSyncing() {
535 syncable::MarkForSyncing(entry_);
536 }
537
538 } // namespace sync_api
OLDNEW
« no previous file with comments | « chrome/browser/sync/internal_api/write_node.h ('k') | chrome/browser/sync/internal_api/write_transaction.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698