| 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/forms/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
 | 
| +  // 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();
 | 
| +  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];
 | 
| +  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 (;;) {
 | 
| +    // |next_request_id_| is actually the most recently used request ID, because
 | 
| +    // 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();
 | 
| +       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)
 | 
| +    message += request[i] + "\n";
 | 
| +  message += "\n";
 | 
| +  size_t written = write(fd_watcher_->fd(), message.data(), message.size());
 | 
| +  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;
 | 
| +  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
 | 
| 
 | 
| 
 |