Index: chrome/browser/password_manager/keyring_proxy/keyring_proxy_client.cc |
=================================================================== |
--- chrome/browser/password_manager/keyring_proxy/keyring_proxy_client.cc (revision 0) |
+++ chrome/browser/password_manager/keyring_proxy/keyring_proxy_client.cc (revision 0) |
@@ -0,0 +1,430 @@ |
+// Copyright (c) 2011 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "chrome/browser/password_manager/keyring_proxy/keyring_proxy_client.h" |
+ |
+#include <errno.h> |
+#include <sys/socket.h> |
+#include <unistd.h> |
+ |
+#include "base/base_paths.h" |
+#include "base/bind.h" |
+#include "base/file_path.h" |
+#include "base/message_loop.h" |
+#include "base/message_pump_libevent.h" |
+#include "base/path_service.h" |
+#include "base/process_util.h" |
+#include "base/string_number_conversions.h" |
+#include "base/stringprintf.h" |
+#include "base/utf_string_conversions.h" |
+#include "chrome/browser/password_manager/keyring_proxy/keyring_proxy.h" |
+#include "chrome/browser/password_manager/keyring_proxy/message_reader.h" |
+#include "content/public/browser/browser_thread.h" |
+#include "webkit/glue/password_form.h" |
+ |
+using content::BrowserThread; |
+ |
+namespace keyring_proxy { |
+ |
+// We need a MessageLoopForIO to watch a file descriptor, but the DB thread |
+// doesn't have one. This class handles actually watching and reading from the |
+// file descriptor on the file thread, then notifying the keyring proxy which |
+// parses the messages and notifies the native backend on the DB thread. |
+class KeyringProxyFDWatcher |
+ : public base::RefCounted<KeyringProxyFDWatcher>, |
+ public base::MessagePumpLibevent::Watcher { |
+ public: |
+ // Takes ownership of proxy_fd and will close it in the destructor. |
+ // Does not take ownership of |client|. ShutDown() must be called before |
+ // the client is destroyed to prevent further callbacks; the client |
+ // should keep a reference to the new KeyringProxyFDWatcher until then. |
+ KeyringProxyFDWatcher(int proxy_fd, KeyringProxyClient* client); |
+ |
+ void Init(); |
+ |
+ int fd() const { return proxy_fd_; } |
+ |
+ void ShutDown(); |
+ |
+ private: |
+ friend class base::RefCounted<KeyringProxyFDWatcher>; |
+ ~KeyringProxyFDWatcher(); |
+ |
+ // These run on the file thread. |
+ void StartWatching(); |
+ void StopWatching(); |
+ |
+ // Implement base::MessagePumpLibevent::Watcher. |
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; |
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; |
+ |
+ const int proxy_fd_; |
+ KeyringProxyClient* client_; |
+ base::MessagePumpLibevent::FileDescriptorWatcher ipc_watcher_; |
+ |
+ // Used to batch data read from the proxy into individual replies. |
+ MessageReader reader_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(KeyringProxyFDWatcher); |
+}; |
+ |
+KeyringProxyFDWatcher::KeyringProxyFDWatcher(int proxy_fd, |
+ KeyringProxyClient* client) |
+ : proxy_fd_(proxy_fd), client_(client) { |
+ // It's not safe to post a task referencing a reference counted object from |
+ // within its constructor: the execution of the task races with the |
+ // constructor returning and the reference it had may be removed before |
+ // another is created. So, we have an Init() method below instead. |
+} |
+ |
+void KeyringProxyFDWatcher::Init() { |
+ if (client_) { |
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
+ base::Bind(&KeyringProxyFDWatcher::StartWatching, |
+ this)); |
+ } |
+} |
+ |
+void KeyringProxyFDWatcher::ShutDown() { |
+ // This will prevent any calls back to the client. It is technically |
vandebo (ex-Chrome)
2011/12/01 19:57:30
Shutdown crashes can be a pain in the butt to diag
|
+ // insufficient as this runs on the UI thread and the check occurs on the file |
+ // thread, so the file thread may have already checked it before we clear it. |
+ // We could fix that with a lock held while client callbacks run on the file |
+ // thread, but for now just ignore it - it would only be a problem if you were |
+ // actively using the proxy during shutdown, which probably shouldn't happen. |
+ client_ = NULL; |
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
+ base::Bind(&KeyringProxyFDWatcher::StopWatching, |
+ this)); |
+} |
+ |
+KeyringProxyFDWatcher::~KeyringProxyFDWatcher() { |
+ // It should not be necessary to stop watching here, since we should already |
+ // have done that in StopWatching() prior to the last reference going away. |
+ // We do it anyway just to be extra sure. |
+ ipc_watcher_.StopWatchingFileDescriptor(); |
vandebo (ex-Chrome)
2011/12/01 19:57:30
Will this always be called on the right thread? Ad
|
+ close(proxy_fd_); |
+} |
+ |
+void KeyringProxyFDWatcher::StartWatching() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ MessageLoopForIO* file_loop = MessageLoopForIO::current(); |
+ bool ok = file_loop->WatchFileDescriptor( |
+ proxy_fd_, true, MessageLoopForIO::WATCH_READ, &ipc_watcher_, this); |
+ DCHECK(ok); |
+} |
+ |
+void KeyringProxyFDWatcher::StopWatching() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ ipc_watcher_.StopWatchingFileDescriptor(); |
+} |
+ |
+void KeyringProxyFDWatcher::OnFileCanReadWithoutBlocking(int fd) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
+ DCHECK_EQ(proxy_fd_, fd); |
+ // We don't need to read all the available data. If there is still data left |
+ // after we return, then we'll get called again because the descriptor will |
+ // still be ready to read. So just read a fixed amount, for simplicity. |
+ char buffer[256]; |
vandebo (ex-Chrome)
2011/12/01 19:57:30
constant somewhere?
|
+ ssize_t available = read(proxy_fd_, buffer, sizeof(buffer)); |
+ if (available > 0) { |
+ // Got some data. Handle it. Note that this may not be a complete message. |
+ ssize_t used = 0; |
+ while (used < available) { |
+ ssize_t handled = reader_.HandleData(&buffer[used], available); |
+ DCHECK_GT(handled, 0); |
+ used += handled; |
+ available -= handled; |
+ if (reader_.is_complete()) { |
+ if (client_) |
+ client_->HandleProxyReply(reader_.lines()); |
+ reader_.Reset(); |
+ } else { |
+ DCHECK_EQ(0, available); |
+ } |
+ } |
+ } else if (available == 0 || errno != EINTR) { |
+ // EOF, or an unexpected error. Stop watching and notify. |
+ LOG(WARNING) << "Unexpected failure reading from keyring proxy: " << errno; |
+ ipc_watcher_.StopWatchingFileDescriptor(); |
+ if (client_) |
+ client_->HandleProxyError(available == 0); |
+ } |
+ // Note that if the read is interrupted, we'll just end up here. That's fine. |
+ // The file descriptor will still be ready and we'll get called again soon. |
+} |
+ |
+void KeyringProxyFDWatcher::OnFileCanWriteWithoutBlocking(int fd) { |
+ // We didn't ask to be notified of writability. |
+ NOTREACHED(); |
+} |
+ |
+const char KeyringProxyClient::kProxyBinary[] = "chrome-keyring-proxy"; |
+ |
+KeyringProxyClient::KeyringProxyClient() : next_request_id_(0) { |
+} |
+ |
+KeyringProxyClient::~KeyringProxyClient() { |
+ base::AutoLock lock(request_lock_); |
+ if (fd_watcher_.get()) |
+ fd_watcher_->ShutDown(); |
+ CancelAllRequests(); |
+} |
+ |
+bool KeyringProxyClient::Connect() { |
+ base::AutoLock lock(request_lock_); |
+ DCHECK(!fd_watcher_.get()); |
+ int fd = LaunchKeyringProxy(); |
+ if (fd < 0) |
+ return false; |
+ fd_watcher_ = new KeyringProxyFDWatcher(fd, this); |
+ fd_watcher_->Init(); |
+ return true; |
+} |
+ |
+void KeyringProxyClient::ConnectForTesting(int fd, bool watch_for_reads) { |
+ if (watch_for_reads) { |
+ fd_watcher_ = new KeyringProxyFDWatcher(fd, this); |
+ fd_watcher_->Init(); |
+ } else { |
+ fd_watcher_ = new KeyringProxyFDWatcher(fd, NULL); |
+ // There's no need to call Init(), as we passed NULL for |client| above. |
+ } |
+} |
+ |
+void KeyringProxyClient::AddLogin(const PasswordForm& form, |
+ const std::string& app_string, |
+ RequestContext* context) { |
+ // If we are asked to save a password with 0 date, use the current time. |
+ // We don't want to actually save passwords as though on January 1, 1970. |
+ time_t date_created = form.date_created.ToTimeT(); |
+ if (!date_created) |
+ date_created = time(NULL); |
+ ProxyMessage request; |
+ request.push_back("+" + form.origin.spec()); // Display name. |
+ request.push_back("+" + UTF16ToUTF8(form.password_value)); |
+ request.push_back("+" + form.origin.spec()); // Origin. |
+ request.push_back("+" + form.action.spec()); |
+ request.push_back("+" + UTF16ToUTF8(form.username_element)); |
+ request.push_back("+" + UTF16ToUTF8(form.username_value)); |
+ request.push_back("+" + UTF16ToUTF8(form.password_element)); |
+ request.push_back("+" + UTF16ToUTF8(form.submit_element)); |
+ request.push_back("+" + form.signon_realm); |
+ request.push_back(form.ssl_valid ? "1" : "0"); |
+ request.push_back(form.preferred ? "1" : "0"); |
+ request.push_back("+" + base::Int64ToString(date_created)); |
+ request.push_back(form.blacklisted_by_user ? "1" : "0"); |
+ request.push_back(StringPrintf("%d", form.scheme)); |
+ request.push_back(app_string); |
+ SendRequest(KeyringProxy::kAddLoginCommand, request, context); |
+} |
+ |
+void KeyringProxyClient::AddLoginSearch(const PasswordForm& form, |
+ const std::string& app_string, |
+ RequestContext* context) { |
+ ProxyMessage request; |
+ request.push_back("+" + form.origin.spec()); |
+ request.push_back("+" + UTF16ToUTF8(form.username_element)); |
+ request.push_back("+" + UTF16ToUTF8(form.username_value)); |
+ request.push_back("+" + UTF16ToUTF8(form.password_element)); |
+ request.push_back("+" + UTF16ToUTF8(form.submit_element)); |
+ request.push_back("+" + form.signon_realm); |
+ request.push_back(app_string); |
+ SendRequest(KeyringProxy::kAddLoginSearchCommand, request, context); |
+} |
+ |
+void KeyringProxyClient::UpdateLoginSearch(const PasswordForm& form, |
+ const std::string& app_string, |
+ RequestContext* context) { |
+ ProxyMessage request; |
+ request.push_back("+" + form.origin.spec()); |
+ request.push_back("+" + UTF16ToUTF8(form.username_element)); |
+ request.push_back("+" + UTF16ToUTF8(form.username_value)); |
+ request.push_back("+" + UTF16ToUTF8(form.password_element)); |
+ request.push_back("+" + form.signon_realm); |
+ request.push_back(app_string); |
+ SendRequest(KeyringProxy::kUpdateLoginSearchCommand, request, context); |
+} |
+ |
+void KeyringProxyClient::RemoveLogin(const PasswordForm& form, |
+ const std::string& app_string, |
+ RequestContext* context) { |
+ ProxyMessage request; |
+ request.push_back("+" + form.origin.spec()); |
+ request.push_back("+" + form.action.spec()); |
+ request.push_back("+" + UTF16ToUTF8(form.username_element)); |
+ request.push_back("+" + UTF16ToUTF8(form.username_value)); |
+ request.push_back("+" + UTF16ToUTF8(form.password_element)); |
+ request.push_back("+" + UTF16ToUTF8(form.submit_element)); |
+ request.push_back("+" + form.signon_realm); |
+ request.push_back(app_string); |
+ SendRequest(KeyringProxy::kRemoveLoginCommand, request, context); |
+} |
+ |
+void KeyringProxyClient::GetLogins(const PasswordForm& form, |
+ const std::string& app_string, |
+ RequestContext* context) { |
+ ProxyMessage request; |
+ request.push_back("+" + form.signon_realm); |
+ request.push_back(app_string); |
+ SendRequest(KeyringProxy::kGetLoginsCommand, request, context); |
+} |
+ |
+void KeyringProxyClient::GetLoginsList(bool blacklisted_by_user, |
+ const std::string& app_string, |
+ RequestContext* context) { |
+ ProxyMessage request; |
+ request.push_back(blacklisted_by_user ? "1" : "0"); |
+ request.push_back(app_string); |
+ SendRequest(KeyringProxy::kGetLoginsListCommand, request, context); |
+} |
+ |
+void KeyringProxyClient::GetAllLogins(const std::string& app_string, |
+ RequestContext* context) { |
+ ProxyMessage request; |
+ request.push_back(app_string); |
+ SendRequest(KeyringProxy::kGetAllLoginsCommand, request, context); |
+} |
+ |
+// static |
+int KeyringProxyClient::LaunchKeyringProxy() { |
+ FilePath chrome_dir; |
+ if (!PathService::Get(base::DIR_EXE, &chrome_dir)) |
+ return -1; |
+ std::vector<std::string> proxy_command; |
+ proxy_command.push_back(chrome_dir.Append(kProxyBinary).value()); |
+ |
+ int fds[2]; |
+ if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) < 0) |
+ return -1; |
+ base::file_handle_mapping_vector ipc; |
+ ipc.push_back(std::make_pair(fds[1], STDIN_FILENO)); |
+ ipc.push_back(std::make_pair(fds[1], STDOUT_FILENO)); |
+ base::LaunchOptions options; |
+ options.fds_to_remap = &ipc; |
+ |
+ base::ProcessHandle proxy_handle; |
+ if (!base::LaunchProcess(proxy_command, options, &proxy_handle)) { |
+ close(fds[0]); |
+ close(fds[1]); |
+ return -1; |
+ } |
+ close(fds[1]); |
+ return fds[0]; |
+} |
+ |
+int KeyringProxyClient::GetRequestId() { |
+ for (;;) { |
vandebo (ex-Chrome)
2011/12/01 19:57:30
Any reason not to use while(true) { ?
|
+ // |next_request_id_| is actually the most recently used request ID, because |
vandebo (ex-Chrome)
2011/12/01 19:57:30
Can we then name it appropriately? last_request_id
|
+ // that makes it easier to write this loop with a preincrement operator. |
+ if (++next_request_id_ < 0) |
+ next_request_id_ = 1; |
+ if (proxy_requests_.find(next_request_id_) == proxy_requests_.end()) |
+ return next_request_id_; |
+ } |
+} |
+ |
+void KeyringProxyClient::CancelRequest(RequestContext* context) { |
+ context->result_code = GNOME_KEYRING_RESULT_CANCELLED; |
+ context->event.Signal(); |
+} |
+ |
+void KeyringProxyClient::CancelAllRequests() { |
+ for (std::map<int, RequestContext*>::iterator it = proxy_requests_.begin(); |
vandebo (ex-Chrome)
2011/12/01 19:57:30
request_lock_.AssertAcquired();
|
+ it != proxy_requests_.end(); ++it) { |
+ CancelRequest(it->second); |
+ } |
+ proxy_requests_.clear(); |
+} |
+ |
+void KeyringProxyClient::SendRequest(char type, |
+ const ProxyMessage& request, |
+ RequestContext* context) { |
+ base::AutoLock lock(request_lock_); |
+ if (!fd_watcher_.get()) { |
+ LOG(WARNING) << "Attempted to use unconnected keyring proxy"; |
+ CancelRequest(context); |
+ return; |
+ } |
+ int id = GetRequestId(); |
+ proxy_requests_[id] = context; |
+ std::string message = StringPrintf("%c%d\n", type, id); |
+ for (size_t i = 0; i < request.size(); ++i) |
vandebo (ex-Chrome)
2011/12/01 19:57:30
You put the message in a vector of strings and the
|
+ message += request[i] + "\n"; |
+ message += "\n"; |
+ size_t written = write(fd_watcher_->fd(), message.data(), message.size()); |
vandebo (ex-Chrome)
2011/12/01 19:57:30
Can you compose your message first, then get the l
|
+ if (written != message.size()) { |
+ LOG(ERROR) << "Failed to write to keyring proxy! Likely failure soon..."; |
+ CancelRequest(context); |
+ } |
+} |
+ |
+void KeyringProxyClient::HandleProxyReply(const ProxyMessage& reply) { |
+ if (!reply.size()) { |
+ LOG(WARNING) << "Got empty reply from keyring proxy"; |
+ return; |
+ } |
+ int id, result_code; |
+ if (sscanf(reply[0].c_str(), "%d %d", &id, &result_code) != 2) { |
+ // We haven't acquired the lock yet so we can just use HandleProxyError(). |
+ HandleProxyError(false); |
+ return; |
+ } |
+ base::AutoLock lock(request_lock_); |
+ std::map<int, RequestContext*>::iterator it = proxy_requests_.find(id); |
+ if (it == proxy_requests_.end()) { |
+ LOG(WARNING) << "Unexpected reply from keyring proxy (ID " << id << ")"; |
+ return; |
+ } |
+ RequestContext* context = it->second; |
+ const size_t kLinesPerPass = 13; |
vandebo (ex-Chrome)
2011/12/01 19:57:30
This isn't so much lines per pass, but lines per r
|
+ if ((reply.size() - 1) % kLinesPerPass != 0 || |
+ (reply.size() > 1 && !context->result_list)) { |
+ LOG(WARNING) << "Invalid reply from keyring proxy (ID " << id << ")"; |
+ fd_watcher_->ShutDown(); |
+ fd_watcher_ = NULL; |
+ CancelAllRequests(); |
+ return; |
+ } |
+ // We don't remove the request from the map until after the checks above. |
+ proxy_requests_.erase(it); |
+ context->result_code = static_cast<GnomeKeyringResult>(result_code); |
+ for (size_t i = 1; i + kLinesPerPass <= reply.size(); i += kLinesPerPass) { |
+ PasswordForm* form = new PasswordForm; |
+ form->origin = GURL(reply[i].substr(1)); |
+ form->action = GURL(reply[i + 1].substr(1)); |
+ form->username_element = UTF8ToUTF16(reply[i + 2].substr(1)); |
+ form->username_value = UTF8ToUTF16(reply[i + 3].substr(1)); |
+ form->password_element = UTF8ToUTF16(reply[i + 4].substr(1)); |
+ form->password_value = UTF8ToUTF16(reply[i + 5].substr(1)); |
+ form->submit_element = UTF8ToUTF16(reply[i + 6].substr(1)); |
+ form->signon_realm = reply[i + 7].substr(1); |
+ form->ssl_valid = reply[i + 8] != "0"; |
+ form->preferred = reply[i + 9] != "0"; |
+ int64 date_created = 0; |
+ bool date_ok = base::StringToInt64(reply[i + 10].substr(1), &date_created); |
+ DCHECK(date_ok); |
+ form->date_created = base::Time::FromTimeT(date_created); |
+ form->blacklisted_by_user = reply[i + 11] != "0"; |
+ unsigned int scheme = 0; |
+ int scheme_ok = sscanf(reply[i + 12].c_str(), "%u", &scheme); |
+ DCHECK_EQ(1, scheme_ok); |
+ form->scheme = static_cast<PasswordForm::Scheme>(scheme); |
+ context->result_list->push_back(form); |
+ } |
+ context->event.Signal(); |
+} |
+ |
+void KeyringProxyClient::HandleProxyError(bool eof) { |
+ base::AutoLock lock(request_lock_); |
+ if (eof) |
+ LOG(WARNING) << "Unexpected EOF reading from keyring proxy"; |
+ else |
+ LOG(WARNING) << "Invalid reply from keyring proxy"; |
+ fd_watcher_->ShutDown(); |
+ fd_watcher_ = NULL; |
+ CancelAllRequests(); |
+} |
+ |
+} // namespace keyring_proxy |
Property changes on: chrome/browser/password_manager/keyring_proxy/keyring_proxy_client.cc |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |