summaryrefslogtreecommitdiff
path: root/chromium/components/password_manager/core/browser/form_parsing/ios_form_parser.cc
blob: 501a3b193beb2c41362da0564e883313ed9d9e69 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/password_manager/core/browser/form_parsing/ios_form_parser.h"

#include <algorithm>
#include <utility>
#include <vector>

#include "base/strings/string_split.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/password_form.h"

using autofill::FormData;
using autofill::FormFieldData;
using autofill::PasswordForm;

using FieldPointersVector = std::vector<const FormFieldData*>;

namespace password_manager {

namespace {

constexpr char kAutocompleteUsername[] = "username";
constexpr char kAutocompleteCurrentPassword[] = "current-password";
constexpr char kAutocompleteNewPassword[] = "new-password";
constexpr char kAutocompleteCreditCardPrefix[] = "cc-";

// Helper struct that is used to return results from the parsing function.
struct ParseResult {
  const FormFieldData* username_field = nullptr;
  const FormFieldData* password_field = nullptr;
  const FormFieldData* new_password_field = nullptr;
  const FormFieldData* confirmation_password_field = nullptr;

  bool IsEmpty() {
    return password_field == nullptr && new_password_field == nullptr;
  }
};

// Checks in a case-insensitive way if credit card autocomplete attributes for
// the given |element| are present.
bool HasCreditCardAutocompleteAttributes(const FormFieldData& field) {
  std::vector<base::StringPiece> tokens = base::SplitStringPiece(
      field.autocomplete_attribute, base::kWhitespaceASCII,
      base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  return std::find_if(
             tokens.begin(), tokens.end(), [](base::StringPiece token) {
               return base::StartsWith(token, kAutocompleteCreditCardPrefix,
                                       base::CompareCase::INSENSITIVE_ASCII);
             }) != tokens.end();
}

// Checks in a case-insensitive way if the autocomplete attribute for the given
// |element| is present and has the specified |value_in_lowercase|.
bool HasAutocompleteAttributeValue(const FormFieldData& field,
                                   base::StringPiece value_in_lowercase) {
  std::vector<base::StringPiece> tokens = base::SplitStringPiece(
      field.autocomplete_attribute, base::kWhitespaceASCII,
      base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);

  return std::find_if(tokens.begin(), tokens.end(),
                      [value_in_lowercase](base::StringPiece token) {
                        return base::LowerCaseEqualsASCII(token,
                                                          value_in_lowercase);
                      }) != tokens.end();
}

// Returns text fields from |fields|.
FieldPointersVector GetTextFields(const std::vector<FormFieldData>& fields) {
  FieldPointersVector result;
  result.reserve(fields.size());
  for (const auto& field : fields) {
    if (field.IsTextInputElement())
      result.push_back(&field);
  }
  return result;
}

// Returns fields that do not have credit card related autocomplete attributes.
FieldPointersVector GetNonCreditCardFields(const FieldPointersVector& fields) {
  FieldPointersVector result;
  result.reserve(fields.size());
  for (const auto* field : fields) {
    if (!HasCreditCardAutocompleteAttributes(*field))
      result.push_back(field);
  }
  return result;
}

// Returns true iff there is a password field.
bool HasPasswordField(const FieldPointersVector& fields) {
  for (const FormFieldData* field : fields)
    if (field->form_control_type == "password")
      return true;
  return false;
}

// Returns true iff there is a focusable field.
bool HasFocusableField(const FieldPointersVector& fields) {
  for (const FormFieldData* field : fields) {
    if (field->is_focusable)
      return true;
  }
  return false;
}

// Tries to parse |fields| based on autocomplete attributes.
// Assumption on the usage autocomplete attributes:
// 1. Not more than 1 field with autocomplete=username.
// 2. Not more than 1 field with autocomplete=current-password.
// 3. Not more than 2 fields with autocomplete=new-password.
// In case of violating of any of these assumption, parsing is unsuccessful.
// Returns nullptr if parsing is unsuccessful.
std::unique_ptr<ParseResult> ParseUsingAutocomplete(
    const FieldPointersVector& fields) {
  std::unique_ptr<ParseResult> result = std::make_unique<ParseResult>();
  for (const FormFieldData* field : fields) {
    if (HasAutocompleteAttributeValue(*field, kAutocompleteUsername)) {
      if (result->username_field)
        return nullptr;
      result->username_field = field;
    } else if (HasAutocompleteAttributeValue(*field,
                                             kAutocompleteCurrentPassword)) {
      if (result->password_field)
        return nullptr;
      result->password_field = field;
    } else if (HasAutocompleteAttributeValue(*field,
                                             kAutocompleteNewPassword)) {
      // The first field with autocomplete=new-password is considered to be
      // new_password_field and the second is confirmation_password_field.
      if (!result->new_password_field)
        result->new_password_field = field;
      else if (!result->confirmation_password_field)
        result->confirmation_password_field = field;
      else
        return nullptr;
    }
  }

  return result->IsEmpty() ? nullptr : std::move(result);
}

// Returns the fields of |fields| which are password fields if |is_password| is
// true and text fields otherwise. In addition, it drops non-focusable fields if
// |only_focusable| is true, and empty fields if |only_non_empty| is true.
FieldPointersVector FilterFields(const FieldPointersVector& fields,
                                 bool is_password,
                                 bool only_focusable,
                                 bool only_non_empty) {
  FieldPointersVector result;
  for (const FormFieldData* field : fields) {
    if ((field->form_control_type == "password") != is_password)
      continue;
    if (only_focusable && !field->is_focusable)
      continue;
    if (only_non_empty && field->value.empty())
      continue;
    result.push_back(field);
  }
  return result;
}

// Returns only relevant password fields from |fields|. Namely
// 1. If there is a focusable password field, return only focusable.
// 2. If mode == SAVING return only non-empty fields (for saving empty fields
// are useless).
// Note that focusability is the proxy for visibility.
FieldPointersVector GetRelevantPasswords(const FieldPointersVector& fields,
                                         FormParsingMode mode) {
  const bool is_there_focusable_password_field =
      std::any_of(fields.begin(), fields.end(), [](const auto* field) {
        return field->is_focusable && field->form_control_type == "password";
      });

  return FilterFields(fields, true, is_there_focusable_password_field,
                      mode == FormParsingMode::SAVING);
}

// Detects different password fields from |passwords|.
void LocateSpecificPasswords(const FieldPointersVector& passwords,
                             const FormFieldData** current_password,
                             const FormFieldData** new_password,
                             const FormFieldData** confirmation_password) {
  switch (passwords.size()) {
    case 1:
      *current_password = passwords[0];
      break;
    case 2:
      if (!passwords[0]->value.empty() &&
          passwords[0]->value == passwords[1]->value) {
        // Two identical non-empty passwords: assume we are seeing a new
        // password with a confirmation. This can be either a sign-up form or a
        // password change form that does not ask for the old password.
        *new_password = passwords[0];
        *confirmation_password = passwords[1];
      } else {
        // Assume first is old password, second is new (no choice but to guess).
        // This case also includes empty passwords in order to allow filling of
        // password change forms (that also could autofill for sign up form, but
        // we can't do anything with this using only client side information).
        *current_password = passwords[0];
        *new_password = passwords[1];
      }
      break;
    default:
      // If there are more 3 passwords it is not very clear what this form it
      // is. Consider only the first 3 passwords in such case in hope that it
      // would be useful.
      if (!passwords[0]->value.empty() &&
          passwords[0]->value == passwords[1]->value &&
          passwords[0]->value == passwords[2]->value) {
        // All passwords are the same. For the specifiy assume that the first
        // field is the current password.
        *current_password = passwords[0];
      } else if (passwords[1]->value == passwords[2]->value) {
        // New password is the duplicated one, and comes second; or empty form
        // with at least 3 password fields.
        *current_password = passwords[0];
        *new_password = passwords[1];
        *confirmation_password = passwords[2];
      } else if (passwords[0]->value == passwords[1]->value) {
        // It is strange that the new password comes first, but trust more which
        // fields are duplicated than the ordering of fields. Assume that
        // any password fields after the new password contain sensitive
        // information that isn't actually a password (security hint, SSN, etc.)
        *new_password = passwords[0];
        *confirmation_password = passwords[1];
      } else {
        // Three different passwords, or first and last match with middle
        // different. No idea which is which. Let's save the first password.
        // Password selection in a prompt will allow to correct the choice.
        *current_password = passwords[0];
      }
  }
}

// Tries to find username elements among text fields from |fields| before
// |first_relevant_password|.
// Returns nullptr if the username is not found.
const FormFieldData* FindUsernameFieldBaseHeuristics(
    const FieldPointersVector& fields,
    const FormFieldData* first_relevant_password,
    FormParsingMode mode) {
  DCHECK(first_relevant_password);
  DCHECK(find(fields.begin(), fields.end(), first_relevant_password) !=
         fields.end());

  // Let username_candidates be all non-password fields before
  // |first_relevant_password|.
  auto first_relevant_password_it =
      std::find(fields.begin(), fields.end(), first_relevant_password);
  FieldPointersVector username_candidates(fields.begin(),
                                          first_relevant_password_it);

  // For saving filter out empty fields.
  const bool consider_only_non_empty = mode == FormParsingMode::SAVING;
  username_candidates =
      FilterFields(username_candidates, false /* is_password */,
                   false /* only_focusable */, consider_only_non_empty);

  // If there is a focusable username candidate than username should be
  // focusable.
  if (HasFocusableField(username_candidates))
    username_candidates = FilterFields(
        username_candidates, false /* is_password */, true /* only_focusable */,
        consider_only_non_empty /* only_non_empty */);

  // According to base heuristics, username should be the last from
  // |username_candidates|.
  return username_candidates.empty() ? nullptr : *username_candidates.rbegin();
}

std::unique_ptr<ParseResult> ParseUsingBaseHeuristics(
    const FieldPointersVector& fields,
    FormParsingMode mode) {
  // Try to find password elements (current, new, confirmation).
  FieldPointersVector passwords = GetRelevantPasswords(fields, mode);
  if (passwords.empty())
    return nullptr;

  std::unique_ptr<ParseResult> result = std::make_unique<ParseResult>();
  LocateSpecificPasswords(passwords, &result->password_field,
                          &result->new_password_field,
                          &result->confirmation_password_field);
  if (result->IsEmpty())
    return nullptr;

  // If password elements are found then try to find a username.
  result->username_field =
      FindUsernameFieldBaseHeuristics(fields, passwords[0], mode);
  return result;
}

// Set username and password fields from |parse_result| in |password_form|.
void SetFields(const ParseResult& parse_result, PasswordForm* password_form) {
  if (parse_result.username_field) {
    password_form->username_element = parse_result.username_field->id;
    password_form->username_value = parse_result.username_field->value;
  }

  if (parse_result.password_field) {
    password_form->password_element = parse_result.password_field->id;
    password_form->password_value = parse_result.password_field->value;
  }

  if (parse_result.new_password_field) {
    password_form->new_password_element = parse_result.new_password_field->id;
    password_form->new_password_value = parse_result.new_password_field->value;
  }

  if (parse_result.confirmation_password_field) {
    password_form->confirmation_password_element =
        parse_result.confirmation_password_field->id;
  }
}

}  // namespace

std::unique_ptr<PasswordForm> ParseFormData(const FormData& form_data,
                                            FormParsingMode mode) {
  FieldPointersVector fields = GetTextFields(form_data.fields);
  fields = GetNonCreditCardFields(fields);

  // Skip forms without password fields.
  if (!HasPasswordField(fields))
    return nullptr;

  // Create parse result and set non-field related information.
  std::unique_ptr<PasswordForm> result = std::make_unique<PasswordForm>();
  result->origin = form_data.origin;
  result->signon_realm = form_data.origin.GetOrigin().spec();
  result->action = form_data.action;
  result->form_data = form_data;

  // Try to parse with autocomplete attributes.
  auto autocomplete_parse_result = ParseUsingAutocomplete(fields);
  if (autocomplete_parse_result) {
    SetFields(*autocomplete_parse_result, result.get());
    return result;
  }

  // Try to parse with base heuristic.
  auto base_heuristics_parse_result = ParseUsingBaseHeuristics(fields, mode);

  if (!base_heuristics_parse_result)
    return nullptr;

  SetFields(*base_heuristics_parse_result, result.get());
  return result;
}

}  // namespace password_manager