Update mojo surfaces bindings and mojo/cc/ glue
[chromium-blink-merge.git] / chrome / browser / password_manager / native_backend_kwallet_x.cc
blob0352542e1ed9136050fd828ff6021b486596b2ad
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"
7 #include <vector>
9 #include "base/bind.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 "chrome/grit/chromium_strings.h"
17 #include "components/autofill/core/common/password_form.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "dbus/bus.h"
20 #include "dbus/message.h"
21 #include "dbus/object_path.h"
22 #include "dbus/object_proxy.h"
23 #include "ui/base/l10n/l10n_util.h"
25 using autofill::PasswordForm;
26 using content::BrowserThread;
28 namespace {
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,
49 bool update_check) {
50 // An update check doesn't care about the submit element.
51 if (!update_check && a.submit_element != b.submit_element)
52 return false;
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,
63 size_t length,
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 << ")";
70 return false;
72 return true;
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)) {
80 if (!warn_only)
81 LOG(ERROR) << "Failed to deserialize URL.";
82 *url = GURL();
83 return false;
85 *url = GURL(url_string);
86 return true;
89 void LogDeserializationWarning(int version,
90 std::string signon_realm,
91 bool warn_only) {
92 if (warn_only) {
93 LOG(WARNING) << "Failed to deserialize version " << version
94 << " KWallet entry (realm: " << signon_realm
95 << ") with native architecture size; will try alternate "
96 << "size.";
97 } else {
98 LOG(ERROR) << "Failed to deserialize version " << version
99 << " KWallet entry (realm: " << signon_realm << ")";
103 } // namespace
105 NativeBackendKWallet::NativeBackendKWallet(LocalProfileId id)
106 : profile_id_(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;
146 event.Wait();
147 return success;
150 void NativeBackendKWallet::InitOnDBThread(scoped_refptr<dbus::Bus> optional_bus,
151 base::WaitableEvent* event,
152 bool* success) {
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;
158 } else {
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);
165 kwallet_proxy_ =
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));
174 event->Signal();
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";
199 return false;
201 dbus::MessageReader reader(response.get());
202 int32_t ret = -1;
203 std::string dbus_name;
204 std::string error;
205 int32_t pid = -1;
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();
210 return false;
212 if (!error.empty() || ret) {
213 LOG(ERROR) << "Error launching kwalletd: error '" << error << "' "
214 << " (code " << ret << ")";
215 return false;
218 return true;
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.
241 if (!enabled) {
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;
265 return INIT_SUCCESS;
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]));
288 *forms[i] = form;
289 updated = true;
292 if (!updated)
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);
298 if (!ok)
299 changes.clear();
301 return changes;
304 bool NativeBackendKWallet::UpdateLogin(
305 const PasswordForm& form,
306 password_manager::PasswordStoreChangeList* changes) {
307 DCHECK(changes);
308 changes->clear();
309 int wallet_handle = WalletHandle();
310 if (wallet_handle == kInvalidKWalletHandle)
311 return false;
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)) {
319 *forms[i] = form;
320 updated = true;
323 if (!updated)
324 return true;
326 if (SetLoginsList(forms.get(), form.signon_realm, wallet_handle)) {
327 changes->push_back(password_manager::PasswordStoreChange(
328 password_manager::PasswordStoreChange::UPDATE, form));
329 return true;
332 return false;
335 bool NativeBackendKWallet::RemoveLogin(const PasswordForm& form) {
336 int wallet_handle = WalletHandle();
337 if (wallet_handle == kInvalidKWalletHandle)
338 return false;
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))
347 delete all_forms[i];
348 else
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);
356 return ok;
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)
378 return false;
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)
385 return false;
386 return GetLoginsList(forms, true, wallet_handle);
389 bool NativeBackendKWallet::GetBlacklistLogins(PasswordFormList* forms) {
390 int wallet_handle = WalletHandle();
391 if (wallet_handle == kInvalidKWalletHandle)
392 return false;
393 return GetLoginsList(forms, false, wallet_handle);
396 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
397 const std::string& signon_realm,
398 int wallet_handle) {
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)";
412 return false;
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();
419 return false;
421 if (!has_entry) {
422 // This is not an error. There just isn't a matching entry.
423 return true;
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)";
439 return false;
441 dbus::MessageReader reader(response.get());
442 const uint8_t* bytes = NULL;
443 size_t length = 0;
444 if (!reader.PopArrayOfBytes(&bytes, &length)) {
445 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
446 << response->ToString();
447 return false;
449 if (!bytes)
450 return false;
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).
456 return true;
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);
465 return true;
468 bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms,
469 bool autofillable,
470 int wallet_handle) {
471 PasswordFormList all_forms;
472 if (!GetAllLogins(&all_forms, wallet_handle))
473 return false;
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]);
480 else
481 delete all_forms[i];
484 return true;
487 bool NativeBackendKWallet::GetAllLogins(PasswordFormList* forms,
488 int wallet_handle) {
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)";
502 return false;
504 dbus::MessageReader reader(response.get());
505 if (!reader.PopArrayOfStrings(&realm_list)) {
506 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
507 << response->ToString();
508 return false;
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)";
525 continue;
527 dbus::MessageReader reader(response.get());
528 const uint8_t* bytes = NULL;
529 size_t length = 0;
530 if (!reader.PopArrayOfBytes(&bytes, &length)) {
531 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
532 << response->ToString();
533 continue;
535 if (!bytes || !CheckSerializedValue(bytes, length, signon_realm))
536 continue;
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);
543 return true;
546 bool NativeBackendKWallet::SetLoginsList(const PasswordFormList& forms,
547 const std::string& signon_realm,
548 int wallet_handle) {
549 if (forms.empty()) {
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());
565 int ret = 0;
566 if (!reader.PopInt32(&ret)) {
567 LOG(ERROR) << "Error reading response from kwalletd (removeEntry): "
568 << response->ToString();
569 return false;
571 if (ret != 0)
572 LOG(ERROR) << "Bad return code " << ret << " from KWallet removeEntry";
573 return ret == 0;
576 Pickle value;
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());
595 int ret = 0;
596 if (!reader.PopInt32(&ret)) {
597 LOG(ERROR) << "Error reading response from kwalletd (writeEntry): "
598 << response->ToString();
599 return false;
601 if (ret != 0)
602 LOG(ERROR) << "Bad return code " << ret << " from KWallet writeEntry";
603 return ret == 0;
606 bool NativeBackendKWallet::RemoveLoginsBetween(
607 base::Time delete_begin,
608 base::Time delete_end,
609 TimestampToCompare date_to_compare,
610 password_manager::PasswordStoreChangeList* changes) {
611 DCHECK(changes);
612 changes->clear();
613 int wallet_handle = WalletHandle();
614 if (wallet_handle == kInvalidKWalletHandle)
615 return false;
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)";
629 return false;
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();
636 return false;
638 while (array.HasMoreData()) {
639 std::string realm;
640 if (!array.PopString(&realm)) {
641 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
642 << response->ToString();
643 return false;
645 realm_list.push_back(realm);
649 bool ok = true;
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)";
662 continue;
664 dbus::MessageReader reader(response.get());
665 const uint8_t* bytes = NULL;
666 size_t length = 0;
667 if (!reader.PopArrayOfBytes(&bytes, &length)) {
668 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
669 << response->ToString();
670 continue;
672 if (!bytes || !CheckSerializedValue(bytes, length, signon_realm))
673 continue;
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]));
691 delete all_forms[i];
692 } else {
693 kept_forms.push_back(all_forms[i]);
697 if (!SetLoginsList(kept_forms, signon_realm, wallet_handle)) {
698 ok = false;
699 changes->clear();
701 STLDeleteElements(&kept_forms);
703 return ok;
706 // static
707 void NativeBackendKWallet::SerializeValue(const PasswordFormList& forms,
708 Pickle* pickle) {
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());
730 pickle->WriteString16(form->display_name);
731 pickle->WriteString(form->avatar_url.spec());
732 pickle->WriteString(form->federation_url.spec());
733 pickle->WriteBool(form->is_zero_click);
737 // static
738 bool NativeBackendKWallet::DeserializeValueSize(const std::string& signon_realm,
739 const PickleIterator& init_iter,
740 int version,
741 bool size_32,
742 bool warn_only,
743 PasswordFormList* forms) {
744 PickleIterator iter = init_iter;
746 uint64_t count = 0;
747 if (size_32) {
748 uint32_t count_32 = 0;
749 if (!iter.ReadUInt32(&count_32)) {
750 LOG(ERROR) << "Failed to deserialize KWallet entry "
751 << "(realm: " << signon_realm << ")";
752 return false;
754 count = count_32;
755 } else {
756 if (!iter.ReadUInt64(&count)) {
757 LOG(ERROR) << "Failed to deserialize KWallet entry "
758 << "(realm: " << signon_realm << ")";
759 return false;
763 if (count > 0xFFFF) {
764 // Trying to pin down the cause of http://crbug.com/80728 (or fix it).
765 // This is a very large number of passwords to be saved for a single realm.
766 // It is almost certainly a corrupt pickle and not real data. Ignore it.
767 // This very well might actually be http://crbug.com/107701, so if we're
768 // reading an old pickle, we don't even log this the first time we try to
769 // read it. (That is, when we're reading the native architecture size.)
770 if (!warn_only) {
771 LOG(ERROR) << "Suspiciously large number of entries in KWallet entry "
772 << "(" << count << "; realm: " << signon_realm << ")";
774 return false;
777 forms->reserve(forms->size() + count);
778 for (uint64_t i = 0; i < count; ++i) {
779 scoped_ptr<PasswordForm> form(new PasswordForm());
780 form->signon_realm.assign(signon_realm);
782 int scheme = 0;
783 int64 date_created = 0;
784 int type = 0;
785 // Note that these will be read back in the order listed due to
786 // short-circuit evaluation. This is important.
787 if (!iter.ReadInt(&scheme) ||
788 !ReadGURL(&iter, warn_only, &form->origin) ||
789 !ReadGURL(&iter, warn_only, &form->action) ||
790 !iter.ReadString16(&form->username_element) ||
791 !iter.ReadString16(&form->username_value) ||
792 !iter.ReadString16(&form->password_element) ||
793 !iter.ReadString16(&form->password_value) ||
794 !iter.ReadString16(&form->submit_element) ||
795 !iter.ReadBool(&form->ssl_valid) ||
796 !iter.ReadBool(&form->preferred) ||
797 !iter.ReadBool(&form->blacklisted_by_user) ||
798 !iter.ReadInt64(&date_created)) {
799 LogDeserializationWarning(version, signon_realm, warn_only);
800 return false;
802 form->scheme = static_cast<PasswordForm::Scheme>(scheme);
803 form->date_created = base::Time::FromTimeT(date_created);
805 if (version > 1) {
806 if (!iter.ReadInt(&type) ||
807 !iter.ReadInt(&form->times_used) ||
808 !autofill::DeserializeFormData(&iter, &form->form_data)) {
809 LogDeserializationWarning(version, signon_realm, false);
810 return false;
812 form->type = static_cast<PasswordForm::Type>(type);
815 if (version > 2) {
816 int64 date_synced = 0;
817 if (!iter.ReadInt64(&date_synced)) {
818 LogDeserializationWarning(version, signon_realm, false);
819 return false;
821 form->date_synced = base::Time::FromInternalValue(date_synced);
824 if (version > 3) {
825 if (!iter.ReadString16(&form->display_name) ||
826 !ReadGURL(&iter, warn_only, &form->avatar_url) ||
827 !ReadGURL(&iter, warn_only, &form->federation_url) ||
828 !iter.ReadBool(&form->is_zero_click)) {
829 LogDeserializationWarning(version, signon_realm, false);
830 return false;
834 forms->push_back(form.release());
837 return true;
840 // static
841 void NativeBackendKWallet::DeserializeValue(const std::string& signon_realm,
842 const Pickle& pickle,
843 PasswordFormList* forms) {
844 PickleIterator iter(pickle);
846 int version = -1;
847 if (!iter.ReadInt(&version) ||
848 version < 0 || version > kPickleVersion) {
849 LOG(ERROR) << "Failed to deserialize KWallet entry "
850 << "(realm: " << signon_realm << ")";
851 return;
854 if (version > 0) {
855 // In current pickles, we expect 64-bit sizes. Failure is an error.
856 DeserializeValueSize(signon_realm, iter, version, false, false, forms);
857 return;
860 const size_t saved_forms_size = forms->size();
861 const bool size_32 = sizeof(size_t) == sizeof(uint32_t);
862 if (!DeserializeValueSize(
863 signon_realm, iter, version, size_32, true, forms)) {
864 // We failed to read the pickle using the native architecture of the system.
865 // Try again with the opposite architecture. Note that we do this even on
866 // 32-bit machines, in case we're reading a 64-bit pickle. (Probably rare,
867 // since mostly we expect upgrades, not downgrades, but both are possible.)
868 forms->resize(saved_forms_size);
869 DeserializeValueSize(signon_realm, iter, version, !size_32, false, forms);
873 int NativeBackendKWallet::WalletHandle() {
874 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
876 // Open the wallet.
877 // TODO(mdm): Are we leaking these handles? Find out.
878 int32_t handle = kInvalidKWalletHandle;
880 dbus::MethodCall method_call(kKWalletInterface, "open");
881 dbus::MessageWriter builder(&method_call);
882 builder.AppendString(wallet_name_); // wallet
883 builder.AppendInt64(0); // wid
884 builder.AppendString(app_name_); // appid
885 scoped_ptr<dbus::Response> response(
886 kwallet_proxy_->CallMethodAndBlock(
887 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
888 if (!response.get()) {
889 LOG(ERROR) << "Error contacting kwalletd (open)";
890 return kInvalidKWalletHandle;
892 dbus::MessageReader reader(response.get());
893 if (!reader.PopInt32(&handle)) {
894 LOG(ERROR) << "Error reading response from kwalletd (open): "
895 << response->ToString();
896 return kInvalidKWalletHandle;
898 if (handle == kInvalidKWalletHandle) {
899 LOG(ERROR) << "Error obtaining KWallet handle";
900 return kInvalidKWalletHandle;
904 // Check if our folder exists.
905 bool has_folder = false;
907 dbus::MethodCall method_call(kKWalletInterface, "hasFolder");
908 dbus::MessageWriter builder(&method_call);
909 builder.AppendInt32(handle); // handle
910 builder.AppendString(folder_name_); // folder
911 builder.AppendString(app_name_); // appid
912 scoped_ptr<dbus::Response> response(
913 kwallet_proxy_->CallMethodAndBlock(
914 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
915 if (!response.get()) {
916 LOG(ERROR) << "Error contacting kwalletd (hasFolder)";
917 return kInvalidKWalletHandle;
919 dbus::MessageReader reader(response.get());
920 if (!reader.PopBool(&has_folder)) {
921 LOG(ERROR) << "Error reading response from kwalletd (hasFolder): "
922 << response->ToString();
923 return kInvalidKWalletHandle;
927 // Create it if it didn't.
928 if (!has_folder) {
929 dbus::MethodCall method_call(kKWalletInterface, "createFolder");
930 dbus::MessageWriter builder(&method_call);
931 builder.AppendInt32(handle); // handle
932 builder.AppendString(folder_name_); // folder
933 builder.AppendString(app_name_); // appid
934 scoped_ptr<dbus::Response> response(
935 kwallet_proxy_->CallMethodAndBlock(
936 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
937 if (!response.get()) {
938 LOG(ERROR) << "Error contacting kwalletd (createFolder)";
939 return kInvalidKWalletHandle;
941 dbus::MessageReader reader(response.get());
942 bool success = false;
943 if (!reader.PopBool(&success)) {
944 LOG(ERROR) << "Error reading response from kwalletd (createFolder): "
945 << response->ToString();
946 return kInvalidKWalletHandle;
948 if (!success) {
949 LOG(ERROR) << "Error creating KWallet folder";
950 return kInvalidKWalletHandle;
954 return handle;
957 std::string NativeBackendKWallet::GetProfileSpecificFolderName() const {
958 // Originally, the folder name was always just "Chrome Form Data".
959 // Now we use it to distinguish passwords for different profiles.
960 return base::StringPrintf("%s (%d)", kKWalletFolder, profile_id_);