Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / components / autofill / content / renderer / password_form_conversion_utils.cc
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
15 using blink::WebDocument;
16 using blink::WebFormControlElement;
17 using blink::WebFormElement;
18 using blink::WebInputElement;
19 using blink::WebString;
20 using blink::WebVector;
21
22 namespace autofill {
23 namespace {
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|.
27 bool 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.
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());
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.
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;
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
248 scoped_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