1// Copyright 2013 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/content/renderer/password_form_conversion_utils.h"
6
7#include "base/strings/string_util.h"
8#include "components/autofill/content/renderer/form_autofill_util.h"
9#include "components/autofill/core/common/password_form.h"
10#include "third_party/WebKit/public/platform/WebString.h"
11#include "third_party/WebKit/public/web/WebDocument.h"
12#include "third_party/WebKit/public/web/WebFormControlElement.h"
13#include "third_party/WebKit/public/web/WebInputElement.h"
14
15using blink::WebDocument;
16using blink::WebFormControlElement;
17using blink::WebFormElement;
18using blink::WebInputElement;
19using blink::WebString;
20using blink::WebVector;
21
22namespace autofill {
23namespace {
24
25// Checks in a case-insensitive way if the autocomplete attribute for the given
26// |element| is present and has the specified |value_in_lowercase|.
27bool HasAutocompleteAttributeValue(const WebInputElement& element,
28                                   const char* value_in_lowercase) {
29  return LowerCaseEqualsASCII(element.getAttribute("autocomplete"),
30                              value_in_lowercase);
31}
32
33// Helper to determine which password is the main (current) one, and which is
34// the new password (e.g., on a sign-up or change password form), if any.
35bool LocateSpecificPasswords(std::vector<WebInputElement> passwords,
36                             WebInputElement* current_password,
37                             WebInputElement* new_password) {
38  DCHECK(current_password && current_password->isNull());
39  DCHECK(new_password && new_password->isNull());
40
41  // First, look for elements marked with either autocomplete='current-password'
42  // or 'new-password' -- if we find any, take the hint, and treat the first of
43  // each kind as the element we are looking for.
44  for (std::vector<WebInputElement>::const_iterator it = passwords.begin();
45       it != passwords.end(); it++) {
46    if (HasAutocompleteAttributeValue(*it, "current-password") &&
47        current_password->isNull()) {
48      *current_password = *it;
49    } else if (HasAutocompleteAttributeValue(*it, "new-password") &&
50        new_password->isNull()) {
51      *new_password = *it;
52    }
53  }
54
55  // If we have seen an element with either of autocomplete attributes above,
56  // take that as a signal that the page author must have intentionally left the
57  // rest of the password fields unmarked. Perhaps they are used for other
58  // purposes, e.g., PINs, OTPs, and the like. So we skip all the heuristics we
59  // normally do, and ignore the rest of the password fields.
60  if (!current_password->isNull() || !new_password->isNull())
61    return true;
62
63  switch (passwords.size()) {
64    case 1:
65      // Single password, easy.
66      *current_password = passwords[0];
67      break;
68    case 2:
69      if (passwords[0].value() == passwords[1].value()) {
70        // Two identical passwords: assume we are seeing a new password with a
71        // confirmation. This can be either a sign-up form or a password change
72        // form that does not ask for the old password.
73        *new_password = passwords[0];
74      } else {
75        // Assume first is old password, second is new (no choice but to guess).
76        *current_password = passwords[0];
77        *new_password = passwords[1];
78      }
79      break;
80    case 3:
81      if (!passwords[0].value().isEmpty() &&
82          passwords[0].value() == passwords[1].value() &&
83          passwords[0].value() == passwords[2].value()) {
84        // All three passwords are the same and non-empty? This does not make
85        // any sense, give up.
86        return false;
87      } else if (passwords[1].value() == passwords[2].value()) {
88        // New password is the duplicated one, and comes second; or empty form
89        // with 3 password fields, in which case we will assume this layout.
90        *current_password = passwords[0];
91        *new_password = passwords[1];
92      } else if (passwords[0].value() == passwords[1].value()) {
93        // It is strange that the new password comes first, but trust more which
94        // fields are duplicated than the ordering of fields.
95        *current_password = passwords[2];
96        *new_password = passwords[0];
97      } else {
98        // Three different passwords, or first and last match with middle
99        // different. No idea which is which, so no luck.
100        return false;
101      }
102      break;
103    default:
104      return false;
105  }
106  return true;
107}
108
109// Get information about a login form encapsulated in a PasswordForm struct.
110void GetPasswordForm(const WebFormElement& form, PasswordForm* password_form) {
111  WebInputElement latest_input_element;
112  WebInputElement username_element;
113  // Caches whether |username_element| is marked with autocomplete='username'.
114  // Needed for performance reasons to avoid recalculating this multiple times.
115  bool has_seen_element_with_autocomplete_username_before = false;
116  std::vector<WebInputElement> passwords;
117  std::vector<base::string16> other_possible_usernames;
118
119  WebVector<WebFormControlElement> control_elements;
120  form.getFormControlElements(control_elements);
121
122  for (size_t i = 0; i < control_elements.size(); ++i) {
123    WebFormControlElement control_element = control_elements[i];
124    if (control_element.isActivatedSubmit())
125      password_form->submit_element = control_element.formControlName();
126
127    WebInputElement* input_element = toWebInputElement(&control_element);
128    if (!input_element || !input_element->isEnabled())
129      continue;
130
131    if (input_element->isPasswordField()) {
132      passwords.push_back(*input_element);
133      // If we have not yet considered any element to be the username so far,
134      // provisionally select the input element just before the first password
135      // element to be the username. This choice will be overruled if we later
136      // find an element with autocomplete='username'.
137      if (username_element.isNull() && !latest_input_element.isNull()) {
138        username_element = latest_input_element;
139        // Remove the selected username from other_possible_usernames.
140        if (!latest_input_element.value().isEmpty()) {
141          DCHECK(!other_possible_usernames.empty());
142          DCHECK_EQ(base::string16(latest_input_element.value()),
143                    other_possible_usernames.back());
144          other_possible_usernames.pop_back();
145        }
146      }
147    }
148
149    // Various input types such as text, url, email can be a username field.
150    if (input_element->isTextField() && !input_element->isPasswordField()) {
151      if (HasAutocompleteAttributeValue(*input_element, "username")) {
152        if (has_seen_element_with_autocomplete_username_before) {
153          // A second or subsequent element marked with autocomplete='username'.
154          // This makes us less confident that we have understood the form. We
155          // will stick to our choice that the first such element was the real
156          // username, but will start collecting other_possible_usernames from
157          // the extra elements marked with autocomplete='username'. Note that
158          // unlike username_element, other_possible_usernames is used only for
159          // autofill, not for form identification, and blank autofill entries
160          // are not useful, so we do not collect empty strings.
161          if (!input_element->value().isEmpty())
162            other_possible_usernames.push_back(input_element->value());
163        } else {
164          // The first element marked with autocomplete='username'. Take the
165          // hint and treat it as the username (overruling the tentative choice
166          // we might have made before). Furthermore, drop all other possible
167          // usernames we have accrued so far: they come from fields not marked
168          // with the autocomplete attribute, making them unlikely alternatives.
169          username_element = *input_element;
170          has_seen_element_with_autocomplete_username_before = true;
171          other_possible_usernames.clear();
172        }
173      } else {
174        if (has_seen_element_with_autocomplete_username_before) {
175          // Having seen elements with autocomplete='username', elements without
176          // this attribute are no longer interesting. No-op.
177        } else {
178          // No elements marked with autocomplete='username' so far whatsoever.
179          // If we have not yet selected a username element even provisionally,
180          // then remember this element for the case when the next field turns
181          // out to be a password. Save a non-empty username as a possible
182          // alternative, at least for now.
183          if (username_element.isNull())
184            latest_input_element = *input_element;
185          if (!input_element->value().isEmpty())
186            other_possible_usernames.push_back(input_element->value());
187        }
188      }
189    }
190  }
191
192  if (!username_element.isNull()) {
193    password_form->username_element = username_element.nameForAutofill();
194    password_form->username_value = username_element.value();
195  }
196
197  // Get the document URL
198  GURL full_origin(form.document().url());
199
200  // Calculate the canonical action URL
201  WebString action = form.action();
202  if (action.isNull())
203    action = WebString(""); // missing 'action' attribute implies current URL
204  GURL full_action(form.document().completeURL(action));
205  if (!full_action.is_valid())
206    return;
207
208  WebInputElement password;
209  WebInputElement new_password;
210  if (!LocateSpecificPasswords(passwords, &password, &new_password))
211    return;
212
213  // We want to keep the path but strip any authentication data, as well as
214  // query and ref portions of URL, for the form action and form origin.
215  GURL::Replacements rep;
216  rep.ClearUsername();
217  rep.ClearPassword();
218  rep.ClearQuery();
219  rep.ClearRef();
220  password_form->action = full_action.ReplaceComponents(rep);
221  password_form->origin = full_origin.ReplaceComponents(rep);
222
223  rep.SetPathStr("");
224  password_form->signon_realm = full_origin.ReplaceComponents(rep).spec();
225
226  password_form->other_possible_usernames.swap(other_possible_usernames);
227
228  if (!password.isNull()) {
229    password_form->password_element = password.nameForAutofill();
230    password_form->password_value = password.value();
231    password_form->password_autocomplete_set = password.autoComplete();
232  }
233  if (!new_password.isNull()) {
234    password_form->new_password_element = new_password.nameForAutofill();
235    password_form->new_password_value = new_password.value();
236  }
237
238  password_form->scheme = PasswordForm::SCHEME_HTML;
239  password_form->ssl_valid = false;
240  password_form->preferred = false;
241  password_form->blacklisted_by_user = false;
242  password_form->type = PasswordForm::TYPE_MANUAL;
243  password_form->use_additional_authentication = false;
244}
245
246}  // namespace
247
248scoped_ptr<PasswordForm> CreatePasswordForm(const WebFormElement& web_form) {
249  if (web_form.isNull())
250    return scoped_ptr<PasswordForm>();
251
252  scoped_ptr<PasswordForm> password_form(new PasswordForm());
253  GetPasswordForm(web_form, password_form.get());
254
255  if (!password_form->action.is_valid())
256    return scoped_ptr<PasswordForm>();
257
258  WebFormElementToFormData(web_form,
259                           blink::WebFormControlElement(),
260                           REQUIRE_NONE,
261                           EXTRACT_NONE,
262                           &password_form->form_data,
263                           NULL /* FormFieldData */);
264
265  return password_form.Pass();
266}
267
268}  // namespace autofill
269