summaryrefslogtreecommitdiffstats
path: root/chrome/browser/password_manager
diff options
context:
space:
mode:
authorstuartmorgan@google.com <stuartmorgan@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-16 17:26:11 +0000
committerstuartmorgan@google.com <stuartmorgan@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-16 17:26:11 +0000
commit4cbf4e5faca5c6d01906d0bbf1092c52f5435d4e (patch)
tree7205364030623ab74137816f3177403c85db0a84 /chrome/browser/password_manager
parentaa82249f5670f88c545039f7ae997643c97fd639 (diff)
downloadchromium_src-4cbf4e5faca5c6d01906d0bbf1092c52f5435d4e.zip
chromium_src-4cbf4e5faca5c6d01906d0bbf1092c52f5435d4e.tar.gz
chromium_src-4cbf4e5faca5c6d01906d0bbf1092c52f5435d4e.tar.bz2
Implement bulk password lookup API in PasswordStoreMac.
Refactor password merge to support the new search. Rename the fill-targeted search on the adapter for better clarity about the distinction between it and the new search. BUG=16485 TEST=none yet; UI doesn't exist. Review URL: http://codereview.chromium.org/149708 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@20877 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/password_manager')
-rw-r--r--chrome/browser/password_manager/password_store_mac.cc181
-rw-r--r--chrome/browser/password_manager/password_store_mac.h8
-rw-r--r--chrome/browser/password_manager/password_store_mac_internal.h33
-rw-r--r--chrome/browser/password_manager/password_store_mac_unittest.cc95
4 files changed, 241 insertions, 76 deletions
diff --git a/chrome/browser/password_manager/password_store_mac.cc b/chrome/browser/password_manager/password_store_mac.cc
index 18b8057..97f9aa0 100644
--- a/chrome/browser/password_manager/password_store_mac.cc
+++ b/chrome/browser/password_manager/password_store_mac.cc
@@ -161,7 +161,7 @@ void KeychainSearch::FindMatchingItems(std::vector<SecKeychainItemRef>* items) {
#pragma mark -
// TODO(stuartmorgan): Convert most of this to private helpers in
-// MacKeychainPaswordFormAdapter once it has sufficient higher-level public
+// MacKeychainPasswordFormAdapter once it has sufficient higher-level public
// methods to provide test coverage.
namespace internal_keychain_helpers {
@@ -369,49 +369,91 @@ PasswordForm* BestKeychainFormForForm(
return partial_match;
}
+// Returns entries from |forms| that are blacklist entries, after removing
+// them from |forms|.
+std::vector<PasswordForm*> ExtractBlacklistForms(
+ std::vector<PasswordForm*>* forms) {
+ std::vector<PasswordForm*> blacklist_forms;
+ for (std::vector<PasswordForm*>::iterator i = forms->begin();
+ i != forms->end();) {
+ PasswordForm* form = *i;
+ if (form->blacklisted_by_user) {
+ blacklist_forms.push_back(form);
+ i = forms->erase(i);
+ } else {
+ ++i;
+ }
+ }
+ return blacklist_forms;
+}
+
+// Deletes and removes from v any element that exists in s.
+template <class T>
+void DeleteVectorElementsInSet(std::vector<T*>* v, const std::set<T*>& s) {
+ for (typename std::vector<T*>::iterator i = v->begin(); i != v->end();) {
+ T* element = *i;
+ if (s.find(element) != s.end()) {
+ delete element;
+ i = v->erase(i);
+ } else {
+ ++i;
+ }
+ }
+}
+
void MergePasswordForms(std::vector<PasswordForm*>* keychain_forms,
std::vector<PasswordForm*>* database_forms,
std::vector<PasswordForm*>* merged_forms) {
+ // Pull out the database blacklist items, since they are used as-is rather
+ // than being merged with keychain forms.
+ std::vector<PasswordForm*> database_blacklist_forms =
+ ExtractBlacklistForms(database_forms);
+
+ // Merge the normal entries.
std::set<PasswordForm*> used_keychain_forms;
for (std::vector<PasswordForm*>::iterator i = database_forms->begin();
i != database_forms->end();) {
PasswordForm* db_form = *i;
- bool use_form = false;
- if (db_form->blacklisted_by_user) {
- // Blacklist entries aren't merged, so just take it directly.
- use_form = true;
- } else {
- // Check for a match in the keychain list.
- PasswordForm* best_match = BestKeychainFormForForm(*db_form,
- keychain_forms);
- if (best_match) {
- used_keychain_forms.insert(best_match);
- db_form->password_value = best_match->password_value;
- use_form = true;
- }
- }
- if (use_form) {
+ PasswordForm* best_match = BestKeychainFormForForm(*db_form,
+ keychain_forms);
+ if (best_match) {
+ used_keychain_forms.insert(best_match);
+ db_form->password_value = best_match->password_value;
merged_forms->push_back(db_form);
i = database_forms->erase(i);
} else {
++i;
}
}
- // Find any remaining keychain entries that we want, and clear out everything
- // we used.
- for (std::vector<PasswordForm*>::iterator i = keychain_forms->begin();
- i != keychain_forms->end();) {
- PasswordForm* keychain_form = *i;
- if (keychain_form->blacklisted_by_user) {
- ++i;
+
+ // Add in the blacklist entries from the database.
+ merged_forms->insert(merged_forms->end(),
+ database_blacklist_forms.begin(),
+ database_blacklist_forms.end());
+
+ // Clear out all the Keychain entries we used.
+ DeleteVectorElementsInSet(keychain_forms, used_keychain_forms);
+}
+
+std::vector<PasswordForm*> GetPasswordsForForms(
+ const MacKeychain& keychain, std::vector<PasswordForm*>* database_forms) {
+ MacKeychainPasswordFormAdapter keychain_adapter(&keychain);
+
+ std::vector<PasswordForm*> merged_forms;
+ for (std::vector<PasswordForm*>::iterator i = database_forms->begin();
+ i != database_forms->end();) {
+ std::vector<PasswordForm*> db_form_container(1, *i);
+ std::vector<PasswordForm*> keychain_matches =
+ keychain_adapter.PasswordsMergeableWithForm(**i);
+ MergePasswordForms(&keychain_matches, &db_form_container, &merged_forms);
+ if (db_form_container.size() == 0) {
+ i = database_forms->erase(i);
} else {
- if (used_keychain_forms.find(keychain_form) == used_keychain_forms.end())
- merged_forms->push_back(keychain_form);
- else
- delete keychain_form;
- i = keychain_forms->erase(i);
+ ++i;
}
+ STLDeleteElements(&keychain_matches);
}
+ return merged_forms;
}
} // namespace internal_keychain_helpers
@@ -419,14 +461,33 @@ void MergePasswordForms(std::vector<PasswordForm*>* keychain_forms,
#pragma mark -
MacKeychainPasswordFormAdapter::MacKeychainPasswordFormAdapter(
- MacKeychain* keychain) : keychain_(keychain), finds_only_owned_(false) {
+ const MacKeychain* keychain)
+ : keychain_(keychain), finds_only_owned_(false) {
+}
+
+std::vector<PasswordForm*>
+ MacKeychainPasswordFormAdapter::PasswordsFillingForm(
+ const PasswordForm& query_form) {
+ std::vector<SecKeychainItemRef> keychain_items =
+ MatchingKeychainItems(query_form.signon_realm, query_form.scheme,
+ NULL, NULL);
+
+ std::vector<PasswordForm*> keychain_forms =
+ CreateFormsFromKeychainItems(keychain_items);
+ for (std::vector<SecKeychainItemRef>::iterator i = keychain_items.begin();
+ i != keychain_items.end(); ++i) {
+ keychain_->Free(*i);
+ }
+ return keychain_forms;
}
std::vector<PasswordForm*>
- MacKeychainPasswordFormAdapter::PasswordsMatchingForm(
+ MacKeychainPasswordFormAdapter::PasswordsMergeableWithForm(
const PasswordForm& query_form) {
+ std::string username = WideToUTF8(query_form.username_value);
std::vector<SecKeychainItemRef> keychain_items =
- KeychainItemsForFillingForm(query_form);
+ MatchingKeychainItems(query_form.signon_realm, query_form.scheme,
+ NULL, username.c_str());
std::vector<PasswordForm*> keychain_forms =
CreateFormsFromKeychainItems(keychain_items);
@@ -523,12 +584,6 @@ std::vector<PasswordForm*>
return keychain_forms;
}
-std::vector<SecKeychainItemRef>
- MacKeychainPasswordFormAdapter::KeychainItemsForFillingForm(
- const PasswordForm& form) {
- return MatchingKeychainItems(form.signon_realm, form.scheme, NULL, NULL);
-}
-
SecKeychainItemRef MacKeychainPasswordFormAdapter::KeychainItemForForm(
const PasswordForm& form) {
// We don't store blacklist entries in the keychain, so the answer to "what
@@ -702,7 +757,7 @@ void PasswordStoreMac::GetLoginsImpl(GetLoginsRequest* request,
const webkit_glue::PasswordForm& form) {
MacKeychainPasswordFormAdapter keychain_adapter(keychain_.get());
std::vector<PasswordForm*> keychain_forms =
- keychain_adapter.PasswordsMatchingForm(form);
+ keychain_adapter.PasswordsFillingForm(form);
std::vector<PasswordForm*> database_forms;
login_metadata_db_->GetLogins(form, &database_forms);
@@ -712,24 +767,50 @@ void PasswordStoreMac::GetLoginsImpl(GetLoginsRequest* request,
&database_forms,
&merged_forms);
+ // Strip any blacklist entries out of the unused Keychain array, then take
+ // all the entries that are left (which we can use as imported passwords).
+ std::vector<PasswordForm*> keychain_blacklist_forms =
+ internal_keychain_helpers::ExtractBlacklistForms(&keychain_forms);
+ merged_forms.insert(merged_forms.end(), keychain_forms.begin(),
+ keychain_forms.end());
+ keychain_forms.clear();
+ STLDeleteElements(&keychain_blacklist_forms);
+
// Clean up any orphaned database entries.
- for (std::vector<PasswordForm*>::iterator i = database_forms.begin();
- i != database_forms.end(); ++i) {
- login_metadata_db_->RemoveLogin(**i);
- }
- // Delete the forms we aren't returning.
+ RemoveDatabaseForms(database_forms);
STLDeleteElements(&database_forms);
- STLDeleteElements(&keychain_forms);
NotifyConsumer(request, merged_forms);
}
void PasswordStoreMac::GetAllLoginsImpl(GetLoginsRequest* request) {
- NOTIMPLEMENTED();
+ std::vector<PasswordForm*> database_forms;
+ login_metadata_db_->GetAllLogins(&database_forms, true);
+
+ std::vector<PasswordForm*> merged_forms =
+ internal_keychain_helpers::GetPasswordsForForms(*keychain_,
+ &database_forms);
+
+ // Clean up any orphaned database entries.
+ RemoveDatabaseForms(database_forms);
+ STLDeleteElements(&database_forms);
+
+ NotifyConsumer(request, merged_forms);
}
void PasswordStoreMac::GetAllAutofillableLoginsImpl(GetLoginsRequest* request) {
- NOTIMPLEMENTED();
+ std::vector<PasswordForm*> database_forms;
+ login_metadata_db_->GetAllLogins(&database_forms, false);
+
+ std::vector<PasswordForm*> merged_forms =
+ internal_keychain_helpers::GetPasswordsForForms(*keychain_,
+ &database_forms);
+
+ // Clean up any orphaned database entries.
+ RemoveDatabaseForms(database_forms);
+ STLDeleteElements(&database_forms);
+
+ NotifyConsumer(request, merged_forms);
}
bool PasswordStoreMac::AddToKeychainIfNecessary(const PasswordForm& form) {
@@ -756,3 +837,11 @@ bool PasswordStoreMac::DatabaseHasFormMatchingKeychainForm(
STLDeleteElements(&database_forms);
return has_match;
}
+
+void PasswordStoreMac::RemoveDatabaseForms(
+ const std::vector<PasswordForm*>& forms) {
+ for (std::vector<PasswordForm*>::const_iterator i = forms.begin();
+ i != forms.end(); ++i) {
+ login_metadata_db_->RemoveLogin(**i);
+ }
+}
diff --git a/chrome/browser/password_manager/password_store_mac.h b/chrome/browser/password_manager/password_store_mac.h
index f00fa76..1ea24e9 100644
--- a/chrome/browser/password_manager/password_store_mac.h
+++ b/chrome/browser/password_manager/password_store_mac.h
@@ -5,6 +5,8 @@
#ifndef CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_MAC_H_
#define CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_MAC_H_
+#include <vector>
+
#include "base/scoped_ptr.h"
#include "chrome/browser/password_manager/password_store.h"
@@ -37,7 +39,11 @@ class PasswordStoreMac : public PasswordStore {
// Returns true if our database contains a form that exactly matches the given
// keychain form.
bool DatabaseHasFormMatchingKeychainForm(
- const webkit_glue::PasswordForm& form);
+ const webkit_glue::PasswordForm& form);
+
+ // Removes the given forms from the database.
+ void RemoveDatabaseForms(
+ const std::vector<webkit_glue::PasswordForm*>& forms);
scoped_ptr<MacKeychain> keychain_;
scoped_ptr<LoginDatabaseMac> login_metadata_db_;
diff --git a/chrome/browser/password_manager/password_store_mac_internal.h b/chrome/browser/password_manager/password_store_mac_internal.h
index 50a4088..ba6e07b 100644
--- a/chrome/browser/password_manager/password_store_mac_internal.h
+++ b/chrome/browser/password_manager/password_store_mac_internal.h
@@ -20,11 +20,17 @@ class MacKeychainPasswordFormAdapter {
// Creates an adapter for |keychain|. This class does not take ownership of
// |keychain|, so the caller must make sure that the keychain outlives the
// created object.
- explicit MacKeychainPasswordFormAdapter(MacKeychain* keychain);
+ explicit MacKeychainPasswordFormAdapter(const MacKeychain* keychain);
// Returns PasswordForms for each keychain entry that could be used to fill
// |form|. Caller is responsible for deleting the returned forms.
- std::vector<webkit_glue::PasswordForm*> PasswordsMatchingForm(
+ std::vector<webkit_glue::PasswordForm*> PasswordsFillingForm(
+ const webkit_glue::PasswordForm& query_form);
+
+ // Returns PasswordForms for each keychain entry that could be merged with
+ // |form|. Differs from PasswordsFillingForm in that the username must match.
+ // Caller is responsible for deleting the returned forms.
+ std::vector<webkit_glue::PasswordForm*> PasswordsMergeableWithForm(
const webkit_glue::PasswordForm& query_form);
// Returns the PasswordForm for the Keychain entry that matches |form| on all
@@ -53,12 +59,6 @@ class MacKeychainPasswordFormAdapter {
std::vector<webkit_glue::PasswordForm*> CreateFormsFromKeychainItems(
const std::vector<SecKeychainItemRef>& items);
- // Searches |keychain| for all items usable for the given form, and returns
- // them. The caller is responsible for calling MacKeychain::Free on the
- // returned items.
- std::vector<SecKeychainItemRef> KeychainItemsForFillingForm(
- const webkit_glue::PasswordForm& form);
-
// Searches |keychain| for the specific keychain entry that corresponds to the
// given form, and returns it (or NULL if no match is found). The caller is
// responsible for calling MacKeychain::Free on on the returned item.
@@ -97,7 +97,7 @@ class MacKeychainPasswordFormAdapter {
bool SetKeychainItemCreatorCode(const SecKeychainItemRef& keychain_item,
OSType creator_code);
- MacKeychain* keychain_;
+ const MacKeychain* keychain_;
// If true, Keychain searches are restricted to items created by Chrome.
bool finds_only_owned_;
@@ -133,15 +133,20 @@ bool FormsMatchForMerge(const webkit_glue::PasswordForm& form_a,
//
// On return, database_forms and keychain_forms will have only unused
// entries; for database_forms that means entries for which no corresponding
-// password can be found (and which aren't blacklist entries), but for
-// keychain_forms it's only entries we explicitly choose not to use (e.g.,
-// blacklist entries from other browsers). Keychain entries that we have no
-// database matches for will still end up in merged_forms, since they have
-// enough information to be used as imported passwords.
+// password can be found (and which aren't blacklist entries), and for
+// keychain_forms it's entries that weren't merged into at least one database
+// form.
void MergePasswordForms(std::vector<webkit_glue::PasswordForm*>* keychain_forms,
std::vector<webkit_glue::PasswordForm*>* database_forms,
std::vector<webkit_glue::PasswordForm*>* merged_forms);
+// Fills in the passwords for as many of the forms in |database_forms| as
+// possible using entries from |keychain| and returns them. On return,
+// |database_forms| will contain only the forms for which no password was found.
+std::vector<webkit_glue::PasswordForm*> GetPasswordsForForms(
+ const MacKeychain& keychain,
+ std::vector<webkit_glue::PasswordForm*>* database_forms);
+
} // internal_keychain_helpers
#endif // CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_MAC_INTERNAL_H_
diff --git a/chrome/browser/password_manager/password_store_mac_unittest.cc b/chrome/browser/password_manager/password_store_mac_unittest.cc
index 4962f19..41d5d1e 100644
--- a/chrome/browser/password_manager/password_store_mac_unittest.cc
+++ b/chrome/browser/password_manager/password_store_mac_unittest.cc
@@ -290,34 +290,49 @@ TEST_F(PasswordStoreMacTest, TestKeychainToFormTranslation) {
TEST_F(PasswordStoreMacTest, TestKeychainSearch) {
struct TestDataAndExpectation {
const PasswordFormData data;
- const size_t expected_matches;
+ const size_t expected_fill_matches;
+ const size_t expected_merge_matches;
};
// Most fields are left blank because we don't care about them for searching.
TestDataAndExpectation test_data[] = {
// An HTML form we've seen.
{ { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, false, 0 }, 2 },
+ NULL, NULL, NULL, NULL, NULL, L"joe_user", NULL, false, false, 0 },
+ 2, 2 },
+ { { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
+ NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, false, 0 },
+ 2, 0 },
// An HTML form we haven't seen
{ { PasswordForm::SCHEME_HTML, "http://www.unseendomain.com/",
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, false, 0 }, 0 },
+ NULL, NULL, NULL, NULL, NULL, L"joe_user", NULL, false, false, 0 },
+ 0, 0 },
// Basic auth that should match.
{ { PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security",
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, false, 0 }, 1 },
+ NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, false,
+ 0 },
+ 1, 1 },
// Basic auth with the wrong port.
{ { PasswordForm::SCHEME_BASIC, "http://some.domain.com:1111/low_security",
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, false, 0 }, 0 },
+ NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, false,
+ 0 },
+ 0, 0 },
// Digest auth we've saved under https, visited with http.
{ { PasswordForm::SCHEME_DIGEST, "http://some.domain.com/high_security",
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, false, 0 }, 0 },
+ NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, false,
+ 0 },
+ 0, 0 },
// Digest auth that should match.
{ { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security",
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, true, 0 }, 1 },
+ NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, true, 0 },
+ 1, 0 },
// Digest auth with the wrong domain.
{ { PasswordForm::SCHEME_DIGEST, "https://some.domain.com/other_domain",
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, true, 0 }, 0 },
+ NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, true,
+ 0 },
+ 0, 0 },
// Garbage forms should have no matches.
{ { PasswordForm::SCHEME_HTML, "foo/bar/baz",
- NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, false, 0 }, 0 },
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, false, 0 }, 0, 0 },
};
MacKeychainPasswordFormAdapter keychain_adapter(keychain_);
@@ -326,14 +341,21 @@ TEST_F(PasswordStoreMacTest, TestKeychainSearch) {
for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
scoped_ptr<PasswordForm> query_form(
CreatePasswordFormFromData(test_data[i].data));
+
+ // Check matches treating the form as a fill target.
std::vector<PasswordForm*> matching_items =
- keychain_adapter.PasswordsMatchingForm(*query_form);
- EXPECT_EQ(test_data[i].expected_matches, matching_items.size());
+ keychain_adapter.PasswordsFillingForm(*query_form);
+ EXPECT_EQ(test_data[i].expected_fill_matches, matching_items.size());
+ STLDeleteElements(&matching_items);
+
+ // Check matches teating the form as a merging target.
+ matching_items = keychain_adapter.PasswordsMergeableWithForm(*query_form);
+ EXPECT_EQ(test_data[i].expected_merge_matches, matching_items.size());
STLDeleteElements(&matching_items);
// None of the pre-seeded items are owned by us, so none should match an
// owned-passwords-only search.
- matching_items = owned_keychain_adapter.PasswordsMatchingForm(*query_form);
+ matching_items = owned_keychain_adapter.PasswordsFillingForm(*query_form);
EXPECT_EQ(0U, matching_items.size());
STLDeleteElements(&matching_items);
}
@@ -522,7 +544,7 @@ TEST_F(PasswordStoreMacTest, TestKeychainRemove) {
PasswordForm* add_form = CreatePasswordFormFromData(test_data[0].data);
EXPECT_TRUE(owned_keychain_adapter.AddPassword(*add_form));
delete add_form;
-
+
for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
scoped_ptr<PasswordForm> form(CreatePasswordFormFromData(
test_data[i].data));
@@ -687,7 +709,7 @@ TEST_F(PasswordStoreMacTest, TestFormMerge) {
test_data[DATABASE_INPUT][current_test].push_back(&db_user_3_with_path);
test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1);
test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1_with_db_path);
- test_data[MERGE_OUTPUT][current_test].push_back(&keychain_user_2);
+ test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_2);
test_data[DATABASE_OUTPUT][current_test].push_back(&db_user_3_with_path);
// Test a merge where Chrome has a blacklist entry, and the keychain has
@@ -701,7 +723,7 @@ TEST_F(PasswordStoreMacTest, TestFormMerge) {
// subpath, and we want access to the password on other paths.
test_data[MERGE_OUTPUT][current_test].push_back(
&database_blacklist_with_path);
- test_data[MERGE_OUTPUT][current_test].push_back(&keychain_user_1);
+ test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_1);
// Test a merge where Chrome has an account, and Keychain has a blacklist
// (from another browser) and the Chrome password data.
@@ -754,3 +776,46 @@ TEST_F(PasswordStoreMacTest, TestFormMerge) {
STLDeleteElements(&merged_forms);
}
}
+
+TEST_F(PasswordStoreMacTest, TestPasswordBulkLookup) {
+ PasswordFormData db_data[] = {
+ { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
+ "http://some.domain.com/", "http://some.domain.com/action.cgi",
+ L"submit", L"username", L"password", L"joe_user", L"",
+ true, false, 1212121212 },
+ { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
+ "http://some.domain.com/page.html",
+ "http://some.domain.com/handlepage.cgi",
+ L"submit", L"username", L"password", L"joe_user", L"",
+ true, false, 1234567890 },
+ { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
+ "http://some.domain.com/page.html",
+ "http://some.domain.com/handlepage.cgi",
+ L"submit", L"username", L"password", L"second-account", L"",
+ true, false, 1240000000 },
+ { PasswordForm::SCHEME_HTML, "http://dont.remember.com/",
+ "http://dont.remember.com/",
+ "http://dont.remember.com/handlepage.cgi",
+ L"submit", L"username", L"password", L"joe_user", L"",
+ true, false, 1240000000 },
+ { PasswordForm::SCHEME_HTML, "http://some.domain.com/",
+ "http://some.domain.com/path.html", "http://some.domain.com/action.cgi",
+ L"submit", L"username", L"password", NULL, NULL,
+ true, false, 1212121212 },
+ };
+ std::vector<PasswordForm*> database_forms;
+ for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(db_data); ++i) {
+ database_forms.push_back(CreatePasswordFormFromData(db_data[i]));
+ }
+ std::vector<PasswordForm*> merged_forms =
+ internal_keychain_helpers::GetPasswordsForForms(*keychain_,
+ &database_forms);
+ EXPECT_EQ(2U, database_forms.size());
+ ASSERT_EQ(3U, merged_forms.size());
+ EXPECT_EQ(std::wstring(L"sekrit"), merged_forms[0]->password_value);
+ EXPECT_EQ(std::wstring(L"sekrit"), merged_forms[1]->password_value);
+ EXPECT_EQ(true, merged_forms[2]->blacklisted_by_user);
+
+ STLDeleteElements(&database_forms);
+ STLDeleteElements(&merged_forms);
+}