password_form_manager.cc revision 23730a6e56a168d1879203e4b3819bb36e3d8f1f
1// Copyright (c) 2012 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/password_manager/core/browser/password_form_manager.h" 6 7#include <algorithm> 8 9#include "base/metrics/histogram.h" 10#include "base/strings/string_split.h" 11#include "base/strings/string_util.h" 12#include "components/autofill/core/browser/autofill_manager.h" 13#include "components/autofill/core/browser/form_structure.h" 14#include "components/autofill/core/browser/validation.h" 15#include "components/autofill/core/common/password_form.h" 16#include "components/password_manager/core/browser/password_manager.h" 17#include "components/password_manager/core/browser/password_manager_client.h" 18#include "components/password_manager/core/browser/password_manager_driver.h" 19#include "components/password_manager/core/browser/password_store.h" 20 21using autofill::FormStructure; 22using autofill::PasswordForm; 23using autofill::PasswordFormMap; 24using base::Time; 25 26namespace { 27 28enum PasswordGenerationSubmissionEvent { 29 // Generated password was submitted and saved. 30 PASSWORD_SUBMITTED, 31 32 // Generated password submission failed. These passwords aren't saved. 33 PASSWORD_SUBMISSION_FAILED, 34 35 // Generated password was not submitted before navigation. Currently these 36 // passwords are not saved. 37 PASSWORD_NOT_SUBMITTED, 38 39 // Generated password was overridden by a non-generated one. This generally 40 // signals that the user was unhappy with the generated password for some 41 // reason. 42 PASSWORD_OVERRIDDEN, 43 44 // Number of enum entries, used for UMA histogram reporting macros. 45 SUBMISSION_EVENT_ENUM_COUNT 46}; 47 48void LogPasswordGenerationSubmissionEvent( 49 PasswordGenerationSubmissionEvent event) { 50 UMA_HISTOGRAM_ENUMERATION("PasswordGeneration.SubmissionEvent", 51 event, SUBMISSION_EVENT_ENUM_COUNT); 52} 53 54} // namespace 55 56PasswordFormManager::PasswordFormManager(PasswordManager* password_manager, 57 PasswordManagerClient* client, 58 PasswordManagerDriver* driver, 59 const PasswordForm& observed_form, 60 bool ssl_valid) 61 : best_matches_deleter_(&best_matches_), 62 observed_form_(observed_form), 63 is_new_login_(true), 64 has_generated_password_(false), 65 password_manager_(password_manager), 66 preferred_match_(NULL), 67 state_(PRE_MATCHING_PHASE), 68 client_(client), 69 driver_(driver), 70 manager_action_(kManagerActionNone), 71 user_action_(kUserActionNone), 72 submit_result_(kSubmitResultNotSubmitted) { 73 if (observed_form_.origin.is_valid()) 74 base::SplitString(observed_form_.origin.path(), '/', &form_path_tokens_); 75 observed_form_.ssl_valid = ssl_valid; 76} 77 78PasswordFormManager::~PasswordFormManager() { 79 UMA_HISTOGRAM_ENUMERATION("PasswordManager.ActionsTakenWithPsl", 80 GetActionsTaken(), 81 kMaxNumActionsTaken); 82 if (has_generated_password_ && submit_result_ == kSubmitResultNotSubmitted) 83 LogPasswordGenerationSubmissionEvent(PASSWORD_NOT_SUBMITTED); 84} 85 86int PasswordFormManager::GetActionsTaken() { 87 return user_action_ + kUserActionMax * (manager_action_ + 88 kManagerActionMax * submit_result_); 89}; 90 91// TODO(timsteele): use a hash of some sort in the future? 92bool PasswordFormManager::DoesManage(const PasswordForm& form, 93 ActionMatch action_match) const { 94 if (form.scheme != PasswordForm::SCHEME_HTML) 95 return observed_form_.signon_realm == form.signon_realm; 96 97 // HTML form case. 98 // At a minimum, username and password element must match. 99 if (!((form.username_element == observed_form_.username_element) && 100 (form.password_element == observed_form_.password_element))) { 101 return false; 102 } 103 104 // When action match is required, the action URL must match, but 105 // the form is allowed to have an empty action URL (See bug 1107719). 106 // Otherwise ignore action URL, this is to allow saving password form with 107 // dynamically changed action URL (See bug 27246). 108 if (form.action.is_valid() && (form.action != observed_form_.action)) { 109 if (action_match == ACTION_MATCH_REQUIRED) 110 return false; 111 } 112 113 // If this is a replay of the same form in the case a user entered an invalid 114 // password, the origin of the new form may equal the action of the "first" 115 // form. 116 if (!((form.origin == observed_form_.origin) || 117 (form.origin == observed_form_.action))) { 118 if (form.origin.SchemeIsSecure() && 119 !observed_form_.origin.SchemeIsSecure()) { 120 // Compare origins, ignoring scheme. There is no easy way to do this 121 // with GURL because clearing the scheme would result in an invalid url. 122 // This is for some sites (such as Hotmail) that begin on an http page and 123 // head to https for the retry when password was invalid. 124 std::string::const_iterator after_scheme1 = form.origin.spec().begin() + 125 form.origin.scheme().length(); 126 std::string::const_iterator after_scheme2 = 127 observed_form_.origin.spec().begin() + 128 observed_form_.origin.scheme().length(); 129 return std::search(after_scheme1, 130 form.origin.spec().end(), 131 after_scheme2, 132 observed_form_.origin.spec().end()) 133 != form.origin.spec().end(); 134 } 135 return false; 136 } 137 return true; 138} 139 140bool PasswordFormManager::IsBlacklisted() { 141 DCHECK_EQ(state_, POST_MATCHING_PHASE); 142 if (preferred_match_ && preferred_match_->blacklisted_by_user) 143 return true; 144 return false; 145} 146 147void PasswordFormManager::PermanentlyBlacklist() { 148 DCHECK_EQ(state_, POST_MATCHING_PHASE); 149 150 // Configure the form about to be saved for blacklist status. 151 pending_credentials_.preferred = true; 152 pending_credentials_.blacklisted_by_user = true; 153 pending_credentials_.username_value.clear(); 154 pending_credentials_.password_value.clear(); 155 156 // Retroactively forget existing matches for this form, so we NEVER prompt or 157 // autofill it again. 158 int num_passwords_deleted = 0; 159 if (!best_matches_.empty()) { 160 PasswordFormMap::const_iterator iter; 161 PasswordStore* password_store = client_->GetPasswordStore(); 162 if (!password_store) { 163 NOTREACHED(); 164 return; 165 } 166 for (iter = best_matches_.begin(); iter != best_matches_.end(); ++iter) { 167 // We want to remove existing matches for this form so that the exact 168 // origin match with |blackisted_by_user == true| is the only result that 169 // shows up in the future for this origin URL. However, we don't want to 170 // delete logins that were actually saved on a different page (hence with 171 // different origin URL) and just happened to match this form because of 172 // the scoring algorithm. See bug 1204493. 173 if (iter->second->origin == observed_form_.origin) { 174 password_store->RemoveLogin(*iter->second); 175 ++num_passwords_deleted; 176 } 177 } 178 } 179 180 UMA_HISTOGRAM_COUNTS("PasswordManager.NumPasswordsDeletedWhenBlacklisting", 181 num_passwords_deleted); 182 183 // Save the pending_credentials_ entry marked as blacklisted. 184 SaveAsNewLogin(false); 185} 186 187void PasswordFormManager::SetUseAdditionalPasswordAuthentication( 188 bool use_additional_authentication) { 189 pending_credentials_.use_additional_authentication = 190 use_additional_authentication; 191} 192 193bool PasswordFormManager::IsNewLogin() { 194 DCHECK_EQ(state_, POST_MATCHING_PHASE); 195 return is_new_login_; 196} 197 198bool PasswordFormManager::IsPendingCredentialsPublicSuffixMatch() { 199 return pending_credentials_.IsPublicSuffixMatch(); 200} 201 202void PasswordFormManager::SetHasGeneratedPassword() { 203 has_generated_password_ = true; 204} 205 206bool PasswordFormManager::HasGeneratedPassword() { 207 // This check is permissive, as the user may have generated a password and 208 // then edited it in the form itself. However, even in this case the user 209 // has already given consent, so we treat these cases the same. 210 return has_generated_password_; 211} 212 213bool PasswordFormManager::HasValidPasswordForm() { 214 DCHECK_EQ(state_, POST_MATCHING_PHASE); 215 // Non-HTML password forms (primarily HTTP and FTP autentication) 216 // do not contain username_element and password_element values. 217 if (observed_form_.scheme != PasswordForm::SCHEME_HTML) 218 return true; 219 return !observed_form_.username_element.empty() && 220 !observed_form_.password_element.empty(); 221} 222 223void PasswordFormManager::ProvisionallySave( 224 const PasswordForm& credentials, 225 OtherPossibleUsernamesAction action) { 226 DCHECK_EQ(state_, POST_MATCHING_PHASE); 227 DCHECK(DoesManage(credentials, ACTION_MATCH_NOT_REQUIRED)); 228 229 // Make sure the important fields stay the same as the initially observed or 230 // autofilled ones, as they may have changed if the user experienced a login 231 // failure. 232 // Look for these credentials in the list containing auto-fill entries. 233 PasswordFormMap::const_iterator it = 234 best_matches_.find(credentials.username_value); 235 if (it != best_matches_.end()) { 236 // The user signed in with a login we autofilled. 237 pending_credentials_ = *it->second; 238 239 // Public suffix matches should always be new logins, since we want to store 240 // them so they can automatically be filled in later. 241 is_new_login_ = IsPendingCredentialsPublicSuffixMatch(); 242 if (is_new_login_) 243 user_action_ = kUserActionChoosePslMatch; 244 245 // Check to see if we're using a known username but a new password. 246 if (pending_credentials_.password_value != credentials.password_value) 247 user_action_ = kUserActionOverride; 248 } else if (action == ALLOW_OTHER_POSSIBLE_USERNAMES && 249 UpdatePendingCredentialsIfOtherPossibleUsername( 250 credentials.username_value)) { 251 // |pending_credentials_| is now set. Note we don't update 252 // |pending_credentials_.username_value| to |credentials.username_value| 253 // yet because we need to keep the original username to modify the stored 254 // credential. 255 selected_username_ = credentials.username_value; 256 is_new_login_ = false; 257 } else { 258 // User typed in a new, unknown username. 259 user_action_ = kUserActionOverride; 260 pending_credentials_ = observed_form_; 261 pending_credentials_.username_value = credentials.username_value; 262 pending_credentials_.other_possible_usernames = 263 credentials.other_possible_usernames; 264 } 265 266 pending_credentials_.action = credentials.action; 267 // If the user selected credentials we autofilled from a PasswordForm 268 // that contained no action URL (IE6/7 imported passwords, for example), 269 // bless it with the action URL from the observed form. See bug 1107719. 270 if (pending_credentials_.action.is_empty()) 271 pending_credentials_.action = observed_form_.action; 272 // Similarly, bless incomplete credentials with *_element info. 273 if (pending_credentials_.password_element.empty()) 274 pending_credentials_.password_element = observed_form_.password_element; 275 if (pending_credentials_.username_element.empty()) 276 pending_credentials_.username_element = observed_form_.username_element; 277 if (pending_credentials_.submit_element.empty()) 278 pending_credentials_.submit_element = observed_form_.submit_element; 279 280 pending_credentials_.password_value = credentials.password_value; 281 pending_credentials_.preferred = credentials.preferred; 282 283 if (user_action_ == kUserActionOverride && 284 pending_credentials_.type == PasswordForm::TYPE_GENERATED && 285 !has_generated_password_) { 286 LogPasswordGenerationSubmissionEvent(PASSWORD_OVERRIDDEN); 287 } 288 289 if (has_generated_password_) 290 pending_credentials_.type = PasswordForm::TYPE_GENERATED; 291} 292 293void PasswordFormManager::Save() { 294 DCHECK_EQ(state_, POST_MATCHING_PHASE); 295 DCHECK(!driver_->IsOffTheRecord()); 296 297 if (IsNewLogin()) 298 SaveAsNewLogin(true); 299 else 300 UpdateLogin(); 301} 302 303void PasswordFormManager::FetchMatchingLoginsFromPasswordStore( 304 PasswordStore::AuthorizationPromptPolicy prompt_policy) { 305 DCHECK_EQ(state_, PRE_MATCHING_PHASE); 306 state_ = MATCHING_PHASE; 307 PasswordStore* password_store = client_->GetPasswordStore(); 308 if (!password_store) { 309 NOTREACHED(); 310 return; 311 } 312 password_store->GetLogins(observed_form_, prompt_policy, this); 313} 314 315bool PasswordFormManager::HasCompletedMatching() { 316 return state_ == POST_MATCHING_PHASE; 317} 318 319void PasswordFormManager::OnRequestDone( 320 const std::vector<PasswordForm*>& logins_result) { 321 // Note that the result gets deleted after this call completes, but we own 322 // the PasswordForm objects pointed to by the result vector, thus we keep 323 // copies to a minimum here. 324 325 int best_score = 0; 326 // These credentials will be in the final result regardless of score. 327 std::vector<PasswordForm> credentials_to_keep; 328 for (size_t i = 0; i < logins_result.size(); i++) { 329 if (IgnoreResult(*logins_result[i])) { 330 delete logins_result[i]; 331 continue; 332 } 333 // Score and update best matches. 334 int current_score = ScoreResult(*logins_result[i]); 335 // This check is here so we can append empty path matches in the event 336 // they don't score as high as others and aren't added to best_matches_. 337 // This is most commonly imported firefox logins. We skip blacklisted 338 // ones because clearly we don't want to autofill them, and secondly 339 // because they only mean something when we have no other matches already 340 // saved in Chrome - in which case they'll make it through the regular 341 // scoring flow below by design. Note signon_realm == origin implies empty 342 // path logins_result, since signon_realm is a prefix of origin for HTML 343 // password forms. 344 // TODO(timsteele): Bug 1269400. We probably should do something more 345 // elegant for any shorter-path match instead of explicitly handling empty 346 // path matches. 347 if ((observed_form_.scheme == PasswordForm::SCHEME_HTML) && 348 (observed_form_.signon_realm == logins_result[i]->origin.spec()) && 349 (current_score > 0) && (!logins_result[i]->blacklisted_by_user)) { 350 credentials_to_keep.push_back(*logins_result[i]); 351 } 352 353 // Always keep generated passwords as part of the result set. If a user 354 // generates a password on a signup form, it should show on a login form 355 // even if they have a previous login saved. 356 // TODO(gcasto): We don't want to cut credentials that were saved on signup 357 // forms even if they weren't generated, but currently it's hard to 358 // distinguish between those forms and two different login forms on the 359 // same domain. Filed http://crbug.com/294468 to look into this. 360 if (logins_result[i]->type == PasswordForm::TYPE_GENERATED) 361 credentials_to_keep.push_back(*logins_result[i]); 362 363 if (current_score < best_score) { 364 delete logins_result[i]; 365 continue; 366 } 367 if (current_score == best_score) { 368 best_matches_[logins_result[i]->username_value] = logins_result[i]; 369 } else if (current_score > best_score) { 370 best_score = current_score; 371 // This new login has a better score than all those up to this point 372 // Note 'this' owns all the PasswordForms in best_matches_. 373 STLDeleteValues(&best_matches_); 374 best_matches_.clear(); 375 preferred_match_ = NULL; // Don't delete, its owned by best_matches_. 376 best_matches_[logins_result[i]->username_value] = logins_result[i]; 377 } 378 preferred_match_ = logins_result[i]->preferred ? logins_result[i] 379 : preferred_match_; 380 } 381 // We're done matching now. 382 state_ = POST_MATCHING_PHASE; 383 384 if (best_score <= 0) { 385 return; 386 } 387 388 for (std::vector<PasswordForm>::const_iterator it = 389 credentials_to_keep.begin(); 390 it != credentials_to_keep.end(); ++it) { 391 // If we don't already have a result with the same username, add the 392 // lower-scored match (if it had equal score it would already be in 393 // best_matches_). 394 if (best_matches_.find(it->username_value) == best_matches_.end()) 395 best_matches_[it->username_value] = new PasswordForm(*it); 396 } 397 398 UMA_HISTOGRAM_COUNTS("PasswordManager.NumPasswordsNotShown", 399 logins_result.size() - best_matches_.size()); 400 401 // It is possible we have at least one match but have no preferred_match_, 402 // because a user may have chosen to 'Forget' the preferred match. So we 403 // just pick the first one and whichever the user selects for submit will 404 // be saved as preferred. 405 DCHECK(!best_matches_.empty()); 406 if (!preferred_match_) 407 preferred_match_ = best_matches_.begin()->second; 408 409 // Check to see if the user told us to ignore this site in the past. 410 if (preferred_match_->blacklisted_by_user) { 411 manager_action_ = kManagerActionBlacklisted; 412 return; 413 } 414 415 // If not blacklisted, inform the driver that password generation is allowed 416 // for |observed_form_|. 417 driver_->AllowPasswordGenerationForForm(&observed_form_); 418 419 // Proceed to autofill. 420 // Note that we provide the choices but don't actually prefill a value if: 421 // (1) we are in Incognito mode, (2) the ACTION paths don't match, 422 // or (3) if it matched using public suffix domain matching. 423 bool wait_for_username = 424 driver_->IsOffTheRecord() || 425 observed_form_.action.GetWithEmptyPath() != 426 preferred_match_->action.GetWithEmptyPath() || 427 preferred_match_->IsPublicSuffixMatch(); 428 if (wait_for_username) 429 manager_action_ = kManagerActionNone; 430 else 431 manager_action_ = kManagerActionAutofilled; 432 password_manager_->Autofill(observed_form_, best_matches_, 433 *preferred_match_, wait_for_username); 434} 435 436void PasswordFormManager::OnGetPasswordStoreResults( 437 const std::vector<autofill::PasswordForm*>& results) { 438 DCHECK_EQ(state_, MATCHING_PHASE); 439 440 if (results.empty()) { 441 state_ = POST_MATCHING_PHASE; 442 // No result means that we visit this site the first time so we don't need 443 // to check whether this site is blacklisted or not. Just send a message 444 // to allow password generation. 445 driver_->AllowPasswordGenerationForForm(&observed_form_); 446 return; 447 } 448 OnRequestDone(results); 449} 450 451bool PasswordFormManager::IgnoreResult(const PasswordForm& form) const { 452 // Ignore change password forms until we have some change password 453 // functionality 454 if (observed_form_.old_password_element.length() != 0) { 455 return true; 456 } 457 // Don't match an invalid SSL form with one saved under secure 458 // circumstances. 459 if (form.ssl_valid && !observed_form_.ssl_valid) { 460 return true; 461 } 462 return false; 463} 464 465void PasswordFormManager::SaveAsNewLogin(bool reset_preferred_login) { 466 DCHECK_EQ(state_, POST_MATCHING_PHASE); 467 DCHECK(IsNewLogin()); 468 // The new_form is being used to sign in, so it is preferred. 469 DCHECK(pending_credentials_.preferred); 470 // new_form contains the same basic data as observed_form_ (because its the 471 // same form), but with the newly added credentials. 472 473 DCHECK(!driver_->IsOffTheRecord()); 474 475 PasswordStore* password_store = client_->GetPasswordStore(); 476 if (!password_store) { 477 NOTREACHED(); 478 return; 479 } 480 481 pending_credentials_.date_created = Time::Now(); 482 SanitizePossibleUsernames(&pending_credentials_); 483 password_store->AddLogin(pending_credentials_); 484 485 if (reset_preferred_login) { 486 UpdatePreferredLoginState(password_store); 487 } 488} 489 490void PasswordFormManager::SanitizePossibleUsernames(PasswordForm* form) { 491 // Remove any possible usernames that could be credit cards or SSN for privacy 492 // reasons. Also remove duplicates, both in other_possible_usernames and 493 // between other_possible_usernames and username_value. 494 std::set<base::string16> set; 495 for (std::vector<base::string16>::iterator it = 496 form->other_possible_usernames.begin(); 497 it != form->other_possible_usernames.end(); ++it) { 498 if (!autofill::IsValidCreditCardNumber(*it) && !autofill::IsSSN(*it)) 499 set.insert(*it); 500 } 501 set.erase(form->username_value); 502 std::vector<base::string16> temp(set.begin(), set.end()); 503 form->other_possible_usernames.swap(temp); 504} 505 506void PasswordFormManager::UpdatePreferredLoginState( 507 PasswordStore* password_store) { 508 DCHECK(password_store); 509 PasswordFormMap::iterator iter; 510 for (iter = best_matches_.begin(); iter != best_matches_.end(); iter++) { 511 if (iter->second->username_value != pending_credentials_.username_value && 512 iter->second->preferred) { 513 // This wasn't the selected login but it used to be preferred. 514 iter->second->preferred = false; 515 if (user_action_ == kUserActionNone) 516 user_action_ = kUserActionChoose; 517 password_store->UpdateLogin(*iter->second); 518 } 519 } 520} 521 522void PasswordFormManager::UpdateLogin() { 523 DCHECK_EQ(state_, POST_MATCHING_PHASE); 524 DCHECK(preferred_match_); 525 // If we're doing an Update, we either autofilled correctly and need to 526 // update the stats, or the user typed in a new password for autofilled 527 // username, or the user selected one of the non-preferred matches, 528 // thus requiring a swap of preferred bits. 529 DCHECK(!IsNewLogin() && pending_credentials_.preferred); 530 DCHECK(!driver_->IsOffTheRecord()); 531 532 PasswordStore* password_store = client_->GetPasswordStore(); 533 if (!password_store) { 534 NOTREACHED(); 535 return; 536 } 537 538 // Update metadata. 539 ++pending_credentials_.times_used; 540 541 // Check to see if this form is a candidate for password generation. 542 CheckForAccountCreationForm(pending_credentials_, observed_form_); 543 544 UpdatePreferredLoginState(password_store); 545 546 // Remove alternate usernames. At this point we assume that we have found 547 // the right username. 548 pending_credentials_.other_possible_usernames.clear(); 549 550 // Update the new preferred login. 551 if (!selected_username_.empty()) { 552 // An other possible username is selected. We set this selected username 553 // as the real username. The PasswordStore API isn't designed to update 554 // username, so we delete the old credentials and add a new one instead. 555 password_store->RemoveLogin(pending_credentials_); 556 pending_credentials_.username_value = selected_username_; 557 password_store->AddLogin(pending_credentials_); 558 } else if ((observed_form_.scheme == PasswordForm::SCHEME_HTML) && 559 (observed_form_.origin.spec().length() > 560 observed_form_.signon_realm.length()) && 561 (observed_form_.signon_realm == 562 pending_credentials_.origin.spec())) { 563 // Note origin.spec().length > signon_realm.length implies the origin has a 564 // path, since signon_realm is a prefix of origin for HTML password forms. 565 // 566 // The user logged in successfully with one of our autofilled logins on a 567 // page with non-empty path, but the autofilled entry was initially saved/ 568 // imported with an empty path. Rather than just mark this entry preferred, 569 // we create a more specific copy for this exact page and leave the "master" 570 // unchanged. This is to prevent the case where that master login is used 571 // on several sites (e.g site.com/a and site.com/b) but the user actually 572 // has a different preference on each site. For example, on /a, he wants the 573 // general empty-path login so it is flagged as preferred, but on /b he logs 574 // in with a different saved entry - we don't want to remove the preferred 575 // status of the former because upon return to /a it won't be the default- 576 // fill match. 577 // TODO(timsteele): Bug 1188626 - expire the master copies. 578 PasswordForm copy(pending_credentials_); 579 copy.origin = observed_form_.origin; 580 copy.action = observed_form_.action; 581 password_store->AddLogin(copy); 582 } else { 583 password_store->UpdateLogin(pending_credentials_); 584 } 585} 586 587bool PasswordFormManager::UpdatePendingCredentialsIfOtherPossibleUsername( 588 const base::string16& username) { 589 for (PasswordFormMap::const_iterator it = best_matches_.begin(); 590 it != best_matches_.end(); ++it) { 591 for (size_t i = 0; i < it->second->other_possible_usernames.size(); ++i) { 592 if (it->second->other_possible_usernames[i] == username) { 593 pending_credentials_ = *it->second; 594 return true; 595 } 596 } 597 } 598 return false; 599} 600 601void PasswordFormManager::CheckForAccountCreationForm( 602 const PasswordForm& pending, const PasswordForm& observed) { 603 // We check to see if the saved form_data is the same as the observed 604 // form_data, which should never be true for passwords saved on account 605 // creation forms. This check is only made the first time a password is used 606 // to cut down on false positives. Specifically a site may have multiple login 607 // forms with different markup, which might look similar to a signup form. 608 if (pending.times_used == 1) { 609 FormStructure pending_structure(pending.form_data); 610 FormStructure observed_structure(observed.form_data); 611 // Ignore |pending_structure| if its FormData has no fields. This is to 612 // weed out those credentials that were saved before FormData was added 613 // to PasswordForm. Even without this check, these FormStructure's won't 614 // be uploaded, but it makes it hard to see if we are encountering 615 // unexpected errors. 616 if (!pending.form_data.fields.empty() && 617 pending_structure.FormSignature() != 618 observed_structure.FormSignature()) { 619 autofill::AutofillManager* autofill_manager; 620 if ((autofill_manager = driver_->GetAutofillManager())) { 621 // Note that this doesn't guarantee that the upload succeeded, only that 622 // |pending.form_data| is considered uploadable. 623 bool success = 624 autofill_manager->UploadPasswordGenerationForm(pending.form_data); 625 UMA_HISTOGRAM_BOOLEAN("PasswordGeneration.UploadStarted", success); 626 } 627 } 628 } 629} 630 631int PasswordFormManager::ScoreResult(const PasswordForm& candidate) const { 632 DCHECK_EQ(state_, MATCHING_PHASE); 633 // For scoring of candidate login data: 634 // The most important element that should match is the origin, followed by 635 // the action, the password name, the submit button name, and finally the 636 // username input field name. 637 // Exact origin match gives an addition of 64 (1 << 6) + # of matching url 638 // dirs. 639 // Partial match gives an addition of 32 (1 << 5) + # matching url dirs 640 // That way, a partial match cannot trump an exact match even if 641 // the partial one matches all other attributes (action, elements) (and 642 // regardless of the matching depth in the URL path). 643 // If public suffix origin match was not used, it gives an addition of 644 // 16 (1 << 4). 645 int score = 0; 646 if (candidate.origin == observed_form_.origin) { 647 // This check is here for the most common case which 648 // is we have a single match in the db for the given host, 649 // so we don't generally need to walk the entire URL path (the else 650 // clause). 651 score += (1 << 6) + static_cast<int>(form_path_tokens_.size()); 652 } else { 653 // Walk the origin URL paths one directory at a time to see how 654 // deep the two match. 655 std::vector<std::string> candidate_path_tokens; 656 base::SplitString(candidate.origin.path(), '/', &candidate_path_tokens); 657 size_t depth = 0; 658 size_t max_dirs = std::min(form_path_tokens_.size(), 659 candidate_path_tokens.size()); 660 while ((depth < max_dirs) && (form_path_tokens_[depth] == 661 candidate_path_tokens[depth])) { 662 depth++; 663 score++; 664 } 665 // do we have a partial match? 666 score += (depth > 0) ? 1 << 5 : 0; 667 } 668 if (observed_form_.scheme == PasswordForm::SCHEME_HTML) { 669 if (!candidate.IsPublicSuffixMatch()) 670 score += 1 << 4; 671 if (candidate.action == observed_form_.action) 672 score += 1 << 3; 673 if (candidate.password_element == observed_form_.password_element) 674 score += 1 << 2; 675 if (candidate.submit_element == observed_form_.submit_element) 676 score += 1 << 1; 677 if (candidate.username_element == observed_form_.username_element) 678 score += 1 << 0; 679 } 680 681 return score; 682} 683 684void PasswordFormManager::SubmitPassed() { 685 submit_result_ = kSubmitResultPassed; 686 if (has_generated_password_) 687 LogPasswordGenerationSubmissionEvent(PASSWORD_SUBMITTED); 688} 689 690void PasswordFormManager::SubmitFailed() { 691 submit_result_ = kSubmitResultFailed; 692 if (has_generated_password_) 693 LogPasswordGenerationSubmissionEvent(PASSWORD_SUBMISSION_FAILED); 694} 695