Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(145)

Side by Side Diff: components/autofill/content/renderer/password_form_conversion_utils.cc

Issue 1014683006: [Password manager] Recognise squashed login+sign-up forms (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: LazyInstance + commented regexp Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/autofill/content/renderer/password_form_conversion_utils.h" 5 #include "components/autofill/content/renderer/password_form_conversion_utils.h"
6 6
7 #include "base/lazy_instance.h"
8 #include "base/memory/scoped_ptr.h"
7 #include "base/strings/string_util.h" 9 #include "base/strings/string_util.h"
8 #include "components/autofill/content/renderer/form_autofill_util.h" 10 #include "components/autofill/content/renderer/form_autofill_util.h"
9 #include "components/autofill/core/common/password_form.h" 11 #include "components/autofill/core/common/password_form.h"
10 #include "third_party/WebKit/public/platform/WebString.h" 12 #include "third_party/WebKit/public/platform/WebString.h"
11 #include "third_party/WebKit/public/web/WebDocument.h" 13 #include "third_party/WebKit/public/web/WebDocument.h"
12 #include "third_party/WebKit/public/web/WebFormControlElement.h" 14 #include "third_party/WebKit/public/web/WebFormControlElement.h"
13 #include "third_party/WebKit/public/web/WebInputElement.h" 15 #include "third_party/WebKit/public/web/WebInputElement.h"
16 #include "third_party/icu/source/i18n/unicode/regex.h"
14 17
15 using blink::WebDocument; 18 using blink::WebDocument;
16 using blink::WebFormControlElement; 19 using blink::WebFormControlElement;
17 using blink::WebFormElement; 20 using blink::WebFormElement;
18 using blink::WebInputElement; 21 using blink::WebInputElement;
19 using blink::WebString; 22 using blink::WebString;
20 using blink::WebVector; 23 using blink::WebVector;
21 24
22 namespace autofill { 25 namespace autofill {
23 namespace { 26 namespace {
24 27
28 // Layout classification of password forms
29 // A layout sequence of a form is the sequence of it's non-password and password
30 // input fields, represented by "N" and "P", respectively. A form like this
31 // <form>
32 // <input type='text' ...>
33 // <input type='hidden' ...>
34 // <input type='password' ...>
35 // <input type='submit' ...>
36 // </form>
37 // has the layout sequence "NP" -- "N" for the first field, and "P" for the
38 // third. The second and fourth fields are ignored, because they are not text
39 // fields.
40 //
41 // The code below classifies the layout (see PasswordForm::Layout) of a form
42 // based on its layout sequence. This is done by assigning layouts regular
43 // expressions over the alphabet {N, P}. LAYOUT_OTHER is implicitly the type
44 // corresponding to all layout sequences not matching any other layout.
45 //
46 // LAYOUT_LOGIN_AND_SIGNUP is classified by NPN+P.*. This corresponds to a form
47 // which starts with a login section (NP) and continues with a sign-up section
48 // (N+P.*). The aim is to distinguish such forms from change password-forms
49 // (N*PPP?.*) and forms which use password fields to store private but
50 // non-password data (could look like, e.g., PN+P.*).
51 const char kLoginAndSignupRegex[] =
52 "NP" // Login section.
53 "N+P" // Sign-up section.
54 ".*"; // Anything beyond that.
Ilya Sherman 2015/03/24 00:28:02 Thanks, this is indeed much nicer to read :)
55
56 struct LoginAndSignupLazyInstanceTraits
57 : public base::DefaultLazyInstanceTraits<icu::RegexMatcher> {
58 static icu::RegexMatcher* New(void* instance) {
59 const icu::UnicodeString icu_pattern(kLoginAndSignupRegex);
60
61 UErrorCode status = U_ZERO_ERROR;
62 // Use placement new to initialize the instance in the preallocated space.
63 // The "(instance)" is very important to force POD type initialization.
64 scoped_ptr<icu::RegexMatcher> matcher(new (instance) icu::RegexMatcher(
65 icu_pattern, UREGEX_CASE_INSENSITIVE, status));
66 DCHECK(U_SUCCESS(status));
67 return matcher.release();
68 }
69 };
70
71 base::LazyInstance<icu::RegexMatcher, LoginAndSignupLazyInstanceTraits>
72 login_and_signup_matcher = LAZY_INSTANCE_INITIALIZER;
73
74 bool MatchesLoginAndSignupPattern(base::StringPiece layout_sequence) {
75 icu::RegexMatcher* matcher = login_and_signup_matcher.Pointer();
76 icu::UnicodeString icu_input(icu::UnicodeString::fromUTF8(
77 icu::StringPiece(layout_sequence.data(), layout_sequence.length())));
78 matcher->reset(icu_input);
79
80 UErrorCode status = U_ZERO_ERROR;
81 UBool match = matcher->find(0, status);
82 DCHECK(U_SUCCESS(status));
83 return match == TRUE;
84 }
85
86 // Given the sequence of non-password and password text input fields of a form,
87 // represented as a string of Ns (non-password) and Ps (password), computes the
88 // layout type of that form.
89 PasswordForm::Layout SequenceToLayout(base::StringPiece layout_sequence) {
90 if (MatchesLoginAndSignupPattern(layout_sequence))
91 return PasswordForm::Layout::LAYOUT_LOGIN_AND_SIGNUP;
92 return PasswordForm::Layout::LAYOUT_OTHER;
93 }
94
25 // Checks in a case-insensitive way if the autocomplete attribute for the given 95 // Checks in a case-insensitive way if the autocomplete attribute for the given
26 // |element| is present and has the specified |value_in_lowercase|. 96 // |element| is present and has the specified |value_in_lowercase|.
27 bool HasAutocompleteAttributeValue(const WebInputElement& element, 97 bool HasAutocompleteAttributeValue(const WebInputElement& element,
28 const char* value_in_lowercase) { 98 const char* value_in_lowercase) {
29 return LowerCaseEqualsASCII(element.getAttribute("autocomplete"), 99 return LowerCaseEqualsASCII(element.getAttribute("autocomplete"),
30 value_in_lowercase); 100 value_in_lowercase);
31 } 101 }
32 102
33 // Helper to determine which password is the main (current) one, and which is 103 // 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. 104 // the new password (e.g., on a sign-up or change password form), if any.
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after
118 nonscript_modified_values) { 188 nonscript_modified_values) {
119 WebInputElement latest_input_element; 189 WebInputElement latest_input_element;
120 WebInputElement username_element; 190 WebInputElement username_element;
121 password_form->username_marked_by_site = false; 191 password_form->username_marked_by_site = false;
122 std::vector<WebInputElement> passwords; 192 std::vector<WebInputElement> passwords;
123 std::vector<base::string16> other_possible_usernames; 193 std::vector<base::string16> other_possible_usernames;
124 194
125 WebVector<WebFormControlElement> control_elements; 195 WebVector<WebFormControlElement> control_elements;
126 form.getFormControlElements(control_elements); 196 form.getFormControlElements(control_elements);
127 197
198 std::string layout_sequence;
199 layout_sequence.reserve(control_elements.size());
128 for (size_t i = 0; i < control_elements.size(); ++i) { 200 for (size_t i = 0; i < control_elements.size(); ++i) {
129 WebFormControlElement control_element = control_elements[i]; 201 WebFormControlElement control_element = control_elements[i];
130 if (control_element.isActivatedSubmit()) 202 if (control_element.isActivatedSubmit())
131 password_form->submit_element = control_element.formControlName(); 203 password_form->submit_element = control_element.formControlName();
132 204
133 WebInputElement* input_element = toWebInputElement(&control_element); 205 WebInputElement* input_element = toWebInputElement(&control_element);
134 if (!input_element || !input_element->isEnabled()) 206 if (!input_element || !input_element->isEnabled())
135 continue; 207 continue;
136 208
209 if (input_element->isTextField()) {
210 if (input_element->isPasswordField())
211 layout_sequence.push_back('P');
212 else
213 layout_sequence.push_back('N');
214 }
215
137 if (input_element->isPasswordField()) { 216 if (input_element->isPasswordField()) {
138 passwords.push_back(*input_element); 217 passwords.push_back(*input_element);
139 // If we have not yet considered any element to be the username so far, 218 // If we have not yet considered any element to be the username so far,
140 // provisionally select the input element just before the first password 219 // provisionally select the input element just before the first password
141 // element to be the username. This choice will be overruled if we later 220 // element to be the username. This choice will be overruled if we later
142 // find an element with autocomplete='username'. 221 // find an element with autocomplete='username'.
143 if (username_element.isNull() && !latest_input_element.isNull()) { 222 if (username_element.isNull() && !latest_input_element.isNull()) {
144 username_element = latest_input_element; 223 username_element = latest_input_element;
145 // Remove the selected username from other_possible_usernames. 224 // Remove the selected username from other_possible_usernames.
146 if (!latest_input_element.value().isEmpty()) { 225 if (!latest_input_element.value().isEmpty()) {
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
187 // out to be a password. Save a non-empty username as a possible 266 // out to be a password. Save a non-empty username as a possible
188 // alternative, at least for now. 267 // alternative, at least for now.
189 if (username_element.isNull()) 268 if (username_element.isNull())
190 latest_input_element = *input_element; 269 latest_input_element = *input_element;
191 if (!input_element->value().isEmpty()) 270 if (!input_element->value().isEmpty())
192 other_possible_usernames.push_back(input_element->value()); 271 other_possible_usernames.push_back(input_element->value());
193 } 272 }
194 } 273 }
195 } 274 }
196 } 275 }
276 password_form->layout = SequenceToLayout(layout_sequence);
197 277
198 if (!username_element.isNull()) { 278 if (!username_element.isNull()) {
199 password_form->username_element = username_element.nameForAutofill(); 279 password_form->username_element = username_element.nameForAutofill();
200 base::string16 username_value = username_element.value(); 280 base::string16 username_value = username_element.value();
201 if (nonscript_modified_values != nullptr) { 281 if (nonscript_modified_values != nullptr) {
202 auto username_iterator = 282 auto username_iterator =
203 nonscript_modified_values->find(username_element); 283 nonscript_modified_values->find(username_element);
204 if (username_iterator != nonscript_modified_values->end()) { 284 if (username_iterator != nonscript_modified_values->end()) {
205 base::string16 typed_username_value = username_iterator->second; 285 base::string16 typed_username_value = username_iterator->second;
206 if (!StartsWith(username_value, typed_username_value, false)) { 286 if (!StartsWith(username_value, typed_username_value, false)) {
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
287 blink::WebFormControlElement(), 367 blink::WebFormControlElement(),
288 REQUIRE_NONE, 368 REQUIRE_NONE,
289 EXTRACT_NONE, 369 EXTRACT_NONE,
290 &password_form->form_data, 370 &password_form->form_data,
291 NULL /* FormFieldData */); 371 NULL /* FormFieldData */);
292 372
293 return password_form.Pass(); 373 return password_form.Pass();
294 } 374 }
295 375
296 } // namespace autofill 376 } // namespace autofill
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698