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 "chrome/grit/chromium_strings.h"
17 #include "components/autofill/core/common/password_form.h"
18 #include "content/public/browser/browser_thread.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
;
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());
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
);
738 bool NativeBackendKWallet::DeserializeValueSize(const std::string
& signon_realm
,
739 const PickleIterator
& init_iter
,
743 PasswordFormList
* forms
) {
744 PickleIterator iter
= init_iter
;
748 uint32_t count_32
= 0;
749 if (!iter
.ReadUInt32(&count_32
)) {
750 LOG(ERROR
) << "Failed to deserialize KWallet entry "
751 << "(realm: " << signon_realm
<< ")";
756 if (!iter
.ReadUInt64(&count
)) {
757 LOG(ERROR
) << "Failed to deserialize KWallet entry "
758 << "(realm: " << signon_realm
<< ")";
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.)
771 LOG(ERROR
) << "Suspiciously large number of entries in KWallet entry "
772 << "(" << count
<< "; realm: " << signon_realm
<< ")";
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
);
783 int64 date_created
= 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
);
802 form
->scheme
= static_cast<PasswordForm::Scheme
>(scheme
);
803 form
->date_created
= base::Time::FromTimeT(date_created
);
806 if (!iter
.ReadInt(&type
) ||
807 !iter
.ReadInt(&form
->times_used
) ||
808 !autofill::DeserializeFormData(&iter
, &form
->form_data
)) {
809 LogDeserializationWarning(version
, signon_realm
, false);
812 form
->type
= static_cast<PasswordForm::Type
>(type
);
816 int64 date_synced
= 0;
817 if (!iter
.ReadInt64(&date_synced
)) {
818 LogDeserializationWarning(version
, signon_realm
, false);
821 form
->date_synced
= base::Time::FromInternalValue(date_synced
);
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);
834 forms
->push_back(form
.release());
841 void NativeBackendKWallet::DeserializeValue(const std::string
& signon_realm
,
842 const Pickle
& pickle
,
843 PasswordFormList
* forms
) {
844 PickleIterator
iter(pickle
);
847 if (!iter
.ReadInt(&version
) ||
848 version
< 0 || version
> kPickleVersion
) {
849 LOG(ERROR
) << "Failed to deserialize KWallet entry "
850 << "(realm: " << signon_realm
<< ")";
855 // In current pickles, we expect 64-bit sizes. Failure is an error.
856 DeserializeValueSize(signon_realm
, iter
, version
, false, false, forms
);
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
));
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.
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
;
949 LOG(ERROR
) << "Error creating KWallet folder";
950 return kInvalidKWalletHandle
;
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_
);