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 "base/strings/string16.h"
6 #include "base/strings/string_util.h"
7 #include "base/strings/stringprintf.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "components/autofill/content/renderer/form_autofill_util.h"
10 #include "components/autofill/content/renderer/password_form_conversion_utils.h"
11 #include "components/autofill/core/browser/form_structure.h"
12 #include "components/autofill/core/common/password_form.h"
13 #include "content/public/test/render_view_test.h"
14 #include "testing/gmock/include/gmock/gmock.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "third_party/WebKit/public/platform/WebVector.h"
17 #include "third_party/WebKit/public/web/WebDocument.h"
18 #include "third_party/WebKit/public/web/WebFormControlElement.h"
19 #include "third_party/WebKit/public/web/WebFormElement.h"
20 #include "third_party/WebKit/public/web/WebInputElement.h"
21 #include "third_party/WebKit/public/web/WebLocalFrame.h"
23 using blink::WebFormControlElement
;
24 using blink::WebFormElement
;
25 using blink::WebFrame
;
26 using blink::WebInputElement
;
27 using blink::WebVector
;
32 const char kTestFormActionURL
[] = "http://cnn.com";
34 // A builder to produce HTML code for a password form composed of the desired
35 // number and kinds of username and password fields.
36 class PasswordFormBuilder
{
38 // Creates a builder to start composing a new form. The form will have the
39 // specified |action| URL.
40 explicit PasswordFormBuilder(const char* action
) {
42 &html_
, "<FORM name=\"Test\" action=\"%s\" method=\"post\">", action
);
45 // Appends a new text-type field at the end of the form, having the specified
46 // |name_and_id|, |value|, and |autocomplete| attributes. The |autocomplete|
47 // argument can take two special values, namely:
48 // 1.) NULL, causing no autocomplete attribute to be added,
49 // 2.) "", causing an empty attribute (i.e. autocomplete="") to be added.
50 void AddTextField(const char* name_and_id
,
52 const char* autocomplete
) {
53 std::string
autocomplete_attribute(autocomplete
?
54 base::StringPrintf("autocomplete=\"%s\"", autocomplete
) : "");
57 "<INPUT type=\"text\" name=\"%s\" id=\"%s\" value=\"%s\" %s/>",
58 name_and_id
, name_and_id
, value
, autocomplete_attribute
.c_str());
61 // Appends a new password-type field at the end of the form, having the
62 // specified |name_and_id|, |value|, and |autocomplete| attributes. Special
63 // values for |autocomplete| are the same as in AddTextField.
64 void AddPasswordField(const char* name_and_id
,
66 const char* autocomplete
) {
67 std::string
autocomplete_attribute(autocomplete
?
68 base::StringPrintf("autocomplete=\"%s\"", autocomplete
): "");
71 "<INPUT type=\"password\" name=\"%s\" id=\"%s\" value=\"%s\" %s/>",
72 name_and_id
, name_and_id
, value
, autocomplete_attribute
.c_str());
75 // Appends a disabled text-type field at the end of the form.
76 void AddDisabledUsernameField() {
77 html_
+= "<INPUT type=\"text\" disabled/>";
80 // Appends a disabled password-type field at the end of the form.
81 void AddDisabledPasswordField() {
82 html_
+= "<INPUT type=\"password\" disabled/>";
85 // Appends a hidden field at the end of the form.
86 void AddHiddenField() { html_
+= "<INPUT type=\"hidden\"/>"; }
88 // Appends a new hidden-type field at the end of the form, having the
89 // specified |name_and_id| and |value| attributes.
90 void AddHiddenField(const char* name_and_id
,
94 "<INPUT type=\"hidden\" name=\"%s\" id=\"%s\" value=\"%s\" />",
95 name_and_id
, name_and_id
, value
);
98 // Appends a new submit-type field at the end of the form with the specified
100 void AddSubmitButton(const char* name
) {
103 "<INPUT type=\"submit\" name=\"%s\" value=\"Submit\"/>",
107 // Returns the HTML code for the form containing the fields that have been
109 std::string
ProduceHTML() const {
110 return html_
+ "</FORM>";
116 DISALLOW_COPY_AND_ASSIGN(PasswordFormBuilder
);
119 // RenderViewTest-based tests crash on Android
120 // http://crbug.com/187500
121 #if defined(OS_ANDROID)
122 #define MAYBE_PasswordFormConversionUtilsTest \
123 DISABLED_PasswordFormConversionUtilsTest
125 #define MAYBE_PasswordFormConversionUtilsTest PasswordFormConversionUtilsTest
126 #endif // defined(OS_ANDROID)
128 class MAYBE_PasswordFormConversionUtilsTest
: public content::RenderViewTest
{
130 MAYBE_PasswordFormConversionUtilsTest() : content::RenderViewTest() {}
131 ~MAYBE_PasswordFormConversionUtilsTest() override
{}
134 // Loads the given |html|, retrieves the sole WebFormElement from it, and then
135 // calls CreatePasswordForm(), passing it the |predictions| to convert it into
136 // a |password_form|. Note that ASSERT() can only be used in void functions,
137 // this is why |password_form| is passed in as a pointer to a scoped_ptr.
138 void LoadHTMLAndConvertForm(const std::string
& html
,
139 scoped_ptr
<PasswordForm
>* password_form
,
140 FormsPredictionsMap
* predictions
) {
142 LoadWebFormFromHTML(html
, &form
);
144 WebVector
<WebFormControlElement
> control_elements
;
145 form
.getFormControlElements(control_elements
);
146 for (size_t i
= 0; i
< control_elements
.size(); ++i
) {
147 WebInputElement
* input_element
= toWebInputElement(&control_elements
[i
]);
148 if (input_element
->hasAttribute("set-activated-submit"))
149 input_element
->setActivatedSubmit(true);
152 *password_form
= CreatePasswordFormFromWebForm(form
, nullptr, predictions
);
155 // Iterates on the form generated by the |html| and adds the fields and type
156 // predictions corresponding to |predictions_positions| to |predictions|.
157 void SetPredictions(const std::string
& html
,
158 FormsPredictionsMap
* predictions
,
159 const std::map
<int, PasswordFormFieldPredictionType
>&
160 predictions_positions
) {
162 LoadWebFormFromHTML(html
, &form
);
165 ASSERT_TRUE(WebFormElementToFormData(form
, WebFormControlElement(),
166 EXTRACT_NONE
, &form_data
, nullptr));
168 FormStructure
form_structure(form_data
);
171 for (std::vector
<AutofillField
*>::const_iterator
172 field
= form_structure
.begin();
173 field
!= form_structure
.end(); ++field
, ++field_index
) {
174 if (predictions_positions
.find(field_index
) !=
175 predictions_positions
.end()) {
176 (*predictions
)[form_data
][*(*field
)] =
177 predictions_positions
.find(field_index
)->second
;
182 // Loads the given |html| and retrieves the sole WebFormElement from it.
183 void LoadWebFormFromHTML(const std::string
& html
, WebFormElement
* form
) {
184 LoadHTML(html
.c_str());
186 WebFrame
* frame
= GetMainFrame();
187 ASSERT_NE(nullptr, frame
);
189 WebVector
<WebFormElement
> forms
;
190 frame
->document().forms(forms
);
191 ASSERT_EQ(1U, forms
.size());
197 DISALLOW_COPY_AND_ASSIGN(MAYBE_PasswordFormConversionUtilsTest
);
202 TEST_F(MAYBE_PasswordFormConversionUtilsTest
, BasicFormAttributes
) {
203 PasswordFormBuilder
builder(kTestFormActionURL
);
204 builder
.AddTextField("username", "johnsmith", NULL
);
205 builder
.AddSubmitButton("inactive_submit");
206 builder
.AddSubmitButton("active_submit");
207 builder
.AddSubmitButton("inactive_submit2");
208 builder
.AddPasswordField("password", "secret", NULL
);
209 std::string html
= builder
.ProduceHTML();
211 scoped_ptr
<PasswordForm
> password_form
;
212 ASSERT_NO_FATAL_FAILURE(
213 LoadHTMLAndConvertForm(html
, &password_form
, nullptr));
214 ASSERT_TRUE(password_form
);
216 EXPECT_EQ("data:", password_form
->signon_realm
);
217 EXPECT_EQ(GURL(kTestFormActionURL
), password_form
->action
);
218 EXPECT_EQ(base::UTF8ToUTF16("username"), password_form
->username_element
);
219 EXPECT_EQ(base::UTF8ToUTF16("johnsmith"), password_form
->username_value
);
220 EXPECT_EQ(base::UTF8ToUTF16("password"), password_form
->password_element
);
221 EXPECT_EQ(base::UTF8ToUTF16("secret"), password_form
->password_value
);
222 EXPECT_EQ(PasswordForm::SCHEME_HTML
, password_form
->scheme
);
223 EXPECT_FALSE(password_form
->ssl_valid
);
224 EXPECT_FALSE(password_form
->preferred
);
225 EXPECT_FALSE(password_form
->blacklisted_by_user
);
226 EXPECT_EQ(PasswordForm::TYPE_MANUAL
, password_form
->type
);
229 TEST_F(MAYBE_PasswordFormConversionUtilsTest
, DisabledFieldsAreIgnored
) {
230 PasswordFormBuilder
builder(kTestFormActionURL
);
231 builder
.AddTextField("username", "johnsmith", NULL
);
232 builder
.AddDisabledUsernameField();
233 builder
.AddDisabledPasswordField();
234 builder
.AddPasswordField("password", "secret", NULL
);
235 builder
.AddSubmitButton("submit");
236 std::string html
= builder
.ProduceHTML();
238 scoped_ptr
<PasswordForm
> password_form
;
239 ASSERT_NO_FATAL_FAILURE(
240 LoadHTMLAndConvertForm(html
, &password_form
, nullptr));
241 ASSERT_TRUE(password_form
);
242 EXPECT_EQ(base::UTF8ToUTF16("username"), password_form
->username_element
);
243 EXPECT_EQ(base::UTF8ToUTF16("johnsmith"), password_form
->username_value
);
244 EXPECT_EQ(base::UTF8ToUTF16("password"), password_form
->password_element
);
245 EXPECT_EQ(base::UTF8ToUTF16("secret"), password_form
->password_value
);
248 TEST_F(MAYBE_PasswordFormConversionUtilsTest
, IdentifyingUsernameFields
) {
249 // Each test case consists of a set of parameters to be plugged into the
250 // PasswordFormBuilder below, plus the corresponding expectations.
252 const char* autocomplete
[3];
253 const char* expected_username_element
;
254 const char* expected_username_value
;
255 const char* expected_other_possible_usernames
;
257 // When no elements are marked with autocomplete='username', the text-type
258 // input field before the first password element should get selected as
259 // the username, and the rest should be marked as alternatives.
260 {{NULL
, NULL
, NULL
}, "username2", "William", "John+Smith"},
261 // When a sole element is marked with autocomplete='username', it should
262 // be treated as the username for sure, with no other_possible_usernames.
263 {{"username", NULL
, NULL
}, "username1", "John", ""},
264 {{NULL
, "username", NULL
}, "username2", "William", ""},
265 {{NULL
, NULL
, "username"}, "username3", "Smith", ""},
266 // When >=2 elements have the attribute, the first should be selected as
267 // the username, and the rest should go to other_possible_usernames.
268 {{"username", "username", NULL
}, "username1", "John", "William"},
269 {{NULL
, "username", "username"}, "username2", "William", "Smith"},
270 {{"username", NULL
, "username"}, "username1", "John", "Smith"},
271 {{"username", "username", "username"}, "username1", "John",
273 // When there is an empty autocomplete attribute (i.e. autocomplete=""),
274 // it should have the same effect as having no attribute whatsoever.
275 {{"", "", ""}, "username2", "William", "John+Smith"},
276 {{"", "", "username"}, "username3", "Smith", ""},
277 {{"username", "", "username"}, "username1", "John", "Smith"},
278 // It should not matter if attribute values are upper or mixed case.
279 {{"USERNAME", NULL
, "uSeRNaMe"}, "username1", "John", "Smith"},
280 {{"uSeRNaMe", NULL
, "USERNAME"}, "username1", "John", "Smith"}};
282 for (size_t i
= 0; i
< arraysize(cases
); ++i
) {
283 for (size_t nonempty_username_fields
= 0; nonempty_username_fields
< 2;
284 ++nonempty_username_fields
) {
285 SCOPED_TRACE(testing::Message()
286 << "Iteration " << i
<< " "
287 << (nonempty_username_fields
? "nonempty" : "empty"));
289 // Repeat each test once with empty, and once with non-empty usernames.
290 // In the former case, no empty other_possible_usernames should be saved.
291 const char* names
[3];
292 if (nonempty_username_fields
) {
294 names
[1] = "William";
297 names
[0] = names
[1] = names
[2] = "";
300 PasswordFormBuilder
builder(kTestFormActionURL
);
301 builder
.AddTextField("username1", names
[0], cases
[i
].autocomplete
[0]);
302 builder
.AddTextField("username2", names
[1], cases
[i
].autocomplete
[1]);
303 builder
.AddPasswordField("password", "secret", NULL
);
304 builder
.AddTextField("username3", names
[2], cases
[i
].autocomplete
[2]);
305 builder
.AddPasswordField("password2", "othersecret", NULL
);
306 builder
.AddSubmitButton("submit");
307 std::string html
= builder
.ProduceHTML();
309 scoped_ptr
<PasswordForm
> password_form
;
310 ASSERT_NO_FATAL_FAILURE(
311 LoadHTMLAndConvertForm(html
, &password_form
, nullptr));
312 ASSERT_TRUE(password_form
);
314 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_username_element
),
315 password_form
->username_element
);
317 if (nonempty_username_fields
) {
318 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_username_value
),
319 password_form
->username_value
);
320 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_other_possible_usernames
),
321 base::JoinString(password_form
->other_possible_usernames
,
322 base::ASCIIToUTF16("+")));
324 EXPECT_TRUE(password_form
->username_value
.empty());
325 EXPECT_TRUE(password_form
->other_possible_usernames
.empty());
328 // Do a basic sanity check that we are still having a password field.
329 EXPECT_EQ(base::UTF8ToUTF16("password"), password_form
->password_element
);
330 EXPECT_EQ(base::UTF8ToUTF16("secret"), password_form
->password_value
);
335 TEST_F(MAYBE_PasswordFormConversionUtilsTest
, IdentifyingTwoPasswordFields
) {
336 // Each test case consists of a set of parameters to be plugged into the
337 // PasswordFormBuilder below, plus the corresponding expectations.
339 const char* password_values
[2];
340 const char* expected_password_element
;
341 const char* expected_password_value
;
342 const char* expected_new_password_element
;
343 const char* expected_new_password_value
;
345 // Two non-empty fields with the same value should be treated as a new
346 // password field plus a confirmation field for the new password.
347 {{"alpha", "alpha"}, "", "", "password1", "alpha"},
348 // The same goes if the fields are yet empty: we speculate that we will
349 // identify them as new password fields once they are filled out, and we
350 // want to keep our abstract interpretation of the form less flaky.
351 {{"", ""}, "password1", "", "password2", ""},
352 // Two different values should be treated as a password change form, one
353 // that also asks for the current password, but only once for the new.
354 {{"alpha", ""}, "password1", "alpha", "password2", ""},
355 {{"", "beta"}, "password1", "", "password2", "beta"},
356 {{"alpha", "beta"}, "password1", "alpha", "password2", "beta"}};
358 for (size_t i
= 0; i
< arraysize(cases
); ++i
) {
359 SCOPED_TRACE(testing::Message() << "Iteration " << i
);
361 PasswordFormBuilder
builder(kTestFormActionURL
);
362 builder
.AddPasswordField("password1", cases
[i
].password_values
[0], NULL
);
363 builder
.AddTextField("username1", "William", NULL
);
364 builder
.AddPasswordField("password2", cases
[i
].password_values
[1], NULL
);
365 builder
.AddTextField("username2", "Smith", NULL
);
366 builder
.AddSubmitButton("submit");
367 std::string html
= builder
.ProduceHTML();
369 scoped_ptr
<PasswordForm
> password_form
;
370 ASSERT_NO_FATAL_FAILURE(
371 LoadHTMLAndConvertForm(html
, &password_form
, nullptr));
372 ASSERT_TRUE(password_form
);
374 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_password_element
),
375 password_form
->password_element
);
376 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_password_value
),
377 password_form
->password_value
);
378 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_new_password_element
),
379 password_form
->new_password_element
);
380 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_new_password_value
),
381 password_form
->new_password_value
);
383 // Do a basic sanity check that we are still selecting the right username.
384 EXPECT_EQ(base::UTF8ToUTF16("username1"), password_form
->username_element
);
385 EXPECT_EQ(base::UTF8ToUTF16("William"), password_form
->username_value
);
386 EXPECT_THAT(password_form
->other_possible_usernames
,
387 testing::ElementsAre(base::UTF8ToUTF16("Smith")));
391 TEST_F(MAYBE_PasswordFormConversionUtilsTest
, IdentifyingThreePasswordFields
) {
392 // Each test case consists of a set of parameters to be plugged into the
393 // PasswordFormBuilder below, plus the corresponding expectations.
395 const char* password_values
[3];
396 const char* expected_password_element
;
397 const char* expected_password_value
;
398 const char* expected_new_password_element
;
399 const char* expected_new_password_value
;
401 // Two fields with the same value, and one different: we should treat this
402 // as a password change form with confirmation for the new password. Note
403 // that we only recognize (current + new + new) and (new + new + current)
404 // without autocomplete attributes.
405 {{"alpha", "", ""}, "password1", "alpha", "password2", ""},
406 {{"", "beta", "beta"}, "password1", "", "password2", "beta"},
407 {{"alpha", "beta", "beta"}, "password1", "alpha", "password2", "beta"},
408 // If confirmed password comes first, assume that the third password
409 // field is related to security question, SSN, or credit card and ignore
411 {{"beta", "beta", "alpha"}, "", "", "password1", "beta"},
412 // If the fields are yet empty, we speculate that we will identify them as
413 // (current + new + new) once they are filled out, so we should classify
414 // them the same for now to keep our abstract interpretation less flaky.
415 {{"", "", ""}, "password1", "", "password2", ""}};
416 // Note: In all other cases, we give up and consider the form invalid.
417 // This is tested in InvalidFormDueToConfusingPasswordFields.
419 for (size_t i
= 0; i
< arraysize(cases
); ++i
) {
420 SCOPED_TRACE(testing::Message() << "Iteration " << i
);
422 PasswordFormBuilder
builder(kTestFormActionURL
);
423 builder
.AddPasswordField("password1", cases
[i
].password_values
[0], NULL
);
424 builder
.AddTextField("username1", "William", NULL
);
425 builder
.AddPasswordField("password2", cases
[i
].password_values
[1], NULL
);
426 builder
.AddTextField("username2", "Smith", NULL
);
427 builder
.AddPasswordField("password3", cases
[i
].password_values
[2], NULL
);
428 builder
.AddSubmitButton("submit");
429 std::string html
= builder
.ProduceHTML();
431 scoped_ptr
<PasswordForm
> password_form
;
432 ASSERT_NO_FATAL_FAILURE(
433 LoadHTMLAndConvertForm(html
, &password_form
, nullptr));
434 ASSERT_TRUE(password_form
);
436 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_password_element
),
437 password_form
->password_element
);
438 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_password_value
),
439 password_form
->password_value
);
440 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_new_password_element
),
441 password_form
->new_password_element
);
442 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_new_password_value
),
443 password_form
->new_password_value
);
445 // Do a basic sanity check that we are still selecting the right username.
446 EXPECT_EQ(base::UTF8ToUTF16("username1"), password_form
->username_element
);
447 EXPECT_EQ(base::UTF8ToUTF16("William"), password_form
->username_value
);
448 EXPECT_THAT(password_form
->other_possible_usernames
,
449 testing::ElementsAre(base::UTF8ToUTF16("Smith")));
453 TEST_F(MAYBE_PasswordFormConversionUtilsTest
,
454 IdentifyingPasswordFieldsWithAutocompleteAttributes
) {
455 // Each test case consists of a set of parameters to be plugged into the
456 // PasswordFormBuilder below, plus the corresponding expectations.
458 const char* autocomplete
[3];
459 const char* expected_password_element
;
460 const char* expected_password_value
;
461 const char* expected_new_password_element
;
462 const char* expected_new_password_value
;
463 bool expected_new_password_marked_by_site
;
465 // When there are elements marked with autocomplete='current-password',
466 // but no elements with 'new-password', we should treat the first of the
467 // former kind as the current password, and ignore all other password
468 // fields, assuming they are not intentionally not marked. They might be
469 // for other purposes, such as PINs, OTPs, and the like. Actual values in
470 // the password fields should be ignored in all cases below.
471 {{"current-password", NULL
, NULL
},
472 "password1", "alpha", "", "", false},
473 {{NULL
, "current-password", NULL
},
474 "password2", "beta", "", "", false},
475 {{NULL
, NULL
, "current-password"},
476 "password3", "gamma", "", "", false},
477 {{NULL
, "current-password", "current-password"},
478 "password2", "beta", "", "", false},
479 {{"current-password", NULL
, "current-password"},
480 "password1", "alpha", "", "", false},
481 {{"current-password", "current-password", NULL
},
482 "password1", "alpha", "", "", false},
483 {{"current-password", "current-password", "current-password"},
484 "password1", "alpha", "", "", false},
485 // The same goes vice versa for autocomplete='new-password'.
486 {{"new-password", NULL
, NULL
},
487 "", "", "password1", "alpha", true},
488 {{NULL
, "new-password", NULL
},
489 "", "", "password2", "beta", true},
490 {{NULL
, NULL
, "new-password"},
491 "", "", "password3", "gamma", true},
492 {{NULL
, "new-password", "new-password"},
493 "", "", "password2", "beta", true},
494 {{"new-password", NULL
, "new-password"},
495 "", "", "password1", "alpha", true},
496 {{"new-password", "new-password", NULL
},
497 "", "", "password1", "alpha", true},
498 {{"new-password", "new-password", "new-password"},
499 "", "", "password1", "alpha", true},
500 // When there is one element marked with autocomplete='current-password',
501 // and one with 'new-password', just comply, regardless of their order.
502 // Ignore the unmarked password field(s) for the same reason as above.
503 {{"current-password", "new-password", NULL
},
504 "password1", "alpha", "password2", "beta", true},
505 {{"current-password", NULL
, "new-password"},
506 "password1", "alpha", "password3", "gamma", true},
507 {{NULL
, "current-password", "new-password"},
508 "password2", "beta", "password3", "gamma", true},
509 {{"new-password", "current-password", NULL
},
510 "password2", "beta", "password1", "alpha", true},
511 {{"new-password", NULL
, "current-password"},
512 "password3", "gamma", "password1", "alpha", true},
513 {{NULL
, "new-password", "current-password"},
514 "password3", "gamma", "password2", "beta", true},
515 // In case of duplicated elements of either kind, go with the first one of
517 {{"current-password", "current-password", "new-password"},
518 "password1", "alpha", "password3", "gamma", true},
519 {{"current-password", "new-password", "current-password"},
520 "password1", "alpha", "password2", "beta", true},
521 {{"new-password", "current-password", "current-password"},
522 "password2", "beta", "password1", "alpha", true},
523 {{"current-password", "new-password", "new-password"},
524 "password1", "alpha", "password2", "beta", true},
525 {{"new-password", "current-password", "new-password"},
526 "password2", "beta", "password1", "alpha", true},
527 {{"new-password", "new-password", "current-password"},
528 "password3", "gamma", "password1", "alpha", true},
529 // When there is an empty autocomplete attribute (i.e. autocomplete=""),
530 // it should have the same effect as having no attribute whatsoever.
531 {{"current-password", "", ""},
532 "password1", "alpha", "", "", false},
533 {{"", "", "new-password"},
534 "", "", "password3", "gamma", true},
535 {{"", "new-password", ""},
536 "", "", "password2", "beta", true},
537 {{"", "current-password", "current-password"},
538 "password2", "beta", "", "", false},
539 {{"new-password", "", "new-password"},
540 "", "", "password1", "alpha", true},
541 {{"new-password", "", "current-password"},
542 "password3", "gamma", "password1", "alpha", true},
543 // It should not matter if attribute values are upper or mixed case.
544 {{NULL
, "current-password", NULL
},
545 "password2", "beta", "", "", false},
546 {{NULL
, "CURRENT-PASSWORD", NULL
},
547 "password2", "beta", "", "", false},
548 {{NULL
, "new-password", NULL
},
549 "", "", "password2", "beta", true},
550 {{NULL
, "nEw-PaSsWoRd", NULL
},
551 "", "", "password2", "beta", true}};
553 for (size_t i
= 0; i
< arraysize(cases
); ++i
) {
554 SCOPED_TRACE(testing::Message() << "Iteration " << i
);
556 PasswordFormBuilder
builder(kTestFormActionURL
);
557 builder
.AddPasswordField("pin1", "123456", NULL
);
558 builder
.AddPasswordField("pin2", "789101", NULL
);
559 builder
.AddPasswordField("password1", "alpha", cases
[i
].autocomplete
[0]);
560 builder
.AddTextField("username1", "William", NULL
);
561 builder
.AddPasswordField("password2", "beta", cases
[i
].autocomplete
[1]);
562 builder
.AddTextField("username2", "Smith", NULL
);
563 builder
.AddPasswordField("password3", "gamma", cases
[i
].autocomplete
[2]);
564 builder
.AddSubmitButton("submit");
565 std::string html
= builder
.ProduceHTML();
567 scoped_ptr
<PasswordForm
> password_form
;
568 ASSERT_NO_FATAL_FAILURE(
569 LoadHTMLAndConvertForm(html
, &password_form
, nullptr));
570 ASSERT_TRUE(password_form
);
572 // In the absence of username autocomplete attributes, the username should
573 // be the text input field before the first password element.
574 // No constellation of password autocomplete attributes should change that.
575 EXPECT_EQ(base::UTF8ToUTF16("username1"), password_form
->username_element
);
576 EXPECT_EQ(base::UTF8ToUTF16("William"), password_form
->username_value
);
577 EXPECT_THAT(password_form
->other_possible_usernames
,
578 testing::ElementsAre(base::UTF8ToUTF16("Smith")));
579 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_password_element
),
580 password_form
->password_element
);
581 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_password_value
),
582 password_form
->password_value
);
583 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_new_password_element
),
584 password_form
->new_password_element
);
585 EXPECT_EQ(base::UTF8ToUTF16(cases
[i
].expected_new_password_value
),
586 password_form
->new_password_value
);
587 EXPECT_EQ(cases
[i
].expected_new_password_marked_by_site
,
588 password_form
->new_password_marked_by_site
);
592 TEST_F(MAYBE_PasswordFormConversionUtilsTest
, InvalidFormDueToBadActionURL
) {
593 PasswordFormBuilder
builder("invalid_target");
594 builder
.AddTextField("username", "JohnSmith", NULL
);
595 builder
.AddSubmitButton("submit");
596 builder
.AddPasswordField("password", "secret", NULL
);
597 std::string html
= builder
.ProduceHTML();
599 scoped_ptr
<PasswordForm
> password_form
;
600 ASSERT_NO_FATAL_FAILURE(
601 LoadHTMLAndConvertForm(html
, &password_form
, nullptr));
602 EXPECT_FALSE(password_form
);
605 TEST_F(MAYBE_PasswordFormConversionUtilsTest
,
606 InvalidFormDueToNoPasswordFields
) {
607 PasswordFormBuilder
builder(kTestFormActionURL
);
608 builder
.AddTextField("username1", "John", NULL
);
609 builder
.AddTextField("username2", "Smith", NULL
);
610 builder
.AddSubmitButton("submit");
611 std::string html
= builder
.ProduceHTML();
613 scoped_ptr
<PasswordForm
> password_form
;
614 ASSERT_NO_FATAL_FAILURE(
615 LoadHTMLAndConvertForm(html
, &password_form
, nullptr));
616 EXPECT_FALSE(password_form
);
619 TEST_F(MAYBE_PasswordFormConversionUtilsTest
,
620 InvalidFormsDueToConfusingPasswordFields
) {
621 // Each test case consists of a set of parameters to be plugged into the
622 // PasswordFormBuilder below.
623 const char* cases
[][3] = {
624 // No autocomplete attributes to guide us, and we see:
625 // * three password values that are all different,
626 // * three password values that are all the same;
627 // * three password values with the first and last matching.
628 // In any case, we should just give up on this form.
629 {"alpha", "beta", "gamma"},
630 {"alpha", "alpha", "alpha"},
631 {"alpha", "beta", "alpha"}};
633 for (size_t i
= 0; i
< arraysize(cases
); ++i
) {
634 SCOPED_TRACE(testing::Message() << "Iteration " << i
);
636 PasswordFormBuilder
builder(kTestFormActionURL
);
637 builder
.AddTextField("username1", "John", NULL
);
638 builder
.AddPasswordField("password1", cases
[i
][0], NULL
);
639 builder
.AddPasswordField("password2", cases
[i
][1], NULL
);
640 builder
.AddPasswordField("password3", cases
[i
][2], NULL
);
641 builder
.AddSubmitButton("submit");
642 std::string html
= builder
.ProduceHTML();
644 scoped_ptr
<PasswordForm
> password_form
;
645 ASSERT_NO_FATAL_FAILURE(
646 LoadHTMLAndConvertForm(html
, &password_form
, nullptr));
647 EXPECT_FALSE(password_form
);
651 TEST_F(MAYBE_PasswordFormConversionUtilsTest
,
652 InvalidFormDueToTooManyPasswordFieldsWithoutAutocompleteAttributes
) {
653 PasswordFormBuilder
builder(kTestFormActionURL
);
654 builder
.AddTextField("username1", "John", NULL
);
655 builder
.AddPasswordField("password1", "alpha", NULL
);
656 builder
.AddPasswordField("password2", "alpha", NULL
);
657 builder
.AddPasswordField("password3", "alpha", NULL
);
658 builder
.AddPasswordField("password4", "alpha", NULL
);
659 builder
.AddSubmitButton("submit");
660 std::string html
= builder
.ProduceHTML();
662 scoped_ptr
<PasswordForm
> password_form
;
663 ASSERT_NO_FATAL_FAILURE(
664 LoadHTMLAndConvertForm(html
, &password_form
, nullptr));
665 EXPECT_FALSE(password_form
);
668 TEST_F(MAYBE_PasswordFormConversionUtilsTest
, LayoutClassificationLogin
) {
669 PasswordFormBuilder
builder(kTestFormActionURL
);
670 builder
.AddHiddenField();
671 builder
.AddTextField("username", "", nullptr);
672 builder
.AddPasswordField("password", "", nullptr);
673 builder
.AddSubmitButton("submit");
674 std::string login_html
= builder
.ProduceHTML();
676 scoped_ptr
<PasswordForm
> login_form
;
677 ASSERT_NO_FATAL_FAILURE(
678 LoadHTMLAndConvertForm(login_html
, &login_form
, nullptr));
679 ASSERT_TRUE(login_form
);
680 EXPECT_EQ(PasswordForm::Layout::LAYOUT_OTHER
, login_form
->layout
);
683 TEST_F(MAYBE_PasswordFormConversionUtilsTest
, LayoutClassificationSignup
) {
684 PasswordFormBuilder
builder(kTestFormActionURL
);
685 builder
.AddTextField("someotherfield", "", nullptr);
686 builder
.AddTextField("username", "", nullptr);
687 builder
.AddPasswordField("new_password", "", nullptr);
688 builder
.AddHiddenField();
689 builder
.AddPasswordField("new_password2", "", nullptr);
690 builder
.AddSubmitButton("submit");
691 std::string signup_html
= builder
.ProduceHTML();
693 scoped_ptr
<PasswordForm
> signup_form
;
694 ASSERT_NO_FATAL_FAILURE(
695 LoadHTMLAndConvertForm(signup_html
, &signup_form
, nullptr));
696 ASSERT_TRUE(signup_form
);
697 EXPECT_EQ(PasswordForm::Layout::LAYOUT_OTHER
, signup_form
->layout
);
700 TEST_F(MAYBE_PasswordFormConversionUtilsTest
, LayoutClassificationChange
) {
701 PasswordFormBuilder
builder(kTestFormActionURL
);
702 builder
.AddTextField("username", "", nullptr);
703 builder
.AddPasswordField("old_password", "", nullptr);
704 builder
.AddHiddenField();
705 builder
.AddPasswordField("new_password", "", nullptr);
706 builder
.AddPasswordField("new_password2", "", nullptr);
707 builder
.AddSubmitButton("submit");
708 std::string change_html
= builder
.ProduceHTML();
710 scoped_ptr
<PasswordForm
> change_form
;
711 ASSERT_NO_FATAL_FAILURE(
712 LoadHTMLAndConvertForm(change_html
, &change_form
, nullptr));
713 ASSERT_TRUE(change_form
);
714 EXPECT_EQ(PasswordForm::Layout::LAYOUT_OTHER
, change_form
->layout
);
717 TEST_F(MAYBE_PasswordFormConversionUtilsTest
,
718 LayoutClassificationLoginPlusSignup_A
) {
719 PasswordFormBuilder
builder(kTestFormActionURL
);
720 builder
.AddTextField("username", "", nullptr);
721 builder
.AddHiddenField();
722 builder
.AddPasswordField("password", "", nullptr);
723 builder
.AddTextField("username2", "", nullptr);
724 builder
.AddTextField("someotherfield", "", nullptr);
725 builder
.AddPasswordField("new_password", "", nullptr);
726 builder
.AddPasswordField("new_password2", "", nullptr);
727 builder
.AddHiddenField();
728 builder
.AddSubmitButton("submit");
729 std::string login_plus_signup_html
= builder
.ProduceHTML();
731 scoped_ptr
<PasswordForm
> login_plus_signup_form
;
732 ASSERT_NO_FATAL_FAILURE(LoadHTMLAndConvertForm(
733 login_plus_signup_html
, &login_plus_signup_form
, nullptr));
734 ASSERT_TRUE(login_plus_signup_form
);
735 EXPECT_EQ(PasswordForm::Layout::LAYOUT_LOGIN_AND_SIGNUP
,
736 login_plus_signup_form
->layout
);
739 TEST_F(MAYBE_PasswordFormConversionUtilsTest
,
740 LayoutClassificationLoginPlusSignup_B
) {
741 PasswordFormBuilder
builder(kTestFormActionURL
);
742 builder
.AddTextField("username", "", nullptr);
743 builder
.AddHiddenField();
744 builder
.AddPasswordField("password", "", nullptr);
745 builder
.AddTextField("username2", "", nullptr);
746 builder
.AddTextField("someotherfield", "", nullptr);
747 builder
.AddPasswordField("new_password", "", nullptr);
748 builder
.AddTextField("someotherfield2", "", nullptr);
749 builder
.AddHiddenField();
750 builder
.AddSubmitButton("submit");
751 std::string login_plus_signup_html
= builder
.ProduceHTML();
753 scoped_ptr
<PasswordForm
> login_plus_signup_form
;
754 ASSERT_NO_FATAL_FAILURE(LoadHTMLAndConvertForm(
755 login_plus_signup_html
, &login_plus_signup_form
, nullptr));
756 ASSERT_TRUE(login_plus_signup_form
);
757 EXPECT_EQ(PasswordForm::Layout::LAYOUT_LOGIN_AND_SIGNUP
,
758 login_plus_signup_form
->layout
);
761 TEST_F(MAYBE_PasswordFormConversionUtilsTest
,
762 CreditCardNumberWithTypePasswordForm
) {
763 PasswordFormBuilder
builder(kTestFormActionURL
);
764 builder
.AddTextField("Credit card owner name", "John Smith", nullptr);
765 builder
.AddPasswordField("Credit card number", "0000 0000 0000 0000",
767 builder
.AddTextField("cvc", "000", nullptr);
768 builder
.AddSubmitButton("submit");
769 std::string html
= builder
.ProduceHTML();
771 std::map
<int, PasswordFormFieldPredictionType
> predictions_positions
;
772 predictions_positions
[1] = PREDICTION_NOT_PASSWORD
;
774 FormsPredictionsMap predictions
;
775 SetPredictions(html
, &predictions
, predictions_positions
);
777 scoped_ptr
<PasswordForm
> password_form
;
778 LoadHTMLAndConvertForm(html
, &password_form
, &predictions
);
779 EXPECT_FALSE(password_form
);
782 TEST_F(MAYBE_PasswordFormConversionUtilsTest
,
783 CreditCardVerificationNumberWithTypePasswordForm
) {
784 PasswordFormBuilder
builder(kTestFormActionURL
);
785 builder
.AddTextField("Credit card owner name", "John Smith", nullptr);
786 builder
.AddTextField("Credit card number", "0000 0000 0000 0000", nullptr);
787 builder
.AddPasswordField("cvc", "000", nullptr);
788 builder
.AddSubmitButton("submit");
789 std::string html
= builder
.ProduceHTML();
791 std::map
<int, PasswordFormFieldPredictionType
> predictions_positions
;
792 predictions_positions
[2] = PREDICTION_NOT_PASSWORD
;
794 FormsPredictionsMap predictions
;
795 SetPredictions(html
, &predictions
, predictions_positions
);
797 scoped_ptr
<PasswordForm
> password_form
;
798 LoadHTMLAndConvertForm(html
, &password_form
, &predictions
);
799 EXPECT_FALSE(password_form
);
802 TEST_F(MAYBE_PasswordFormConversionUtilsTest
,
803 CreditCardNumberWithTypePasswordFormWithAutocomplete
) {
804 PasswordFormBuilder
builder(kTestFormActionURL
);
805 builder
.AddTextField("Credit card owner name", "John Smith", nullptr);
806 builder
.AddPasswordField("Credit card number", "0000 0000 0000 0000",
808 builder
.AddTextField("cvc", "000", nullptr);
809 builder
.AddSubmitButton("submit");
810 std::string html
= builder
.ProduceHTML();
812 std::map
<int, PasswordFormFieldPredictionType
> predictions_positions
;
813 predictions_positions
[1] = PREDICTION_NOT_PASSWORD
;
815 FormsPredictionsMap predictions
;
816 SetPredictions(html
, &predictions
, predictions_positions
);
818 scoped_ptr
<PasswordForm
> password_form
;
819 LoadHTMLAndConvertForm(html
, &password_form
, &predictions
);
820 EXPECT_TRUE(password_form
);
823 TEST_F(MAYBE_PasswordFormConversionUtilsTest
,
824 CreditCardVerificationNumberWithTypePasswordFormWithAutocomplete
) {
825 PasswordFormBuilder
builder(kTestFormActionURL
);
826 builder
.AddTextField("Credit card owner name", "John Smith", nullptr);
827 builder
.AddTextField("Credit card number", "0000 0000 0000 0000", nullptr);
828 builder
.AddPasswordField("cvc", "000", "new-password");
829 builder
.AddSubmitButton("submit");
830 std::string html
= builder
.ProduceHTML();
832 std::map
<int, PasswordFormFieldPredictionType
> predictions_positions
;
833 predictions_positions
[2] = PREDICTION_NOT_PASSWORD
;
835 FormsPredictionsMap predictions
;
836 SetPredictions(html
, &predictions
, predictions_positions
);
838 scoped_ptr
<PasswordForm
> password_form
;
839 LoadHTMLAndConvertForm(html
, &password_form
, &predictions
);
840 EXPECT_TRUE(password_form
);
843 TEST_F(MAYBE_PasswordFormConversionUtilsTest
, IsGaiaReauthFormIgnored
) {
844 scoped_ptr
<PasswordFormBuilder
> builder
;
845 FormsPredictionsMap predictions
;
847 WebVector
<WebFormControlElement
> control_elements
;
849 // A common password form is parsed successfully.
850 builder
.reset(new PasswordFormBuilder("https://example.com"));
851 builder
->AddTextField("username", "", nullptr);
852 builder
->AddPasswordField("password", "", nullptr);
853 std::string html
= builder
->ProduceHTML();
854 LoadWebFormFromHTML(html
, &form
);
855 form
.getFormControlElements(control_elements
);
856 EXPECT_FALSE(IsGaiaReauthenticationForm(
857 GURL("https://example.com"), control_elements
));
859 // A common password form, even if it appears on a GAIA reauth url,
860 // is parsed successfully.
861 builder
.reset(new PasswordFormBuilder("https://accounts.google.com"));
862 builder
->AddTextField("username", "", nullptr);
863 builder
->AddPasswordField("password", "", nullptr);
864 html
= builder
->ProduceHTML();
865 LoadWebFormFromHTML(html
, &form
);
866 form
.getFormControlElements(control_elements
);
867 EXPECT_FALSE(IsGaiaReauthenticationForm(
868 GURL("https://accounts.google.com"), control_elements
));
870 // Not a transactional reauth.
871 builder
.reset(new PasswordFormBuilder("https://accounts.google.com"));
872 builder
->AddTextField("username", "", nullptr);
873 builder
->AddPasswordField("password", "", nullptr);
874 builder
->AddHiddenField(
875 "continue", "https://passwords.google.com/settings");
876 html
= builder
->ProduceHTML();
877 LoadWebFormFromHTML(html
, &form
);
878 form
.getFormControlElements(control_elements
);
879 EXPECT_FALSE(IsGaiaReauthenticationForm(
880 GURL("https://accounts.google.com"), control_elements
));
882 // A reauth form that is not for a password site is parsed successfuly.
883 builder
.reset(new PasswordFormBuilder("https://accounts.google.com"));
884 builder
->AddTextField("username", "", nullptr);
885 builder
->AddPasswordField("password", "", nullptr);
886 builder
->AddHiddenField("rart", "");
887 builder
->AddHiddenField("continue", "https://mail.google.com");
888 html
= builder
->ProduceHTML();
889 LoadWebFormFromHTML(html
, &form
);
890 form
.getFormControlElements(control_elements
);
891 EXPECT_FALSE(IsGaiaReauthenticationForm(
892 GURL("https://accounts.google.com"), control_elements
));
894 // A reauth form for a password site is ignored.
895 builder
.reset(new PasswordFormBuilder("https://accounts.google.com"));
896 builder
->AddTextField("username", "", nullptr);
897 builder
->AddPasswordField("password", "", nullptr);
898 builder
->AddHiddenField("rart", "");
899 builder
->AddHiddenField("continue", "https://passwords.google.com");
900 html
= builder
->ProduceHTML();
901 LoadWebFormFromHTML(html
, &form
);
902 form
.getFormControlElements(control_elements
);
903 EXPECT_TRUE(IsGaiaReauthenticationForm(
904 GURL("https://accounts.google.com"), control_elements
));
906 // Password site is inaccesible via HTTP, but because of HSTS the following
907 // link should still continue to https://passwords.google.com.
908 builder
.reset(new PasswordFormBuilder("https://accounts.google.com"));
909 builder
->AddTextField("username", "", nullptr);
910 builder
->AddPasswordField("password", "", nullptr);
911 builder
->AddHiddenField("rart", "");
912 builder
->AddHiddenField("continue", "http://passwords.google.com");
913 html
= builder
->ProduceHTML();
914 LoadWebFormFromHTML(html
, &form
);
915 form
.getFormControlElements(control_elements
);
916 EXPECT_TRUE(IsGaiaReauthenticationForm(
917 GURL("https://accounts.google.com"), control_elements
));
919 // Make sure testing sites are disabled as well.
920 builder
.reset(new PasswordFormBuilder("https://accounts.google.com"));
921 builder
->AddTextField("username", "", nullptr);
922 builder
->AddPasswordField("password", "", nullptr);
923 builder
->AddHiddenField("rart", "");
924 builder
->AddHiddenField(
925 "continue", "https://passwords-ac-testing.corp.google.com/settings");
926 html
= builder
->ProduceHTML();
927 LoadWebFormFromHTML(html
, &form
);
928 form
.getFormControlElements(control_elements
);
929 EXPECT_TRUE(IsGaiaReauthenticationForm(
930 GURL("https://accounts.google.com"), control_elements
));
932 // Specifying default port doesn't change anything.
933 builder
.reset(new PasswordFormBuilder("https://accounts.google.com"));
934 builder
->AddTextField("username", "", nullptr);
935 builder
->AddPasswordField("password", "", nullptr);
936 builder
->AddHiddenField("rart", "");
937 builder
->AddHiddenField("continue", "passwords.google.com:443");
938 html
= builder
->ProduceHTML();
939 LoadWebFormFromHTML(html
, &form
);
940 form
.getFormControlElements(control_elements
);
941 EXPECT_TRUE(IsGaiaReauthenticationForm(
942 GURL("https://accounts.google.com"), control_elements
));
944 // Encoded URL is considered the same.
945 builder
.reset(new PasswordFormBuilder("https://accounts.google.com"));
946 builder
->AddTextField("username", "", nullptr);
947 builder
->AddPasswordField("password", "", nullptr);
948 builder
->AddHiddenField("rart", "");
949 builder
->AddHiddenField(
950 "continue", "https://passwords.%67oogle.com/settings&rart=123");
951 html
= builder
->ProduceHTML();
952 LoadWebFormFromHTML(html
, &form
);
953 form
.getFormControlElements(control_elements
);
954 EXPECT_TRUE(IsGaiaReauthenticationForm(
955 GURL("https://accounts.google.com"), control_elements
));
957 // Fully qualified domain name is considered a different hostname by GURL.
958 // Ideally this would not be the case, but this quirk can be avoided by
959 // verification on the server. This test is simply documentation of this
961 builder
.reset(new PasswordFormBuilder("https://accounts.google.com"));
962 builder
->AddTextField("username", "", nullptr);
963 builder
->AddPasswordField("password", "", nullptr);
964 builder
->AddHiddenField("rart", "");
965 builder
->AddHiddenField("continue", "https://passwords.google.com./settings");
966 html
= builder
->ProduceHTML();
967 LoadWebFormFromHTML(html
, &form
);
968 form
.getFormControlElements(control_elements
);
969 EXPECT_TRUE(IsGaiaReauthenticationForm(
970 GURL("https://accounts.google.com"), control_elements
));
972 // A correctly looking form, but on a different page.
973 builder
.reset(new PasswordFormBuilder("https://google.com"));
974 builder
->AddTextField("username", "", nullptr);
975 builder
->AddPasswordField("password", "", nullptr);
976 builder
->AddHiddenField("rart", "");
977 builder
->AddHiddenField("continue", "https://passwords.google.com");
978 html
= builder
->ProduceHTML();
979 LoadWebFormFromHTML(html
, &form
);
980 form
.getFormControlElements(control_elements
);
981 EXPECT_FALSE(IsGaiaReauthenticationForm(
982 GURL("https://google.com"), control_elements
));
985 } // namespace autofill