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/base_node.h"
6
7 #include <stack>
8
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "sync/internal_api/public/base_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/nigori_specifics.pb.h"
18 #include "sync/protocol/password_specifics.pb.h"
19 #include "sync/protocol/session_specifics.pb.h"
20 #include "sync/protocol/theme_specifics.pb.h"
21 #include "sync/protocol/typed_url_specifics.pb.h"
22 #include "sync/syncable/directory.h"
23 #include "sync/syncable/entry.h"
24 #include "sync/syncable/syncable_id.h"
25 #include "sync/util/time.h"
26
27 using sync_pb::AutofillProfileSpecifics;
28
29 namespace syncer {
30
31 using syncable::SPECIFICS;
32
33 // Helper function to look up the int64 metahandle of an object given the ID
34 // string.
IdToMetahandle(syncable::BaseTransaction * trans,const syncable::Id & id)35 static int64 IdToMetahandle(syncable::BaseTransaction* trans,
36 const syncable::Id& id) {
37 syncable::Entry entry(trans, syncable::GET_BY_ID, id);
38 if (!entry.good())
39 return kInvalidId;
40 return entry.GetMetahandle();
41 }
42
BaseNode()43 BaseNode::BaseNode() : password_data_(new sync_pb::PasswordSpecificsData) {}
44
~BaseNode()45 BaseNode::~BaseNode() {}
46
DecryptIfNecessary()47 bool BaseNode::DecryptIfNecessary() {
48 if (!GetEntry()->GetUniqueServerTag().empty())
49 return true; // Ignore unique folders.
50 const sync_pb::EntitySpecifics& specifics =
51 GetEntry()->GetSpecifics();
52 if (specifics.has_password()) {
53 // Passwords have their own legacy encryption structure.
54 scoped_ptr<sync_pb::PasswordSpecificsData> data(DecryptPasswordSpecifics(
55 specifics, GetTransaction()->GetCryptographer()));
56 if (!data) {
57 LOG(ERROR) << "Failed to decrypt password specifics.";
58 return false;
59 }
60 password_data_.swap(data);
61 return true;
62 }
63
64 // We assume any node with the encrypted field set has encrypted data and if
65 // not we have no work to do, with the exception of bookmarks. For bookmarks
66 // we must make sure the bookmarks data has the title field supplied. If not,
67 // we fill the unencrypted_data_ with a copy of the bookmark specifics that
68 // follows the new bookmarks format.
69 if (!specifics.has_encrypted()) {
70 if (GetModelType() == BOOKMARKS &&
71 !specifics.bookmark().has_title() &&
72 !GetTitle().empty()) { // Last check ensures this isn't a new node.
73 // We need to fill in the title.
74 std::string title = GetTitle();
75 std::string server_legal_title;
76 SyncAPINameToServerName(title, &server_legal_title);
77 DVLOG(1) << "Reading from legacy bookmark, manually returning title "
78 << title;
79 unencrypted_data_.CopyFrom(specifics);
80 unencrypted_data_.mutable_bookmark()->set_title(
81 server_legal_title);
82 }
83 return true;
84 }
85
86 const sync_pb::EncryptedData& encrypted = specifics.encrypted();
87 std::string plaintext_data = GetTransaction()->GetCryptographer()->
88 DecryptToString(encrypted);
89 if (plaintext_data.length() == 0) {
90 LOG(ERROR) << "Failed to decrypt encrypted node of type "
91 << ModelTypeToString(GetModelType()) << ".";
92 // Debugging for crbug.com/123223. We failed to decrypt the data, which
93 // means we applied an update without having the key or lost the key at a
94 // later point.
95 CHECK(false);
96 return false;
97 } else if (!unencrypted_data_.ParseFromString(plaintext_data)) {
98 // Debugging for crbug.com/123223. We should never succeed in decrypting
99 // but fail to parse into a protobuf.
100 CHECK(false);
101 return false;
102 }
103 DVLOG(2) << "Decrypted specifics of type "
104 << ModelTypeToString(GetModelType())
105 << " with content: " << plaintext_data;
106 return true;
107 }
108
GetUnencryptedSpecifics(const syncable::Entry * entry) const109 const sync_pb::EntitySpecifics& BaseNode::GetUnencryptedSpecifics(
110 const syncable::Entry* entry) const {
111 const sync_pb::EntitySpecifics& specifics = entry->GetSpecifics();
112 if (specifics.has_encrypted()) {
113 DCHECK_NE(GetModelTypeFromSpecifics(unencrypted_data_), UNSPECIFIED);
114 return unencrypted_data_;
115 } else {
116 // Due to the change in bookmarks format, we need to check to see if this is
117 // a legacy bookmarks (and has no title field in the proto). If it is, we
118 // return the unencrypted_data_, which was filled in with the title by
119 // DecryptIfNecessary().
120 if (GetModelType() == BOOKMARKS) {
121 const sync_pb::BookmarkSpecifics& bookmark_specifics =
122 specifics.bookmark();
123 if (bookmark_specifics.has_title() ||
124 GetTitle().empty() || // For the empty node case
125 !GetEntry()->GetUniqueServerTag().empty()) {
126 // It's possible we previously had to convert and set
127 // |unencrypted_data_| but then wrote our own data, so we allow
128 // |unencrypted_data_| to be non-empty.
129 return specifics;
130 } else {
131 DCHECK_EQ(GetModelTypeFromSpecifics(unencrypted_data_), BOOKMARKS);
132 return unencrypted_data_;
133 }
134 } else {
135 DCHECK_EQ(GetModelTypeFromSpecifics(unencrypted_data_), UNSPECIFIED);
136 return specifics;
137 }
138 }
139 }
140
GetParentId() const141 int64 BaseNode::GetParentId() const {
142 return IdToMetahandle(GetTransaction()->GetWrappedTrans(),
143 GetEntry()->GetParentId());
144 }
145
GetId() const146 int64 BaseNode::GetId() const {
147 return GetEntry()->GetMetahandle();
148 }
149
GetModificationTime() const150 base::Time BaseNode::GetModificationTime() const {
151 return GetEntry()->GetMtime();
152 }
153
GetIsFolder() const154 bool BaseNode::GetIsFolder() const {
155 return GetEntry()->GetIsDir();
156 }
157
GetTitle() const158 std::string BaseNode::GetTitle() const {
159 std::string result;
160 // TODO(zea): refactor bookmarks to not need this functionality.
161 if (BOOKMARKS == GetModelType() &&
162 GetEntry()->GetSpecifics().has_encrypted()) {
163 // Special case for legacy bookmarks dealing with encryption.
164 ServerNameToSyncAPIName(GetBookmarkSpecifics().title(), &result);
165 } else {
166 ServerNameToSyncAPIName(GetEntry()->GetNonUniqueName(),
167 &result);
168 }
169 return result;
170 }
171
HasChildren() const172 bool BaseNode::HasChildren() const {
173 syncable::Directory* dir = GetTransaction()->GetDirectory();
174 syncable::BaseTransaction* trans = GetTransaction()->GetWrappedTrans();
175 return dir->HasChildren(trans, GetEntry()->GetId());
176 }
177
GetPredecessorId() const178 int64 BaseNode::GetPredecessorId() const {
179 syncable::Id id_string = GetEntry()->GetPredecessorId();
180 if (id_string.IsRoot())
181 return kInvalidId;
182 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
183 }
184
GetSuccessorId() const185 int64 BaseNode::GetSuccessorId() const {
186 syncable::Id id_string = GetEntry()->GetSuccessorId();
187 if (id_string.IsRoot())
188 return kInvalidId;
189 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
190 }
191
GetFirstChildId() const192 int64 BaseNode::GetFirstChildId() const {
193 syncable::Id id_string = GetEntry()->GetFirstChildId();
194 if (id_string.IsRoot())
195 return kInvalidId;
196 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
197 }
198
GetChildIds(std::vector<int64> * result) const199 void BaseNode::GetChildIds(std::vector<int64>* result) const {
200 GetEntry()->GetChildHandles(result);
201 }
202
GetTotalNodeCount() const203 int BaseNode::GetTotalNodeCount() const {
204 return GetEntry()->GetTotalNodeCount();
205 }
206
GetPositionIndex() const207 int BaseNode::GetPositionIndex() const {
208 return GetEntry()->GetPositionIndex();
209 }
210
ToValue() const211 base::DictionaryValue* BaseNode::ToValue() const {
212 return GetEntry()->ToValue(GetTransaction()->GetCryptographer());
213 }
214
GetExternalId() const215 int64 BaseNode::GetExternalId() const {
216 return GetEntry()->GetLocalExternalId();
217 }
218
GetAppSpecifics() const219 const sync_pb::AppSpecifics& BaseNode::GetAppSpecifics() const {
220 DCHECK_EQ(GetModelType(), APPS);
221 return GetEntitySpecifics().app();
222 }
223
GetAutofillSpecifics() const224 const sync_pb::AutofillSpecifics& BaseNode::GetAutofillSpecifics() const {
225 DCHECK_EQ(GetModelType(), AUTOFILL);
226 return GetEntitySpecifics().autofill();
227 }
228
GetAutofillProfileSpecifics() const229 const AutofillProfileSpecifics& BaseNode::GetAutofillProfileSpecifics() const {
230 DCHECK_EQ(GetModelType(), AUTOFILL_PROFILE);
231 return GetEntitySpecifics().autofill_profile();
232 }
233
GetBookmarkSpecifics() const234 const sync_pb::BookmarkSpecifics& BaseNode::GetBookmarkSpecifics() const {
235 DCHECK_EQ(GetModelType(), BOOKMARKS);
236 return GetEntitySpecifics().bookmark();
237 }
238
GetNigoriSpecifics() const239 const sync_pb::NigoriSpecifics& BaseNode::GetNigoriSpecifics() const {
240 DCHECK_EQ(GetModelType(), NIGORI);
241 return GetEntitySpecifics().nigori();
242 }
243
GetPasswordSpecifics() const244 const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const {
245 DCHECK_EQ(GetModelType(), PASSWORDS);
246 return *password_data_;
247 }
248
GetThemeSpecifics() const249 const sync_pb::ThemeSpecifics& BaseNode::GetThemeSpecifics() const {
250 DCHECK_EQ(GetModelType(), THEMES);
251 return GetEntitySpecifics().theme();
252 }
253
GetTypedUrlSpecifics() const254 const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const {
255 DCHECK_EQ(GetModelType(), TYPED_URLS);
256 return GetEntitySpecifics().typed_url();
257 }
258
GetExtensionSpecifics() const259 const sync_pb::ExtensionSpecifics& BaseNode::GetExtensionSpecifics() const {
260 DCHECK_EQ(GetModelType(), EXTENSIONS);
261 return GetEntitySpecifics().extension();
262 }
263
GetSessionSpecifics() const264 const sync_pb::SessionSpecifics& BaseNode::GetSessionSpecifics() const {
265 DCHECK_EQ(GetModelType(), SESSIONS);
266 return GetEntitySpecifics().session();
267 }
268
GetDeviceInfoSpecifics() const269 const sync_pb::DeviceInfoSpecifics& BaseNode::GetDeviceInfoSpecifics() const {
270 DCHECK_EQ(GetModelType(), DEVICE_INFO);
271 return GetEntitySpecifics().device_info();
272 }
273
GetExperimentsSpecifics() const274 const sync_pb::ExperimentsSpecifics& BaseNode::GetExperimentsSpecifics() const {
275 DCHECK_EQ(GetModelType(), EXPERIMENTS);
276 return GetEntitySpecifics().experiments();
277 }
278
279 const sync_pb::PriorityPreferenceSpecifics&
GetPriorityPreferenceSpecifics() const280 BaseNode::GetPriorityPreferenceSpecifics() const {
281 DCHECK_EQ(GetModelType(), PRIORITY_PREFERENCES);
282 return GetEntitySpecifics().priority_preference();
283 }
284
GetEntitySpecifics() const285 const sync_pb::EntitySpecifics& BaseNode::GetEntitySpecifics() const {
286 return GetUnencryptedSpecifics(GetEntry());
287 }
288
GetModelType() const289 ModelType BaseNode::GetModelType() const {
290 return GetEntry()->GetModelType();
291 }
292
GetAttachmentIds() const293 const syncer::AttachmentIdList BaseNode::GetAttachmentIds() const {
294 AttachmentIdList result;
295 const sync_pb::AttachmentMetadata& metadata =
296 GetEntry()->GetAttachmentMetadata();
297 for (int i = 0; i < metadata.record_size(); ++i) {
298 result.push_back(AttachmentId::CreateFromProto(metadata.record(i).id()));
299 }
300 return result;
301 }
302
SetUnencryptedSpecifics(const sync_pb::EntitySpecifics & specifics)303 void BaseNode::SetUnencryptedSpecifics(
304 const sync_pb::EntitySpecifics& specifics) {
305 ModelType type = GetModelTypeFromSpecifics(specifics);
306 DCHECK_NE(UNSPECIFIED, type);
307 if (GetModelType() != UNSPECIFIED) {
308 DCHECK_EQ(GetModelType(), type);
309 }
310 unencrypted_data_.CopyFrom(specifics);
311 }
312
313 } // namespace syncer
314