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 "base/values.h" 12 #include "sync/internal_api/public/base_transaction.h" 13 #include "sync/internal_api/syncapi_internal.h" 14 #include "sync/protocol/app_specifics.pb.h" 15 #include "sync/protocol/autofill_specifics.pb.h" 16 #include "sync/protocol/bookmark_specifics.pb.h" 17 #include "sync/protocol/extension_specifics.pb.h" 18 #include "sync/protocol/nigori_specifics.pb.h" 19 #include "sync/protocol/password_specifics.pb.h" 20 #include "sync/protocol/session_specifics.pb.h" 21 #include "sync/protocol/theme_specifics.pb.h" 22 #include "sync/protocol/typed_url_specifics.pb.h" 23 #include "sync/syncable/directory.h" 24 #include "sync/syncable/entry.h" 25 #include "sync/syncable/syncable_id.h" 26 #include "sync/util/time.h" 27 28 using sync_pb::AutofillProfileSpecifics; 29 30 namespace syncer { 31 32 using syncable::SPECIFICS; 33 34 // Helper function to look up the int64 metahandle of an object given the ID 35 // string. 36 static int64 IdToMetahandle(syncable::BaseTransaction* trans, 37 const syncable::Id& id) { 38 syncable::Entry entry(trans, syncable::GET_BY_ID, id); 39 if (!entry.good()) 40 return kInvalidId; 41 return entry.Get(syncable::META_HANDLE); 42 } 43 44 static bool EndsWithSpace(const std::string& string) { 45 return !string.empty() && *string.rbegin() == ' '; 46 } 47 48 // In the reverse direction, if a server name matches the pattern of a 49 // server-illegal name followed by one or more spaces, remove the trailing 50 // space. 51 static void ServerNameToSyncAPIName(const std::string& server_name, 52 std::string* out) { 53 CHECK(out); 54 int length_to_copy = server_name.length(); 55 if (IsNameServerIllegalAfterTrimming(server_name) && 56 EndsWithSpace(server_name)) { 57 --length_to_copy; 58 } 59 *out = std::string(server_name.c_str(), length_to_copy); 60 } 61 62 BaseNode::BaseNode() : password_data_(new sync_pb::PasswordSpecificsData) {} 63 64 BaseNode::~BaseNode() {} 65 66 bool BaseNode::DecryptIfNecessary() { 67 if (!GetEntry()->Get(syncable::UNIQUE_SERVER_TAG).empty()) 68 return true; // Ignore unique folders. 69 const sync_pb::EntitySpecifics& specifics = 70 GetEntry()->Get(syncable::SPECIFICS); 71 if (specifics.has_password()) { 72 // Passwords have their own legacy encryption structure. 73 scoped_ptr<sync_pb::PasswordSpecificsData> data(DecryptPasswordSpecifics( 74 specifics, GetTransaction()->GetCryptographer())); 75 if (!data) { 76 LOG(ERROR) << "Failed to decrypt password specifics."; 77 return false; 78 } 79 password_data_.swap(data); 80 return true; 81 } 82 83 // We assume any node with the encrypted field set has encrypted data and if 84 // not we have no work to do, with the exception of bookmarks. For bookmarks 85 // we must make sure the bookmarks data has the title field supplied. If not, 86 // we fill the unencrypted_data_ with a copy of the bookmark specifics that 87 // follows the new bookmarks format. 88 if (!specifics.has_encrypted()) { 89 if (GetModelType() == BOOKMARKS && 90 !specifics.bookmark().has_title() && 91 !GetTitle().empty()) { // Last check ensures this isn't a new node. 92 // We need to fill in the title. 93 std::string title = GetTitle(); 94 std::string server_legal_title; 95 SyncAPINameToServerName(title, &server_legal_title); 96 DVLOG(1) << "Reading from legacy bookmark, manually returning title " 97 << title; 98 unencrypted_data_.CopyFrom(specifics); 99 unencrypted_data_.mutable_bookmark()->set_title( 100 server_legal_title); 101 } 102 return true; 103 } 104 105 const sync_pb::EncryptedData& encrypted = specifics.encrypted(); 106 std::string plaintext_data = GetTransaction()->GetCryptographer()-> 107 DecryptToString(encrypted); 108 if (plaintext_data.length() == 0) { 109 LOG(ERROR) << "Failed to decrypt encrypted node of type " 110 << ModelTypeToString(GetModelType()) << "."; 111 // Debugging for crbug.com/123223. We failed to decrypt the data, which 112 // means we applied an update without having the key or lost the key at a 113 // later point. 114 CHECK(false); 115 return false; 116 } else if (!unencrypted_data_.ParseFromString(plaintext_data)) { 117 // Debugging for crbug.com/123223. We should never succeed in decrypting 118 // but fail to parse into a protobuf. 119 CHECK(false); 120 return false; 121 } 122 DVLOG(2) << "Decrypted specifics of type " 123 << ModelTypeToString(GetModelType()) 124 << " with content: " << plaintext_data; 125 return true; 126 } 127 128 const sync_pb::EntitySpecifics& BaseNode::GetUnencryptedSpecifics( 129 const syncable::Entry* entry) const { 130 const sync_pb::EntitySpecifics& specifics = entry->Get(SPECIFICS); 131 if (specifics.has_encrypted()) { 132 DCHECK_NE(GetModelTypeFromSpecifics(unencrypted_data_), UNSPECIFIED); 133 return unencrypted_data_; 134 } else { 135 // Due to the change in bookmarks format, we need to check to see if this is 136 // a legacy bookmarks (and has no title field in the proto). If it is, we 137 // return the unencrypted_data_, which was filled in with the title by 138 // DecryptIfNecessary(). 139 if (GetModelType() == BOOKMARKS) { 140 const sync_pb::BookmarkSpecifics& bookmark_specifics = 141 specifics.bookmark(); 142 if (bookmark_specifics.has_title() || 143 GetTitle().empty() || // For the empty node case 144 !GetEntry()->Get(syncable::UNIQUE_SERVER_TAG).empty()) { 145 // It's possible we previously had to convert and set 146 // |unencrypted_data_| but then wrote our own data, so we allow 147 // |unencrypted_data_| to be non-empty. 148 return specifics; 149 } else { 150 DCHECK_EQ(GetModelTypeFromSpecifics(unencrypted_data_), BOOKMARKS); 151 return unencrypted_data_; 152 } 153 } else { 154 DCHECK_EQ(GetModelTypeFromSpecifics(unencrypted_data_), UNSPECIFIED); 155 return specifics; 156 } 157 } 158 } 159 160 int64 BaseNode::GetParentId() const { 161 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), 162 GetEntry()->Get(syncable::PARENT_ID)); 163 } 164 165 int64 BaseNode::GetId() const { 166 return GetEntry()->Get(syncable::META_HANDLE); 167 } 168 169 base::Time BaseNode::GetModificationTime() const { 170 return GetEntry()->Get(syncable::MTIME); 171 } 172 173 bool BaseNode::GetIsFolder() const { 174 return GetEntry()->Get(syncable::IS_DIR); 175 } 176 177 std::string BaseNode::GetTitle() const { 178 std::string result; 179 // TODO(zea): refactor bookmarks to not need this functionality. 180 if (BOOKMARKS == GetModelType() && 181 GetEntry()->Get(syncable::SPECIFICS).has_encrypted()) { 182 // Special case for legacy bookmarks dealing with encryption. 183 ServerNameToSyncAPIName(GetBookmarkSpecifics().title(), &result); 184 } else { 185 ServerNameToSyncAPIName(GetEntry()->Get(syncable::NON_UNIQUE_NAME), 186 &result); 187 } 188 return result; 189 } 190 191 bool BaseNode::HasChildren() const { 192 syncable::Directory* dir = GetTransaction()->GetDirectory(); 193 syncable::BaseTransaction* trans = GetTransaction()->GetWrappedTrans(); 194 return dir->HasChildren(trans, GetEntry()->Get(syncable::ID)); 195 } 196 197 int64 BaseNode::GetPredecessorId() const { 198 syncable::Id id_string = GetEntry()->GetPredecessorId(); 199 if (id_string.IsRoot()) 200 return kInvalidId; 201 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string); 202 } 203 204 int64 BaseNode::GetSuccessorId() const { 205 syncable::Id id_string = GetEntry()->GetSuccessorId(); 206 if (id_string.IsRoot()) 207 return kInvalidId; 208 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string); 209 } 210 211 int64 BaseNode::GetFirstChildId() const { 212 syncable::Id id_string = GetEntry()->GetFirstChildId(); 213 if (id_string.IsRoot()) 214 return kInvalidId; 215 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string); 216 } 217 218 void BaseNode::GetChildIds(std::vector<int64>* result) const { 219 GetEntry()->GetChildHandles(result); 220 } 221 222 int BaseNode::GetTotalNodeCount() const { 223 return GetEntry()->GetTotalNodeCount(); 224 } 225 226 int BaseNode::GetPositionIndex() const { 227 return GetEntry()->GetPositionIndex(); 228 } 229 230 base::DictionaryValue* BaseNode::GetSummaryAsValue() const { 231 base::DictionaryValue* node_info = new base::DictionaryValue(); 232 node_info->SetString("id", base::Int64ToString(GetId())); 233 node_info->SetBoolean("isFolder", GetIsFolder()); 234 node_info->SetString("title", GetTitle()); 235 node_info->Set("type", ModelTypeToValue(GetModelType())); 236 return node_info; 237 } 238 239 base::DictionaryValue* BaseNode::GetDetailsAsValue() const { 240 base::DictionaryValue* node_info = GetSummaryAsValue(); 241 node_info->SetString( 242 "modificationTime", GetTimeDebugString(GetModificationTime())); 243 node_info->SetString("parentId", base::Int64ToString(GetParentId())); 244 // Specifics are already in the Entry value, so no need to duplicate 245 // it here. 246 node_info->SetString("externalId", base::Int64ToString(GetExternalId())); 247 if (GetEntry()->ShouldMaintainPosition() && 248 !GetEntry()->Get(syncable::IS_DEL)) { 249 node_info->SetString("successorId", base::Int64ToString(GetSuccessorId())); 250 node_info->SetString( 251 "predecessorId", base::Int64ToString(GetPredecessorId())); 252 } 253 if (GetEntry()->Get(syncable::IS_DIR)) { 254 node_info->SetString( 255 "firstChildId", base::Int64ToString(GetFirstChildId())); 256 } 257 node_info->Set( 258 "entry", GetEntry()->ToValue(GetTransaction()->GetCryptographer())); 259 return node_info; 260 } 261 262 int64 BaseNode::GetExternalId() const { 263 return GetEntry()->Get(syncable::LOCAL_EXTERNAL_ID); 264 } 265 266 const sync_pb::AppSpecifics& BaseNode::GetAppSpecifics() const { 267 DCHECK_EQ(GetModelType(), APPS); 268 return GetEntitySpecifics().app(); 269 } 270 271 const sync_pb::AutofillSpecifics& BaseNode::GetAutofillSpecifics() const { 272 DCHECK_EQ(GetModelType(), AUTOFILL); 273 return GetEntitySpecifics().autofill(); 274 } 275 276 const AutofillProfileSpecifics& BaseNode::GetAutofillProfileSpecifics() const { 277 DCHECK_EQ(GetModelType(), AUTOFILL_PROFILE); 278 return GetEntitySpecifics().autofill_profile(); 279 } 280 281 const sync_pb::BookmarkSpecifics& BaseNode::GetBookmarkSpecifics() const { 282 DCHECK_EQ(GetModelType(), BOOKMARKS); 283 return GetEntitySpecifics().bookmark(); 284 } 285 286 const sync_pb::NigoriSpecifics& BaseNode::GetNigoriSpecifics() const { 287 DCHECK_EQ(GetModelType(), NIGORI); 288 return GetEntitySpecifics().nigori(); 289 } 290 291 const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const { 292 DCHECK_EQ(GetModelType(), PASSWORDS); 293 return *password_data_; 294 } 295 296 const sync_pb::ThemeSpecifics& BaseNode::GetThemeSpecifics() const { 297 DCHECK_EQ(GetModelType(), THEMES); 298 return GetEntitySpecifics().theme(); 299 } 300 301 const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const { 302 DCHECK_EQ(GetModelType(), TYPED_URLS); 303 return GetEntitySpecifics().typed_url(); 304 } 305 306 const sync_pb::ExtensionSpecifics& BaseNode::GetExtensionSpecifics() const { 307 DCHECK_EQ(GetModelType(), EXTENSIONS); 308 return GetEntitySpecifics().extension(); 309 } 310 311 const sync_pb::SessionSpecifics& BaseNode::GetSessionSpecifics() const { 312 DCHECK_EQ(GetModelType(), SESSIONS); 313 return GetEntitySpecifics().session(); 314 } 315 316 const sync_pb::ManagedUserSettingSpecifics& 317 BaseNode::GetManagedUserSettingSpecifics() const { 318 DCHECK_EQ(GetModelType(), MANAGED_USER_SETTINGS); 319 return GetEntitySpecifics().managed_user_setting(); 320 } 321 322 const sync_pb::ManagedUserSpecifics& BaseNode::GetManagedUserSpecifics() const { 323 DCHECK_EQ(GetModelType(), MANAGED_USERS); 324 return GetEntitySpecifics().managed_user(); 325 } 326 327 const sync_pb::DeviceInfoSpecifics& BaseNode::GetDeviceInfoSpecifics() const { 328 DCHECK_EQ(GetModelType(), DEVICE_INFO); 329 return GetEntitySpecifics().device_info(); 330 } 331 332 const sync_pb::ExperimentsSpecifics& BaseNode::GetExperimentsSpecifics() const { 333 DCHECK_EQ(GetModelType(), EXPERIMENTS); 334 return GetEntitySpecifics().experiments(); 335 } 336 337 const sync_pb::PriorityPreferenceSpecifics& 338 BaseNode::GetPriorityPreferenceSpecifics() const { 339 DCHECK_EQ(GetModelType(), PRIORITY_PREFERENCES); 340 return GetEntitySpecifics().priority_preference(); 341 } 342 343 const sync_pb::EntitySpecifics& BaseNode::GetEntitySpecifics() const { 344 return GetUnencryptedSpecifics(GetEntry()); 345 } 346 347 ModelType BaseNode::GetModelType() const { 348 return GetEntry()->GetModelType(); 349 } 350 351 void BaseNode::SetUnencryptedSpecifics( 352 const sync_pb::EntitySpecifics& specifics) { 353 ModelType type = GetModelTypeFromSpecifics(specifics); 354 DCHECK_NE(UNSPECIFIED, type); 355 if (GetModelType() != UNSPECIFIED) { 356 DCHECK_EQ(GetModelType(), type); 357 } 358 unencrypted_data_.CopyFrom(specifics); 359 } 360 361 } // namespace syncer 362