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.
5 #include "components/autofill/content/renderer/password_form_conversion_utils.h"
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"
15 using blink::WebDocument
;
16 using blink::WebFormControlElement
;
17 using blink::WebFormElement
;
18 using blink::WebInputElement
;
19 using blink::WebString
;
20 using blink::WebVector
;
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|.
27 bool HasAutocompleteAttributeValue(const WebInputElement
& element
,
28 const char* value_in_lowercase
) {
29 return LowerCaseEqualsASCII(element
.getAttribute("autocomplete"),
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.
35 bool 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());
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()) {
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())
63 switch (passwords
.size()) {
65 // Single password, easy.
66 *current_password
= passwords
[0];
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];
75 // Assume first is old password, second is new (no choice but to guess).
76 *current_password
= passwords
[0];
77 *new_password
= passwords
[1];
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.
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];
98 // Three different passwords, or first and last match with middle
99 // different. No idea which is which, so no luck.
109 // Get information about a login form encapsulated in a PasswordForm struct.
110 void 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
;
119 WebVector
<WebFormControlElement
> control_elements
;
120 form
.getFormControlElements(control_elements
);
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();
127 WebInputElement
* input_element
= toWebInputElement(&control_element
);
128 if (!input_element
|| !input_element
->isEnabled())
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();
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());
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();
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.
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());
192 if (!username_element
.isNull()) {
193 password_form
->username_element
= username_element
.nameForAutofill();
194 password_form
->username_value
= username_element
.value();
197 // Get the document URL
198 GURL
full_origin(form
.document().url());
200 // Calculate the canonical action URL
201 WebString action
= form
.action();
203 action
= WebString(""); // missing 'action' attribute implies current URL
204 GURL
full_action(form
.document().completeURL(action
));
205 if (!full_action
.is_valid())
208 WebInputElement password
;
209 WebInputElement new_password
;
210 if (!LocateSpecificPasswords(passwords
, &password
, &new_password
))
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
;
220 password_form
->action
= full_action
.ReplaceComponents(rep
);
221 password_form
->origin
= full_origin
.ReplaceComponents(rep
);
224 password_form
->signon_realm
= full_origin
.ReplaceComponents(rep
).spec();
226 password_form
->other_possible_usernames
.swap(other_possible_usernames
);
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();
233 if (!new_password
.isNull()) {
234 password_form
->new_password_element
= new_password
.nameForAutofill();
235 password_form
->new_password_value
= new_password
.value();
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;
248 scoped_ptr
<PasswordForm
> CreatePasswordForm(const WebFormElement
& web_form
) {
249 if (web_form
.isNull())
250 return scoped_ptr
<PasswordForm
>();
252 scoped_ptr
<PasswordForm
> password_form(new PasswordForm());
253 GetPasswordForm(web_form
, password_form
.get());
255 if (!password_form
->action
.is_valid())
256 return scoped_ptr
<PasswordForm
>();
258 WebFormElementToFormData(web_form
,
259 blink::WebFormControlElement(),
262 &password_form
->form_data
,
263 NULL
/* FormFieldData */);
265 return password_form
.Pass();
268 } // namespace autofill