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

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: Just rebased 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/memory/singleton.h"
7 #include "base/strings/string_util.h" 8 #include "base/strings/string_util.h"
8 #include "components/autofill/content/renderer/form_autofill_util.h" 9 #include "components/autofill/content/renderer/form_autofill_util.h"
9 #include "components/autofill/core/common/password_form.h" 10 #include "components/autofill/core/common/password_form.h"
10 #include "third_party/WebKit/public/platform/WebString.h" 11 #include "third_party/WebKit/public/platform/WebString.h"
11 #include "third_party/WebKit/public/web/WebDocument.h" 12 #include "third_party/WebKit/public/web/WebDocument.h"
12 #include "third_party/WebKit/public/web/WebFormControlElement.h" 13 #include "third_party/WebKit/public/web/WebFormControlElement.h"
13 #include "third_party/WebKit/public/web/WebInputElement.h" 14 #include "third_party/WebKit/public/web/WebInputElement.h"
15 #include "third_party/icu/source/i18n/unicode/regex.h"
14 16
15 using blink::WebDocument; 17 using blink::WebDocument;
16 using blink::WebFormControlElement; 18 using blink::WebFormControlElement;
17 using blink::WebFormElement; 19 using blink::WebFormElement;
18 using blink::WebInputElement; 20 using blink::WebInputElement;
19 using blink::WebString; 21 using blink::WebString;
20 using blink::WebVector; 22 using blink::WebVector;
21 23
22 namespace autofill { 24 namespace autofill {
23 namespace { 25 namespace {
24 26
27 // Layout classification of password forms
28 // A layout sequence of a form is the sequence of it's non-password and password
29 // input fields, represented by "N" and "P", respectively. A form like this
30 // <form>
31 // <input type='text' ...>
32 // <input type='hidden' ...>
33 // <input type='password' ...>
34 // <input type='submit' ...>
35 // </form>
36 // has the layout sequence "NP" -- "N" for the first field, and "P" for the
37 // third. The second and fourth fields are ignored, because they are not text
38 // fields.
39 //
40 // The code below classifies the layout (see PasswordForm::Layout) of a form
41 // based on its layout sequence. This is done by assigning layouts regular
42 // expressions over the alphabet {N, P}. LAYOUT_OTHER is implicitly the type
43 // corresponding to all layout sequences not matching any other layout.
44 //
45 // LAYOUT_LOGIN_AND_SIGNUP is classified by NPN+P.*. This corresponds to a form
46 // which starts with a login section (NP) and continues with a sign-up section
47 // (N+P.*). The aim is to distinguish such forms from change password-forms
48 // (N*PPP?.*) and forms which use password fields to store private but
49 // non-password data (could look like, e.g., PN+P.*).
50 const char kLoginAndSignupRegex[] = "NPN+P.*";
51
52 // A singleton class that serves as a cache of the compiled regex pattern from
53 // above.
54 class LayoutRegex {
55 public:
56 static LayoutRegex* GetInstance();
57
58 // Returns the compiled regex matcher.
59 icu::RegexMatcher* GetLoginAndSignupMatcher();
60
61 private:
62 LayoutRegex();
63 ~LayoutRegex();
64 friend struct DefaultSingletonTraits<LayoutRegex>;
65
66 scoped_ptr<icu::RegexMatcher> login_and_signup_matcher_;
67
68 DISALLOW_COPY_AND_ASSIGN(LayoutRegex);
69 };
70
71 // static
72 LayoutRegex* LayoutRegex::GetInstance() {
73 return Singleton<LayoutRegex>::get();
74 }
75
76 LayoutRegex::LayoutRegex() {
77 }
78
79 LayoutRegex::~LayoutRegex() {
80 }
Ilya Sherman 2015/03/20 22:43:08 Since you only have a single regex used in this fi
vabr (Chromium) 2015/03/23 14:23:44 Good call, I forgot about LazyInstance. Done.
81
82 icu::RegexMatcher* LayoutRegex::GetLoginAndSignupMatcher() {
83 if (!login_and_signup_matcher_) {
84 const icu::UnicodeString icu_pattern(kLoginAndSignupRegex);
85
86 UErrorCode status = U_ZERO_ERROR;
87 login_and_signup_matcher_.reset(
88 new icu::RegexMatcher(icu_pattern, UREGEX_CASE_INSENSITIVE, status));
89 DCHECK(U_SUCCESS(status));
90 }
91 return login_and_signup_matcher_.get();
92 }
93
94 bool MatchesLoginAndSignupPattern(base::StringPiece layout_sequence) {
95 icu::RegexMatcher* matcher =
96 LayoutRegex::GetInstance()->GetLoginAndSignupMatcher();
97 icu::UnicodeString icu_input(icu::UnicodeString::fromUTF8(
98 icu::StringPiece(layout_sequence.data(), layout_sequence.length())));
99 matcher->reset(icu_input);
100
101 UErrorCode status = U_ZERO_ERROR;
102 UBool match = matcher->find(0, status);
103 DCHECK(U_SUCCESS(status));
104 return match == TRUE;
Ilya Sherman 2015/03/20 22:43:08 Man, I forgot how much uglier ICU's regex code is
vabr (Chromium) 2015/03/23 14:23:44 It looks like RE2 outperforms ICU significantly: s
105 }
106
107 // Given the sequence of non-password and password text input fields of a form,
108 // represented as a string of Ns (non-password) and Ps (password), computes the
109 // layout type of that form.
110 PasswordForm::Layout SequenceToLayout(base::StringPiece layout_sequence) {
111 if (MatchesLoginAndSignupPattern(layout_sequence))
112 return PasswordForm::Layout::LAYOUT_LOGIN_AND_SIGNUP;
113 return PasswordForm::Layout::LAYOUT_OTHER;
114 }
115
25 // Checks in a case-insensitive way if the autocomplete attribute for the given 116 // Checks in a case-insensitive way if the autocomplete attribute for the given
26 // |element| is present and has the specified |value_in_lowercase|. 117 // |element| is present and has the specified |value_in_lowercase|.
27 bool HasAutocompleteAttributeValue(const WebInputElement& element, 118 bool HasAutocompleteAttributeValue(const WebInputElement& element,
28 const char* value_in_lowercase) { 119 const char* value_in_lowercase) {
29 return LowerCaseEqualsASCII(element.getAttribute("autocomplete"), 120 return LowerCaseEqualsASCII(element.getAttribute("autocomplete"),
30 value_in_lowercase); 121 value_in_lowercase);
31 } 122 }
32 123
33 // Helper to determine which password is the main (current) one, and which is 124 // 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. 125 // 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) { 209 nonscript_modified_values) {
119 WebInputElement latest_input_element; 210 WebInputElement latest_input_element;
120 WebInputElement username_element; 211 WebInputElement username_element;
121 password_form->username_marked_by_site = false; 212 password_form->username_marked_by_site = false;
122 std::vector<WebInputElement> passwords; 213 std::vector<WebInputElement> passwords;
123 std::vector<base::string16> other_possible_usernames; 214 std::vector<base::string16> other_possible_usernames;
124 215
125 WebVector<WebFormControlElement> control_elements; 216 WebVector<WebFormControlElement> control_elements;
126 form.getFormControlElements(control_elements); 217 form.getFormControlElements(control_elements);
127 218
219 std::string layout_sequence;
220 layout_sequence.reserve(control_elements.size());
128 for (size_t i = 0; i < control_elements.size(); ++i) { 221 for (size_t i = 0; i < control_elements.size(); ++i) {
129 WebFormControlElement control_element = control_elements[i]; 222 WebFormControlElement control_element = control_elements[i];
130 if (control_element.isActivatedSubmit()) 223 if (control_element.isActivatedSubmit())
131 password_form->submit_element = control_element.formControlName(); 224 password_form->submit_element = control_element.formControlName();
132 225
133 WebInputElement* input_element = toWebInputElement(&control_element); 226 WebInputElement* input_element = toWebInputElement(&control_element);
134 if (!input_element || !input_element->isEnabled()) 227 if (!input_element || !input_element->isEnabled())
135 continue; 228 continue;
136 229
230 if (input_element->isTextField()) {
231 if (input_element->isPasswordField())
232 layout_sequence.push_back('P');
233 else
234 layout_sequence.push_back('N');
235 }
236
137 if (input_element->isPasswordField()) { 237 if (input_element->isPasswordField()) {
138 passwords.push_back(*input_element); 238 passwords.push_back(*input_element);
139 // If we have not yet considered any element to be the username so far, 239 // 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 240 // provisionally select the input element just before the first password
141 // element to be the username. This choice will be overruled if we later 241 // element to be the username. This choice will be overruled if we later
142 // find an element with autocomplete='username'. 242 // find an element with autocomplete='username'.
143 if (username_element.isNull() && !latest_input_element.isNull()) { 243 if (username_element.isNull() && !latest_input_element.isNull()) {
144 username_element = latest_input_element; 244 username_element = latest_input_element;
145 // Remove the selected username from other_possible_usernames. 245 // Remove the selected username from other_possible_usernames.
146 if (!latest_input_element.value().isEmpty()) { 246 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 287 // out to be a password. Save a non-empty username as a possible
188 // alternative, at least for now. 288 // alternative, at least for now.
189 if (username_element.isNull()) 289 if (username_element.isNull())
190 latest_input_element = *input_element; 290 latest_input_element = *input_element;
191 if (!input_element->value().isEmpty()) 291 if (!input_element->value().isEmpty())
192 other_possible_usernames.push_back(input_element->value()); 292 other_possible_usernames.push_back(input_element->value());
193 } 293 }
194 } 294 }
195 } 295 }
196 } 296 }
297 password_form->layout = SequenceToLayout(layout_sequence);
197 298
198 if (!username_element.isNull()) { 299 if (!username_element.isNull()) {
199 password_form->username_element = username_element.nameForAutofill(); 300 password_form->username_element = username_element.nameForAutofill();
200 base::string16 username_value = username_element.value(); 301 base::string16 username_value = username_element.value();
201 if (nonscript_modified_values != nullptr) { 302 if (nonscript_modified_values != nullptr) {
202 auto username_iterator = 303 auto username_iterator =
203 nonscript_modified_values->find(username_element); 304 nonscript_modified_values->find(username_element);
204 if (username_iterator != nonscript_modified_values->end()) { 305 if (username_iterator != nonscript_modified_values->end()) {
205 base::string16 typed_username_value = username_iterator->second; 306 base::string16 typed_username_value = username_iterator->second;
206 if (!StartsWith(username_value, typed_username_value, false)) { 307 if (!StartsWith(username_value, typed_username_value, false)) {
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
287 blink::WebFormControlElement(), 388 blink::WebFormControlElement(),
288 REQUIRE_NONE, 389 REQUIRE_NONE,
289 EXTRACT_NONE, 390 EXTRACT_NONE,
290 &password_form->form_data, 391 &password_form->form_data,
291 NULL /* FormFieldData */); 392 NULL /* FormFieldData */);
292 393
293 return password_form.Pass(); 394 return password_form.Pass();
294 } 395 }
295 396
296 } // namespace autofill 397 } // namespace autofill
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698