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
27using sync_pb::AutofillProfileSpecifics;
28
29namespace syncer {
30
31using syncable::SPECIFICS;
32
33// Helper function to look up the int64 metahandle of an object given the ID
34// string.
35static 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
43BaseNode::BaseNode() : password_data_(new sync_pb::PasswordSpecificsData) {}
44
45BaseNode::~BaseNode() {}
46
47bool 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
109const 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
141int64 BaseNode::GetParentId() const {
142  return IdToMetahandle(GetTransaction()->GetWrappedTrans(),
143                        GetEntry()->GetParentId());
144}
145
146int64 BaseNode::GetId() const {
147  return GetEntry()->GetMetahandle();
148}
149
150base::Time BaseNode::GetModificationTime() const {
151  return GetEntry()->GetMtime();
152}
153
154bool BaseNode::GetIsFolder() const {
155  return GetEntry()->GetIsDir();
156}
157
158std::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
172bool BaseNode::HasChildren() const {
173  syncable::Directory* dir = GetTransaction()->GetDirectory();
174  syncable::BaseTransaction* trans = GetTransaction()->GetWrappedTrans();
175  return dir->HasChildren(trans, GetEntry()->GetId());
176}
177
178int64 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
185int64 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
192int64 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
199void BaseNode::GetChildIds(std::vector<int64>* result) const {
200  GetEntry()->GetChildHandles(result);
201}
202
203int BaseNode::GetTotalNodeCount() const {
204  return GetEntry()->GetTotalNodeCount();
205}
206
207int BaseNode::GetPositionIndex() const {
208  return GetEntry()->GetPositionIndex();
209}
210
211base::DictionaryValue* BaseNode::ToValue() const {
212  return GetEntry()->ToValue(GetTransaction()->GetCryptographer());
213}
214
215int64 BaseNode::GetExternalId() const {
216  return GetEntry()->GetLocalExternalId();
217}
218
219const sync_pb::AppSpecifics& BaseNode::GetAppSpecifics() const {
220  DCHECK_EQ(GetModelType(), APPS);
221  return GetEntitySpecifics().app();
222}
223
224const sync_pb::AutofillSpecifics& BaseNode::GetAutofillSpecifics() const {
225  DCHECK_EQ(GetModelType(), AUTOFILL);
226  return GetEntitySpecifics().autofill();
227}
228
229const AutofillProfileSpecifics& BaseNode::GetAutofillProfileSpecifics() const {
230  DCHECK_EQ(GetModelType(), AUTOFILL_PROFILE);
231  return GetEntitySpecifics().autofill_profile();
232}
233
234const sync_pb::BookmarkSpecifics& BaseNode::GetBookmarkSpecifics() const {
235  DCHECK_EQ(GetModelType(), BOOKMARKS);
236  return GetEntitySpecifics().bookmark();
237}
238
239const sync_pb::NigoriSpecifics& BaseNode::GetNigoriSpecifics() const {
240  DCHECK_EQ(GetModelType(), NIGORI);
241  return GetEntitySpecifics().nigori();
242}
243
244const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const {
245  DCHECK_EQ(GetModelType(), PASSWORDS);
246  return *password_data_;
247}
248
249const sync_pb::ThemeSpecifics& BaseNode::GetThemeSpecifics() const {
250  DCHECK_EQ(GetModelType(), THEMES);
251  return GetEntitySpecifics().theme();
252}
253
254const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const {
255  DCHECK_EQ(GetModelType(), TYPED_URLS);
256  return GetEntitySpecifics().typed_url();
257}
258
259const sync_pb::ExtensionSpecifics& BaseNode::GetExtensionSpecifics() const {
260  DCHECK_EQ(GetModelType(), EXTENSIONS);
261  return GetEntitySpecifics().extension();
262}
263
264const sync_pb::SessionSpecifics& BaseNode::GetSessionSpecifics() const {
265  DCHECK_EQ(GetModelType(), SESSIONS);
266  return GetEntitySpecifics().session();
267}
268
269const sync_pb::DeviceInfoSpecifics& BaseNode::GetDeviceInfoSpecifics() const {
270  DCHECK_EQ(GetModelType(), DEVICE_INFO);
271  return GetEntitySpecifics().device_info();
272}
273
274const sync_pb::ExperimentsSpecifics& BaseNode::GetExperimentsSpecifics() const {
275  DCHECK_EQ(GetModelType(), EXPERIMENTS);
276  return GetEntitySpecifics().experiments();
277}
278
279const sync_pb::PriorityPreferenceSpecifics&
280    BaseNode::GetPriorityPreferenceSpecifics() const {
281  DCHECK_EQ(GetModelType(), PRIORITY_PREFERENCES);
282  return GetEntitySpecifics().priority_preference();
283}
284
285const sync_pb::EntitySpecifics& BaseNode::GetEntitySpecifics() const {
286  return GetUnencryptedSpecifics(GetEntry());
287}
288
289ModelType BaseNode::GetModelType() const {
290  return GetEntry()->GetModelType();
291}
292
293const 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
303void 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