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