Home | History | Annotate | Download | only in internal_api
      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