1 // Copyright 2018 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 "components/autofill/core/browser/webdata/autofill_profile_sync_difference_tracker.h"
6
7 #include "base/strings/utf_string_conversions.h"
8 #include "components/autofill/core/browser/autofill_profile_sync_util.h"
9 #include "components/autofill/core/browser/data_model/autofill_profile.h"
10 #include "components/autofill/core/browser/data_model/autofill_profile_comparator.h"
11 #include "components/autofill/core/browser/field_types.h"
12 #include "components/autofill/core/browser/webdata/autofill_profile_sync_bridge.h"
13 #include "components/autofill/core/browser/webdata/autofill_table.h"
14 #include "components/sync/model/model_error.h"
15
16 namespace autofill {
17
18 using base::Optional;
19 using syncer::ModelError;
20
AutofillProfileSyncDifferenceTracker(AutofillTable * table)21 AutofillProfileSyncDifferenceTracker::AutofillProfileSyncDifferenceTracker(
22 AutofillTable* table)
23 : table_(table) {}
24
~AutofillProfileSyncDifferenceTracker()25 AutofillProfileSyncDifferenceTracker::~AutofillProfileSyncDifferenceTracker() {}
26
27 Optional<ModelError>
IncorporateRemoteProfile(std::unique_ptr<AutofillProfile> remote)28 AutofillProfileSyncDifferenceTracker::IncorporateRemoteProfile(
29 std::unique_ptr<AutofillProfile> remote) {
30 const std::string remote_storage_key =
31 GetStorageKeyFromAutofillProfile(*remote);
32
33 if (!GetLocalOnlyEntries()) {
34 return ModelError(FROM_HERE, "Failed reading from WebDatabase.");
35 }
36
37 Optional<AutofillProfile> local_with_same_storage_key =
38 ReadEntry(remote_storage_key);
39
40 if (local_with_same_storage_key) {
41 // The remote profile already exists locally with the same key. Update
42 // the local entry with remote data.
43 std::unique_ptr<AutofillProfile> updated =
44 std::make_unique<AutofillProfile>(local_with_same_storage_key.value());
45 // We ignore remote updates to a verified profile because we want to keep
46 // the exact version that the user edited by hand.
47 if (local_with_same_storage_key->IsVerified() && !remote->IsVerified()) {
48 return base::nullopt;
49 }
50 updated->OverwriteDataFrom(*remote);
51 // TODO(jkrcal): if |updated| deviates from |remote|, we should sync it back
52 // up. The only way |updated| can differ is having some extra fields
53 // compared to |remote|. Thus, this cannot lead to an infinite loop of
54 // commits from two clients as each commit decreases the set of empty
55 // fields. This invariant depends on the implementation of
56 // OverwriteDataFrom() and thus should be enforced by a DCHECK.
57
58 if (!updated->EqualsForSyncPurposes(*local_with_same_storage_key)) {
59 // We need to write back locally new changes in this entry.
60 update_to_local_.push_back(std::move(updated));
61 }
62 GetLocalOnlyEntries()->erase(remote_storage_key);
63 return base::nullopt;
64 }
65
66 // Check if profile appears under a different storage key to be de-duplicated.
67 // TODO(crbug.com/1043683): Deal with rare cases when an remote update
68 // contains several exact duplicates (with different guids). We should not
69 // only search in local only entries but also in |update_to_local_| and
70 // |add_to_local_|. Likely needs a bit of refactoring to make the resulting
71 // code easy to understand.
72 for (const auto& pair : *GetLocalOnlyEntries()) {
73 const std::string& local_storage_key = pair.first;
74 const AutofillProfile& local = *pair.second;
75
76 // Look for exact duplicates, compare only profile contents (and
77 // ignore origin and language code in comparison).
78 if (local.Compare(*remote) == 0) {
79 // A duplicate found: keep the version with the bigger storage key.
80 DVLOG(2)
81 << "[AUTOFILL SYNC] The profile "
82 << base::UTF16ToUTF8(local.GetRawInfo(NAME_FIRST))
83 << base::UTF16ToUTF8(local.GetRawInfo(NAME_LAST))
84 << " already exists with a different storage key; keep the bigger "
85 << (remote_storage_key > local_storage_key ? "remote" : "local")
86 << " key " << std::max(remote_storage_key, local_storage_key)
87 << " and delete the smaller key "
88 << std::min(remote_storage_key, local_storage_key);
89 if (remote_storage_key > local_storage_key) {
90 // We keep the remote entity and delete the local one.
91 // Ensure that a verified profile can never revert back to an unverified
92 // one. In such a case, take over the old origin for the new entry.
93 if (local.IsVerified() && !remote->IsVerified()) {
94 remote->set_origin(local.origin());
95 // Save a copy of the remote profile also to sync.
96 save_to_sync_.push_back(std::make_unique<AutofillProfile>(*remote));
97 }
98 add_to_local_.push_back(std::move(remote));
99 // Deleting from sync is a no-op if it is local-only so far.
100 // There are a few ways how a synced local entry A could theoretically
101 // receive a remote duplicate B with a larger GUID:
102 // 1) Remote entity B got uploaded by another client through initial
103 // sync. That client thus also knew about A and issued a deletion of
104 // A at the same time. This client (if receiving creation of B
105 // first) resolves the conflict in the same way and re-issues the
106 // deletion of A. In most cases the redundant deletion does not even
107 // get sent as the processor already knows A got deleted remotely.
108 // 2) Remote entity B got uploaded by another client through race
109 // condition (i.e. not knowing about A, yet). In practice, this only
110 // happens when two clients simultaneously convert a server profile
111 // into local profiles. If the other client goes offline before
112 // receiving A, this client is responsible for deleting A from the
113 // server and thus must issue a deletion. (In most cases, the other
114 // client does not go offline and thus both clients issue a deletion
115 // of A independently).
116 // 3) (a paranoid case) Remote entity B got uploaded by another client
117 // by an error, i.e. already as a duplicate given their local state.
118 // Through standard flows, it should be impossible (duplicates are
119 // cought early in PDM code so such a change attempt does not even
120 // propagate to the sync bridge). Still, it's good to treat this
121 // case here for robustness.
122 delete_from_sync_.insert(local_storage_key);
123 DeleteFromLocal(local_storage_key);
124 } else {
125 // We keep the local entity and delete the remote one.
126 // Ensure that a verified profile can never revert back to an unverified
127 // one. In such a case, modify the origin and re-upload. Otherwise,
128 // there's no need to upload it: either is was already uploaded before
129 // (if this is incremental sync) or we'll upload it with all the
130 // remaining data in GetLocalOnlyEntries (if this is an initial sync).
131 if (remote->IsVerified() && !local.IsVerified()) {
132 auto modified_local = std::make_unique<AutofillProfile>(local);
133 modified_local->set_origin(remote->origin());
134 update_to_local_.push_back(
135 std::make_unique<AutofillProfile>(*modified_local));
136 save_to_sync_.push_back(std::move(modified_local));
137 // The local entity is already marked for upload so it is not local
138 // only anymore (we do not want to upload it once again while flushing
139 // if this is initial sync).
140 GetLocalOnlyEntries()->erase(local_storage_key);
141 }
142 delete_from_sync_.insert(remote_storage_key);
143 }
144 return base::nullopt;
145 }
146 }
147
148 // If no duplicate was found, just add the remote profile.
149 add_to_local_.push_back(std::move(remote));
150 return base::nullopt;
151 }
152
153 Optional<ModelError>
IncorporateRemoteDelete(const std::string & storage_key)154 AutofillProfileSyncDifferenceTracker::IncorporateRemoteDelete(
155 const std::string& storage_key) {
156 DCHECK(!storage_key.empty());
157 DeleteFromLocal(storage_key);
158 return base::nullopt;
159 }
160
FlushToLocal(base::OnceClosure autofill_changes_callback)161 Optional<ModelError> AutofillProfileSyncDifferenceTracker::FlushToLocal(
162 base::OnceClosure autofill_changes_callback) {
163 for (const std::string& storage_key : delete_from_local_) {
164 if (!table_->RemoveAutofillProfile(storage_key)) {
165 return ModelError(FROM_HERE, "Failed deleting from WebDatabase");
166 }
167 }
168 for (const std::unique_ptr<AutofillProfile>& entry : add_to_local_) {
169 if (!table_->AddAutofillProfile(*entry)) {
170 return ModelError(FROM_HERE, "Failed updating WebDatabase");
171 }
172 }
173 for (const std::unique_ptr<AutofillProfile>& entry : update_to_local_) {
174 if (!table_->UpdateAutofillProfile(*entry)) {
175 return ModelError(FROM_HERE, "Failed updating WebDatabase");
176 }
177 }
178 if (!delete_from_local_.empty() || !add_to_local_.empty() ||
179 !update_to_local_.empty()) {
180 std::move(autofill_changes_callback).Run();
181 }
182 return base::nullopt;
183 }
184
FlushToSync(std::vector<std::unique_ptr<AutofillProfile>> * profiles_to_upload_to_sync,std::vector<std::string> * profiles_to_delete_from_sync)185 Optional<ModelError> AutofillProfileSyncDifferenceTracker::FlushToSync(
186 std::vector<std::unique_ptr<AutofillProfile>>* profiles_to_upload_to_sync,
187 std::vector<std::string>* profiles_to_delete_from_sync) {
188 for (std::unique_ptr<AutofillProfile>& entry : save_to_sync_) {
189 profiles_to_upload_to_sync->push_back(std::move(entry));
190 }
191 for (const std::string& entry : delete_from_sync_) {
192 profiles_to_delete_from_sync->push_back(std::move(entry));
193 }
194 return base::nullopt;
195 }
196
ReadEntry(const std::string & storage_key)197 Optional<AutofillProfile> AutofillProfileSyncDifferenceTracker::ReadEntry(
198 const std::string& storage_key) {
199 DCHECK(GetLocalOnlyEntries());
200 auto iter = GetLocalOnlyEntries()->find(storage_key);
201 if (iter != GetLocalOnlyEntries()->end()) {
202 return *iter->second;
203 }
204 return base::nullopt;
205 }
206
DeleteFromLocal(const std::string & storage_key)207 void AutofillProfileSyncDifferenceTracker::DeleteFromLocal(
208 const std::string& storage_key) {
209 DCHECK(GetLocalOnlyEntries());
210 delete_from_local_.insert(storage_key);
211 GetLocalOnlyEntries()->erase(storage_key);
212 }
213
214 std::map<std::string, std::unique_ptr<AutofillProfile>>*
GetLocalOnlyEntries()215 AutofillProfileSyncDifferenceTracker::GetLocalOnlyEntries() {
216 if (!InitializeLocalOnlyEntriesIfNeeded()) {
217 return nullptr;
218 }
219 return &local_only_entries_;
220 }
221
222 bool AutofillProfileSyncDifferenceTracker::
InitializeLocalOnlyEntriesIfNeeded()223 InitializeLocalOnlyEntriesIfNeeded() {
224 if (local_only_entries_initialized_) {
225 return true;
226 }
227
228 std::vector<std::unique_ptr<AutofillProfile>> entries;
229 if (!table_->GetAutofillProfiles(&entries)) {
230 return false;
231 }
232
233 for (std::unique_ptr<AutofillProfile>& entry : entries) {
234 std::string storage_key = GetStorageKeyFromAutofillProfile(*entry);
235 local_only_entries_[storage_key] = std::move(entry);
236 }
237
238 local_only_entries_initialized_ = true;
239 return true;
240 }
241
242 AutofillProfileInitialSyncDifferenceTracker::
AutofillProfileInitialSyncDifferenceTracker(AutofillTable * table)243 AutofillProfileInitialSyncDifferenceTracker(AutofillTable* table)
244 : AutofillProfileSyncDifferenceTracker(table) {}
245
246 AutofillProfileInitialSyncDifferenceTracker::
~AutofillProfileInitialSyncDifferenceTracker()247 ~AutofillProfileInitialSyncDifferenceTracker() {}
248
249 Optional<ModelError>
IncorporateRemoteDelete(const std::string & storage_key)250 AutofillProfileInitialSyncDifferenceTracker::IncorporateRemoteDelete(
251 const std::string& storage_key) {
252 // Remote delete is not allowed in initial sync.
253 NOTREACHED();
254 return base::nullopt;
255 }
256
FlushToSync(std::vector<std::unique_ptr<AutofillProfile>> * profiles_to_upload_to_sync,std::vector<std::string> * profiles_to_delete_from_sync)257 Optional<ModelError> AutofillProfileInitialSyncDifferenceTracker::FlushToSync(
258 std::vector<std::unique_ptr<AutofillProfile>>* profiles_to_upload_to_sync,
259 std::vector<std::string>* profiles_to_delete_from_sync) {
260 // First, flush standard updates to sync.
261 AutofillProfileSyncDifferenceTracker::FlushToSync(
262 profiles_to_upload_to_sync, profiles_to_delete_from_sync);
263
264 // For initial sync, we additionally need to upload all local only entries.
265 if (!GetLocalOnlyEntries()) {
266 return ModelError(FROM_HERE, "Failed reading from WebDatabase.");
267 }
268 for (auto& pair : *GetLocalOnlyEntries()) {
269 std::string storage_key = pair.first;
270 // No deletions coming from remote are allowed for initial sync.
271 DCHECK(delete_from_local_.count(storage_key) == 0);
272 profiles_to_upload_to_sync->push_back(std::move(pair.second));
273 }
274 return base::nullopt;
275 }
276
277 Optional<ModelError>
MergeSimilarEntriesForInitialSync(const std::string & app_locale)278 AutofillProfileInitialSyncDifferenceTracker::MergeSimilarEntriesForInitialSync(
279 const std::string& app_locale) {
280 if (!GetLocalOnlyEntries()) {
281 return ModelError(FROM_HERE, "Failed reading from WebDatabase.");
282 }
283
284 // This merge cannot happen on the fly during IncorporateRemoteSpecifics().
285 // Namely, we do not want to merge a local entry with a _similar_ remote
286 // entry if anoter perfectly fitting remote entry comes later during the
287 // initial sync (a remote entry fits perfectly to a given local entry if
288 // it has fully equal data or even the same storage key). After all the calls
289 // to IncorporateRemoteSpecifics() are over, GetLocalOnlyEntries() only
290 // contains unmatched entries that can be safely merged with similar remote
291 // entries.
292
293 AutofillProfileComparator comparator(app_locale);
294 // Loop over all new remote entries to find merge candidates. Using
295 // non-const reference because we want to update |remote| in place if
296 // needed.
297 for (std::unique_ptr<AutofillProfile>& remote : add_to_local_) {
298 Optional<AutofillProfile> local =
299 FindMergeableLocalEntry(*remote, comparator);
300 if (!local) {
301 continue;
302 }
303
304 DVLOG(2)
305 << "[AUTOFILL SYNC] A similar profile to "
306 << base::UTF16ToUTF8(remote->GetRawInfo(NAME_FIRST))
307 << base::UTF16ToUTF8(remote->GetRawInfo(NAME_LAST))
308 << " already exists with a different storage key; keep the remote key"
309 << GetStorageKeyFromAutofillProfile(*remote)
310 << ", merge local data into it and delete the local key"
311 << GetStorageKeyFromAutofillProfile(*local);
312
313 // For similar profile pairs, the local profile is always removed and its
314 // content merged (if applicable) in the profile that came from sync.
315 AutofillProfile remote_before_merge = *remote;
316 remote->MergeDataFrom(*local, app_locale);
317 if (!remote->EqualsForSyncPurposes(remote_before_merge)) {
318 // We need to sync new changes in the entry back to the server.
319 save_to_sync_.push_back(std::make_unique<AutofillProfile>(*remote));
320 // |remote| is updated in place within |add_to_local_| so the newest
321 // merged version is stored to local.
322 }
323
324 DeleteFromLocal(GetStorageKeyFromAutofillProfile(*local));
325 }
326
327 return base::nullopt;
328 }
329
330 Optional<AutofillProfile>
FindMergeableLocalEntry(const AutofillProfile & remote,const AutofillProfileComparator & comparator)331 AutofillProfileInitialSyncDifferenceTracker::FindMergeableLocalEntry(
332 const AutofillProfile& remote,
333 const AutofillProfileComparator& comparator) {
334 DCHECK(GetLocalOnlyEntries());
335
336 // Both the remote and the local entry need to be non-verified to be
337 // mergeable.
338 if (remote.IsVerified()) {
339 return base::nullopt;
340 }
341
342 // Check if there is a mergeable local profile.
343 for (const auto& pair : *GetLocalOnlyEntries()) {
344 const AutofillProfile& local_candidate = *pair.second;
345 if (!local_candidate.IsVerified() &&
346 comparator.AreMergeable(local_candidate, remote)) {
347 return local_candidate;
348 }
349 }
350 return base::nullopt;
351 }
352
353 } // namespace autofill
354