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 "chrome/browser/password_manager/login_database.h" 6 7 #include <algorithm> 8 #include <limits> 9 10 #include "base/files/file_path.h" 11 #include "base/logging.h" 12 #include "base/metrics/histogram.h" 13 #include "base/pickle.h" 14 #include "base/strings/string_util.h" 15 #include "base/time/time.h" 16 #include "components/autofill/core/common/password_form.h" 17 #include "sql/connection.h" 18 #include "sql/statement.h" 19 #include "sql/transaction.h" 20 21 using autofill::PasswordForm; 22 23 static const int kCurrentVersionNumber = 4; 24 static const int kCompatibleVersionNumber = 1; 25 26 namespace { 27 28 // Convenience enum for interacting with SQL queries that use all the columns. 29 enum LoginTableColumns { 30 COLUMN_ORIGIN_URL = 0, 31 COLUMN_ACTION_URL, 32 COLUMN_USERNAME_ELEMENT, 33 COLUMN_USERNAME_VALUE, 34 COLUMN_PASSWORD_ELEMENT, 35 COLUMN_PASSWORD_VALUE, 36 COLUMN_SUBMIT_ELEMENT, 37 COLUMN_SIGNON_REALM, 38 COLUMN_SSL_VALID, 39 COLUMN_PREFERRED, 40 COLUMN_DATE_CREATED, 41 COLUMN_BLACKLISTED_BY_USER, 42 COLUMN_SCHEME, 43 COLUMN_PASSWORD_TYPE, 44 COLUMN_POSSIBLE_USERNAMES, 45 COLUMN_TIMES_USED, 46 COLUMN_FORM_DATA 47 }; 48 49 } // namespace 50 51 LoginDatabase::LoginDatabase() { 52 } 53 54 LoginDatabase::~LoginDatabase() { 55 } 56 57 bool LoginDatabase::Init(const base::FilePath& db_path) { 58 // Set pragmas for a small, private database (based on WebDatabase). 59 db_.set_page_size(2048); 60 db_.set_cache_size(32); 61 db_.set_exclusive_locking(); 62 db_.set_restrict_to_user(); 63 64 if (!db_.Open(db_path)) { 65 LOG(WARNING) << "Unable to open the password store database."; 66 return false; 67 } 68 69 sql::Transaction transaction(&db_); 70 transaction.Begin(); 71 72 // Check the database version. 73 if (!meta_table_.Init(&db_, kCurrentVersionNumber, 74 kCompatibleVersionNumber)) { 75 db_.Close(); 76 return false; 77 } 78 if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) { 79 LOG(WARNING) << "Password store database is too new."; 80 db_.Close(); 81 return false; 82 } 83 84 // Initialize the tables. 85 if (!InitLoginsTable()) { 86 LOG(WARNING) << "Unable to initialize the password store database."; 87 db_.Close(); 88 return false; 89 } 90 91 // Save the path for DeleteDatabaseFile(). 92 db_path_ = db_path; 93 94 // If the file on disk is an older database version, bring it up to date. 95 if (!MigrateOldVersionsAsNeeded()) { 96 LOG(WARNING) << "Unable to migrate database"; 97 db_.Close(); 98 return false; 99 } 100 101 if (!transaction.Commit()) { 102 db_.Close(); 103 return false; 104 } 105 106 return true; 107 } 108 109 bool LoginDatabase::MigrateOldVersionsAsNeeded() { 110 switch (meta_table_.GetVersionNumber()) { 111 case 1: 112 if (!db_.Execute("ALTER TABLE logins " 113 "ADD COLUMN password_type INTEGER") || 114 !db_.Execute("ALTER TABLE logins " 115 "ADD COLUMN possible_usernames BLOB")) { 116 return false; 117 } 118 meta_table_.SetVersionNumber(2); 119 // Fall through. 120 case 2: 121 if (!db_.Execute("ALTER TABLE logins ADD COLUMN times_used INTEGER")) { 122 return false; 123 } 124 meta_table_.SetVersionNumber(3); 125 // Fall through. 126 case 3: 127 // We need to check if the column exists because of 128 // https://crbug.com/295851 129 if (!db_.DoesColumnExist("logins", "form_data") && 130 !db_.Execute("ALTER TABLE logins ADD COLUMN form_data BLOB")) { 131 return false; 132 } 133 meta_table_.SetVersionNumber(4); 134 // Fall through. 135 case kCurrentVersionNumber: 136 // Already up to date 137 return true; 138 default: 139 NOTREACHED(); 140 return false; 141 } 142 } 143 144 bool LoginDatabase::InitLoginsTable() { 145 if (!db_.DoesTableExist("logins")) { 146 if (!db_.Execute("CREATE TABLE logins (" 147 "origin_url VARCHAR NOT NULL, " 148 "action_url VARCHAR, " 149 "username_element VARCHAR, " 150 "username_value VARCHAR, " 151 "password_element VARCHAR, " 152 "password_value BLOB, " 153 "submit_element VARCHAR, " 154 "signon_realm VARCHAR NOT NULL," 155 "ssl_valid INTEGER NOT NULL," 156 "preferred INTEGER NOT NULL," 157 "date_created INTEGER NOT NULL," 158 "blacklisted_by_user INTEGER NOT NULL," 159 "scheme INTEGER NOT NULL," 160 "password_type INTEGER," 161 "possible_usernames BLOB," 162 "times_used INTEGER," 163 "form_data BLOB," 164 "UNIQUE " 165 "(origin_url, username_element, " 166 "username_value, password_element, " 167 "submit_element, signon_realm))")) { 168 NOTREACHED(); 169 return false; 170 } 171 if (!db_.Execute("CREATE INDEX logins_signon ON " 172 "logins (signon_realm)")) { 173 NOTREACHED(); 174 return false; 175 } 176 } 177 return true; 178 } 179 180 void LoginDatabase::ReportMetrics() { 181 sql::Statement s(db_.GetCachedStatement( 182 SQL_FROM_HERE, 183 "SELECT signon_realm, blacklisted_by_user, COUNT(username_value) " 184 "FROM logins GROUP BY signon_realm, blacklisted_by_user")); 185 186 if (!s.is_valid()) 187 return; 188 189 int total_accounts = 0; 190 int blacklisted_sites = 0; 191 while (s.Step()) { 192 int blacklisted = s.ColumnInt(1); 193 int accounts_per_site = s.ColumnInt(2); 194 if (blacklisted) { 195 ++blacklisted_sites; 196 } else { 197 total_accounts += accounts_per_site; 198 UMA_HISTOGRAM_CUSTOM_COUNTS("PasswordManager.AccountsPerSite", 199 accounts_per_site, 0, 32, 6); 200 } 201 } 202 UMA_HISTOGRAM_CUSTOM_COUNTS("PasswordManager.TotalAccounts", 203 total_accounts, 0, 32, 6); 204 UMA_HISTOGRAM_CUSTOM_COUNTS("PasswordManager.BlacklistedSites", 205 blacklisted_sites, 0, 32, 6); 206 207 sql::Statement usage_statement(db_.GetCachedStatement( 208 SQL_FROM_HERE, 209 "SELECT password_type, times_used FROM logins")); 210 211 if (!usage_statement.is_valid()) 212 return; 213 214 while (usage_statement.Step()) { 215 PasswordForm::Type type = static_cast<PasswordForm::Type>( 216 usage_statement.ColumnInt(0)); 217 218 if (type == PasswordForm::TYPE_GENERATED) { 219 UMA_HISTOGRAM_CUSTOM_COUNTS( 220 "PasswordManager.TimesGeneratedPasswordUsed", 221 usage_statement.ColumnInt(1), 0, 100, 10); 222 } else { 223 UMA_HISTOGRAM_CUSTOM_COUNTS( 224 "PasswordManager.TimesPasswordUsed", 225 usage_statement.ColumnInt(1), 0, 100, 10); 226 } 227 } 228 } 229 230 bool LoginDatabase::AddLogin(const PasswordForm& form) { 231 std::string encrypted_password; 232 if (EncryptedString(form.password_value, &encrypted_password) != 233 ENCRYPTION_RESULT_SUCCESS) 234 return false; 235 236 // You *must* change LoginTableColumns if this query changes. 237 sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE, 238 "INSERT OR REPLACE INTO logins " 239 "(origin_url, action_url, username_element, username_value, " 240 " password_element, password_value, submit_element, " 241 " signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, " 242 " scheme, password_type, possible_usernames, times_used, form_data) " 243 "VALUES " 244 "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")); 245 s.BindString(COLUMN_ORIGIN_URL, form.origin.spec()); 246 s.BindString(COLUMN_ACTION_URL, form.action.spec()); 247 s.BindString16(COLUMN_USERNAME_ELEMENT, form.username_element); 248 s.BindString16(COLUMN_USERNAME_VALUE, form.username_value); 249 s.BindString16(COLUMN_PASSWORD_ELEMENT, form.password_element); 250 s.BindBlob(COLUMN_PASSWORD_VALUE, encrypted_password.data(), 251 static_cast<int>(encrypted_password.length())); 252 s.BindString16(COLUMN_SUBMIT_ELEMENT, form.submit_element); 253 s.BindString(COLUMN_SIGNON_REALM, form.signon_realm); 254 s.BindInt(COLUMN_SSL_VALID, form.ssl_valid); 255 s.BindInt(COLUMN_PREFERRED, form.preferred); 256 s.BindInt64(COLUMN_DATE_CREATED, form.date_created.ToTimeT()); 257 s.BindInt(COLUMN_BLACKLISTED_BY_USER, form.blacklisted_by_user); 258 s.BindInt(COLUMN_SCHEME, form.scheme); 259 s.BindInt(COLUMN_PASSWORD_TYPE, form.type); 260 Pickle usernames_pickle = SerializeVector(form.other_possible_usernames); 261 s.BindBlob(COLUMN_POSSIBLE_USERNAMES, 262 usernames_pickle.data(), 263 usernames_pickle.size()); 264 s.BindInt(COLUMN_TIMES_USED, form.times_used); 265 Pickle form_data_pickle; 266 autofill::SerializeFormData(form.form_data, &form_data_pickle); 267 s.BindBlob(COLUMN_FORM_DATA, 268 form_data_pickle.data(), 269 form_data_pickle.size()); 270 271 return s.Run(); 272 } 273 274 bool LoginDatabase::UpdateLogin(const PasswordForm& form, int* items_changed) { 275 std::string encrypted_password; 276 if (EncryptedString(form.password_value, &encrypted_password) != 277 ENCRYPTION_RESULT_SUCCESS) 278 return false; 279 280 sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE, 281 "UPDATE logins SET " 282 "action_url = ?, " 283 "password_value = ?, " 284 "ssl_valid = ?, " 285 "preferred = ?, " 286 "possible_usernames = ?, " 287 "times_used = ? " 288 "WHERE origin_url = ? AND " 289 "username_element = ? AND " 290 "username_value = ? AND " 291 "password_element = ? AND " 292 "signon_realm = ?")); 293 s.BindString(0, form.action.spec()); 294 s.BindBlob(1, encrypted_password.data(), 295 static_cast<int>(encrypted_password.length())); 296 s.BindInt(2, form.ssl_valid); 297 s.BindInt(3, form.preferred); 298 Pickle pickle = SerializeVector(form.other_possible_usernames); 299 s.BindBlob(4, pickle.data(), pickle.size()); 300 s.BindInt(5, form.times_used); 301 s.BindString(6, form.origin.spec()); 302 s.BindString16(7, form.username_element); 303 s.BindString16(8, form.username_value); 304 s.BindString16(9, form.password_element); 305 s.BindString(10, form.signon_realm); 306 307 if (!s.Run()) 308 return false; 309 310 if (items_changed) 311 *items_changed = db_.GetLastChangeCount(); 312 313 return true; 314 } 315 316 bool LoginDatabase::RemoveLogin(const PasswordForm& form) { 317 // Remove a login by UNIQUE-constrained fields. 318 sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE, 319 "DELETE FROM logins WHERE " 320 "origin_url = ? AND " 321 "username_element = ? AND " 322 "username_value = ? AND " 323 "password_element = ? AND " 324 "submit_element = ? AND " 325 "signon_realm = ? ")); 326 s.BindString(0, form.origin.spec()); 327 s.BindString16(1, form.username_element); 328 s.BindString16(2, form.username_value); 329 s.BindString16(3, form.password_element); 330 s.BindString16(4, form.submit_element); 331 s.BindString(5, form.signon_realm); 332 333 return s.Run(); 334 } 335 336 bool LoginDatabase::RemoveLoginsCreatedBetween(const base::Time delete_begin, 337 const base::Time delete_end) { 338 sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE, 339 "DELETE FROM logins WHERE " 340 "date_created >= ? AND date_created < ?")); 341 s.BindInt64(0, delete_begin.ToTimeT()); 342 s.BindInt64(1, delete_end.is_null() ? std::numeric_limits<int64>::max() 343 : delete_end.ToTimeT()); 344 345 return s.Run(); 346 } 347 348 LoginDatabase::EncryptionResult LoginDatabase::InitPasswordFormFromStatement( 349 PasswordForm* form, 350 sql::Statement& s) const { 351 std::string encrypted_password; 352 s.ColumnBlobAsString(COLUMN_PASSWORD_VALUE, &encrypted_password); 353 base::string16 decrypted_password; 354 EncryptionResult encryption_result = 355 DecryptedString(encrypted_password, &decrypted_password); 356 if (encryption_result != ENCRYPTION_RESULT_SUCCESS) 357 return encryption_result; 358 359 std::string tmp = s.ColumnString(COLUMN_ORIGIN_URL); 360 form->origin = GURL(tmp); 361 tmp = s.ColumnString(COLUMN_ACTION_URL); 362 form->action = GURL(tmp); 363 form->username_element = s.ColumnString16(COLUMN_USERNAME_ELEMENT); 364 form->username_value = s.ColumnString16(COLUMN_USERNAME_VALUE); 365 form->password_element = s.ColumnString16(COLUMN_PASSWORD_ELEMENT); 366 form->password_value = decrypted_password; 367 form->submit_element = s.ColumnString16(COLUMN_SUBMIT_ELEMENT); 368 tmp = s.ColumnString(COLUMN_SIGNON_REALM); 369 form->signon_realm = tmp; 370 form->ssl_valid = (s.ColumnInt(COLUMN_SSL_VALID) > 0); 371 form->preferred = (s.ColumnInt(COLUMN_PREFERRED) > 0); 372 form->date_created = base::Time::FromTimeT( 373 s.ColumnInt64(COLUMN_DATE_CREATED)); 374 form->blacklisted_by_user = (s.ColumnInt(COLUMN_BLACKLISTED_BY_USER) > 0); 375 int scheme_int = s.ColumnInt(COLUMN_SCHEME); 376 DCHECK((scheme_int >= 0) && (scheme_int <= PasswordForm::SCHEME_OTHER)); 377 form->scheme = static_cast<PasswordForm::Scheme>(scheme_int); 378 int type_int = s.ColumnInt(COLUMN_PASSWORD_TYPE); 379 DCHECK(type_int >= 0 && type_int <= PasswordForm::TYPE_GENERATED); 380 form->type = static_cast<PasswordForm::Type>(type_int); 381 Pickle pickle( 382 static_cast<const char*>(s.ColumnBlob(COLUMN_POSSIBLE_USERNAMES)), 383 s.ColumnByteLength(COLUMN_POSSIBLE_USERNAMES)); 384 form->other_possible_usernames = DeserializeVector(pickle); 385 form->times_used = s.ColumnInt(COLUMN_TIMES_USED); 386 Pickle form_data_pickle( 387 static_cast<const char*>(s.ColumnBlob(COLUMN_FORM_DATA)), 388 s.ColumnByteLength(COLUMN_FORM_DATA)); 389 PickleIterator form_data_iter(form_data_pickle); 390 autofill::DeserializeFormData(&form_data_iter, &form->form_data); 391 return ENCRYPTION_RESULT_SUCCESS; 392 } 393 394 bool LoginDatabase::GetLogins(const PasswordForm& form, 395 std::vector<PasswordForm*>* forms) const { 396 DCHECK(forms); 397 // You *must* change LoginTableColumns if this query changes. 398 const std::string sql_query = "SELECT origin_url, action_url, " 399 "username_element, username_value, " 400 "password_element, password_value, submit_element, " 401 "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, " 402 "scheme, password_type, possible_usernames, times_used, form_data " 403 "FROM logins WHERE signon_realm == ? "; 404 sql::Statement s; 405 const GURL signon_realm(form.signon_realm); 406 std::string registered_domain = 407 PSLMatchingHelper::GetRegistryControlledDomain(signon_realm); 408 PSLMatchingHelper::PSLDomainMatchMetric psl_domain_match_metric = 409 PSLMatchingHelper::PSL_DOMAIN_MATCH_NONE; 410 if (psl_helper_.ShouldPSLDomainMatchingApply(registered_domain)) { 411 // We are extending the original SQL query with one that includes more 412 // possible matches based on public suffix domain matching. Using a regexp 413 // here is just an optimization to not have to parse all the stored entries 414 // in the |logins| table. The result (scheme, domain and port) is verified 415 // further down using GURL. See the functions SchemeMatches, 416 // RegistryControlledDomainMatches and PortMatches. 417 const std::string extended_sql_query = 418 sql_query + "OR signon_realm REGEXP ? "; 419 // TODO(nyquist) Re-enable usage of GetCachedStatement when 420 // http://crbug.com/248608 is fixed. 421 s.Assign(db_.GetUniqueStatement(extended_sql_query.c_str())); 422 // We need to escape . in the domain. Since the domain has already been 423 // sanitized using GURL, we do not need to escape any other characters. 424 base::ReplaceChars(registered_domain, ".", "\\.", ®istered_domain); 425 std::string scheme = signon_realm.scheme(); 426 // We need to escape . in the scheme. Since the scheme has already been 427 // sanitized using GURL, we do not need to escape any other characters. 428 // The scheme soap.beep is an example with '.'. 429 base::ReplaceChars(scheme, ".", "\\.", &scheme); 430 const std::string port = signon_realm.port(); 431 // For a signon realm such as http://foo.bar/, this regexp will match 432 // domains on the form http://foo.bar/, http://www.foo.bar/, 433 // http://www.mobile.foo.bar/. It will not match http://notfoo.bar/. 434 // The scheme and port has to be the same as the observed form. 435 std::string regexp = "^(" + scheme + ":\\/\\/)([\\w-]+\\.)*" + 436 registered_domain + "(:" + port + ")?\\/$"; 437 s.BindString(0, form.signon_realm); 438 s.BindString(1, regexp); 439 } else { 440 psl_domain_match_metric = PSLMatchingHelper::PSL_DOMAIN_MATCH_DISABLED; 441 s.Assign(db_.GetCachedStatement(SQL_FROM_HERE, sql_query.c_str())); 442 s.BindString(0, form.signon_realm); 443 } 444 445 while (s.Step()) { 446 scoped_ptr<PasswordForm> new_form(new PasswordForm()); 447 EncryptionResult result = InitPasswordFormFromStatement(new_form.get(), s); 448 if (result == ENCRYPTION_RESULT_SERVICE_FAILURE) 449 return false; 450 if (result == ENCRYPTION_RESULT_ITEM_FAILURE) 451 continue; 452 DCHECK(result == ENCRYPTION_RESULT_SUCCESS); 453 if (psl_helper_.IsMatchingEnabled()) { 454 if (!PSLMatchingHelper::IsPublicSuffixDomainMatch(new_form->signon_realm, 455 form.signon_realm)) { 456 // The database returned results that should not match. Skipping result. 457 continue; 458 } 459 if (form.signon_realm != new_form->signon_realm) { 460 psl_domain_match_metric = PSLMatchingHelper::PSL_DOMAIN_MATCH_FOUND; 461 // This is not a perfect match, so we need to create a new valid result. 462 // We do this by copying over origin, signon realm and action from the 463 // observed form and setting the original signon realm to what we found 464 // in the database. We use the fact that |original_signon_realm| is 465 // non-empty to communicate that this match was found using public 466 // suffix matching. 467 new_form->original_signon_realm = new_form->signon_realm; 468 new_form->origin = form.origin; 469 new_form->signon_realm = form.signon_realm; 470 new_form->action = form.action; 471 } 472 } 473 forms->push_back(new_form.release()); 474 } 475 UMA_HISTOGRAM_ENUMERATION("PasswordManager.PslDomainMatchTriggering", 476 psl_domain_match_metric, 477 PSLMatchingHelper::PSL_DOMAIN_MATCH_COUNT); 478 return s.Succeeded(); 479 } 480 481 bool LoginDatabase::GetLoginsCreatedBetween( 482 const base::Time begin, 483 const base::Time end, 484 std::vector<autofill::PasswordForm*>* forms) const { 485 DCHECK(forms); 486 sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE, 487 "SELECT origin_url, action_url, " 488 "username_element, username_value, " 489 "password_element, password_value, submit_element, " 490 "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, " 491 "scheme, password_type, possible_usernames, times_used, form_data " 492 "FROM logins WHERE date_created >= ? AND date_created < ?" 493 "ORDER BY origin_url")); 494 s.BindInt64(0, begin.ToTimeT()); 495 s.BindInt64(1, end.is_null() ? std::numeric_limits<int64>::max() 496 : end.ToTimeT()); 497 498 while (s.Step()) { 499 scoped_ptr<PasswordForm> new_form(new PasswordForm()); 500 EncryptionResult result = InitPasswordFormFromStatement(new_form.get(), s); 501 if (result == ENCRYPTION_RESULT_SERVICE_FAILURE) 502 return false; 503 if (result == ENCRYPTION_RESULT_ITEM_FAILURE) 504 continue; 505 DCHECK(result == ENCRYPTION_RESULT_SUCCESS); 506 forms->push_back(new_form.release()); 507 } 508 return s.Succeeded(); 509 } 510 511 bool LoginDatabase::GetAutofillableLogins( 512 std::vector<PasswordForm*>* forms) const { 513 return GetAllLoginsWithBlacklistSetting(false, forms); 514 } 515 516 bool LoginDatabase::GetBlacklistLogins( 517 std::vector<PasswordForm*>* forms) const { 518 return GetAllLoginsWithBlacklistSetting(true, forms); 519 } 520 521 bool LoginDatabase::GetAllLoginsWithBlacklistSetting( 522 bool blacklisted, std::vector<PasswordForm*>* forms) const { 523 DCHECK(forms); 524 // You *must* change LoginTableColumns if this query changes. 525 sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE, 526 "SELECT origin_url, action_url, " 527 "username_element, username_value, " 528 "password_element, password_value, submit_element, " 529 "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, " 530 "scheme, password_type, possible_usernames, times_used, form_data " 531 "FROM logins WHERE blacklisted_by_user == ? " 532 "ORDER BY origin_url")); 533 s.BindInt(0, blacklisted ? 1 : 0); 534 535 while (s.Step()) { 536 scoped_ptr<PasswordForm> new_form(new PasswordForm()); 537 EncryptionResult result = InitPasswordFormFromStatement(new_form.get(), s); 538 if (result == ENCRYPTION_RESULT_SERVICE_FAILURE) 539 return false; 540 if (result == ENCRYPTION_RESULT_ITEM_FAILURE) 541 continue; 542 DCHECK(result == ENCRYPTION_RESULT_SUCCESS); 543 forms->push_back(new_form.release()); 544 } 545 return s.Succeeded(); 546 } 547 548 bool LoginDatabase::DeleteAndRecreateDatabaseFile() { 549 DCHECK(db_.is_open()); 550 meta_table_.Reset(); 551 db_.Close(); 552 sql::Connection::Delete(db_path_); 553 return Init(db_path_); 554 } 555 556 Pickle LoginDatabase::SerializeVector( 557 const std::vector<base::string16>& vec) const { 558 Pickle p; 559 for (size_t i = 0; i < vec.size(); ++i) { 560 p.WriteString16(vec[i]); 561 } 562 return p; 563 } 564 565 std::vector<base::string16> LoginDatabase::DeserializeVector( 566 const Pickle& p) const { 567 std::vector<base::string16> ret; 568 base::string16 str; 569 570 PickleIterator iterator(p); 571 while (iterator.ReadString16(&str)) { 572 ret.push_back(str); 573 } 574 return ret; 575 } 576