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 if (passwords
.empty())
66 switch (passwords
.size()) {
68 // Single password, easy.
69 *current_password
= passwords
[0];
72 if (passwords
[0].value() == passwords
[1].value()) {
73 // Two identical passwords: assume we are seeing a new password with a
74 // confirmation. This can be either a sign-up form or a password change
75 // form that does not ask for the old password.
76 *new_password
= passwords
[0];
78 // Assume first is old password, second is new (no choice but to guess).
79 *current_password
= passwords
[0];
80 *new_password
= passwords
[1];
84 if (!passwords
[0].value().isEmpty() &&
85 passwords
[0].value() == passwords
[1].value() &&
86 passwords
[0].value() == passwords
[2].value()) {
87 // All three passwords are the same and non-empty? This does not make
88 // any sense, give up.
90 } else if (passwords
[1].value() == passwords
[2].value()) {
91 // New password is the duplicated one, and comes second; or empty form
92 // with 3 password fields, in which case we will assume this layout.
93 *current_password
= passwords
[0];
94 *new_password
= passwords
[1];
95 } else if (passwords
[0].value() == passwords
[1].value()) {
96 // It is strange that the new password comes first, but trust more which
97 // fields are duplicated than the ordering of fields. Assume that
98 // any password fields after the new password contain sensitive
99 // information that isn't actually a password (security hint, SSN, etc.)
100 *new_password
= passwords
[0];
102 // Three different passwords, or first and last match with middle
103 // different. No idea which is which, so no luck.
110 // Get information about a login form encapsulated in a PasswordForm struct.
111 // If an element of |form| has an entry in |user_modified_elements|, the
112 // associated string is used instead of the element's value to create
114 void GetPasswordForm(const WebFormElement
& form
,
115 PasswordForm
* password_form
,
116 const std::map
<const blink::WebInputElement
,
117 blink::WebString
>* user_modified_elements
) {
118 WebInputElement latest_input_element
;
119 WebInputElement username_element
;
120 password_form
->username_marked_by_site
= false;
121 std::vector
<WebInputElement
> passwords
;
122 std::vector
<base::string16
> other_possible_usernames
;
124 WebVector
<WebFormControlElement
> control_elements
;
125 form
.getFormControlElements(control_elements
);
127 for (size_t i
= 0; i
< control_elements
.size(); ++i
) {
128 WebFormControlElement control_element
= control_elements
[i
];
129 if (control_element
.isActivatedSubmit())
130 password_form
->submit_element
= control_element
.formControlName();
132 WebInputElement
* input_element
= toWebInputElement(&control_element
);
133 if (!input_element
|| !input_element
->isEnabled())
136 if (input_element
->isPasswordField()) {
137 passwords
.push_back(*input_element
);
138 // If we have not yet considered any element to be the username so far,
139 // provisionally select the input element just before the first password
140 // element to be the username. This choice will be overruled if we later
141 // find an element with autocomplete='username'.
142 if (username_element
.isNull() && !latest_input_element
.isNull()) {
143 username_element
= latest_input_element
;
144 // Remove the selected username from other_possible_usernames.
145 if (!latest_input_element
.value().isEmpty()) {
146 DCHECK(!other_possible_usernames
.empty());
147 DCHECK_EQ(base::string16(latest_input_element
.value()),
148 other_possible_usernames
.back());
149 other_possible_usernames
.pop_back();
154 // Various input types such as text, url, email can be a username field.
155 if (input_element
->isTextField() && !input_element
->isPasswordField()) {
156 if (HasAutocompleteAttributeValue(*input_element
, "username")) {
157 if (password_form
->username_marked_by_site
) {
158 // A second or subsequent element marked with autocomplete='username'.
159 // This makes us less confident that we have understood the form. We
160 // will stick to our choice that the first such element was the real
161 // username, but will start collecting other_possible_usernames from
162 // the extra elements marked with autocomplete='username'. Note that
163 // unlike username_element, other_possible_usernames is used only for
164 // autofill, not for form identification, and blank autofill entries
165 // are not useful, so we do not collect empty strings.
166 if (!input_element
->value().isEmpty())
167 other_possible_usernames
.push_back(input_element
->value());
169 // The first element marked with autocomplete='username'. Take the
170 // hint and treat it as the username (overruling the tentative choice
171 // we might have made before). Furthermore, drop all other possible
172 // usernames we have accrued so far: they come from fields not marked
173 // with the autocomplete attribute, making them unlikely alternatives.
174 username_element
= *input_element
;
175 password_form
->username_marked_by_site
= true;
176 other_possible_usernames
.clear();
179 if (password_form
->username_marked_by_site
) {
180 // Having seen elements with autocomplete='username', elements without
181 // this attribute are no longer interesting. No-op.
183 // No elements marked with autocomplete='username' so far whatsoever.
184 // If we have not yet selected a username element even provisionally,
185 // then remember this element for the case when the next field turns
186 // out to be a password. Save a non-empty username as a possible
187 // alternative, at least for now.
188 if (username_element
.isNull())
189 latest_input_element
= *input_element
;
190 if (!input_element
->value().isEmpty())
191 other_possible_usernames
.push_back(input_element
->value());
197 if (!username_element
.isNull()) {
198 password_form
->username_element
= username_element
.nameForAutofill();
199 base::string16 username_value
= username_element
.value();
200 if (user_modified_elements
!= nullptr) {
201 auto username_iterator
= user_modified_elements
->find(username_element
);
202 if (username_iterator
!= user_modified_elements
->end()) {
203 base::string16 typed_username_value
= username_iterator
->second
;
204 if (!StartsWith(username_value
, typed_username_value
, false)) {
205 // We check that |username_value| was not obtained by autofilling
206 // |typed_username_value|. In case when it was, |typed_username_value|
207 // is incomplete, so we should leave autofilled value.
208 username_value
= typed_username_value
;
212 password_form
->username_value
= username_value
;
215 // Get the document URL
216 GURL
full_origin(form
.document().url());
218 // Calculate the canonical action URL
219 WebString action
= form
.action();
221 action
= WebString(""); // missing 'action' attribute implies current URL
222 GURL
full_action(form
.document().completeURL(action
));
223 if (!full_action
.is_valid())
226 WebInputElement password
;
227 WebInputElement new_password
;
228 if (!LocateSpecificPasswords(passwords
, &password
, &new_password
))
231 // We want to keep the path but strip any authentication data, as well as
232 // query and ref portions of URL, for the form action and form origin.
233 GURL::Replacements rep
;
238 password_form
->action
= full_action
.ReplaceComponents(rep
);
239 password_form
->origin
= full_origin
.ReplaceComponents(rep
);
242 password_form
->signon_realm
= full_origin
.ReplaceComponents(rep
).spec();
244 password_form
->other_possible_usernames
.swap(other_possible_usernames
);
246 if (!password
.isNull()) {
247 password_form
->password_element
= password
.nameForAutofill();
248 blink::WebString password_value
= password
.value();
249 if (user_modified_elements
!= nullptr) {
250 auto password_iterator
= user_modified_elements
->find(password
);
251 if (password_iterator
!= user_modified_elements
->end())
252 password_value
= password_iterator
->second
;
254 password_form
->password_value
= password_value
;
255 password_form
->password_autocomplete_set
= password
.autoComplete();
257 if (!new_password
.isNull()) {
258 password_form
->new_password_element
= new_password
.nameForAutofill();
259 password_form
->new_password_value
= new_password
.value();
262 password_form
->scheme
= PasswordForm::SCHEME_HTML
;
263 password_form
->ssl_valid
= false;
264 password_form
->preferred
= false;
265 password_form
->blacklisted_by_user
= false;
266 password_form
->type
= PasswordForm::TYPE_MANUAL
;
271 scoped_ptr
<PasswordForm
> CreatePasswordForm(
272 const WebFormElement
& web_form
,
273 const std::map
<const blink::WebInputElement
, blink::WebString
>*
274 user_modified_elements
) {
275 if (web_form
.isNull())
276 return scoped_ptr
<PasswordForm
>();
278 scoped_ptr
<PasswordForm
> password_form(new PasswordForm());
279 GetPasswordForm(web_form
, password_form
.get(), user_modified_elements
);
281 if (!password_form
->action
.is_valid())
282 return scoped_ptr
<PasswordForm
>();
284 WebFormElementToFormData(web_form
,
285 blink::WebFormControlElement(),
288 &password_form
->form_data
,
289 NULL
/* FormFieldData */);
291 return password_form
.Pass();
294 } // namespace autofill