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.
5 #include "chrome/browser/password_manager/native_backend_kwallet_x.h"
10 #include "base/logging.h"
11 #include "base/pickle.h"
12 #include "base/stl_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/synchronization/waitable_event.h"
15 #include "base/threading/thread_restrictions.h"
16 #include "components/autofill/core/common/password_form.h"
17 #include "content/public/browser/browser_thread.h"
19 #include "dbus/message.h"
20 #include "dbus/object_path.h"
21 #include "dbus/object_proxy.h"
22 #include "grit/chromium_strings.h"
23 #include "ui/base/l10n/l10n_util.h"
25 using autofill::PasswordForm;
26 using content::BrowserThread;
30 // We could localize this string, but then changing your locale would cause
31 // you to lose access to all your stored passwords. Maybe best not to do that.
32 // Name of the folder to store passwords in.
33 const char kKWalletFolder[] = "Chrome Form Data";
35 // DBus service, path, and interface names for klauncher and kwalletd.
36 const char kKWalletServiceName[] = "org.kde.kwalletd";
37 const char kKWalletPath[] = "/modules/kwalletd";
38 const char kKWalletInterface[] = "org.kde.KWallet";
39 const char kKLauncherServiceName[] = "org.kde.klauncher";
40 const char kKLauncherPath[] = "/KLauncher";
41 const char kKLauncherInterface[] = "org.kde.KLauncher";
43 // Compares two PasswordForms and returns true if they are the same.
44 // If |update_check| is false, we only check the fields that are checked by
45 // LoginDatabase::UpdateLogin() when updating logins; otherwise, we check the
46 // fields that are checked by LoginDatabase::RemoveLogin() for removing them.
47 bool CompareForms(const autofill::PasswordForm& a,
48 const autofill::PasswordForm& b,
50 // An update check doesn't care about the submit element.
51 if (!update_check && a.submit_element != b.submit_element)
53 return a.origin == b.origin &&
54 a.password_element == b.password_element &&
55 a.signon_realm == b.signon_realm &&
56 a.username_element == b.username_element &&
57 a.username_value == b.username_value;
60 // Checks a serialized list of PasswordForms for sanity. Returns true if OK.
61 // Note that |realm| is only used for generating a useful warning message.
62 bool CheckSerializedValue(const uint8_t* byte_array,
64 const std::string& realm) {
65 const Pickle::Header* header =
66 reinterpret_cast<const Pickle::Header*>(byte_array);
67 if (length < sizeof(*header) ||
68 header->payload_size > length - sizeof(*header)) {
69 LOG(WARNING) << "Invalid KWallet entry detected (realm: " << realm << ")";
75 // Convenience function to read a GURL from a Pickle. Assumes the URL has
76 // been written as a UTF-8 string. Returns true on success.
77 bool ReadGURL(PickleIterator* iter, bool warn_only, GURL* url) {
78 std::string url_string;
79 if (!iter->ReadString(&url_string)) {
81 LOG(ERROR) << "Failed to deserialize URL.";
85 *url = GURL(url_string);
89 void LogDeserializationWarning(int version,
90 std::string signon_realm,
93 LOG(WARNING) << "Failed to deserialize version " << version
94 << " KWallet entry (realm: " << signon_realm
95 << ") with native architecture size; will try alternate "
98 LOG(ERROR) << "Failed to deserialize version " << version
99 << " KWallet entry (realm: " << signon_realm << ")";
105 NativeBackendKWallet::NativeBackendKWallet(LocalProfileId id)
107 kwallet_proxy_(NULL),
108 app_name_(l10n_util::GetStringUTF8(IDS_PRODUCT_NAME)) {
109 folder_name_ = GetProfileSpecificFolderName();
112 NativeBackendKWallet::~NativeBackendKWallet() {
113 // This destructor is called on the thread that is destroying the Profile
114 // containing the PasswordStore that owns this NativeBackend. Generally that
115 // won't be the DB thread; it will be the UI thread. So we post a message to
116 // shut it down on the DB thread, and it will be destructed afterward when the
117 // scoped_refptr<dbus::Bus> goes out of scope. The NativeBackend will be
118 // destroyed before that occurs, but that's OK.
119 if (session_bus_.get()) {
120 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
121 base::Bind(&dbus::Bus::ShutdownAndBlock,
122 session_bus_.get()));
126 bool NativeBackendKWallet::Init() {
127 // Without the |optional_bus| parameter, a real bus will be instantiated.
128 return InitWithBus(scoped_refptr<dbus::Bus>());
131 bool NativeBackendKWallet::InitWithBus(scoped_refptr<dbus::Bus> optional_bus) {
132 // We must synchronously do a few DBus calls to figure out if initialization
133 // succeeds, but later, we'll want to do most work on the DB thread. So we
134 // have to do the initialization on the DB thread here too, and wait for it.
135 bool success = false;
136 base::WaitableEvent event(false, false);
137 // NativeBackendKWallet isn't reference counted, but we wait for InitWithBus
138 // to finish, so we can safely use base::Unretained here.
139 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
140 base::Bind(&NativeBackendKWallet::InitOnDBThread,
141 base::Unretained(this),
142 optional_bus, &event, &success));
144 // This ScopedAllowWait should not be here. http://crbug.com/125331
145 base::ThreadRestrictions::ScopedAllowWait allow_wait;
150 void NativeBackendKWallet::InitOnDBThread(scoped_refptr<dbus::Bus> optional_bus,
151 base::WaitableEvent* event,
153 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
154 DCHECK(!session_bus_.get());
155 if (optional_bus.get()) {
156 // The optional_bus parameter is given when this method is called in tests.
157 session_bus_ = optional_bus;
159 // Get a (real) connection to the session bus.
160 dbus::Bus::Options options;
161 options.bus_type = dbus::Bus::SESSION;
162 options.connection_type = dbus::Bus::PRIVATE;
163 session_bus_ = new dbus::Bus(options);
166 session_bus_->GetObjectProxy(kKWalletServiceName,
167 dbus::ObjectPath(kKWalletPath));
168 // kwalletd may not be running. If we get a temporary failure initializing it,
169 // try to start it and then try again. (Note the short-circuit evaluation.)
170 const InitResult result = InitWallet();
171 *success = (result == INIT_SUCCESS ||
172 (result == TEMPORARY_FAIL &&
173 StartKWalletd() && InitWallet() == INIT_SUCCESS));
177 bool NativeBackendKWallet::StartKWalletd() {
178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
179 // Sadly kwalletd doesn't use DBus activation, so we have to make a call to
180 // klauncher to start it.
181 dbus::ObjectProxy* klauncher =
182 session_bus_->GetObjectProxy(kKLauncherServiceName,
183 dbus::ObjectPath(kKLauncherPath));
185 dbus::MethodCall method_call(kKLauncherInterface,
186 "start_service_by_desktop_name");
187 dbus::MessageWriter builder(&method_call);
188 std::vector<std::string> empty;
189 builder.AppendString("kwalletd"); // serviceName
190 builder.AppendArrayOfStrings(empty); // urls
191 builder.AppendArrayOfStrings(empty); // envs
192 builder.AppendString(std::string()); // startup_id
193 builder.AppendBool(false); // blind
194 scoped_ptr<dbus::Response> response(
195 klauncher->CallMethodAndBlock(
196 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
197 if (!response.get()) {
198 LOG(ERROR) << "Error contacting klauncher to start kwalletd";
201 dbus::MessageReader reader(response.get());
203 std::string dbus_name;
206 if (!reader.PopInt32(&ret) || !reader.PopString(&dbus_name) ||
207 !reader.PopString(&error) || !reader.PopInt32(&pid)) {
208 LOG(ERROR) << "Error reading response from klauncher to start kwalletd: "
209 << response->ToString();
212 if (!error.empty() || ret) {
213 LOG(ERROR) << "Error launching kwalletd: error '" << error << "' "
214 << " (code " << ret << ")";
221 NativeBackendKWallet::InitResult NativeBackendKWallet::InitWallet() {
222 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
224 // Check that KWallet is enabled.
225 dbus::MethodCall method_call(kKWalletInterface, "isEnabled");
226 scoped_ptr<dbus::Response> response(
227 kwallet_proxy_->CallMethodAndBlock(
228 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
229 if (!response.get()) {
230 LOG(ERROR) << "Error contacting kwalletd (isEnabled)";
231 return TEMPORARY_FAIL;
233 dbus::MessageReader reader(response.get());
234 bool enabled = false;
235 if (!reader.PopBool(&enabled)) {
236 LOG(ERROR) << "Error reading response from kwalletd (isEnabled): "
237 << response->ToString();
238 return PERMANENT_FAIL;
240 // Not enabled? Don't use KWallet. But also don't warn here.
242 VLOG(1) << "kwalletd reports that KWallet is not enabled.";
243 return PERMANENT_FAIL;
248 // Get the wallet name.
249 dbus::MethodCall method_call(kKWalletInterface, "networkWallet");
250 scoped_ptr<dbus::Response> response(
251 kwallet_proxy_->CallMethodAndBlock(
252 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
253 if (!response.get()) {
254 LOG(ERROR) << "Error contacting kwalletd (networkWallet)";
255 return TEMPORARY_FAIL;
257 dbus::MessageReader reader(response.get());
258 if (!reader.PopString(&wallet_name_)) {
259 LOG(ERROR) << "Error reading response from kwalletd (networkWallet): "
260 << response->ToString();
261 return PERMANENT_FAIL;
268 password_manager::PasswordStoreChangeList NativeBackendKWallet::AddLogin(
269 const PasswordForm& form) {
270 int wallet_handle = WalletHandle();
271 if (wallet_handle == kInvalidKWalletHandle)
272 return password_manager::PasswordStoreChangeList();
274 ScopedVector<autofill::PasswordForm> forms;
275 GetLoginsList(&forms.get(), form.signon_realm, wallet_handle);
277 // We search for a login to update, rather than unconditionally appending the
278 // login, because in some cases (especially involving sync) we can be asked to
279 // add a login that already exists. In these cases we want to just update.
280 bool updated = false;
281 password_manager::PasswordStoreChangeList changes;
282 for (size_t i = 0; i < forms.size(); ++i) {
283 // Use the more restrictive removal comparison, so that we never have
284 // duplicate logins that would all be removed together by RemoveLogin().
285 if (CompareForms(form, *forms[i], false)) {
286 changes.push_back(password_manager::PasswordStoreChange(
287 password_manager::PasswordStoreChange::REMOVE, *forms[i]));
293 forms.push_back(new PasswordForm(form));
294 changes.push_back(password_manager::PasswordStoreChange(
295 password_manager::PasswordStoreChange::ADD, form));
297 bool ok = SetLoginsList(forms.get(), form.signon_realm, wallet_handle);
304 bool NativeBackendKWallet::UpdateLogin(
305 const PasswordForm& form,
306 password_manager::PasswordStoreChangeList* changes) {
309 int wallet_handle = WalletHandle();
310 if (wallet_handle == kInvalidKWalletHandle)
313 ScopedVector<autofill::PasswordForm> forms;
314 GetLoginsList(&forms.get(), form.signon_realm, wallet_handle);
316 bool updated = false;
317 for (size_t i = 0; i < forms.size(); ++i) {
318 if (CompareForms(form, *forms[i], true)) {
326 if (SetLoginsList(forms.get(), form.signon_realm, wallet_handle)) {
327 changes->push_back(password_manager::PasswordStoreChange(
328 password_manager::PasswordStoreChange::UPDATE, form));
335 bool NativeBackendKWallet::RemoveLogin(const PasswordForm& form) {
336 int wallet_handle = WalletHandle();
337 if (wallet_handle == kInvalidKWalletHandle)
340 PasswordFormList all_forms;
341 GetLoginsList(&all_forms, form.signon_realm, wallet_handle);
343 PasswordFormList kept_forms;
344 kept_forms.reserve(all_forms.size());
345 for (size_t i = 0; i < all_forms.size(); ++i) {
346 if (CompareForms(form, *all_forms[i], false))
349 kept_forms.push_back(all_forms[i]);
352 // Update the entry in the wallet, possibly deleting it.
353 bool ok = SetLoginsList(kept_forms, form.signon_realm, wallet_handle);
355 STLDeleteElements(&kept_forms);
359 bool NativeBackendKWallet::RemoveLoginsCreatedBetween(
360 base::Time delete_begin,
361 base::Time delete_end,
362 password_manager::PasswordStoreChangeList* changes) {
363 return RemoveLoginsBetween(
364 delete_begin, delete_end, CREATION_TIMESTAMP, changes);
367 bool NativeBackendKWallet::RemoveLoginsSyncedBetween(
368 base::Time delete_begin,
369 base::Time delete_end,
370 password_manager::PasswordStoreChangeList* changes) {
371 return RemoveLoginsBetween(delete_begin, delete_end, SYNC_TIMESTAMP, changes);
374 bool NativeBackendKWallet::GetLogins(const PasswordForm& form,
375 PasswordFormList* forms) {
376 int wallet_handle = WalletHandle();
377 if (wallet_handle == kInvalidKWalletHandle)
379 return GetLoginsList(forms, form.signon_realm, wallet_handle);
382 bool NativeBackendKWallet::GetAutofillableLogins(PasswordFormList* forms) {
383 int wallet_handle = WalletHandle();
384 if (wallet_handle == kInvalidKWalletHandle)
386 return GetLoginsList(forms, true, wallet_handle);
389 bool NativeBackendKWallet::GetBlacklistLogins(PasswordFormList* forms) {
390 int wallet_handle = WalletHandle();
391 if (wallet_handle == kInvalidKWalletHandle)
393 return GetLoginsList(forms, false, wallet_handle);
396 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
397 const std::string& signon_realm,
399 // Is there an entry in the wallet?
401 dbus::MethodCall method_call(kKWalletInterface, "hasEntry");
402 dbus::MessageWriter builder(&method_call);
403 builder.AppendInt32(wallet_handle); // handle
404 builder.AppendString(folder_name_); // folder
405 builder.AppendString(signon_realm); // key
406 builder.AppendString(app_name_); // appid
407 scoped_ptr<dbus::Response> response(
408 kwallet_proxy_->CallMethodAndBlock(
409 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
410 if (!response.get()) {
411 LOG(ERROR) << "Error contacting kwalletd (hasEntry)";
414 dbus::MessageReader reader(response.get());
415 bool has_entry = false;
416 if (!reader.PopBool(&has_entry)) {
417 LOG(ERROR) << "Error reading response from kwalletd (hasEntry): "
418 << response->ToString();
422 // This is not an error. There just isn't a matching entry.
428 dbus::MethodCall method_call(kKWalletInterface, "readEntry");
429 dbus::MessageWriter builder(&method_call);
430 builder.AppendInt32(wallet_handle); // handle
431 builder.AppendString(folder_name_); // folder
432 builder.AppendString(signon_realm); // key
433 builder.AppendString(app_name_); // appid
434 scoped_ptr<dbus::Response> response(
435 kwallet_proxy_->CallMethodAndBlock(
436 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
437 if (!response.get()) {
438 LOG(ERROR) << "Error contacting kwalletd (readEntry)";
441 dbus::MessageReader reader(response.get());
442 const uint8_t* bytes = NULL;
444 if (!reader.PopArrayOfBytes(&bytes, &length)) {
445 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
446 << response->ToString();
451 if (!CheckSerializedValue(bytes, length, signon_realm)) {
452 // This is weird, but we choose not to call it an error. There is an
453 // invalid entry somehow, but by just ignoring it, we make it easier to
454 // repair without having to delete it using kwalletmanager (that is, by
455 // just saving a new password within this realm to overwrite it).
459 // Can't we all just agree on whether bytes are signed or not? Please?
460 Pickle pickle(reinterpret_cast<const char*>(bytes), length);
461 PasswordFormList all_forms;
462 DeserializeValue(signon_realm, pickle, forms);
468 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
471 PasswordFormList all_forms;
472 if (!GetAllLogins(&all_forms, wallet_handle))
475 // We have to read all the entries, and then filter them here.
476 forms->reserve(forms->size() + all_forms.size());
477 for (size_t i = 0; i < all_forms.size(); ++i) {
478 if (all_forms[i]->blacklisted_by_user == !autofillable)
479 forms->push_back(all_forms[i]);
487 bool NativeBackendKWallet::GetAllLogins(PasswordFormList* forms,
489 // We could probably also use readEntryList here.
490 std::vector<std::string> realm_list;
492 dbus::MethodCall method_call(kKWalletInterface, "entryList");
493 dbus::MessageWriter builder(&method_call);
494 builder.AppendInt32(wallet_handle); // handle
495 builder.AppendString(folder_name_); // folder
496 builder.AppendString(app_name_); // appid
497 scoped_ptr<dbus::Response> response(
498 kwallet_proxy_->CallMethodAndBlock(
499 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
500 if (!response.get()) {
501 LOG(ERROR) << "Error contacting kwalletd (entryList)";
504 dbus::MessageReader reader(response.get());
505 if (!reader.PopArrayOfStrings(&realm_list)) {
506 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
507 << response->ToString();
512 for (size_t i = 0; i < realm_list.size(); ++i) {
513 const std::string& signon_realm = realm_list[i];
514 dbus::MethodCall method_call(kKWalletInterface, "readEntry");
515 dbus::MessageWriter builder(&method_call);
516 builder.AppendInt32(wallet_handle); // handle
517 builder.AppendString(folder_name_); // folder
518 builder.AppendString(signon_realm); // key
519 builder.AppendString(app_name_); // appid
520 scoped_ptr<dbus::Response> response(
521 kwallet_proxy_->CallMethodAndBlock(
522 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
523 if (!response.get()) {
524 LOG(ERROR) << "Error contacting kwalletd (readEntry)";
527 dbus::MessageReader reader(response.get());
528 const uint8_t* bytes = NULL;
530 if (!reader.PopArrayOfBytes(&bytes, &length)) {
531 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
532 << response->ToString();
535 if (!bytes || !CheckSerializedValue(bytes, length, signon_realm))
538 // Can't we all just agree on whether bytes are signed or not? Please?
539 Pickle pickle(reinterpret_cast<const char*>(bytes), length);
540 PasswordFormList all_forms;
541 DeserializeValue(signon_realm, pickle, forms);
546 bool NativeBackendKWallet::SetLoginsList(const PasswordFormList& forms,
547 const std::string& signon_realm,
550 // No items left? Remove the entry from the wallet.
551 dbus::MethodCall method_call(kKWalletInterface, "removeEntry");
552 dbus::MessageWriter builder(&method_call);
553 builder.AppendInt32(wallet_handle); // handle
554 builder.AppendString(folder_name_); // folder
555 builder.AppendString(signon_realm); // key
556 builder.AppendString(app_name_); // appid
557 scoped_ptr<dbus::Response> response(
558 kwallet_proxy_->CallMethodAndBlock(
559 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
560 if (!response.get()) {
561 LOG(ERROR) << "Error contacting kwalletd (removeEntry)";
562 return kInvalidKWalletHandle;
564 dbus::MessageReader reader(response.get());
566 if (!reader.PopInt32(&ret)) {
567 LOG(ERROR) << "Error reading response from kwalletd (removeEntry): "
568 << response->ToString();
572 LOG(ERROR) << "Bad return code " << ret << " from KWallet removeEntry";
577 SerializeValue(forms, &value);
579 dbus::MethodCall method_call(kKWalletInterface, "writeEntry");
580 dbus::MessageWriter builder(&method_call);
581 builder.AppendInt32(wallet_handle); // handle
582 builder.AppendString(folder_name_); // folder
583 builder.AppendString(signon_realm); // key
584 builder.AppendArrayOfBytes(static_cast<const uint8_t*>(value.data()),
585 value.size()); // value
586 builder.AppendString(app_name_); // appid
587 scoped_ptr<dbus::Response> response(
588 kwallet_proxy_->CallMethodAndBlock(
589 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
590 if (!response.get()) {
591 LOG(ERROR) << "Error contacting kwalletd (writeEntry)";
592 return kInvalidKWalletHandle;
594 dbus::MessageReader reader(response.get());
596 if (!reader.PopInt32(&ret)) {
597 LOG(ERROR) << "Error reading response from kwalletd (writeEntry): "
598 << response->ToString();
602 LOG(ERROR) << "Bad return code " << ret << " from KWallet writeEntry";
606 bool NativeBackendKWallet::RemoveLoginsBetween(
607 base::Time delete_begin,
608 base::Time delete_end,
609 TimestampToCompare date_to_compare,
610 password_manager::PasswordStoreChangeList* changes) {
613 int wallet_handle = WalletHandle();
614 if (wallet_handle == kInvalidKWalletHandle)
617 // We could probably also use readEntryList here.
618 std::vector<std::string> realm_list;
620 dbus::MethodCall method_call(kKWalletInterface, "entryList");
621 dbus::MessageWriter builder(&method_call);
622 builder.AppendInt32(wallet_handle); // handle
623 builder.AppendString(folder_name_); // folder
624 builder.AppendString(app_name_); // appid
625 scoped_ptr<dbus::Response> response(kwallet_proxy_->CallMethodAndBlock(
626 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
627 if (!response.get()) {
628 LOG(ERROR) << "Error contacting kwalletd (entryList)";
631 dbus::MessageReader reader(response.get());
632 dbus::MessageReader array(response.get());
633 if (!reader.PopArray(&array)) {
634 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
635 << response->ToString();
638 while (array.HasMoreData()) {
640 if (!array.PopString(&realm)) {
641 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
642 << response->ToString();
645 realm_list.push_back(realm);
650 for (size_t i = 0; i < realm_list.size(); ++i) {
651 const std::string& signon_realm = realm_list[i];
652 dbus::MethodCall method_call(kKWalletInterface, "readEntry");
653 dbus::MessageWriter builder(&method_call);
654 builder.AppendInt32(wallet_handle); // handle
655 builder.AppendString(folder_name_); // folder
656 builder.AppendString(signon_realm); // key
657 builder.AppendString(app_name_); // appid
658 scoped_ptr<dbus::Response> response(kwallet_proxy_->CallMethodAndBlock(
659 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
660 if (!response.get()) {
661 LOG(ERROR) << "Error contacting kwalletd (readEntry)";
664 dbus::MessageReader reader(response.get());
665 const uint8_t* bytes = NULL;
667 if (!reader.PopArrayOfBytes(&bytes, &length)) {
668 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
669 << response->ToString();
672 if (!bytes || !CheckSerializedValue(bytes, length, signon_realm))
675 // Can't we all just agree on whether bytes are signed or not? Please?
676 Pickle pickle(reinterpret_cast<const char*>(bytes), length);
677 PasswordFormList all_forms;
678 DeserializeValue(signon_realm, pickle, &all_forms);
680 PasswordFormList kept_forms;
681 kept_forms.reserve(all_forms.size());
682 base::Time autofill::PasswordForm::*date_member =
683 date_to_compare == CREATION_TIMESTAMP
684 ? &autofill::PasswordForm::date_created
685 : &autofill::PasswordForm::date_synced;
686 for (size_t i = 0; i < all_forms.size(); ++i) {
687 if (delete_begin <= all_forms[i]->*date_member &&
688 (delete_end.is_null() || all_forms[i]->*date_member < delete_end)) {
689 changes->push_back(password_manager::PasswordStoreChange(
690 password_manager::PasswordStoreChange::REMOVE, *all_forms[i]));
693 kept_forms.push_back(all_forms[i]);
697 if (!SetLoginsList(kept_forms, signon_realm, wallet_handle)) {
701 STLDeleteElements(&kept_forms);
707 void NativeBackendKWallet::SerializeValue(const PasswordFormList& forms,
709 pickle->WriteInt(kPickleVersion);
710 pickle->WriteUInt64(forms.size());
711 for (PasswordFormList::const_iterator it = forms.begin();
712 it != forms.end(); ++it) {
713 const PasswordForm* form = *it;
714 pickle->WriteInt(form->scheme);
715 pickle->WriteString(form->origin.spec());
716 pickle->WriteString(form->action.spec());
717 pickle->WriteString16(form->username_element);
718 pickle->WriteString16(form->username_value);
719 pickle->WriteString16(form->password_element);
720 pickle->WriteString16(form->password_value);
721 pickle->WriteString16(form->submit_element);
722 pickle->WriteBool(form->ssl_valid);
723 pickle->WriteBool(form->preferred);
724 pickle->WriteBool(form->blacklisted_by_user);
725 pickle->WriteInt64(form->date_created.ToTimeT());
726 pickle->WriteInt(form->type);
727 pickle->WriteInt(form->times_used);
728 autofill::SerializeFormData(form->form_data, pickle);
729 pickle->WriteInt64(form->date_synced.ToInternalValue());
734 bool NativeBackendKWallet::DeserializeValueSize(const std::string& signon_realm,
735 const PickleIterator& init_iter,
739 PasswordFormList* forms) {
740 PickleIterator iter = init_iter;
744 uint32_t count_32 = 0;
745 if (!iter.ReadUInt32(&count_32)) {
746 LOG(ERROR) << "Failed to deserialize KWallet entry "
747 << "(realm: " << signon_realm << ")";
752 if (!iter.ReadUInt64(&count)) {
753 LOG(ERROR) << "Failed to deserialize KWallet entry "
754 << "(realm: " << signon_realm << ")";
759 if (count > 0xFFFF) {
760 // Trying to pin down the cause of http://crbug.com/80728 (or fix it).
761 // This is a very large number of passwords to be saved for a single realm.
762 // It is almost certainly a corrupt pickle and not real data. Ignore it.
763 // This very well might actually be http://crbug.com/107701, so if we're
764 // reading an old pickle, we don't even log this the first time we try to
765 // read it. (That is, when we're reading the native architecture size.)
767 LOG(ERROR) << "Suspiciously large number of entries in KWallet entry "
768 << "(" << count << "; realm: " << signon_realm << ")";
773 forms->reserve(forms->size() + count);
774 for (uint64_t i = 0; i < count; ++i) {
775 scoped_ptr<PasswordForm> form(new PasswordForm());
776 form->signon_realm.assign(signon_realm);
779 int64 date_created = 0;
781 // Note that these will be read back in the order listed due to
782 // short-circuit evaluation. This is important.
783 if (!iter.ReadInt(&scheme) ||
784 !ReadGURL(&iter, warn_only, &form->origin) ||
785 !ReadGURL(&iter, warn_only, &form->action) ||
786 !iter.ReadString16(&form->username_element) ||
787 !iter.ReadString16(&form->username_value) ||
788 !iter.ReadString16(&form->password_element) ||
789 !iter.ReadString16(&form->password_value) ||
790 !iter.ReadString16(&form->submit_element) ||
791 !iter.ReadBool(&form->ssl_valid) ||
792 !iter.ReadBool(&form->preferred) ||
793 !iter.ReadBool(&form->blacklisted_by_user) ||
794 !iter.ReadInt64(&date_created)) {
795 LogDeserializationWarning(version, signon_realm, warn_only);
798 form->scheme = static_cast<PasswordForm::Scheme>(scheme);
799 form->date_created = base::Time::FromTimeT(date_created);
802 if (!iter.ReadInt(&type) ||
803 !iter.ReadInt(&form->times_used) ||
804 !autofill::DeserializeFormData(&iter, &form->form_data)) {
805 LogDeserializationWarning(version, signon_realm, false);
808 form->type = static_cast<PasswordForm::Type>(type);
812 int64 date_synced = 0;
813 if (!iter.ReadInt64(&date_synced)) {
814 LogDeserializationWarning(version, signon_realm, false);
817 form->date_synced = base::Time::FromInternalValue(date_synced);
820 forms->push_back(form.release());
827 void NativeBackendKWallet::DeserializeValue(const std::string& signon_realm,
828 const Pickle& pickle,
829 PasswordFormList* forms) {
830 PickleIterator iter(pickle);
833 if (!iter.ReadInt(&version) ||
834 version < 0 || version > kPickleVersion) {
835 LOG(ERROR) << "Failed to deserialize KWallet entry "
836 << "(realm: " << signon_realm << ")";
841 // In current pickles, we expect 64-bit sizes. Failure is an error.
842 DeserializeValueSize(signon_realm, iter, version, false, false, forms);
846 const size_t saved_forms_size = forms->size();
847 const bool size_32 = sizeof(size_t) == sizeof(uint32_t);
848 if (!DeserializeValueSize(
849 signon_realm, iter, version, size_32, true, forms)) {
850 // We failed to read the pickle using the native architecture of the system.
851 // Try again with the opposite architecture. Note that we do this even on
852 // 32-bit machines, in case we're reading a 64-bit pickle. (Probably rare,
853 // since mostly we expect upgrades, not downgrades, but both are possible.)
854 forms->resize(saved_forms_size);
855 DeserializeValueSize(signon_realm, iter, version, !size_32, false, forms);
859 int NativeBackendKWallet::WalletHandle() {
860 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
863 // TODO(mdm): Are we leaking these handles? Find out.
864 int32_t handle = kInvalidKWalletHandle;
866 dbus::MethodCall method_call(kKWalletInterface, "open");
867 dbus::MessageWriter builder(&method_call);
868 builder.AppendString(wallet_name_); // wallet
869 builder.AppendInt64(0); // wid
870 builder.AppendString(app_name_); // appid
871 scoped_ptr<dbus::Response> response(
872 kwallet_proxy_->CallMethodAndBlock(
873 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
874 if (!response.get()) {
875 LOG(ERROR) << "Error contacting kwalletd (open)";
876 return kInvalidKWalletHandle;
878 dbus::MessageReader reader(response.get());
879 if (!reader.PopInt32(&handle)) {
880 LOG(ERROR) << "Error reading response from kwalletd (open): "
881 << response->ToString();
882 return kInvalidKWalletHandle;
884 if (handle == kInvalidKWalletHandle) {
885 LOG(ERROR) << "Error obtaining KWallet handle";
886 return kInvalidKWalletHandle;
890 // Check if our folder exists.
891 bool has_folder = false;
893 dbus::MethodCall method_call(kKWalletInterface, "hasFolder");
894 dbus::MessageWriter builder(&method_call);
895 builder.AppendInt32(handle); // handle
896 builder.AppendString(folder_name_); // folder
897 builder.AppendString(app_name_); // appid
898 scoped_ptr<dbus::Response> response(
899 kwallet_proxy_->CallMethodAndBlock(
900 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
901 if (!response.get()) {
902 LOG(ERROR) << "Error contacting kwalletd (hasFolder)";
903 return kInvalidKWalletHandle;
905 dbus::MessageReader reader(response.get());
906 if (!reader.PopBool(&has_folder)) {
907 LOG(ERROR) << "Error reading response from kwalletd (hasFolder): "
908 << response->ToString();
909 return kInvalidKWalletHandle;
913 // Create it if it didn't.
915 dbus::MethodCall method_call(kKWalletInterface, "createFolder");
916 dbus::MessageWriter builder(&method_call);
917 builder.AppendInt32(handle); // handle
918 builder.AppendString(folder_name_); // folder
919 builder.AppendString(app_name_); // appid
920 scoped_ptr<dbus::Response> response(
921 kwallet_proxy_->CallMethodAndBlock(
922 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
923 if (!response.get()) {
924 LOG(ERROR) << "Error contacting kwalletd (createFolder)";
925 return kInvalidKWalletHandle;
927 dbus::MessageReader reader(response.get());
928 bool success = false;
929 if (!reader.PopBool(&success)) {
930 LOG(ERROR) << "Error reading response from kwalletd (createFolder): "
931 << response->ToString();
932 return kInvalidKWalletHandle;
935 LOG(ERROR) << "Error creating KWallet folder";
936 return kInvalidKWalletHandle;
943 std::string NativeBackendKWallet::GetProfileSpecificFolderName() const {
944 // Originally, the folder name was always just "Chrome Form Data".
945 // Now we use it to distinguish passwords for different profiles.
946 return base::StringPrintf("%s (%d)", kKWalletFolder, profile_id_);