diff options
author | nick@chromium.org <nick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-10 06:05:27 +0000 |
---|---|---|
committer | nick@chromium.org <nick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-10 06:05:27 +0000 |
commit | 5852edc1b6eab234b9e048c41dd0d664ae7fc747 (patch) | |
tree | 9e5d8eb4833b76cdb11e66fc3607689e0f5e0122 /chrome/browser/sync/notifier/communicator | |
parent | f6059e37f8b8ac335ce18a189a13e702974a1c7e (diff) | |
download | chromium_src-5852edc1b6eab234b9e048c41dd0d664ae7fc747.zip chromium_src-5852edc1b6eab234b9e048c41dd0d664ae7fc747.tar.gz chromium_src-5852edc1b6eab234b9e048c41dd0d664ae7fc747.tar.bz2 |
Initial commit of sync engine code to browser/sync.
The code is not built on any platform yet. That will arrive
as a subsequent checkin.
This is an implementation of the interface exposed earlier
through syncapi.h. It is the client side of a sync
protocol that lets users sync their browser data
(currently, just bookmarks) with their Google Account.
Table of contents:
browser/sync/
protocol - The protocol definition, and
other definitions necessary to connect to
the service.
syncable/ - defines a data model for syncable objects,
and provides a sqlite-based backing store
for this model.
engine/ - includes the core sync logic, including commiting
changes to the server, downloading changes from
the server, resolving conflicts, other parts of
the sync algorithm.
engine/net - parts of the sync engine focused on the
business of talking to the server. Some of
this is binds a generic "server connection"
interface to a concrete implementation
provided by Chromium.
notifier - the part of the syncer focused on the business
of sending and receiving xmpp notifications.
Notifications are used instead of polling to
achieve very low latency change propagation.
util - not necessarily sync specific utility code. Much
of this is scaffolding which should either be
replaced by, or merged with, the utility code
in base/.
BUG=none
TEST=this code includes its own suite of unit tests.
Review URL: http://codereview.chromium.org/194065
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@25850 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/sync/notifier/communicator')
33 files changed, 4526 insertions, 0 deletions
diff --git a/chrome/browser/sync/notifier/communicator/auth_task.cc b/chrome/browser/sync/notifier/communicator/auth_task.cc new file mode 100644 index 0000000..11eba2d --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/auth_task.cc @@ -0,0 +1,69 @@ +// Copyright (c) 2009 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/sync/notifier/communicator/auth_task.h" + +#include "chrome/browser/sync/notifier/gaia_auth/gaiaauth.h" +#include "chrome/browser/sync/notifier/communicator/login.h" +#include "chrome/browser/sync/notifier/communicator/login_settings.h" +#include "chrome/browser/sync/notifier/communicator/product_info.h" +#include "talk/base/common.h" +#include "talk/base/urlencode.h" +#include "talk/xmpp/xmppclient.h" + +namespace notifier { +const char kTalkGadgetAuthPath[] = "/auth"; + +AuthTask::AuthTask(talk_base::Task* parent, Login* login, const char* url) + : talk_base::Task(parent), + login_(login), + url_(url), + use_gaia_redirect_(true) { + ASSERT(login && !url_.empty()); +} + +int AuthTask::ProcessStart() { + auth_.reset(new buzz::GaiaAuth(GetUserAgentString(), + GetProductSignature())); + auth_->SignalAuthDone.connect(this, &AuthTask::OnAuthDone); + auth_->StartTokenAuth(login_->xmpp_client()->jid().BareJid(), + login_->login_settings().user_settings().pass(), + use_gaia_redirect_ ? "gaia" : service_); + return STATE_RESPONSE; +} + +int AuthTask::ProcessResponse() { + ASSERT(auth_.get()); + if (!auth_->IsAuthDone()) { + return STATE_BLOCKED; + } + if (!auth_->IsAuthorized()) { + SignalAuthError(!auth_->HadError()); + return STATE_ERROR; + } + + std::string uber_url; + if (use_gaia_redirect_) { + uber_url = auth_->CreateAuthenticatedUrl(url_, service_); + } else { + uber_url = redir_auth_prefix_ + auth_->GetAuthCookie(); + uber_url += redir_continue_; + uber_url += UrlEncodeString(url_); + } + + if (uber_url == "") { + SignalAuthError(true); + return STATE_ERROR; + } + + SignalAuthDone(uber_url); + return STATE_DONE; +} + + +void AuthTask::OnAuthDone() { + Wake(); +} + +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/auth_task.h b/chrome/browser/sync/notifier/communicator/auth_task.h new file mode 100644 index 0000000..b5141f8 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/auth_task.h @@ -0,0 +1,77 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_AUTH_TASK_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_AUTH_TASK_H_ + +#include <string> +#include "talk/base/scoped_ptr.h" +#include "talk/base/sigslot.h" +#include "talk/base/task.h" + +namespace buzz { +class GaiaAuth; +} + +namespace notifier { +class Login; + +// Create an authenticated talk url from an unauthenticated url +class AuthTask : public talk_base::Task, public sigslot::has_slots<> { + public: + AuthTask(talk_base::Task* parent, Login* login, const char* url); + + // An abort method which doesn't take any parameters. + // (talk_base::Task::Abort() has a default parameter.) + // + // The primary purpose of this method is to allow a + // signal to be hooked up to abort this task. + void Abort() { + talk_base::Task::Abort(); + } + + void set_service(const char* service) { + service_ = service; + } + + void set_use_gaia_redirect(bool use_gaia_redirect) { + use_gaia_redirect_ = use_gaia_redirect; + } + + void set_redir_auth_prefix(const char* redir_auth_prefix) { + redir_auth_prefix_ = redir_auth_prefix; + } + + void set_redir_continue(const char* redir_continue) { + redir_continue_ = redir_continue; + } + + sigslot::signal1<const std::string&> SignalAuthDone; + sigslot::signal1<bool> SignalAuthError; + + protected: + virtual int ProcessStart(); + virtual int ProcessResponse(); + + private: + void OnAuthDone(); + + scoped_ptr<buzz::GaiaAuth> auth_; + Login* login_; + std::string service_; + std::string url_; + + // the following members are used for cases where we don't want to + // redirect through gaia, but rather via the end-site's mechanism + // (We need this for orkut) + bool use_gaia_redirect_; + std::string redir_auth_prefix_; + std::string redir_continue_; + + DISALLOW_COPY_AND_ASSIGN(AuthTask); +}; + +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_AUTH_TASK_H_ diff --git a/chrome/browser/sync/notifier/communicator/auto_reconnect.cc b/chrome/browser/sync/notifier/communicator/auto_reconnect.cc new file mode 100644 index 0000000..eadfe46 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/auto_reconnect.cc @@ -0,0 +1,155 @@ +// Copyright (c) 2009 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/sync/notifier/communicator/auto_reconnect.h" + +#include "chrome/browser/sync/notifier/base/network_status_detector_task.h" +#include "chrome/browser/sync/notifier/base/time.h" +#include "chrome/browser/sync/notifier/base/timer.h" +#include "talk/base/common.h" + +namespace notifier { +const int kResetReconnectInfoDelaySec = 2; + +AutoReconnect::AutoReconnect(talk_base::Task* parent, + NetworkStatusDetectorTask* network_status) + : reconnect_interval_ns_(0), + reconnect_timer_(NULL), + delayed_reset_timer_(NULL), + parent_(parent), + is_idle_(false) { + SetupReconnectInterval(); + if (network_status) { + network_status->SignalNetworkStateDetected.connect( + this, &AutoReconnect::OnNetworkStateDetected); + } +} + +void AutoReconnect::OnNetworkStateDetected(bool was_alive, bool is_alive) { + if (is_retrying() && !was_alive && is_alive) { + // Reconnect in 1 to 9 seconds (vary the time a little to try to avoid + // spikey behavior on network hiccups). + StartReconnectTimerWithInterval((rand() % 9 + 1) * kSecsTo100ns); + } +} + +int AutoReconnect::seconds_until() const { + if (!is_retrying() || !reconnect_timer_->get_timeout_time()) { + return 0; + } + int64 time_until_100ns = + reconnect_timer_->get_timeout_time() - GetCurrent100NSTime(); + if (time_until_100ns < 0) { + return 0; + } + + // Do a ceiling on the value (to avoid returning before its time) + return (time_until_100ns + kSecsTo100ns - 1) / kSecsTo100ns; +} + +void AutoReconnect::StartReconnectTimer() { + StartReconnectTimerWithInterval(reconnect_interval_ns_); +} + +void AutoReconnect::StartReconnectTimerWithInterval(time64 interval_ns) { + // Don't call StopReconnectTimer because we don't + // want other classes to detect that the intermediate state of + // the timer being stopped. (We're avoiding the call to SignalTimerStartStop + // while reconnect_timer_ is NULL.) + if (reconnect_timer_) { + reconnect_timer_->Abort(); + reconnect_timer_ = NULL; + } + reconnect_timer_ = new Timer(parent_, + static_cast<int>(interval_ns / kSecsTo100ns), + false); // repeat + reconnect_timer_->SignalTimeout.connect(this, + &AutoReconnect::DoReconnect); + SignalTimerStartStop(); +} + +void AutoReconnect::DoReconnect() { + reconnect_timer_ = NULL; + + // if timed out again, double autoreconnect time up to 30 minutes + reconnect_interval_ns_ *= 2; + if (reconnect_interval_ns_ > 30 * kMinsTo100ns) { + reconnect_interval_ns_ = 30 * kMinsTo100ns; + } + SignalStartConnection(); +} + +void AutoReconnect::StopReconnectTimer() { + if (reconnect_timer_) { + reconnect_timer_->Abort(); + reconnect_timer_ = NULL; + SignalTimerStartStop(); + } +} + +void AutoReconnect::StopDelayedResetTimer() { + if (delayed_reset_timer_) { + delayed_reset_timer_->Abort(); + delayed_reset_timer_ = NULL; + } +} + +void AutoReconnect::ResetState() { + StopDelayedResetTimer(); + StopReconnectTimer(); + SetupReconnectInterval(); +} + +void AutoReconnect::SetupReconnectInterval() { + if (is_idle_) { + // If we were idle, start the timer over again (120 - 360 seconds). + reconnect_interval_ns_ = (rand() % 240 + 120) * kSecsTo100ns; + } else { + // If we weren't idle, try the connection 5 - 25 seconds later. + reconnect_interval_ns_ = (rand() % 20 + 5) * kSecsTo100ns; + } +} + +void AutoReconnect::OnPowerSuspend(bool suspended) { + if (suspended) { + // When the computer comes back on, ensure that the reconnect + // happens quickly (5 - 25 seconds). + reconnect_interval_ns_ = (rand() % 20 + 5) * kSecsTo100ns; + } +} + +void AutoReconnect::OnClientStateChange(Login::ConnectionState state) { + // On any state change, stop the reset timer. + StopDelayedResetTimer(); + switch (state) { + case Login::STATE_RETRYING: + // do nothing + break; + + case Login::STATE_CLOSED: + // When the user has been logged out and no auto-reconnect + // is happening, then the autoreconnect intervals should be + // reset. + ResetState(); + break; + + case Login::STATE_OPENING: + StopReconnectTimer(); + break; + + case Login::STATE_OPENED: + // Reset autoreconnect timeout sequence after being connected + // for a bit of time. This helps in the case that we are + // connecting briefly and then getting disconnect like when + // an account hits an abuse limit. + StopReconnectTimer(); + delayed_reset_timer_ = new Timer(parent_, + kResetReconnectInfoDelaySec, + false); // repeat + delayed_reset_timer_->SignalTimeout.connect(this, + &AutoReconnect::ResetState); + break; + } +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/auto_reconnect.h b/chrome/browser/sync/notifier/communicator/auto_reconnect.h new file mode 100644 index 0000000..f4ee4ec --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/auto_reconnect.h @@ -0,0 +1,71 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_AUTO_RECONNECT_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_AUTO_RECONNECT_H_ +#include <string> + +#include "chrome/browser/sync/notifier/base/time.h" +#include "chrome/browser/sync/notifier/communicator/login.h" +#include "talk/base/sigslot.h" + +namespace talk_base { +class Task; +} + +namespace notifier { +class NetworkStatusDetectorTask; +class Timer; + +class AutoReconnect : public sigslot::has_slots<> { + public: + AutoReconnect(talk_base::Task* parent, + NetworkStatusDetectorTask* network_status); + void StartReconnectTimer(); + void StopReconnectTimer(); + void OnClientStateChange(Login::ConnectionState state); + + // Callback when power is suspended + void OnPowerSuspend(bool suspended); + + void set_idle(bool idle) { + is_idle_ = idle; + } + + // Returns true if the auto-retry is to be done (pending a countdown) + bool is_retrying() const { + return reconnect_timer_ != NULL; + } + + int seconds_until() const; + sigslot::signal0<> SignalTimerStartStop; + sigslot::signal0<> SignalStartConnection; + private: + void StartReconnectTimerWithInterval(time64 interval_ns); + void DoReconnect(); + void ResetState(); + void SetupReconnectInterval(); + void StopDelayedResetTimer(); + + void OnNetworkStateDetected(bool was_alive, bool is_alive); + + time64 reconnect_interval_ns_; + Timer* reconnect_timer_; + Timer* delayed_reset_timer_; + talk_base::Task* parent_; + + bool is_idle_; + DISALLOW_COPY_AND_ASSIGN(AutoReconnect); +}; + +// Wait 2 seconds until after we actually connect to +// reset reconnect related items. +// +// The reason for this delay is to avoid the situation in which buzz +// is trying to block the client due to abuse and the client responses +// by going into rapid reconnect mode, which makes the problem more severe. +extern const int kResetReconnectInfoDelaySec; + +} // namespace notifier +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_AUTO_RECONNECT_H_ diff --git a/chrome/browser/sync/notifier/communicator/connection_options.cc b/chrome/browser/sync/notifier/communicator/connection_options.cc new file mode 100644 index 0000000..2d49bb6 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/connection_options.cc @@ -0,0 +1,16 @@ +// Copyright (c) 2009 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/sync/notifier/communicator/connection_options.h" + +namespace notifier { + +ConnectionOptions::ConnectionOptions() + : autodetect_proxy_(true), + auto_reconnect_(true), + proxy_port_(0), + use_proxy_auth_(0), + allow_unverified_certs_(false) { +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/connection_options.h b/chrome/browser/sync/notifier/communicator/connection_options.h new file mode 100644 index 0000000..6b559f0 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/connection_options.h @@ -0,0 +1,55 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_CONNECTION_OPTIONS_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_CONNECTION_OPTIONS_H_ + +#include <string> + +#include "talk/base/cryptstring.h" +#include "talk/base/helpers.h" + +namespace notifier { + +class ConnectionOptions { + public: + ConnectionOptions(); + + bool autodetect_proxy() const { return autodetect_proxy_; } + bool auto_reconnect() const { return auto_reconnect_; } + const std::string& proxy_host() const { return proxy_host_; } + int proxy_port() const { return proxy_port_; } + bool use_proxy_auth() const { return use_proxy_auth_; } + const std::string& auth_user() const { return auth_user_; } + const talk_base::CryptString& auth_pass() const { return auth_pass_; } + bool allow_unverified_certs() const { return allow_unverified_certs_; } + + void set_autodetect_proxy(bool f) { autodetect_proxy_ = f; } + void set_auto_reconnect(bool f) { auto_reconnect_ = f; } + void set_proxy_host(const std::string& val) { proxy_host_ = val; } + void set_proxy_port(int val) { proxy_port_ = val; } + void set_use_proxy_auth(bool f) { use_proxy_auth_ = f; } + void set_auth_user(const std::string& val) { auth_user_ = val; } + void set_auth_pass(const talk_base::CryptString& val) { auth_pass_ = val; } + + // Setting this to true opens a security hole, so it is + // *highly* recommended that you don't do this. + void set_allow_unverified_certs(bool allow_unverified_certs) { + allow_unverified_certs_ = allow_unverified_certs; + } + + private: + bool autodetect_proxy_; + bool auto_reconnect_; + std::string proxy_host_; + int proxy_port_; + bool use_proxy_auth_; + std::string auth_user_; + talk_base::CryptString auth_pass_; + bool allow_unverified_certs_; + // allow the copy constructor and operator= +}; +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_CONNECTION_OPTIONS_H_ diff --git a/chrome/browser/sync/notifier/communicator/connection_settings.cc b/chrome/browser/sync/notifier/communicator/connection_settings.cc new file mode 100644 index 0000000..320a396 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/connection_settings.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2009 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 <deque> +#include <string> +#include <vector> + +#include "chrome/browser/sync/notifier/communicator/connection_settings.h" +#include "talk/base/helpers.h" +#include "talk/xmpp/xmppclientsettings.h" + +namespace notifier { + +class RandomGenerator { + public: + int operator()(int ceiling) { + return static_cast<int>(cricket::CreateRandomId() % ceiling); + } +}; + +void ConnectionSettings::FillXmppClientSettings( + buzz::XmppClientSettings* xcs) const { + assert(xcs); + xcs->set_protocol(protocol_); + xcs->set_server(server_); + xcs->set_proxy(proxy_.type); + if (proxy_.type != talk_base::PROXY_NONE) { + xcs->set_proxy_host(proxy_.address.IPAsString()); + xcs->set_proxy_port(proxy_.address.port()); + } + if ((proxy_.type != talk_base::PROXY_NONE) && !proxy_.username.empty()) { + xcs->set_use_proxy_auth(true); + xcs->set_proxy_user(proxy_.username); + xcs->set_proxy_pass(proxy_.password); + } else { + xcs->set_use_proxy_auth(false); + } +} + +void ConnectionSettingsList::AddPermutations(const std::string& hostname, + const std::vector<uint32>& iplist, + int16 port, + bool special_port_magic, + bool proxy_only) { + // randomize the list. This ensures the iplist isn't always + // evaluated in the order returned by DNS + std::vector<uint32> iplist_random = iplist; + RandomGenerator rg; + std::random_shuffle(iplist_random.begin(), iplist_random.end(), rg); + + // Put generated addresses in a new deque, then append on the list_, since + // there are order dependencies and AddPermutations() may be called more + // than once. + std::deque<ConnectionSettings> list_temp; + + // Permute addresses for this server. In some cases we haven't resolved the + // to ip addresses. + talk_base::SocketAddress server(hostname, port, false); + if (iplist_random.empty()) { + // We couldn't pre-resolve the hostname, so let's hope it will resolve + // further down the pipeline (by a proxy, for example). + PermuteForAddress(server, special_port_magic, proxy_only, &list_temp); + } else { + // Generate a set of possibilities for each server address. + // Don't do permute duplicates. + for (size_t index = 0; index < iplist_random.size(); ++index) { + if (std::find(iplist_seen_.begin(), iplist_seen_.end(), + iplist_random[index]) != iplist_seen_.end()) { + continue; + } + iplist_seen_.push_back(iplist_random[index]); + server.SetResolvedIP(iplist_random[index]); + PermuteForAddress(server, special_port_magic, proxy_only, &list_temp); + } + } + + // Add this list to the instance list + while (list_temp.size() != 0) { + list_.push_back(list_temp[0]); + list_temp.pop_front(); + } +} + + +void ConnectionSettingsList::PermuteForAddress( + const talk_base::SocketAddress& server, + bool special_port_magic, + bool proxy_only, + std::deque<ConnectionSettings>* list_temp) { + assert(list_temp); + *(template_.mutable_server()) = server; + + // Use all of the original settings + list_temp->push_back(template_); + + // Try alternate port + if (special_port_magic) { + ConnectionSettings settings(template_); + settings.set_protocol(cricket::PROTO_SSLTCP); + settings.mutable_server()->SetPort(443); + // HTTPS proxies usually require port 443, so try it first + if ((template_.proxy().type == talk_base::PROXY_HTTPS) || + (template_.proxy().type == talk_base::PROXY_UNKNOWN)) { + list_temp->push_front(settings); + } else { + list_temp->push_back(settings); + } + } + + if (!proxy_only) { + // Try without the proxy + if (template_.proxy().type != talk_base::PROXY_NONE) { + ConnectionSettings settings(template_); + settings.mutable_proxy()->type = talk_base::PROXY_NONE; + list_temp->push_back(settings); + + if (special_port_magic) { + settings.set_protocol(cricket::PROTO_SSLTCP); + settings.mutable_server()->SetPort(443); + list_temp->push_back(settings); + } + } + } +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/connection_settings.h b/chrome/browser/sync/notifier/communicator/connection_settings.h new file mode 100644 index 0000000..d83b1fc --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/connection_settings.h @@ -0,0 +1,78 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_CONNECTION_SETTINGS_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_CONNECTION_SETTINGS_H_ +#include <deque> +#include <string> +#include <vector> + +#include "talk/p2p/base/port.h" + +namespace buzz { + class XmppClientSettings; +} + +namespace notifier { + +class ConnectionSettings { + public: + ConnectionSettings() : protocol_(cricket::PROTO_TCP) {} + + cricket::ProtocolType protocol() { return protocol_; } + const talk_base::SocketAddress& server() const { return server_; } + const talk_base::ProxyInfo& proxy() const { return proxy_; } + + void set_protocol(cricket::ProtocolType protocol) { protocol_ = protocol; } + talk_base::SocketAddress* mutable_server() { return &server_; } + talk_base::ProxyInfo* mutable_proxy() { return &proxy_; } + + void FillXmppClientSettings(buzz::XmppClientSettings* xcs) const; + + private: + cricket::ProtocolType protocol_; // PROTO_TCP, PROTO_SSLTCP, etc. + talk_base::SocketAddress server_; // Server + talk_base::ProxyInfo proxy_; // Proxy info + // need copy constructor due to use in stl deque +}; + +class ConnectionSettingsList { + public: + ConnectionSettingsList() {} + + void SetProxy(const talk_base::ProxyInfo& proxy) { + *(template_.mutable_proxy()) = proxy; + } + + const talk_base::ProxyInfo& proxy() const { + return template_.proxy(); + } + + int GetCount() { return list_.size(); } + ConnectionSettings* GetSettings(size_t index) { return &list_[index]; } + + void ClearPermutations() { + list_.clear(); + iplist_seen_.clear(); + } + + void AddPermutations(const std::string& hostname, + const std::vector<uint32>& iplist, + int16 port, + bool special_port_magic, + bool proxy_only); + private: + void PermuteForAddress(const talk_base::SocketAddress& server, + bool special_port_magic, + bool proxy_only, + std::deque<ConnectionSettings>* list_temp); + + ConnectionSettings template_; + std::deque<ConnectionSettings> list_; + std::vector<uint32> iplist_seen_; + DISALLOW_COPY_AND_ASSIGN(ConnectionSettingsList); +}; +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_CONNECTION_SETTINGS_H_ diff --git a/chrome/browser/sync/notifier/communicator/const_communicator.h b/chrome/browser/sync/notifier/communicator/const_communicator.h new file mode 100644 index 0000000..79bb92e --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/const_communicator.h @@ -0,0 +1,11 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_CONST_COMMUNICATOR_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_CONST_COMMUNICATOR_H_ +namespace notifier { +// The default port for jabber/xmpp communications +const int kDefaultXmppPort = 5222; +} // namespace notifier +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_CONST_COMMUNICATOR_H_ diff --git a/chrome/browser/sync/notifier/communicator/login.cc b/chrome/browser/sync/notifier/communicator/login.cc new file mode 100644 index 0000000..5614dba --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/login.cc @@ -0,0 +1,361 @@ +// Copyright (c) 2009 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 <string> + +#include "chrome/browser/sync/notifier/communicator/login.h" + +#include "chrome/browser/sync/notifier/base/network_status_detector_task.h" +#include "chrome/browser/sync/notifier/base/time.h" +#include "chrome/browser/sync/notifier/base/timer.h" +#include "chrome/browser/sync/notifier/communicator/auto_reconnect.h" +#include "chrome/browser/sync/notifier/communicator/connection_options.h" +#include "chrome/browser/sync/notifier/communicator/login_settings.h" +#include "chrome/browser/sync/notifier/communicator/product_info.h" +#include "chrome/browser/sync/notifier/communicator/single_login_attempt.h" +#include "talk/base/common.h" +#include "talk/base/firewallsocketserver.h" +#include "talk/base/logging.h" +#include "talk/base/taskrunner.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/asyncsocket.h" +#include "talk/xmpp/prexmppauth.h" +#include "talk/xmpp/xmppclient.h" +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/xmpp/xmppengine.h" + +namespace notifier { + +// redirect valid for 5 minutes +static const time64 kRedirectTimeoutNs = 5 * kMinsTo100ns; + +// Disconnect if network stays down for more than 10 seconds. +static const int kDisconnectionDelaySecs = 10; + +Login::Login(talk_base::Task* parent, + const buzz::XmppClientSettings& user_settings, + const ConnectionOptions& options, + std::string lang, + ServerInformation* server_list, + int server_count, + NetworkStatusDetectorTask* network_status, + talk_base::FirewallManager* firewall, + bool no_gaia_auth, + bool proxy_only, + bool previous_login_successful) + : login_settings_(new LoginSettings(user_settings, + options, + lang, + server_list, + server_count, + firewall, + no_gaia_auth, + proxy_only)), + single_attempt_(NULL), + successful_connection_(previous_login_successful), + parent_(parent), + state_(STATE_OPENING), + redirect_time_ns_(0), + redirect_port_(0), + unexpected_disconnect_occurred_(false), + reset_unexpected_timer_(NULL), + google_host_(user_settings.host()), + google_user_(user_settings.user()), + disconnect_timer_(NULL) { + if (!network_status) { + network_status = NetworkStatusDetectorTask::Create(parent_); + if (network_status) { + // On linux we don't have an implementation of NetworkStatusDetectorTask. + network_status->Start(); + } + } + network_status->SignalNetworkStateDetected.connect( + this, &Login::OnNetworkStateDetected); + auto_reconnect_.reset(new AutoReconnect(parent_, network_status)); + auto_reconnect_->SignalStartConnection.connect(this, + &Login::StartConnection); + auto_reconnect_->SignalTimerStartStop.connect( + this, + &Login::OnAutoReconnectTimerChange); + SignalClientStateChange.connect(auto_reconnect_.get(), + &AutoReconnect::OnClientStateChange); + SignalIdleChange.connect(auto_reconnect_.get(), + &AutoReconnect::set_idle); + SignalPowerSuspended.connect(auto_reconnect_.get(), + &AutoReconnect::OnPowerSuspend); +} + +// defined so that the destructors are executed here (and +// the corresponding classes don't need to be included in +// the header file) +Login::~Login() { + if (single_attempt_) { + single_attempt_->Abort(); + single_attempt_ = NULL; + } +} + +void Login::StartConnection() { + // If there is a server redirect, use it. + if (GetCurrent100NSTime() < redirect_time_ns_ + kRedirectTimeoutNs) { + // Override server/port with redirect values + talk_base::SocketAddress server_override; + server_override.SetIP(redirect_server_, false); + ASSERT(redirect_port_ != 0); + server_override.SetPort(redirect_port_); + login_settings_->set_server_override(server_override); + } else { + login_settings_->clear_server_override(); + } + + if (single_attempt_) { + single_attempt_->Abort(); + single_attempt_ = NULL; + } + single_attempt_ = new SingleLoginAttempt(parent_, + login_settings_.get(), + successful_connection_); + + // Do the signaling hook-ups. + single_attempt_->SignalLoginFailure.connect(this, &Login::OnLoginFailure); + single_attempt_->SignalRedirect.connect(this, &Login::OnRedirect); + single_attempt_->SignalClientStateChange.connect( + this, + &Login::OnClientStateChange) ; + single_attempt_->SignalUnexpectedDisconnect.connect( + this, + &Login::OnUnexpectedDisconnect); + single_attempt_->SignalLogoff.connect( + this, + &Login::OnLogoff); + single_attempt_->SignalNeedAutoReconnect.connect( + this, + &Login::DoAutoReconnect); + SignalLogInput.repeat(single_attempt_->SignalLogInput); + SignalLogOutput.repeat(single_attempt_->SignalLogOutput); + + single_attempt_->Start(); +} + +const std::string& Login::google_host() const { + return google_host_; +} + +const std::string& Login::google_user() const { + return google_user_; +} + +const talk_base::ProxyInfo& Login::proxy() const { + return proxy_info_; +} + +void Login::OnLoginFailure(const LoginFailure& failure) { + auto_reconnect_->StopReconnectTimer(); + HandleClientStateChange(STATE_CLOSED); + SignalLoginFailure(failure); +} + +void Login::OnLogoff() { + HandleClientStateChange(STATE_CLOSED); +} + +void Login::OnClientStateChange(buzz::XmppEngine::State state) { + ConnectionState new_state = STATE_CLOSED; + + switch (state) { + case buzz::XmppEngine::STATE_NONE: + case buzz::XmppEngine::STATE_CLOSED: + // Ignore the closed state (because + // we may be trying the next dns entry). + // + // But we go to this state for other + // signals when there is no retry happening. + new_state = state_; + break; + + case buzz::XmppEngine::STATE_START: + case buzz::XmppEngine::STATE_OPENING: + new_state = STATE_OPENING; + break; + + case buzz::XmppEngine::STATE_OPEN: + new_state = STATE_OPENED; + break; + + default: + ASSERT(false); + break; + } + HandleClientStateChange(new_state); +} + +void Login::HandleClientStateChange(ConnectionState new_state) { + // Do we need to transition between the retrying and closed states? + if (auto_reconnect_->is_retrying()) { + if (new_state == STATE_CLOSED) { + new_state = STATE_RETRYING; + } + } else { + if (new_state == STATE_RETRYING) { + new_state = STATE_CLOSED; + } + } + + if (new_state != state_) { + state_ = new_state; + if (reset_unexpected_timer_) { + reset_unexpected_timer_->Abort(); + reset_unexpected_timer_ = NULL; + } + + if (state_ == STATE_OPENED) { + successful_connection_ = true; + + google_host_ = single_attempt_->xmpp_client()->jid().domain(); + google_user_ = single_attempt_->xmpp_client()->jid().node(); + proxy_info_ = single_attempt_->proxy(); + + reset_unexpected_timer_ = new Timer(parent_, + kResetReconnectInfoDelaySec, + false); // repeat + reset_unexpected_timer_->SignalTimeout.connect( + this, + &Login::ResetUnexpectedDisconnect); + } + SignalClientStateChange(state_); + } +} + +void Login::OnAutoReconnectTimerChange() { + if (!single_attempt_ || !single_attempt_->xmpp_client()) { + HandleClientStateChange(STATE_CLOSED); + return; + } + OnClientStateChange(single_attempt_->xmpp_client()->GetState()); +} + +buzz::XmppClient* Login::xmpp_client() { + if (!single_attempt_) { + return NULL; + } + return single_attempt_->xmpp_client(); +} + +int Login::seconds_until_reconnect() const { + return auto_reconnect_->seconds_until(); +} + +void Login::UseNextConnection() { + if (!single_attempt_) { + // Just in case, there is an obscure case that causes + // this to get called when there is no single_attempt_. + return; + } + single_attempt_->UseNextConnection(); +} + +void Login::UseCurrentConnection() { + if (!single_attempt_) { + // Just in case, there is an obscure case that causes + // this to get called when there is no single_attempt_. + return; + } + single_attempt_->UseCurrentConnection(); +} + +void Login::OnRedirect(const std::string& redirect_server, int redirect_port) { + ASSERT(redirect_port_ != 0); + + redirect_time_ns_ = GetCurrent100NSTime(); + redirect_server_ = redirect_server; + redirect_port_ = redirect_port; + + // Drop the current connection, and start the login process again + StartConnection(); +} + +void Login::OnUnexpectedDisconnect() { + if (reset_unexpected_timer_) { + reset_unexpected_timer_->Abort(); + reset_unexpected_timer_ = NULL; + } + + // Start the login process again + if (unexpected_disconnect_occurred_) { + // If we already have received an unexpected disconnect recently, + // then our account may have be jailed due to abuse, so we shouldn't + // make the situation worse by trying really hard to reconnect. + // Instead, we'll do the autoreconnect route, which has exponential + // back-off. + DoAutoReconnect(); + return; + } + StartConnection(); + unexpected_disconnect_occurred_ = true; +} + +void Login::ResetUnexpectedDisconnect() { + reset_unexpected_timer_ = NULL; + unexpected_disconnect_occurred_ = false; +} + +void Login::DoAutoReconnect() { + bool allow_auto_reconnect = + login_settings_->connection_options().auto_reconnect(); + // Start the reconnect time before aborting the connection + // to ensure that AutoReconnect::is_retrying() is true, so + // that the Login doesn't transition to the CLOSED state + // (which would cause the reconnection timer to reset + // and not double). + if (allow_auto_reconnect) { + auto_reconnect_->StartReconnectTimer(); + } + + if (single_attempt_) { + single_attempt_->Abort(); + single_attempt_ = NULL; + } + + if (!allow_auto_reconnect) { + HandleClientStateChange(STATE_CLOSED); + return; + } +} + +void Login::OnNetworkStateDetected(bool was_alive, bool is_alive) { + if (was_alive && !is_alive) { + // Our network connection just went down. + // Setup a timer to disconnect. Don't disconnect immediately to avoid + // constant connection/disconnection due to flaky network interfaces. + ASSERT(disconnect_timer_ == NULL); + disconnect_timer_ = new Timer(parent_, kDisconnectionDelaySecs, false); + disconnect_timer_->SignalTimeout.connect(this, + &Login::OnDisconnectTimeout); + } else if (!was_alive && is_alive) { + // Our connection has come back up. If we have a disconnect timer going, + // abort it so we don't disconnect. + if (disconnect_timer_) { + disconnect_timer_->Abort(); + // It will free itself. + disconnect_timer_ = NULL; + } + } +} + +void Login::OnDisconnectTimeout() { + disconnect_timer_ = NULL; + + if (state_ != STATE_OPENED) { + return; + } + + if (single_attempt_) { + single_attempt_->Abort(); + single_attempt_ = NULL; + } + + StartConnection(); +} + +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/login.h b/chrome/browser/sync/notifier/communicator/login.h new file mode 100644 index 0000000..480f52b --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/login.h @@ -0,0 +1,155 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_LOGIN_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_LOGIN_H_ +#include <string> + +#include "chrome/browser/sync/notifier/base/time.h" +#include "chrome/browser/sync/notifier/gaia_auth/sigslotrepeater.h" +#include "talk/base/proxyinfo.h" +#include "talk/base/scoped_ptr.h" +#include "talk/base/sigslot.h" +#include "talk/xmpp/xmppengine.h" + +namespace buzz { +class CaptchaChallenge; +class XmppClient; +class XmppEngine; +class XmppClientSettings; +} // namespace buzz + +namespace talk_base { +class FirewallManager; +struct ProxyInfo; +class Task; +} // namespace talk_base + +namespace notifier { +class AutoReconnect; +class ConnectionOptions; +class LoginFailure; +class LoginSettings; +class NetworkStatusDetectorTask; +struct ServerInformation; +class SingleLoginAttempt; +class Timer; + +// Does the login, keeps it alive (with refreshing cookies +// and reattempting login when disconnected), figures out +// what actions to take on the various errors that may occur. +class Login : public sigslot::has_slots<> { + public: + // network_status and firewall may be NULL + Login(talk_base::Task* parent, + const buzz::XmppClientSettings& user_settings, + const ConnectionOptions& options, + std::string lang, + ServerInformation* server_list, + int server_count, + NetworkStatusDetectorTask* network_status, + talk_base::FirewallManager* firewall, + bool no_gaia_auth, + bool proxy_only, + bool previous_login_successful); + ~Login(); + + enum ConnectionState { + STATE_CLOSED, + // Same as the closed state but indicates that a countdown is happening + // for auto-retrying the connection. + STATE_RETRYING, + STATE_OPENING, + STATE_OPENED, + }; + + ConnectionState connection_state() const { + return state_; + } + + void StartConnection(); + void UseNextConnection(); + void UseCurrentConnection(); + buzz::XmppClient* xmpp_client(); + + // Start the auto-reconnect. It may not do the auto-reconnect + // if auto-reconnect is turned off. + void DoAutoReconnect(); + + const LoginSettings& login_settings() const { + return *(login_settings_.get()); + } + + // Returns the best guess at the host responsible for + // the account (which we use to determine if it is + // a dasher account or not). + // + // After login this may return a more accurate answer, + // which accounts for open sign-up accounts. + const std::string& google_host() const; + + // Analogous to google_host but for the user account. + // ("fred" in "fred@gmail.com") + const std::string& google_user() const; + + // Returns the proxy that is being used to connect (or + // the default proxy information if all attempted + // connections failed). + // + // Do not call until StartConnection has been called. + const talk_base::ProxyInfo& proxy() const; + + int seconds_until_reconnect() const; + + // SignalClientStateChange(ConnectionState new_state); + sigslot::signal1<ConnectionState> SignalClientStateChange; + + sigslot::signal1<const LoginFailure&> SignalLoginFailure; + sigslot::repeater2<const char*, int> SignalLogInput; + sigslot::repeater2<const char*, int> SignalLogOutput; + sigslot::repeater1<bool> SignalIdleChange; + + // The creator should hook this up to a signal that indicates when the power + // is being suspended. + sigslot::repeater1<bool> SignalPowerSuspended; + + private: + void OnRedirect(const std::string& redirect_server, int redirect_port); + void OnUnexpectedDisconnect(); + void OnClientStateChange(buzz::XmppEngine::State state); + void OnLoginFailure(const LoginFailure& failure); + void OnLogoff(); + void OnAutoReconnectTimerChange(); + + void HandleClientStateChange(ConnectionState new_state); + void ResetUnexpectedDisconnect(); + + void OnNetworkStateDetected(bool was_alive, bool is_alive); + void OnDisconnectTimeout(); + + scoped_ptr<LoginSettings> login_settings_; + scoped_ptr<AutoReconnect> auto_reconnect_; + SingleLoginAttempt* single_attempt_; + bool successful_connection_; + talk_base::Task* parent_; + + ConnectionState state_; + + // server redirect information + time64 redirect_time_ns_; + std::string redirect_server_; + int redirect_port_; + bool unexpected_disconnect_occurred_; + Timer* reset_unexpected_timer_; + std::string google_host_; + std::string google_user_; + talk_base::ProxyInfo proxy_info_; + + Timer* disconnect_timer_; + + DISALLOW_COPY_AND_ASSIGN(Login); +}; +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_LOGIN_H_ diff --git a/chrome/browser/sync/notifier/communicator/login_failure.cc b/chrome/browser/sync/notifier/communicator/login_failure.cc new file mode 100644 index 0000000..6f29d87 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/login_failure.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2009 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/sync/notifier/communicator/login_failure.h" + +#include "talk/xmpp/prexmppauth.h" + +namespace notifier { + +LoginFailure::LoginFailure(LoginError error) + : error_(error), + xmpp_error_(buzz::XmppEngine::ERROR_NONE), + subcode_(0) { +} + +LoginFailure::LoginFailure(LoginError error, + buzz::XmppEngine::Error xmpp_error, + int subcode) + : error_(error), + xmpp_error_(xmpp_error), + subcode_(subcode) { +} + +LoginFailure::LoginFailure(LoginError error, + buzz::XmppEngine::Error xmpp_error, + int subcode, + const buzz::CaptchaChallenge& captcha) + : error_(error), + xmpp_error_(xmpp_error), + subcode_(subcode), + captcha_(new buzz::CaptchaChallenge(captcha)) { +} + +buzz::XmppEngine::Error LoginFailure::xmpp_error() const { + ASSERT(error_ == XMPP_ERROR); + return xmpp_error_; +} + +const buzz::CaptchaChallenge& LoginFailure::captcha() const { + ASSERT(xmpp_error_ == buzz::XmppEngine::ERROR_UNAUTHORIZED || + xmpp_error_ == buzz::XmppEngine::ERROR_MISSING_USERNAME); + return *captcha_.get(); +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/login_failure.h b/chrome/browser/sync/notifier/communicator/login_failure.h new file mode 100644 index 0000000..cbddc00 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/login_failure.h @@ -0,0 +1,69 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_LOGIN_FAILURE_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_LOGIN_FAILURE_H_ + +#include "talk/base/common.h" +#include "talk/xmpp/xmppengine.h" + +namespace buzz { +class CaptchaChallenge; +} + +namespace notifier { + +class LoginFailure { + public: + enum LoginError { + // Check the xmpp_error for more information + XMPP_ERROR, + + // If the certificate has expired, it usually means that the + // computer's clock isn't set correctly. + CERTIFICATE_EXPIRED_ERROR, + + // Apparently, there is a proxy that needs authentication information. + PROXY_AUTHENTICATION_ERROR, + }; + + LoginFailure(LoginError error); + LoginFailure(LoginError error, + buzz::XmppEngine::Error xmpp_error, + int subcode); + LoginFailure(LoginError error, + buzz::XmppEngine::Error xmpp_error, + int subcode, + const buzz::CaptchaChallenge& captcha); + + // Used as the first level of error information + LoginError error() const { + return error_; + } + + // Returns the XmppEngine only. Valid if and only if error() == XMPP_ERROR + // + // Handler should mimic logic from PhoneWindow::ShowConnectionError + // (except that the DiagnoseConnectionError has already been done). + buzz::XmppEngine::Error xmpp_error() const; + + // Returns the captcha challenge. Valid if and only if + // xmpp_error is buzz::XmppEngine::ERROR_UNAUTHORIZED or + // buzz::XmppEngine::ERROR_MISSING_USERNAME + // + // See PhoneWindow::HandleConnectionPasswordError for how to handle this + // (after the if (..) { LoginAccountAndConnectionSetting(); ...} because + // that is done by SingleLoginAttempt. + const buzz::CaptchaChallenge& captcha() const; + + private: + LoginError error_; + buzz::XmppEngine::Error xmpp_error_; + int subcode_; + scoped_ptr<buzz::CaptchaChallenge> captcha_; + + DISALLOW_COPY_AND_ASSIGN(LoginFailure); +}; +} // namespace notifier +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_LOGIN_FAILURE_H_ diff --git a/chrome/browser/sync/notifier/communicator/login_settings.cc b/chrome/browser/sync/notifier/communicator/login_settings.cc new file mode 100644 index 0000000..2983dd8 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/login_settings.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2009 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 <string> + +#include "chrome/browser/sync/notifier/communicator/login_settings.h" + +#include "chrome/browser/sync/notifier/communicator/connection_options.h" +#include "chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h" +#include "talk/base/common.h" +#include "talk/base/socketaddress.h" +#include "talk/xmpp/xmppclientsettings.h" + +namespace notifier { + +LoginSettings::LoginSettings(const buzz::XmppClientSettings& user_settings, + const ConnectionOptions& options, + std::string lang, + ServerInformation* server_list, + int server_count, + talk_base::FirewallManager* firewall, + bool no_gaia_auth, + bool proxy_only) + : proxy_only_(proxy_only), + no_gaia_auth_(no_gaia_auth), + firewall_(firewall), + lang_(lang), + server_list_(new ServerInformation[server_count]), + server_count_(server_count), + user_settings_(new buzz::XmppClientSettings(user_settings)), + connection_options_(new ConnectionOptions(options)) { + // Note: firewall may be NULL + ASSERT(server_list != 0); + ASSERT(server_count > 0); + for (int i = 0; i < server_count_; ++i) { + server_list_[i] = server_list[i]; + } +} + +// defined so that the destructors are executed here (and +// the corresponding classes don't need to be included in +// the header file) +LoginSettings::~LoginSettings() { +} + +void LoginSettings::set_server_override( + const talk_base::SocketAddress& server) { + server_override_.reset(new ServerInformation()); + server_override_->server = server; + server_override_->special_port_magic = server_list_[0].special_port_magic; +} + +void LoginSettings::clear_server_override() { + server_override_.reset(); +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/login_settings.h b/chrome/browser/sync/notifier/communicator/login_settings.h new file mode 100644 index 0000000..3e9b971 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/login_settings.h @@ -0,0 +1,97 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_LOGIN_SETTINGS_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_LOGIN_SETTINGS_H_ +#include <string> + +#include "chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h" +#include "talk/base/scoped_ptr.h" + +namespace buzz { +class XmppClientSettings; +} + +namespace talk_base { +class FirewallManager; +class SocketAddress; +} + +namespace notifier { +class ConnectionOptions; +struct ServerInformation; + +class LoginSettings { + public: + LoginSettings(const buzz::XmppClientSettings& user_settings, + const ConnectionOptions& options, + std::string lang, + ServerInformation* server_list, + int server_count, + talk_base::FirewallManager* firewall, + bool no_gaia_auth, + bool proxy_only); + + ~LoginSettings(); + + // Note: firewall() may return NULL. + // + // Could be a const method, but it allows + // modification of part (FirewallManager) of its state. + talk_base::FirewallManager* firewall() { + return firewall_; + } + + bool no_gaia_auth() const { + return no_gaia_auth_; + } + + bool proxy_only() const { + return proxy_only_; + } + + const std::string& lang() const { + return lang_; + } + + const ServerInformation* server_list() const { + return server_override_.get() ? server_override_.get() : server_list_.get(); + } + + int server_count() const { + return server_override_.get() ? 1 : server_count_; + } + + const buzz::XmppClientSettings& user_settings() const { + return *user_settings_.get(); + } + + buzz::XmppClientSettings* modifiable_user_settings() { + return user_settings_.get(); + } + + const ConnectionOptions& connection_options() const { + return *connection_options_.get(); + } + + void set_server_override(const talk_base::SocketAddress& server); + void clear_server_override(); + + private: + bool proxy_only_; + bool no_gaia_auth_; + talk_base::FirewallManager* firewall_; + std::string lang_; + + talk_base::scoped_array<ServerInformation> server_list_; + int server_count_; + // Used to handle redirects + scoped_ptr<ServerInformation> server_override_; + + scoped_ptr<buzz::XmppClientSettings> user_settings_; + scoped_ptr<ConnectionOptions> connection_options_; + DISALLOW_COPY_AND_ASSIGN(LoginSettings); +}; +} // namespace notifier +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_LOGIN_SETTINGS_H_ diff --git a/chrome/browser/sync/notifier/communicator/mailbox.cc b/chrome/browser/sync/notifier/communicator/mailbox.cc new file mode 100644 index 0000000..22b6690 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/mailbox.cc @@ -0,0 +1,682 @@ +// Copyright (c) 2009 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/sync/notifier/communicator/mailbox.h" + +#include <assert.h> +#include <stdlib.h> + +#include <stack> +#include <vector> + +#include "chrome/browser/sync/notifier/base/string.h" +#include "chrome/browser/sync/notifier/base/utils.h" +#include "chrome/browser/sync/notifier/communicator/xml_parse_helpers.h" +#include "talk/base/basictypes.h" +#include "talk/base/common.h" +#include "talk/base/stringutils.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/constants.h" + +namespace notifier { + +// Labels are a list of strings seperated by a '|' character. +// The '|' character is escaped with a backslash ('\\') and the +// backslash is also escaped with a backslash. +static void ParseLabelSet(const std::string& text, + MessageThread::StringSet* labels) { + const char* input_cur = text.c_str(); + const char* input_end = input_cur + text.size(); + char* result = new char[text.size() + 1]; + char* next_write = result; + + while(input_cur < input_end) { + if (*input_cur == '|') { + if (next_write != result) { + *next_write = '\0'; + labels->insert(std::string(result)); + next_write = result; + } + input_cur++; + continue; + } + + if (*input_cur == '\\') { + // skip a character in the input and break if we are at the end + input_cur++; + if (input_cur >= input_end) + break; + } + *next_write = *input_cur; + next_write++; + input_cur++; + } + + if (next_write != result) { + *next_write = '\0'; + labels->insert(std::string(result)); + } + + delete [] result; +} + +// ----------------------------------------------------------------------------- + +std::string MailAddress::safe_name() const { + if (!name().empty()) { + return name(); + } + + if (!address().empty()) { + size_t at = address().find('@'); + if (at == std::string::npos) { + return address(); + } + + if (at != 0) { + return address().substr(0, at); + } + } + + return std::string("(unknown)"); +} + +// ----------------------------------------------------------------------------- +MessageThread::~MessageThread() { + Clear(); +} + +void MessageThread::Clear() { + delete labels_; + labels_ = NULL; + + delete senders_; + senders_ = NULL; +} + +MessageThread& MessageThread::operator=(const MessageThread& r) { + if (&r != this) { + Clear(); + // Copy everything + r.AssertValid(); + thread_id_ = r.thread_id_; + date64_ = r.date64_; + message_count_ = r.message_count_; + personal_level_ = r.personal_level_; + subject_ = r.subject_; + snippet_ = r.snippet_; + + if (r.labels_) + labels_ = new StringSet(*r.labels_); + else + labels_ = new StringSet; + if (r.senders_) + senders_ = new MailSenderList(*r.senders_); + else + senders_ = new MailSenderList; + } + AssertValid(); + return *this; +} + +MessageThread* MessageThread::CreateFromXML( + const buzz::XmlElement* src) { + MessageThread* info = new MessageThread(); + if (!info || !info->InitFromXml(src)) { + delete info; + return NULL; + } + return info; +} + +// Init from a chunk of XML +bool MessageThread::InitFromXml(const buzz::XmlElement* src) { + labels_ = new StringSet; + senders_ = new MailSenderList; + + if (src->Name() != buzz::kQnMailThreadInfo) + return false; + + if (!ParseInt64Attr(src, buzz::kQnMailTid, &thread_id_)) + return false; + if (!ParseInt64Attr(src, buzz::kQnMailDate, &date64_)) + return false; + if (!ParseIntAttr(src, buzz::kQnMailMessages, &message_count_)) + return false; + if (!ParseIntAttr(src, buzz::kQnMailParticipation, &personal_level_)) + return false; + + const buzz::XmlElement* senders = src->FirstNamed(buzz::kQnMailSenders); + if (!senders) + return false; + for (const buzz::XmlElement* child = senders->FirstElement(); + child != NULL; + child = child->NextElement()) { + if (child->Name() != buzz::kQnMailSender) + continue; + std::string address; + if (!ParseStringAttr(child, buzz::kQnMailAddress, &address)) + continue; + std::string name; + ParseStringAttr(child, buzz::kQnMailName, &name); + bool originator = false; + ParseBoolAttr(child, buzz::kQnMailOriginator, &originator); + bool unread = false; + ParseBoolAttr(child, buzz::kQnMailUnread, &unread); + + senders_->push_back(MailSender(name, address, unread, originator)); + } + + const buzz::XmlElement* labels = src->FirstNamed(buzz::kQnMailLabels); + if (!labels) + return false; + ParseLabelSet(labels->BodyText(), labels_); + + const buzz::XmlElement* subject = src->FirstNamed(buzz::kQnMailSubject); + if (!subject) + return false; + subject_ = subject->BodyText(); + + const buzz::XmlElement* snippet = src->FirstNamed(buzz::kQnMailSnippet); + if (!snippet) + return false; + snippet_ = snippet->BodyText(); + + AssertValid(); + return true; +} + +bool MessageThread::starred() const { + return (labels_->find("^t") != labels_->end()); +} + +bool MessageThread::unread() const { + return (labels_->find("^u") != labels_->end()); +} + +#ifdef _DEBUG +// non-debug version is inline and empty +void MessageThread::AssertValid() const { + assert(thread_id_ != 0); + assert(senders_ != NULL); + // In some (odd) cases, gmail may send email with no sender. + // assert(!senders_->empty()); + assert(message_count_ > 0); + assert(labels_ != NULL); +} +#endif + + + +MailBox* MailBox::CreateFromXML(const buzz::XmlElement* src) { + MailBox* mail_box = new MailBox(); + if (!mail_box || !mail_box->InitFromXml(src)) { + delete mail_box; + return NULL; + } + return mail_box; +} + +// Init from a chunk of XML +bool MailBox::InitFromXml(const buzz::XmlElement* src) +{ + if (src->Name() != buzz::kQnMailBox) + return false; + + if (!ParseIntAttr(src, buzz::kQnMailTotalMatched, &mailbox_size_)) + return false; + + estimate_ = false; + ParseBoolAttr(src, buzz::kQnMailTotalEstimate, &estimate_); + + first_index_ = 0; + ParseIntAttr(src, buzz::kQnMailFirstIndex, &first_index_); + + result_time_ = 0; + ParseInt64Attr(src, buzz::kQnMailResultTime, &result_time_); + + highest_thread_id_ = 0; + + const buzz::XmlElement* thread_element = src->FirstNamed(buzz::kQnMailThreadInfo); + while (thread_element) { + MessageThread* thread = MessageThread::CreateFromXML(thread_element); + if (thread) { + if (thread->thread_id() > highest_thread_id_) + highest_thread_id_ = thread->thread_id(); + threads_.push_back(MessageThreadPointer(thread)); + } + thread_element = thread_element->NextNamed(buzz::kQnMailThreadInfo); + } + return true; +} + +const size_t kMaxShortnameLength = 12; + +// Tip: If you extend this list of chars, do not include '-' +const char name_delim[] = " ,.:;\'\"()[]{}<>*@"; + +class SenderFormatter { + public: + // sender should not be deleted while this class is in use. + SenderFormatter(const MailSender& sender, + const std::string& me_address) + : sender_(sender), + visible_(false), + short_format_(true), + space_(kMaxShortnameLength) { + me_ = talk_base::ascicmp(me_address.c_str(), + sender.address().c_str()) == 0; + } + + bool visible() const { + return visible_; + } + + bool is_unread() const { + return sender_.unread(); + } + + const std::string name() const { + return name_; + } + + void set_short_format(bool short_format) { + short_format_ = short_format; + UpdateName(); + } + + void set_visible(bool visible) { + visible_ = visible; + UpdateName(); + } + + void set_space(size_t space) { + space_ = space; + UpdateName(); + } + + private: + // Attempt to shorten to the first word in a person's name + // We could revisit and do better at international punctuation, + // but this is what cricket did, and it should be removed + // soon when gmail does the notification instead of us + // forming it on the client. + static void ShortenName(std::string* name) { + size_t start = name->find_first_not_of(name_delim); + if (start != std::string::npos && start > 0) { + name->erase(0, start); + } + start = name->find_first_of(name_delim); + if (start != std::string::npos) { + name->erase(start); + } + } + + void UpdateName() { + // Update the name if is going to be used. + if (!visible_) { + return; + } + + if (me_) { + name_ = "me"; + return; + } + + if (sender_.name().empty() && sender_.address().empty()) { + name_ = ""; + return; + } + + name_ = sender_.name(); + // Handle the case of no name or a name looks like an email address. + // When mail is sent to "Quality@example.com" <quality-team@example.com>, + // we shouldn't show "Quality@example.com" as the name. + // Instead use the email address (without the @...) + if (name_.empty() || name_.find_first_of("@") != std::string::npos) { + name_ = sender_.address(); + size_t at_index = name_.find_first_of("@"); + if (at_index != std::string::npos) { + name_.erase(at_index); + } + } else if (short_format_) { + ShortenName(&name_); + } + + if (name_.empty()) { + name_ = "(unknown)"; + } + + // Abbreviate if too long. + if (name_.size() > space_) { + name_.replace(space_ - 1, std::string::npos, "."); + } + } + + const MailSender& sender_; + std::string name_; + bool visible_; + bool short_format_; + size_t space_; + bool me_; + DISALLOW_COPY_AND_ASSIGN(SenderFormatter); +}; + +const char kNormalSeparator[] = ", "; +const char kEllidedSeparator[] = " .."; + +std::string FormatName(const std::string& name, bool bold) { + std::string formatted_name; + if (bold) { + formatted_name.append("<b>"); + } + formatted_name.append(HtmlEncode(name)); + if (bold) { + formatted_name.append("</b>"); + } + return formatted_name; +} + +class SenderFormatterList { + public: + // sender_list must not be deleted while this class is being used. + SenderFormatterList(const MailSenderList& sender_list, + const std::string& me_address) + : state_(INITIAL_STATE), + are_any_read_(false), + index_(-1), + first_unread_index_(-1) { + // Add all read messages. + const MailSender* originator = NULL; + bool any_unread = false; + for (size_t i = 0; i < sender_list.size(); ++i) { + if (sender_list[i].originator()) { + originator = &sender_list[i]; + } + if (sender_list[i].unread()) { + any_unread = true; + continue; + } + are_any_read_ = true; + if (!sender_list[i].originator()) { + senders_.push_back(new SenderFormatter(sender_list[i], + me_address)); + } + } + + // There may not be an orignator (if there no senders). + if (originator) { + senders_.insert(senders_.begin(), new SenderFormatter(*originator, + me_address)); + } + + // Add all unread messages. + if (any_unread) { + // It should be rare, but there may be cases when all of the + // senders appear to have read the message. + first_unread_index_ = is_first_unread() ? 0 : senders_.size(); + for (size_t i = 0; i < sender_list.size(); ++i) { + if (!sender_list[i].unread()) { + continue; + } + // Don't add the originator if it is already at the + // start of the "unread" list. + if (sender_list[i].originator() && is_first_unread()) { + continue; + } + senders_.push_back(new SenderFormatter(sender_list[i], + me_address)); + } + } + } + + ~SenderFormatterList() { + CleanupSequence(&senders_); + } + + std::string GetHtml(int space) { + index_ = -1; + state_ = INITIAL_STATE; + while (!added_.empty()) { + added_.pop(); + } + + // If there is only one sender, let it take up all of the space. + if (senders_.size() == 1) { + senders_[0]->set_space(space); + senders_[0]->set_short_format(false); + } + + int length = 1; + // Add as many senders as we can in the given space. + // Computes the visible length at each iteration, + // but does not construct the actual html. + while (length < space && AddNextSender()) { + int new_length = ConstructHtml(is_first_unread(), NULL); + // Remove names to avoid truncating + // * if there will be at least 2 left or + // * if the spacing <= 2 characters per sender and there + // is at least one left. + if ((new_length > space && + (visible_count() > 2 || + (ComputeSpacePerSender(space) <= 2 && visible_count() > 1)))) { + RemoveLastAddedSender(); + break; + } + length = new_length; + } + + if (length > space) { + int max = ComputeSpacePerSender(space); + for (size_t i = 0; i < senders_.size(); ++i) { + if (senders_[i]->visible()) { + senders_[i]->set_space(max); + } + } + } + + // Now construct the actual html + std::string html_list; + length = ConstructHtml(is_first_unread(), &html_list); + if (length > space) { + LOG(LS_WARNING) << "LENGTH: " << length << " exceeds " + << space << " " << html_list; + } + return html_list; + } + + private: + int ComputeSpacePerSender(int space) const { + // Why the "- 2"? To allow for the " .. " which may occur + // after the names, and no matter what always allow at least + // 2 characters per sender. + return talk_base::_max<int>(space / visible_count() - 2, 2); + } + + // Finds the next sender that should be added to the "from" list + // and sets it to visible. + // + // This method may be called until it returns false or + // until RemoveLastAddedSender is called. + bool AddNextSender() { + // The progression is: + // 1. Add the person who started the thread, which is the first message. + // 2. Add the first unread message (unless it has already been added). + // 3. Add the last message (unless it has already been added). + // 4. Add the message that is just before the last message processed + // (unless it has already been added). + // If there is no message (i.e. at index -1), return false. + // + // Typically, this method is called until it returns false or + // all of the space available is used. + switch (state_) { + case INITIAL_STATE: + state_ = FIRST_MESSAGE; + index_ = 0; + // If the server behaves odd and doesn't send us any senders, + // do something graceful. + if (senders_.size() == 0) { + return false; + } + break; + + case FIRST_MESSAGE: + if (first_unread_index_ >= 0) { + state_ = FIRST_UNREAD_MESSAGE; + index_ = first_unread_index_; + break; + } + // fall through + case FIRST_UNREAD_MESSAGE: + state_ = LAST_MESSAGE; + index_ = senders_.size() - 1; + break; + + case LAST_MESSAGE: + case PREVIOUS_MESSAGE: + state_ = PREVIOUS_MESSAGE; + index_--; + break; + + case REMOVED_MESSAGE: + default: + ASSERT(false); + return false; + } + + if (index_ < 0) { + return false; + } + + if (!senders_[index_]->visible()) { + added_.push(index_); + senders_[index_]->set_visible(true); + } + return true; + } + + // Makes the last added sender not visible. + // + // May be called while visible_count() > 0. + void RemoveLastAddedSender() { + state_ = REMOVED_MESSAGE; + int index = added_.top(); + added_.pop(); + senders_[index]->set_visible(false); + } + + // Constructs the html of the SenderList and returns the length of the + // visible text. + // + // The algorithm simply walks down the list of Senders, appending + // the html for each visible sender, and adding ellipsis or commas + // in between, whichever is appropriate. + // + // html Filled with html. Maybe NULL if the html doesn't + // need to be constructed yet (useful for simply + // determining the length of the visible text). + // + // returns The approximate visible length of the html. + int ConstructHtml(bool first_is_unread, + std::string* html) const { + if (senders_.empty()) { + return 0; + } + + int length = 0; + + // The first is always visible + const SenderFormatter* sender = senders_[0]; + const std::string& originator_name = sender->name(); + length += originator_name.length(); + if (html) { + html->append(FormatName(originator_name, first_is_unread)); + } + + bool elided = false; + const char* between = ""; + for (size_t i = 1; i < senders_.size(); i++) { + sender = senders_[i]; + + if (sender->visible()) { + // Handle the separator + between = elided ? " " : kNormalSeparator; + // Ignore the , for length because it is so narrow, + // so in both cases above the space is the only things + // that counts for spaces. + length++; + + // Handle the name + const std::string name = sender->name(); + length += name.size(); + + // Construct the html + if (html) { + html->append(between); + html->append(FormatName(name, sender->is_unread())); + } + elided = false; + } else if (!elided) { + between = kEllidedSeparator; + length += 2; // ".." is narrow + if (html) { + html->append(between); + } + elided = true; + } + } + return length; + } + + bool is_first_unread() const { + return !are_any_read_; + } + + size_t visible_count() const { + return added_.size(); + } + + enum MessageState { + INITIAL_STATE, + FIRST_MESSAGE, + FIRST_UNREAD_MESSAGE, + LAST_MESSAGE, + PREVIOUS_MESSAGE, + REMOVED_MESSAGE, + }; + + // What state we were in during the last "stateful" function call. + MessageState state_; + bool are_any_read_; + std::vector<SenderFormatter*> senders_; + std::stack<int> added_; + int index_; + int first_unread_index_; + DISALLOW_COPY_AND_ASSIGN(SenderFormatterList); +}; + + +std::string GetSenderHtml(const MailSenderList& sender_list, + int message_count, + const std::string& me_address, + int space) { + // There has to be at least 9 spaces to show something reasonable. + ASSERT(space >= 10); + std::string count_html; + if (message_count > 1) { + std::string count(IntToString(message_count)); + space -= (count.size()); + count_html.append(" ("); + count_html.append(count); + count_html.append(")"); + // Reduce space due to parenthesis and . + space -= 2; + } + + SenderFormatterList senders(sender_list, me_address); + std::string html_list(senders.GetHtml(space)); + html_list.append(count_html); + return html_list; +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/mailbox.h b/chrome/browser/sync/notifier/communicator/mailbox.h new file mode 100644 index 0000000..009de73 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/mailbox.h @@ -0,0 +1,166 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_MAILBOX_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_MAILBOX_H_ + +#include <set> +#include <string> +#include <vector> + +#include "talk/base/basictypes.h" +#include "talk/base/linked_ptr.h" + +namespace buzz { +class XmlElement; +} + +namespace notifier { +// ----------------------------------------------------------------------------- +class MailAddress { + public: + MailAddress(const std::string& name, const std::string& address) + : name_(name), + address_(address) { + } + const std::string& name() const { return name_; } + const std::string& address() const { return address_; } + std::string safe_name() const; // will return *something* + private: + std::string name_; + std::string address_; +}; + +// ----------------------------------------------------------------------------- +class MailSender : public MailAddress { + public: + MailSender(const std::string& name, const std::string& address, bool unread, + bool originator) + : MailAddress(name, address), + unread_(unread), + originator_(originator) { + } + + MailSender(const MailSender& r) + : MailAddress(r.name(), r.address()) { + unread_ = r.unread_; + originator_ = r.originator_; + } + + bool unread() const { return unread_; } + bool originator() const { return originator_; } + + private: + bool unread_; + bool originator_; +}; + +typedef std::vector<MailSender> MailSenderList; + +// ----------------------------------------------------------------------------- +// MessageThread: everything there is to know about a mail thread. +class MessageThread { + public: + MessageThread(const MessageThread& r) { + labels_ = NULL; + senders_ = NULL; + *this = r; + } + + ~MessageThread(); + + // Try to parse the XML to create a MessageThreadInfo. If NULL is returned + // then we either ran out of memory or there was an error in parsing the + // XML + static MessageThread* CreateFromXML(const buzz::XmlElement* src); + + MessageThread& operator=(const MessageThread& r); + + // SameThreadAs : name is self evident + bool SameThreadAs(const MessageThread& r) { + AssertValid(); + r.AssertValid(); + return (thread_id_ == r.thread_id_); + } + + // SameAs : true if thread has same id and messages + // Assumes that messages don't disappear from threads. + bool SameAs(const MessageThread& r) { + AssertValid(); + r.AssertValid(); + return SameThreadAs(r) && + message_count_ == r.message_count_; + } + + typedef std::set<std::string> StringSet; + + int64 thread_id() const { return thread_id_; } + const StringSet* labels() const { return labels_; } + int64 date64() const { return date64_; } + MailSenderList* senders() const { return senders_; } + int personal_level() const { return personal_level_; } + int message_count() const { return message_count_; } + const std::string& subject() const { return subject_; } + const std::string& snippet() const { return snippet_; } + bool starred() const; + bool unread() const; + +#ifdef _DEBUG + void AssertValid() const; +#else + inline void AssertValid() const {} +#endif + + private: + void Clear(); + + private: + MessageThread() : senders_(NULL), labels_(NULL) {} + bool InitFromXml(const buzz::XmlElement* src); + + int64 thread_id_; + int64 date64_; + int message_count_; + int personal_level_; + std::string subject_; + std::string snippet_; + MailSenderList* senders_; + StringSet* labels_; +}; + +typedef talk_base::linked_ptr<MessageThread> MessageThreadPointer; +typedef std::vector<MessageThreadPointer> MessageThreadVector; + +// ----------------------------------------------------------------------------- +class MailBox { + public: + static MailBox* CreateFromXML(const buzz::XmlElement* src); + + const MessageThreadVector& threads() const { return threads_; } + int mailbox_size() const { return mailbox_size_; } + int first_index() const { return first_index_; } + bool estimate() const { return estimate_; } + int64 result_time() const { return result_time_; } + int64 highest_thread_id() const { return highest_thread_id_; } + + private: + MailBox() {} + bool InitFromXml(const buzz::XmlElement* src); + + MessageThreadVector threads_; + + int mailbox_size_; + int first_index_; + bool estimate_; + int64 result_time_; + int64 highest_thread_id_; +}; + +std::string GetSenderHtml(const MailSenderList& sender_list, + int message_count, + const std::string& me_address, + int space); +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_MAILBOX_H_ diff --git a/chrome/browser/sync/notifier/communicator/mailbox_unittest.cc b/chrome/browser/sync/notifier/communicator/mailbox_unittest.cc new file mode 100644 index 0000000..1d498d1 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/mailbox_unittest.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2009 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/sync/notifier/communicator/mailbox.h" +#include "notifier/testing/notifier/unittest.h" + +namespace notifier { +TEST_NOTIFIER_F(MailBoxTest); + +TEST_F(MailBoxTest, SingleSenderHtml) { + std::string me_address("random@company.com"); + MailSenderList sender_list; + sender_list.push_back(MailSender("Alex Smith", "a@a.com", true, true)); + std::string sender_html = GetSenderHtml(sender_list, 1, me_address, 25); + ASSERT_STREQ("<b>Alex Smith</b>", sender_html.c_str()); +} + +TEST_F(MailBoxTest, TruncatedSingleSenderHtml) { + std::string me_address("random@company.com"); + MailSenderList sender_list; + sender_list.push_back(MailSender( + "Alex Smith AReallyLongLastNameThatWillBeTruncated", + "a@a.com", + true, + true)); + std::string sender_html = GetSenderHtml(sender_list, 1, me_address, 25); + ASSERT_STREQ("<b>Alex Smith AReallyLongLa.</b>", sender_html.c_str()); +} + +TEST_F(MailBoxTest, SingleSenderManyTimesHtml) { + std::string me_address("random@company.com"); + MailSenderList sender_list; + sender_list.push_back(MailSender("Alex Smith", "a@a.com", true, true)); + std::string sender_html = GetSenderHtml(sender_list, 10, me_address, 25); + ASSERT_STREQ("<b>Alex Smith</b> (10)", sender_html.c_str()); +} + +TEST_F(MailBoxTest, SenderWithMeHtml) { + std::string me_address("randOm@comPany.Com"); + MailSenderList sender_list; + sender_list.push_back( + MailSender("Alex Smith", "alex@jones.com", false, false)); + sender_list.push_back( + MailSender("Your Name Goes Here", "raNdom@coMpany.cOm", true, true)); + std::string sender_html = GetSenderHtml(sender_list, 5, me_address, 25); + ASSERT_STREQ("me, Alex, <b>me</b> (5)", sender_html.c_str()); +} + +TEST_F(MailBoxTest, SenderHtmlWithAllUnread) { + std::string me_address("random@company.com"); + MailSenderList sender_list; + sender_list.push_back( + MailSender("Alex Smith", "alex@jones.com", true, false)); + sender_list.push_back(MailSender( + "Your Name Goes Here", + "foo@company.com", + true, + true)); + sender_list.push_back(MailSender("", "bob@davis.com", true, false)); + std::string sender_html = GetSenderHtml(sender_list, 100, me_address, 25); + ASSERT_STREQ("<b>Your</b>, <b>Alex</b>, <b>bob</b> (100)", + sender_html.c_str()); +} + +TEST_F(MailBoxTest, SenderHtmlWithTruncatedNames) { + std::string me_address("random@company.com"); + MailSenderList sender_list; + sender_list.push_back(MailSender( + "ReallyLongName Smith", + "alex@jones.com", + true, + false)); + sender_list.push_back(MailSender( + "AnotherVeryLongFirstNameIsHere", + "foo@company.com", + true, + true)); + std::string sender_html = GetSenderHtml(sender_list, 2, me_address, 25); + ASSERT_STREQ("<b>AnotherV.</b>, <b>ReallyLo.</b> (2)", + sender_html.c_str()); +} + +TEST_F(MailBoxTest, SenderWithTwoSendersShowing) { + std::string me_address("random@company.com"); + MailSenderList sender_list; + sender_list.push_back( + MailSender("ALongishName Smith", "alex@jones.com", false, false)); + sender_list.push_back( + MailSender("AnotherBigName", "no@company.com", true, false)); + sender_list.push_back( + MailSender("Person1", "no1@company.com", true, false)); + sender_list.push_back( + MailSender("Person2", "no2@company.com", false, true)); + std::string sender_html = GetSenderHtml(sender_list, 6, me_address, 25); + ASSERT_STREQ("Person2 .. <b>AnotherB.</b> .. (6)", + sender_html.c_str()); +} + +TEST_F(MailBoxTest, SenderWithThreeSendersShowing) { + std::string me_address("random@company.com"); + MailSenderList sender_list; + sender_list.push_back( + MailSender("Joe Smith", "alex@jones.com", false, false)); + sender_list.push_back( + MailSender("Bob Other", "no@company.com", true, false)); + sender_list.push_back( + MailSender("Person0", "no0@company.com", true, false)); + sender_list.push_back( + MailSender("Person1", "no1@company.com", true, false)); + sender_list.push_back( + MailSender("ted", "ted@company.com", false, true)); + std::string sender_html = GetSenderHtml(sender_list, 6, me_address, 25); + ASSERT_STREQ( + "ted .. <b>Bob</b> .. <b>Person1</b> (6)", + sender_html.c_str()); +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/product_info.cc b/chrome/browser/sync/notifier/communicator/product_info.cc new file mode 100644 index 0000000..c1deafb --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/product_info.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2009 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 <string> + +namespace notifier { +std::string GetUserAgentString() { + return kXmppProductName; +} + +std::string GetProductSignature() { + return kXmppProductName; +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/product_info.h b/chrome/browser/sync/notifier/communicator/product_info.h new file mode 100644 index 0000000..1da60b0 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/product_info.h @@ -0,0 +1,14 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_ +#include <string> + +namespace notifier { +std::string GetUserAgentString(); +std::string GetProductSignature(); +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_ diff --git a/chrome/browser/sync/notifier/communicator/single_login_attempt.cc b/chrome/browser/sync/notifier/communicator/single_login_attempt.cc new file mode 100644 index 0000000..68fe272 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/single_login_attempt.cc @@ -0,0 +1,562 @@ +// Copyright (c) 2009 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 <string> + +#include "chrome/browser/sync/notifier/communicator/single_login_attempt.h" + +#include "chrome/browser/sync/notifier/communicator/connection_options.h" +#include "chrome/browser/sync/notifier/communicator/connection_settings.h" +#include "chrome/browser/sync/notifier/communicator/const_communicator.h" +#include "chrome/browser/sync/notifier/communicator/login_failure.h" +#include "chrome/browser/sync/notifier/communicator/login_settings.h" +#include "chrome/browser/sync/notifier/communicator/product_info.h" +#include "chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h" +#include "chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.h" +#include "chrome/browser/sync/notifier/gaia_auth/gaiaauth.h" +#include "talk/base/asynchttprequest.h" +#include "talk/base/firewallsocketserver.h" +#include "talk/base/signalthread.h" +#include "talk/base/taskrunner.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/constants.h" +#include "talk/xmpp/prexmppauth.h" +#include "talk/xmpp/xmppclient.h" +#include "talk/xmpp/xmppclientsettings.h" + +namespace notifier { +static void FillProxyInfo(const buzz::XmppClientSettings& xcs, + talk_base::ProxyInfo* proxy) { + ASSERT(proxy != NULL); + proxy->type = xcs.proxy(); + proxy->address.SetIP(xcs.proxy_host()); + proxy->address.SetPort(xcs.proxy_port()); + if (xcs.use_proxy_auth()) { + proxy->username = xcs.proxy_user(); + proxy->password = xcs.proxy_pass(); + } +} + +static void GetClientErrorInformation( + buzz::XmppClient* client, + buzz::XmppEngine::Error* error, + int* subcode, + buzz::XmlElement** stream_error, + buzz::CaptchaChallenge* captcha_challenge) { + ASSERT(client != NULL); + ASSERT(error && subcode && stream_error && captcha_challenge); + + *error = client->GetError(subcode); + *captcha_challenge = client->GetCaptchaChallenge(); + + *stream_error = NULL; + if (*error == buzz::XmppEngine::ERROR_STREAM) { + const buzz::XmlElement* error_element = client->GetStreamError(); + if (error_element) { + *stream_error = new buzz::XmlElement(*error_element); + } + } +} + +SingleLoginAttempt::SingleLoginAttempt(talk_base::Task* parent, + LoginSettings* login_settings, + bool successful_connection) + : talk_base::Task(parent), + state_(buzz::XmppEngine::STATE_NONE), + code_(buzz::XmppEngine::ERROR_NONE), + subcode_(0), + need_authentication_(false), + certificate_expired_(false), + cookie_refreshed_(false), + successful_connection_(successful_connection), + login_settings_(login_settings), + client_(NULL) { + connection_generator_.reset(new XmppConnectionGenerator( + this, + &login_settings_->connection_options(), + login_settings_->proxy_only(), + login_settings_->server_list(), + login_settings_->server_count())); + + connection_generator_->SignalExhaustedSettings.connect( + this, + &SingleLoginAttempt::OnAttemptedAllConnections); + connection_generator_->SignalNewSettings.connect( + this, + &SingleLoginAttempt::DoLogin); +} + +SingleLoginAttempt::~SingleLoginAttempt() { + // If this assertion goes off, it means that "Stop()" didn't get + // called like it should have been. + ASSERT(client_ == NULL); +} + +bool SingleLoginAttempt::auto_reconnect() const { + return login_settings_->connection_options().auto_reconnect(); +} + +const talk_base::ProxyInfo& SingleLoginAttempt::proxy() const { + ASSERT(connection_generator_.get()); + return connection_generator_->proxy(); +} + +int SingleLoginAttempt::ProcessStart() { + ASSERT(GetState() == talk_base::Task::STATE_START); + connection_generator_->StartGenerating(); + + // After being started, this class is callback driven and does + // signaling from those callbacks (with checks to see if it is + // done if it may be called back from something that isn't a child task). + return talk_base::Task::STATE_BLOCKED; +} + +void SingleLoginAttempt::Stop() { + ClearClient(); + talk_base::Task::Stop(); + + // No more signals should happen after being stopped. + // (This is needed because some of these signals + // happen due to other components doing signaling which + // may continue running even though this task is stopped.) + SignalUnexpectedDisconnect.disconnect_all(); + SignalRedirect.disconnect_all(); + SignalLoginFailure.disconnect_all(); + SignalNeedAutoReconnect.disconnect_all(); + SignalClientStateChange.disconnect_all(); +} + +void SingleLoginAttempt::OnAttemptedAllConnections( + bool successfully_resolved_dns, + int first_dns_error) { + + // Maybe we needed proxy authentication? + if (need_authentication_) { + LoginFailure failure(LoginFailure::PROXY_AUTHENTICATION_ERROR); + SignalLoginFailure(failure); + return; + } + + if (certificate_expired_) { + LoginFailure failure(LoginFailure::CERTIFICATE_EXPIRED_ERROR); + SignalLoginFailure(failure); + return; + } + + if (!successfully_resolved_dns) { + code_ = buzz::XmppEngine::ERROR_SOCKET; + subcode_ = first_dns_error; + } + + LOG(INFO) << "Connection failed with error " << code_; + + // We were connected and we had a problem + if (successful_connection_ && auto_reconnect()) { + SignalNeedAutoReconnect(); + // expect to be deleted at this point + return; + } + + DiagnoseConnectionError(); +} + +void SingleLoginAttempt::UseNextConnection() { + ASSERT(connection_generator_.get() != NULL); + ClearClient(); + connection_generator_->UseNextConnection(); +} + +void SingleLoginAttempt::UseCurrentConnection() { + ASSERT(connection_generator_.get() != NULL); + ClearClient(); + connection_generator_->UseCurrentConnection(); +} + +void SingleLoginAttempt::DoLogin( + const ConnectionSettings& connection_settings) { + if (client_) { + return; + } + + buzz::XmppClientSettings client_settings; + // set the user settings portion + *static_cast<buzz::XmppClientSettings*>(&client_settings) = + login_settings_->user_settings(); + // fill in the rest of the client settings + connection_settings.FillXmppClientSettings(&client_settings); + + client_ = new buzz::XmppClient(this); + SignalLogInput.repeat(client_->SignalLogInput); + SignalLogOutput.repeat(client_->SignalLogOutput); + + // listen for connection progress + client_->SignalStateChange.connect(this, + &SingleLoginAttempt::OnClientStateChange); + + // transition to "start" + OnClientStateChange(buzz::XmppEngine::STATE_START); + // start connecting + client_->Connect(client_settings, login_settings_->lang(), + CreateSocket(client_settings), + CreatePreXmppAuth(client_settings)); + client_->Start(); +} + +void SingleLoginAttempt::OnAuthenticationError() { + // We can check this flag later if all connection options fail + need_authentication_ = true; +} + +void SingleLoginAttempt::OnCertificateExpired() { + // We can check this flag later if all connection options fail + certificate_expired_ = true; +} + + +buzz::AsyncSocket* SingleLoginAttempt::CreateSocket( + const buzz::XmppClientSettings& xcs) { + bool allow_unverified_certs = + login_settings_->connection_options().allow_unverified_certs(); + XmppSocketAdapter* adapter = new XmppSocketAdapter(xcs, + allow_unverified_certs); + adapter->SignalAuthenticationError.connect( + this, + &SingleLoginAttempt::OnAuthenticationError); + if (login_settings_->firewall()) { + adapter->set_firewall(true); + } + return adapter; +} + +buzz::PreXmppAuth* SingleLoginAttempt::CreatePreXmppAuth( + const buzz::XmppClientSettings& xcs) { + if (login_settings_->no_gaia_auth()) + return NULL; + + // For GMail, use Gaia preauthentication over HTTP + buzz::GaiaAuth* auth = new buzz::GaiaAuth(GetUserAgentString(), + GetProductSignature()); + auth->SignalAuthenticationError.connect( + this, + &SingleLoginAttempt::OnAuthenticationError); + auth->SignalCertificateExpired.connect( + this, + &SingleLoginAttempt::OnCertificateExpired); + auth->SignalFreshAuthCookie.connect( + this, + &SingleLoginAttempt::OnFreshAuthCookie); + auth->set_token_service(xcs.token_service()); + + talk_base::ProxyInfo proxy; + FillProxyInfo(xcs, &proxy); + auth->set_proxy(proxy); + auth->set_firewall(login_settings_->firewall()); + return auth; +} + +void SingleLoginAttempt::OnFreshAuthCookie(const std::string& auth_cookie) { + // Remember this is a fresh cookie + cookie_refreshed_ = true; + + // TODO(sync): do the cookie logic (part of which is in the #if 0 below) + + // The following code is what PhoneWindow does for the equivalent method +#if 0 + // Save cookie + AccountInfo current(account_history_.current()); + current.set_auth_cookie(auth_cookie); + account_history_.set_current(current); + + // Calc next time to refresh cookie, between 5 and 10 days + // The cookie has 14 days of life; this gives at least 4 days of retries + // before the current cookie expires, maximizing the chance of + // having a valid cookie next time the connection servers go down. + FTULL now; + + // NOTE: The following line is win32. Address this when implementing this + // code (doing "the cookie logic") + GetSystemTimeAsFileTime(&(now.ft)); + ULONGLONG five_days = (ULONGLONG)10000 * 1000 * 60 * 60 * 24 * 5; // 5 days + ULONGLONG random = (ULONGLONG)10000 * // get to 100 ns units + ((rand() % (5 * 24 * 60)) * (60 * 1000) + // random min. in 5 day period + (rand() % 1000) * 60); // random 1/1000th of a minute + next_cookie_refresh_ = now.ull + five_days + random; // 5-10 days +#endif +} + +void SingleLoginAttempt::DiagnoseConnectionError() { + switch (code_) { + case buzz::XmppEngine::ERROR_MISSING_USERNAME: + case buzz::XmppEngine::ERROR_NETWORK_TIMEOUT: + case buzz::XmppEngine::ERROR_DOCUMENT_CLOSED: + case buzz::XmppEngine::ERROR_BIND: + case buzz::XmppEngine::ERROR_AUTH: + case buzz::XmppEngine::ERROR_TLS: + case buzz::XmppEngine::ERROR_UNAUTHORIZED: + case buzz::XmppEngine::ERROR_VERSION: + case buzz::XmppEngine::ERROR_STREAM: + case buzz::XmppEngine::ERROR_XML: + case buzz::XmppEngine::ERROR_NONE: + default: { + LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_); + SignalLoginFailure(failure); + return; + } + + // The following errors require diagnosistics: + // * spurious close of connection + // * socket errors after auth + case buzz::XmppEngine::ERROR_CONNECTION_CLOSED: + case buzz::XmppEngine::ERROR_SOCKET: + break; + } + + talk_base::AsyncHttpRequest *http_request = + new talk_base::AsyncHttpRequest(GetUserAgentString()); + http_request->set_host("www.google.com"); + http_request->set_port(80); + http_request->set_secure(false); + http_request->request().path = "/"; + http_request->request().verb = talk_base::HV_GET; + + talk_base::ProxyInfo proxy; + ASSERT(connection_generator_.get() != NULL); + if (connection_generator_.get()) { + proxy = connection_generator_->proxy(); + } + http_request->set_proxy(proxy); + http_request->set_firewall(login_settings_->firewall()); + + http_request->SignalWorkDone.connect(this, + &SingleLoginAttempt::OnHttpTestDone); + http_request->Start(); + http_request->Release(); +} + +void SingleLoginAttempt::OnHttpTestDone(talk_base::SignalThread* thread) { + ASSERT(thread != NULL); + + talk_base::AsyncHttpRequest* request = + static_cast<talk_base::AsyncHttpRequest*>(thread); + + if (request->response().scode == 200) { + // We were able to do an HTTP GET of www.google.com:80 + + // + // The original error should be reported + // + LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_); + SignalLoginFailure(failure); + return; + } + + // Otherwise lets transmute the error into ERROR_SOCKET, and put + // the subcode as an indicator of what we think the problem + // might be. + +#if 0 + // TODO(sync): determine if notifier has an analogous situation + + // + // We weren't able to do an HTTP GET of www.google.com:80 + // + GAutoupdater::Version version_logged_in(g_options.version_logged_in()); + GAutoupdater::Version version_installed(GetProductVersion().c_str()); + if (version_logged_in < version_installed) { + // + // Google Talk has been updated and can no longer connect + // to the Google Talk Service. Your firewall is probably + // not allowing the new version of Google Talk to connect + // to the internet. Please adjust your firewall settings + // to allow the new version of Google Talk to connect to + // the internet. + // + // We'll use the "error=1" to help figure this out for now + // + LoginFailure failure(LoginFailure::XMPP_ERROR, + buzz::XmppEngine::ERROR_SOCKET, + 1); + SignalLoginFailure(failure); + return; + } +#endif + + // + // Any other checking we can add here? + // + + // + // Google Talk is unable to use your internet connection. Either your + // network isn't configured or Google Talk is being blocked by + // a local firewall. + // + // We'll use the "error=0" to help figure this out for now + // + LoginFailure failure(LoginFailure::XMPP_ERROR, + buzz::XmppEngine::ERROR_SOCKET, + 0); + SignalLoginFailure(failure); +} + +void SingleLoginAttempt::OnClientStateChange(buzz::XmppEngine::State state) { + if (state_ == state) + return; + + buzz::XmppEngine::State previous_state = state_; + state_ = state; + + switch (state) { + case buzz::XmppEngine::STATE_NONE: + case buzz::XmppEngine::STATE_START: + case buzz::XmppEngine::STATE_OPENING: + // do nothing + break; + case buzz::XmppEngine::STATE_OPEN: + successful_connection_ = true; + break; + case buzz::XmppEngine::STATE_CLOSED: + OnClientStateChangeClosed(previous_state); + break; + } + SignalClientStateChange(state); + if (state_ == buzz::XmppEngine::STATE_CLOSED) { + OnClientStateChange(buzz::XmppEngine::STATE_NONE); + } +} + +void SingleLoginAttempt::ClearClient() { + if (client_ != NULL) { + client_->Disconnect(); + + // If this assertion goes off, it means that the disconnect didn't occur + // properly. See SingleLoginAttempt::OnClientStateChange, + // case XmppEngine::STATE_CLOSED + ASSERT(client_ == NULL); + } +} + +void SingleLoginAttempt::OnClientStateChangeClosed( + buzz::XmppEngine::State previous_state) { + buzz::XmppEngine::Error error = buzz::XmppEngine::ERROR_NONE; + int error_subcode = 0; + buzz::CaptchaChallenge captcha_challenge; + buzz::XmlElement* stream_error_ptr; + GetClientErrorInformation(client_, + &error, + &error_subcode, + &stream_error_ptr, + &captcha_challenge); + scoped_ptr<buzz::XmlElement> stream_error(stream_error_ptr); + + client_->SignalStateChange.disconnect(this); + client_ = NULL; + + if (error == buzz::XmppEngine::ERROR_NONE) { + SignalLogoff(); + return; + } else if (previous_state == buzz::XmppEngine::STATE_OPEN) { + // Handler should attempt reconnect + SignalUnexpectedDisconnect(); + return; + } else { + HandleConnectionError(error, error_subcode, stream_error.get(), + captcha_challenge); + } +} + +void SingleLoginAttempt::HandleConnectionPasswordError( + const buzz::CaptchaChallenge& captcha_challenge) { + LOG(LS_VERBOSE) << "SingleLoginAttempt::HandleConnectionPasswordError"; + + // Clear the auth cookie + std::string current_auth_cookie = + login_settings_->user_settings().auth_cookie(); + login_settings_->modifiable_user_settings()->set_auth_cookie(""); + // If there was an auth cookie and it was the same as the last + // auth cookie, then it is a stale cookie. Retry login. + if (!current_auth_cookie.empty() && !cookie_refreshed_) { + UseCurrentConnection(); + return; + } + + LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_, + captcha_challenge); + SignalLoginFailure(failure); +} + +void SingleLoginAttempt::HandleConnectionError( + buzz::XmppEngine::Error code, + int subcode, + const buzz::XmlElement* stream_error, + const buzz::CaptchaChallenge& captcha_challenge) { + LOG_F(LS_VERBOSE) << "(" << code << ", " << subcode << ")"; + + // Save off the error code information, so we can use it + // to tell the user what went wrong if all else fails + code_ = code; + subcode_ = subcode; + if ((code_ == buzz::XmppEngine::ERROR_UNAUTHORIZED) || + (code_ == buzz::XmppEngine::ERROR_MISSING_USERNAME)) { + // There was a problem with credentials (username/password) + HandleConnectionPasswordError(captcha_challenge); + return; + } + + // Unexpected disconnect, + // Unreachable host, + // Or internal server binding error - + // All these are temporary problems, so continue reconnecting + + // GaiaAuth signals this directly via SignalCertificateExpired, but + // SChannelAdapter propagates the error through SocketWindow as a socket + // error. + if (code_ == buzz::XmppEngine::ERROR_SOCKET && + subcode_ == SEC_E_CERT_EXPIRED) { + certificate_expired_ = true; + } + + login_settings_->modifiable_user_settings()->set_resource(""); + + // Look for stream::error server redirection stanza "see-other-host" + if (stream_error) { + const buzz::XmlElement* other = + stream_error->FirstNamed(buzz::QN_XSTREAM_SEE_OTHER_HOST); + if (other) { + const buzz::XmlElement* text = + stream_error->FirstNamed(buzz::QN_XSTREAM_TEXT); + if (text) { + // Yep, its a "stream:error" with "see-other-host" text, let's + // parse out the server:port, and then reconnect with that. + const std::string& redirect = text->BodyText(); + unsigned int colon = redirect.find(":"); + int redirect_port = kDefaultXmppPort; + std::string redirect_server; + if (colon == std::string::npos) { + redirect_server = redirect; + } else { + redirect_server = redirect.substr(0, colon); + const std::string& port_text = redirect.substr(colon + 1); + std::istringstream ist(port_text); + ist >> redirect_port; + } + // we never allow a redirect to port 0 + if (redirect_port == 0) { + redirect_port = kDefaultXmppPort; + } + SignalRedirect(redirect_server, redirect_port); + // may be deleted at this point + return; + } + } + } + + ASSERT(connection_generator_.get() != NULL); + if (!connection_generator_.get()) { + return; + } + + // Iterate to the next possible connection (still trying to connect) + UseNextConnection(); +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/single_login_attempt.h b/chrome/browser/sync/notifier/communicator/single_login_attempt.h new file mode 100644 index 0000000..ec265ea --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/single_login_attempt.h @@ -0,0 +1,139 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_ +#include <string> + +#include "chrome/browser/sync/notifier/communicator/login.h" +#include "talk/base/scoped_ptr.h" +#include "talk/base/sigslot.h" +#include "talk/base/task.h" +#include "talk/xmpp/xmppengine.h" + +namespace buzz { +class AsyncSocket; +class CaptchaChallenge; +class PreXmppAuth; +class XmppClient; +class XmppClientSettings; +class XmppClientSettings; +} + +namespace talk_base { +class FirewallManager; +struct ProxyInfo; +class SignalThread; +class Task; +} + +namespace notifier { +class ConnectionSettings; +class LoginFailure; +class LoginSettings; +struct ServerInformation; +class XmppConnectionGenerator; + +// Handles all of the aspects of a single login attempt +// (across multiple ip addresses) and maintainence. By containing +// this within one class, when another login attempt is made, +// this class will be disposed and all of the signalling for the +// previous login attempt will be cleaned up immediately. +// +// This is a task to allow for cleaning this up when a signal +// is being fired. Technically, delete this during the firing of +// a signal could work but it is fragile. +class SingleLoginAttempt : public talk_base::Task, public sigslot::has_slots<> { + public: + SingleLoginAttempt(talk_base::Task* parent, + LoginSettings* login_settings, + bool successful_connection); + ~SingleLoginAttempt(); + virtual int ProcessStart(); + void UseNextConnection(); + void UseCurrentConnection(); + + buzz::XmppClient* xmpp_client() { + return client_; + } + + // Returns the proxy that is being used to connect (or + // the default proxy information if all attempted + // connections failed). + const talk_base::ProxyInfo& proxy() const; + + // Typically handled by creating a new SingleLoginAttempt + // and doing StartConnection + sigslot::signal0<> SignalUnexpectedDisconnect; + + // Typically handled by setting storing the redirect for 5 seconds, + // and setting it into LoginSettings, then creating a new SingleLoginAttempt, + // and doing StartConnection. + // + // SignalRedirect(const std::string& redirect_server, int redirect_port); + sigslot::signal2<const std::string&, int> SignalRedirect; + + sigslot::signal0<> SignalNeedAutoReconnect; + + // SignalClientStateChange(buzz::XmppEngine::State new_state); + sigslot::signal1<buzz::XmppEngine::State> SignalClientStateChange; + + // See the LoginFailure for how to handle this. + sigslot::signal1<const LoginFailure&> SignalLoginFailure; + + // Sent when there is a graceful log-off (state goes to closed + // with no error) + sigslot::signal0<> SignalLogoff; + + sigslot::repeater2<const char*, int> SignalLogInput; + sigslot::repeater2<const char*, int> SignalLogOutput; + + protected: + virtual void Stop(); + + private: + void DoLogin(const ConnectionSettings& connection_settings); + buzz::AsyncSocket* CreateSocket(const buzz::XmppClientSettings& xcs); + buzz::PreXmppAuth* CreatePreXmppAuth(const buzz::XmppClientSettings& xcs); + + // cleans up any xmpp client state to get ready for a new one + void ClearClient(); + + void HandleConnectionError( + buzz::XmppEngine::Error code, + int subcode, + const buzz::XmlElement* stream_error, + const buzz::CaptchaChallenge& captcha_challenge); + void HandleConnectionPasswordError( + const buzz::CaptchaChallenge& captcha_challenge); + + void DiagnoseConnectionError(); + void OnHttpTestDone(talk_base::SignalThread* thread); + + void OnAuthenticationError(); + void OnCertificateExpired(); + void OnFreshAuthCookie(const std::string& auth_cookie); + void OnClientStateChange(buzz::XmppEngine::State state); + void OnClientStateChangeClosed(buzz::XmppEngine::State previous_state); + void OnAttemptedAllConnections(bool successfully_resolved_dns, + int first_dns_error); + + bool auto_reconnect() const; + + buzz::XmppEngine::State state_; + buzz::XmppEngine::Error code_; + int subcode_; + bool need_authentication_; + bool certificate_expired_; + bool cookie_refreshed_; + bool successful_connection_; + LoginSettings* login_settings_; + buzz::XmppClient* client_; + scoped_ptr<XmppConnectionGenerator> connection_generator_; + + DISALLOW_COPY_AND_ASSIGN(SingleLoginAttempt); +}; +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_ diff --git a/chrome/browser/sync/notifier/communicator/talk_auth_task.cc b/chrome/browser/sync/notifier/communicator/talk_auth_task.cc new file mode 100644 index 0000000..a69609c7 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/talk_auth_task.cc @@ -0,0 +1,73 @@ +// Copyright (c) 2009 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/sync/notifier/communicator/talk_auth_task.h" + +#include "chrome/browser/sync/notifier/communicator/login.h" +#include "chrome/browser/sync/notifier/communicator/login_settings.h" +#include "chrome/browser/sync/notifier/communicator/product_info.h" +#include "chrome/browser/sync/notifier/gaia_auth/gaiaauth.h" +#include "talk/base/common.h" +#include "talk/base/urlencode.h" +#include "talk/xmpp/xmppclient.h" + +namespace notifier { +const char kTalkGadgetAuthPath[] = "/auth"; + +TalkAuthTask::TalkAuthTask(talk_base::Task* parent, + Login* login, + const char* url) + : talk_base::Task(parent), + login_(login), + url_(url) { + ASSERT(login && !url_.empty()); +} + +int TalkAuthTask::ProcessStart() { + auth_.reset(new buzz::GaiaAuth(GetUserAgentString(), + GetProductSignature())); + auth_->SignalAuthDone.connect( + this, + &TalkAuthTask::OnAuthDone); + auth_->StartAuth(login_->xmpp_client()->jid().BareJid(), + login_->login_settings().user_settings().pass(), + "talk"); + return STATE_RESPONSE; +} + +int TalkAuthTask::ProcessResponse() { + ASSERT(auth_.get()); + if (!auth_->IsAuthDone()) { + return STATE_BLOCKED; + } + SignalAuthDone(*this); + return STATE_DONE; +} + + +void TalkAuthTask::OnAuthDone() { + Wake(); +} + +bool TalkAuthTask::HadError() const { + return auth_->HadError(); +} + +std::string TalkAuthTask::GetAuthenticatedUrl( + const char* talk_base_url) const { + ASSERT(talk_base_url && *talk_base_url && !auth_->HadError()); + + std::string auth_url(talk_base_url); + auth_url.append(kTalkGadgetAuthPath); + auth_url.append("?silent=true&redirect=true&host="); + auth_url.append(UrlEncodeString(url_)); + auth_url.append("&auth="); + auth_url.append(auth_->GetAuth()); + return auth_url; +} + +std::string TalkAuthTask::GetSID() const { + return auth_->GetSID(); +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/talk_auth_task.h b/chrome/browser/sync/notifier/communicator/talk_auth_task.h new file mode 100644 index 0000000..2f690a37 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/talk_auth_task.h @@ -0,0 +1,62 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_TALK_AUTH_TASK_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_TALK_AUTH_TASK_H_ + +#include "talk/base/scoped_ptr.h" +#include "talk/base/sigslot.h" +#include "talk/base/task.h" + +namespace buzz { +class GaiaAuth; +} + +namespace notifier { +class Login; + +// Create an authenticated talk url from an unauthenticated url +class TalkAuthTask : public talk_base::Task, public sigslot::has_slots<> { + public: + TalkAuthTask(talk_base::Task* parent, + Login* login, + const char* url); + + // An abort method which doesn't take any parameters. + // (talk_base::Task::Abort() has a default parameter.) + // + // The primary purpose of this method is to allow a + // signal to be hooked up to abort this task. + void Abort() { + talk_base::Task::Abort(); + } + + const std::string& url() { + return url_; + } + + std::string GetAuthenticatedUrl(const char* talk_base_url) const; + std::string GetSID() const; + + sigslot::signal1<const TalkAuthTask&> SignalAuthDone; + + bool HadError() const; + + // TODO(sync): add captcha support + + protected: + virtual int ProcessStart(); + virtual int ProcessResponse(); + + private: + void OnAuthDone(); + + scoped_ptr<buzz::GaiaAuth> auth_; + Login* login_; + std::string url_; + DISALLOW_COPY_AND_ASSIGN(TalkAuthTask); +}; +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_TALK_AUTH_TASK_H_ diff --git a/chrome/browser/sync/notifier/communicator/xml_parse_helpers-inl.h b/chrome/browser/sync/notifier/communicator/xml_parse_helpers-inl.h new file mode 100644 index 0000000..b400218 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xml_parse_helpers-inl.h @@ -0,0 +1,24 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XML_PARSE_HELPERS_INL_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XML_PARSE_HELPERS_INL_H_ + +#include <sstream> + +#include "chrome/browser/sync/notifier/communicator/xml_parse_helpers.h" +#include "talk/xmllite/xmlelement.h" + +namespace notifier { + +template<class T> +void SetAttr(buzz::XmlElement* xml, const buzz::QName& name, const T& data) { + std::ostringstream ost; + ost << data; + xml->SetAttr(name, ost.str()); +} + +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XML_PARSE_HELPERS_INL_H_ diff --git a/chrome/browser/sync/notifier/communicator/xml_parse_helpers.cc b/chrome/browser/sync/notifier/communicator/xml_parse_helpers.cc new file mode 100644 index 0000000..b05f439 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xml_parse_helpers.cc @@ -0,0 +1,185 @@ +// Copyright (c) 2009 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/sync/notifier/communicator/xml_parse_helpers.h" +#include "chrome/browser/sync/notifier/communicator/xml_parse_helpers-inl.h" + +#include <string> + +#include "chrome/browser/sync/notifier/base/string.h" +#include "talk/base/basicdefs.h" +#include "talk/base/stream.h" +#include "talk/xmllite/xmlbuilder.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlparser.h" +#include "talk/xmllite/xmlprinter.h" +#include "talk/xmpp/jid.h" + +namespace notifier { + +buzz::XmlElement* ReadXmlFromStream(talk_base::StreamInterface* stream) { + buzz::XmlBuilder builder; + buzz::XmlParser parser(&builder); + + const int kBufferSize = 4 * 1024; + char buf[kBufferSize]; + + talk_base::StreamResult result = talk_base::SR_SUCCESS; + while(true) { + size_t read = 0; + + // Read a chunk + result = stream->Read(buf, kBufferSize, &read, NULL); + if (result != talk_base::SR_SUCCESS) + break; + + // Pass it to the parser + parser.Parse(buf, read, false); + } + + if (result == talk_base::SR_EOS) { + parser.Parse(NULL, 0, true); + return builder.CreateElement(); + } + + return NULL; +} + +bool ParseInt64Attr(const buzz::XmlElement* element, + const buzz::QName& attribute, int64* result) { + if (!element->HasAttr(attribute)) + return false; + std::string text = element->Attr(attribute); + char* error = NULL; +#ifdef POSIX + *result = atoll(text.c_str()); +#else + *result = _strtoi64(text.c_str(), &error, 10); +#endif + return text.c_str() != error; +} + +bool ParseIntAttr(const buzz::XmlElement* element, const buzz::QName& attribute, + int* result) { + if (!element->HasAttr(attribute)) + return false; + std::string text = element->Attr(attribute); + char* error = NULL; + *result = static_cast<int>(strtol(text.c_str(), &error, 10)); + return text.c_str() != error; +} + +bool ParseBoolAttr(const buzz::XmlElement* element, + const buzz::QName& attribute, bool* result) { + int int_value = 0; + if (!ParseIntAttr(element, attribute, &int_value)) + return false; + *result = int_value != 0; + return true; +} + +bool ParseStringAttr(const buzz::XmlElement* element, + const buzz::QName& attribute, std::string* result) { + if (!element->HasAttr(attribute)) + return false; + *result = element->Attr(attribute); + return true; +} + +void WriteXmlToStream(talk_base::StreamInterface* stream, + const buzz::XmlElement* xml) { + // Save it all to a string and then write that string out to disk. + // + // This is probably really inefficient in multiple ways. We probably + // have an entire string copy of the XML in memory twice -- once in the + // stream and once in the string. There is probably a way to get the data + // directly out of the stream but I don't have the time to decode the stream + // classes right now. + std::ostringstream s; + buzz::XmlPrinter::PrintXml(&s, xml); + std::string output_string = s.str(); + stream->WriteAll(output_string.data(), output_string.length(), NULL, NULL); +} + +bool SetInt64Attr(buzz::XmlElement* element, const buzz::QName& attribute, + int64 value) { + if (!element->HasAttr(attribute)) + return false; + element->AddAttr(attribute, Int64ToString(value).c_str()); + return true; +} + +bool SetIntAttr(buzz::XmlElement* element, const buzz::QName& attribute, + int value) { + if (!element->HasAttr(attribute)) + return false; + element->AddAttr(attribute, IntToString(value).c_str()); + return true; +} + +bool SetBoolAttr(buzz::XmlElement* element, const buzz::QName& attribute, + bool value) { + int int_value = 0; + if (value) { + int_value = 1; + } + return SetIntAttr(element, attribute, int_value); +} + +bool SetStringAttr(buzz::XmlElement* element, const buzz::QName& attribute, + const std::string& value) { + if (!element->HasAttr(attribute)) + return false; + element->AddAttr(attribute, value); + return true; +} + + +// XmlStream + +XmlStream::XmlStream() + : state_(talk_base::SS_OPEN), + builder_(new buzz::XmlBuilder()), + parser_(new buzz::XmlParser(builder_.get())) { +} + +XmlStream::~XmlStream() { +} + +buzz::XmlElement* XmlStream::CreateElement() { + if (talk_base::SS_OPEN == state_) { + Close(); + } + return builder_->CreateElement(); +} + +talk_base::StreamResult XmlStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (error) + *error = -1; + return talk_base::SR_ERROR; +} + +talk_base::StreamResult XmlStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (talk_base::SS_OPEN != state_) { + if (error) + *error = -1; + return talk_base::SR_ERROR; + } + parser_->Parse(static_cast<const char*>(data), data_len, false); + if (written) + *written = data_len; + return talk_base::SR_SUCCESS; +} + +void XmlStream::Close() { + if (talk_base::SS_OPEN != state_) + return; + + parser_->Parse(NULL, 0, true); + state_ = talk_base::SS_CLOSED; +} + +} // namespace buzz diff --git a/chrome/browser/sync/notifier/communicator/xml_parse_helpers.h b/chrome/browser/sync/notifier/communicator/xml_parse_helpers.h new file mode 100644 index 0000000..0c918bd --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xml_parse_helpers.h @@ -0,0 +1,75 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XML_PARSE_HELPERS_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XML_PARSE_HELPERS_H_ + +#include <string> + +#include "talk/base/basictypes.h" +#include "talk/base/scoped_ptr.h" +#include "talk/base/stream.h" + +namespace buzz { +class XmlBuilder; +class XmlElement; +class XmlParser; +class QName; +} + +namespace notifier { +buzz::XmlElement* ReadXmlFromStream(talk_base::StreamInterface* stream); +bool ParseInt64Attr(const buzz::XmlElement* element, + const buzz::QName& attribute, int64* result); +bool ParseIntAttr(const buzz::XmlElement* element, + const buzz::QName& attribute, int* result); +bool ParseBoolAttr(const buzz::XmlElement* element, + const buzz::QName& attribute, bool* result); +bool ParseStringAttr(const buzz::XmlElement* element, + const buzz::QName& attribute, std::string* result); + +void WriteXmlToStream(talk_base::StreamInterface* stream, + const buzz::XmlElement* xml); +bool SetInt64Attr(buzz::XmlElement* element, const buzz::QName& attribute, + int64 result); +bool SetIntAttr(buzz::XmlElement* element, const buzz::QName& attribute, + int result); +bool SetBoolAttr(buzz::XmlElement* element, const buzz::QName& attribute, + bool result); +bool SetStringAttr(buzz::XmlElement* element, const buzz::QName& attribute, + const std::string& result); + +template<class T> +void SetAttr(buzz::XmlElement* xml, const buzz::QName& name, const T& data); + +/////////////////////////////////////////////////////////////////////////////// +// XmlStream +/////////////////////////////////////////////////////////////////////////////// + +class XmlStream : public talk_base::StreamInterface { + public: + XmlStream(); + virtual ~XmlStream(); + + buzz::XmlElement* CreateElement(); + + virtual talk_base::StreamState GetState() const { return state_; } + + virtual talk_base::StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual talk_base::StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + + private: + talk_base::StreamState state_; + scoped_ptr<buzz::XmlBuilder> builder_; + scoped_ptr<buzz::XmlParser> parser_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace buzz + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XML_PARSE_HELPERS_H_ diff --git a/chrome/browser/sync/notifier/communicator/xmpp_connection_generator.cc b/chrome/browser/sync/notifier/communicator/xmpp_connection_generator.cc new file mode 100644 index 0000000..f3a5f4c --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xmpp_connection_generator.cc @@ -0,0 +1,210 @@ +// Copyright (c) 2009 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. + +// XmppConnectionGenerator does the following algorithm: +// proxy = ResolveProxyInformation(connection_options) +// for server in server_list +// get dns_addresses for server +// connection_list = (dns_addresses X connection methods X proxy).shuffle() +// for connection in connection_list +// yield connection + +#include "chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h" + +#include <vector> + +#include "chrome/browser/sync/notifier/base/async_dns_lookup.h" +#include "chrome/browser/sync/notifier/base/signal_thread_task.h" +#include "chrome/browser/sync/notifier/communicator/connection_options.h" +#include "chrome/browser/sync/notifier/communicator/connection_settings.h" +#include "chrome/browser/sync/notifier/communicator/product_info.h" +#include "talk/base/autodetectproxy.h" +#include "talk/base/httpcommon.h" +#include "talk/base/logging.h" +#include "talk/base/task.h" +#include "talk/base/thread.h" +#include "talk/xmpp/prexmppauth.h" +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/xmpp/xmppengine.h" + +namespace notifier { + +XmppConnectionGenerator::XmppConnectionGenerator( + talk_base::Task* parent, + const ConnectionOptions* options, + bool proxy_only, + const ServerInformation* server_list, + int server_count) + : settings_list_(new ConnectionSettingsList()), + settings_index_(0), + server_list_(new ServerInformation[server_count]), + server_count_(server_count), + server_index_(-1), + proxy_only_(proxy_only), + successfully_resolved_dns_(false), + first_dns_error_(0), + options_(options), + parent_(parent) { + assert(parent); + assert(options); + assert(server_count_ > 0); + for (int i = 0; i < server_count_; ++i) { + server_list_[i] = server_list[i]; + } +} + +XmppConnectionGenerator::~XmppConnectionGenerator() { + LOG(LS_VERBOSE) << "XmppConnectionGenerator::~XmppConnectionGenerator"; +} + +const talk_base::ProxyInfo& XmppConnectionGenerator::proxy() const { + assert(settings_list_.get()); + if (settings_index_ >= settings_list_->GetCount()) { + return settings_list_->proxy(); + } + + ConnectionSettings* settings = settings_list_->GetSettings(settings_index_); + return settings->proxy(); +} + +// Starts resolving proxy information +void XmppConnectionGenerator::StartGenerating() { + LOG(LS_VERBOSE) << "XmppConnectionGenerator::StartGenerating"; + + talk_base::AutoDetectProxy* proxy_detect = + new talk_base::AutoDetectProxy(GetUserAgentString()); + + if (options_->autodetect_proxy()) { + // Pretend the xmpp server is https, when detecting whether a proxy is + // required to connect. + talk_base::Url<char> host_url("/", + server_list_[0].server.IPAsString().c_str(), + server_list_[0].server.port()); + host_url.set_secure(true); + proxy_detect->set_server_url(host_url.url()); + } else if (options_->proxy_host().length()) { + talk_base::SocketAddress proxy(options_->proxy_host(), + options_->proxy_port()); + proxy_detect->set_proxy(proxy); + } + proxy_detect->set_auth_info(options_->use_proxy_auth(), + options_->auth_user(), + talk_base::CryptString(options_->auth_pass())); + + SignalThreadTask<talk_base::AutoDetectProxy>* wrapper_task = + new SignalThreadTask<talk_base::AutoDetectProxy>(parent_, &proxy_detect); + wrapper_task->SignalWorkDone.connect( + this, + &XmppConnectionGenerator::OnProxyDetect); + wrapper_task->Start(); +} + +void XmppConnectionGenerator::OnProxyDetect( + talk_base::AutoDetectProxy* proxy_detect) { + LOG(LS_VERBOSE) << "XmppConnectionGenerator::OnProxyDetect"; + + ASSERT(settings_list_.get()); + ASSERT(proxy_detect); + settings_list_->SetProxy(proxy_detect->proxy()); + + // Start iterating through the connections (which + // are generated on demand). + UseNextConnection(); +} + +void XmppConnectionGenerator::UseNextConnection() { + // Trying to connect + + // Iterate to the next possible connection + settings_index_++; + if (settings_index_ < settings_list_->GetCount()) { + // We have more connection settings in the settings_list_ to try, kick + // off the next one. + UseCurrentConnection(); + return; + } + + // Iterate to the next possible server + server_index_++; + if (server_index_ < server_count_) { + AsyncDNSLookup* dns_lookup = new AsyncDNSLookup( + server_list_[server_index_].server); + SignalThreadTask<AsyncDNSLookup>* wrapper_task = + new SignalThreadTask<AsyncDNSLookup>(parent_, &dns_lookup); + wrapper_task->SignalWorkDone.connect( + this, + &XmppConnectionGenerator::OnServerDNSResolved); + wrapper_task->Start(); + return; + } + + // All out of possibilities + HandleExhaustedConnections(); +} + +void XmppConnectionGenerator::OnServerDNSResolved( + AsyncDNSLookup* dns_lookup) { + LOG(LS_VERBOSE) << "XmppConnectionGenerator::OnServerDNSResolved"; + + // Print logging info + LOG(LS_VERBOSE) << " server: " << + server_list_[server_index_].server.ToString() << + " error: " << dns_lookup->error(); + if (first_dns_error_ == 0 && dns_lookup->error() != 0) { + first_dns_error_ = dns_lookup->error(); + } + + if (!successfully_resolved_dns_ && dns_lookup->ip_list().size() > 0) { + successfully_resolved_dns_ = true; + } + + for (int i = 0; i < static_cast<int>(dns_lookup->ip_list().size()); ++i) { + LOG(LS_VERBOSE) + << " ip " << i << " : " + << talk_base::SocketAddress::IPToString(dns_lookup->ip_list()[i]); + } + + // Build the ip list + assert(settings_list_.get()); + settings_index_ = -1; + settings_list_->ClearPermutations(); + settings_list_->AddPermutations( + server_list_[server_index_].server.IPAsString(), + dns_lookup->ip_list(), + server_list_[server_index_].server.port(), + server_list_[server_index_].special_port_magic, + proxy_only_); + + UseNextConnection(); +} + +static const char * const PROTO_NAMES[cricket::PROTO_LAST+1] = { + "udp", "tcp", "ssltcp" +}; + +static const char* ProtocolToString(cricket::ProtocolType proto) { + return PROTO_NAMES[proto]; +} + +void XmppConnectionGenerator::UseCurrentConnection() { + LOG(LS_VERBOSE) << "XmppConnectionGenerator::UseCurrentConnection"; + + ConnectionSettings* settings = settings_list_->GetSettings(settings_index_); + LOG(LS_INFO) << "*** Attempting " + << ProtocolToString(settings->protocol()) << " connection to " + << settings->server().IPAsString() << ":" + << settings->server().port() + << " (via " << ProxyToString(settings->proxy().type) + << " proxy @ " << settings->proxy().address.IPAsString() << ":" + << settings->proxy().address.port() << ")"; + + SignalNewSettings(*settings); +} + +void XmppConnectionGenerator::HandleExhaustedConnections() { + LOG_F(LS_VERBOSE) << "(" << buzz::XmppEngine::ERROR_SOCKET + << ", " << first_dns_error_ << ")"; + SignalExhaustedSettings(successfully_resolved_dns_, first_dns_error_); +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h b/chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h new file mode 100644 index 0000000..03a7a8f --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h @@ -0,0 +1,81 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_CONNECTION_GENERATOR_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_CONNECTION_GENERATOR_H_ +#include <vector> + +#include "talk/base/scoped_ptr.h" +#include "talk/base/sigslot.h" +#include "talk/base/socketaddress.h" + +namespace talk_base { +class AutoDetectProxy; +struct ProxyInfo; +class SignalThread; +class Task; +} + +namespace notifier { +class AsyncDNSLookup; +class ConnectionOptions; +class ConnectionSettings; +class ConnectionSettingsList; + +struct ServerInformation { + talk_base::SocketAddress server; + bool special_port_magic; +}; + +// Resolves dns names and iterates through the various ip address +// and transport combinations. +class XmppConnectionGenerator : public sigslot::has_slots<> { + public: + // parent is the parent for any tasks needed during this operation + // proxy_only indicates if true connections are only attempted using the proxy + // server_list is the list of connections to attempt in priority order + // server_count is the number of items in the server list + XmppConnectionGenerator(talk_base::Task* parent, + const ConnectionOptions* options, + bool proxy_only, + const ServerInformation* server_list, + int server_count); + ~XmppConnectionGenerator(); + + // Only call this once. Create a new XmppConnectionGenerator and + // delete the current one if the process needs to start again. + void StartGenerating(); + + void UseNextConnection(); + void UseCurrentConnection(); + + const talk_base::ProxyInfo& proxy() const; + + sigslot::signal1<const ConnectionSettings&> SignalNewSettings; + + // SignalExhaustedSettings(bool successfully_resolved_dns, + // int first_dns_error); + sigslot::signal2<bool, int> SignalExhaustedSettings; + + private: + void OnProxyDetect(talk_base::AutoDetectProxy* proxy_detect); + void OnServerDNSResolved(AsyncDNSLookup* dns_lookup); + void HandleExhaustedConnections(); + + talk_base::scoped_ptr<ConnectionSettingsList> settings_list_; + int settings_index_; // the setting that is currently being used + talk_base::scoped_array<ServerInformation> server_list_; + int server_count_; + int server_index_; // the server that is current being used + bool proxy_only_; + bool successfully_resolved_dns_; + int first_dns_error_; + const ConnectionOptions* options_; + + talk_base::Task* parent_; + DISALLOW_COPY_AND_ASSIGN(XmppConnectionGenerator); +}; +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_CONNECTION_GENERATOR_H_ diff --git a/chrome/browser/sync/notifier/communicator/xmpp_log.cc b/chrome/browser/sync/notifier/communicator/xmpp_log.cc new file mode 100644 index 0000000..30b0036 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xmpp_log.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2009 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. + +#if LOGGING + +#include "chrome/browser/sync/notifier/communicator/xmpp_log.h" + +#include <iomanip> +#include <string> +#include <vector> + +#include "chrome/browser/sync/notifier/base/time.h" +#include "talk/base/common.h" +#include "talk/base/logging.h" + +namespace notifier { + +static bool IsAuthTag(const char* str, size_t len) { + // Beware that str is not NULL terminated + if (str[0] == '<' && + str[1] == 'a' && + str[2] == 'u' && + str[3] == 't' && + str[4] == 'h' && + str[5] <= ' ') { + std::string tag(str, len); + if (tag.find("mechanism") != std::string::npos) + return true; + } + return false; +} + +static bool IsChatText(const char* str, size_t len) { + // Beware that str is not NULL terminated + if (str[0] == '<' && + str[1] == 'm' && + str[2] == 'e' && + str[3] == 's' && + str[4] == 's' && + str[5] == 'a' && + str[6] == 'g' && + str[7] == 'e' && + str[8] <= ' ') { + std::string tag(str, len); + if (tag.find("chat") != std::string::npos) + return true; + } + return false; +} + +void XmppLog::XmppPrint(bool output) { + std::vector<char>* buffer = output ? + &xmpp_output_buffer_ : &xmpp_input_buffer_; + const bool log_chat = LOG_CHECK_LEVEL(LS_SENSITIVE); + if (buffer->size()) { + char* time_string = GetLocalTimeAsString(); + LOG(INFO) << (output ? "SEND >>>>>>>>>>>>>>>>>>>>>>>>>" : + "RECV <<<<<<<<<<<<<<<<<<<<<<<<<") + << " : " << time_string; + + int start = 0; + int nest = 3; + for (int i = 0; i < static_cast<int>(buffer->size()); ++i) { + if ((*buffer)[i] == '>') { + bool indent; + if ((i > 0) && ((*buffer)[i - 1] == '/')) { + indent = false; + } else if ((start + 1 < static_cast<int>(buffer->size())) && + ((*buffer)[start + 1] == '/')) { + indent = false; + nest -= 2; + } else { + indent = true; + } + + // Output a tag + LOG(INFO) << std::setw(nest) << " " + << std::string(&((*buffer)[start]), i + 1 - start); + + if (indent) + nest += 2; + + // Note if it's a PLAIN auth tag + if (IsAuthTag(&((*buffer)[start]), i + 1 - start)) { + censor_password_ = true; + } else if (!log_chat && IsChatText(&((*buffer)[start]), + i + 1 - start)) { + censor_password_ = true; + } + + start = i + 1; + } + + if ((*buffer)[i] == '<' && start < i) { + if (censor_password_) { + LOG(INFO) << std::setw(nest) << " " << "## TEXT REMOVED ##"; + censor_password_ = false; + } else { + LOG(INFO) << std::setw(nest) << " " + << std::string(&((*buffer)[start]), i - start); + } + start = i; + } + } + buffer->erase(buffer->begin(), buffer->begin() + start); + } +} +} // namespace notifier + +#endif // if LOGGING diff --git a/chrome/browser/sync/notifier/communicator/xmpp_log.h b/chrome/browser/sync/notifier/communicator/xmpp_log.h new file mode 100644 index 0000000..a6d12bd --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xmpp_log.h @@ -0,0 +1,45 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_LOG_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_LOG_H_ + +#if LOGGING + +#include <vector> + +#include "talk/base/basictypes.h" +#include "talk/base/sigslot.h" + +namespace notifier { + +// Log the xmpp input and output. +class XmppLog : public sigslot::has_slots<> { + public: + XmppLog() : censor_password_(false) { + } + + void Input(const char* data, int len) { + xmpp_input_buffer_.insert(xmpp_input_buffer_.end(), data, data + len); + XmppPrint(false); + } + + void Output(const char* data, int len) { + xmpp_output_buffer_.insert(xmpp_output_buffer_.end(), data, data + len); + XmppPrint(true); + } + + private: + void XmppPrint(bool output); + + std::vector<char> xmpp_input_buffer_; + std::vector<char> xmpp_output_buffer_; + bool censor_password_; + DISALLOW_COPY_AND_ASSIGN(XmppLog); +}; +} // namespace notifier + +#endif // if LOGGING + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_LOG_H_ diff --git a/chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.cc b/chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.cc new file mode 100644 index 0000000..9bd65db --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.cc @@ -0,0 +1,437 @@ +// Copyright (c) 2009 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/sync/notifier/communicator/xmpp_socket_adapter.h" + +#include <iomanip> +#include <string> + +#include "chrome/browser/sync/notifier/communicator/product_info.h" +#include "talk/base/byteorder.h" +#include "talk/base/common.h" +#include "talk/base/firewallsocketserver.h" +#include "talk/base/logging.h" +#include "talk/base/socketadapters.h" +#include "talk/base/ssladapter.h" +#include "talk/xmpp/xmppengine.h" + +namespace notifier { + +XmppSocketAdapter::XmppSocketAdapter(const buzz::XmppClientSettings& xcs, + bool allow_unverified_certs) + : state_(STATE_CLOSED), + error_(ERROR_NONE), + wsa_error_(0), + socket_(NULL), + protocol_(xcs.protocol()), + firewall_(false), + write_buffer_(NULL), + write_buffer_length_(0), + write_buffer_capacity_(0), + allow_unverified_certs_(allow_unverified_certs) { + proxy_.type = xcs.proxy(); + proxy_.address.SetIP(xcs.proxy_host(), false); + proxy_.address.SetPort(xcs.proxy_port()); + proxy_.username = xcs.proxy_user(); + proxy_.password = xcs.proxy_pass(); +} + +XmppSocketAdapter::~XmppSocketAdapter() { + FreeState(); + + // Clean up any previous socket - cannot delete socket on close because + // close happens during the child socket's stack callback. + if (socket_) { + delete socket_; + socket_ = NULL; + } +} + +bool XmppSocketAdapter::FreeState() { + int code = 0; + + // Clean up the socket + if (socket_ && !(state_ == STATE_CLOSED || state_ == STATE_CLOSING)) { + code = socket_->Close(); + } + + delete[] write_buffer_; + write_buffer_ = NULL; + write_buffer_length_ = 0; + write_buffer_capacity_ = 0; + + if (code) { + SetWSAError(code); + return false; + } + return true; +} + +bool XmppSocketAdapter::Connect(const talk_base::SocketAddress& addr) { + if (state_ != STATE_CLOSED) { + SetError(ERROR_WRONGSTATE); + return false; + } + + LOG(LS_INFO) << "XmppSocketAdapter::Connect(" << addr.ToString() << ")"; + + // Clean up any previous socket - cannot delete socket on close because + // close happens during the child socket's stack callback. + if (socket_) { + delete socket_; + socket_ = NULL; + } + + talk_base::AsyncSocket* socket = + talk_base::Thread::Current()->socketserver() + ->CreateAsyncSocket(SOCK_STREAM); + if (!socket) { + SetWSAError(WSA_NOT_ENOUGH_MEMORY); + return false; + } + + if (firewall_) { + // TODO(sync): Change this to make WSAAsyncSockets support current thread + // socket server + talk_base::FirewallSocketServer* fw = + static_cast<talk_base::FirewallSocketServer*>( + talk_base::Thread::Current()->socketserver()); + socket = fw->WrapSocket(socket, SOCK_STREAM); + } + + if (proxy_.type) { + talk_base::AsyncSocket* proxy_socket = 0; + if (proxy_.type == talk_base::PROXY_SOCKS5) { + proxy_socket = new talk_base::AsyncSocksProxySocket( + socket, proxy_.address, proxy_.username, proxy_.password); + } else { + // Note: we are trying unknown proxies as HTTPS currently + proxy_socket = new talk_base::AsyncHttpsProxySocket(socket, + GetUserAgentString(), proxy_.address, + proxy_.username, proxy_.password); + } + if (!proxy_socket) { + SetWSAError(WSA_NOT_ENOUGH_MEMORY); + delete socket; + return false; + } + socket = proxy_socket; // for our purposes the proxy is now the socket + } + +// #if defined(PRODUCTION) + if (protocol_ == cricket::PROTO_SSLTCP) { + talk_base::AsyncSocket *fake_ssl_socket = + new talk_base::AsyncSSLSocket(socket); + if (!fake_ssl_socket) { + SetWSAError(WSA_NOT_ENOUGH_MEMORY); + delete socket; + return false; + } + socket = fake_ssl_socket; // for our purposes the SSL socket is the socket + } +// #endif // PRODUCTION + +#if defined(FEATURE_ENABLE_SSL) + talk_base::SSLAdapter* ssl = talk_base::SSLAdapter::Create(socket); + socket = ssl; +#endif + +// #if !defined(PRODUCTION) +// if (protocol_ == cricket::PROTO_SSLTCP) { +// ssl->set_ignore_bad_cert(true); +// ssl->StartSSL(addr.hostname().c_str(), true); +// } +// #endif // PRODUCTION + + socket->SignalReadEvent.connect(this, &XmppSocketAdapter::OnReadEvent); + socket->SignalWriteEvent.connect(this, &XmppSocketAdapter::OnWriteEvent); + socket->SignalConnectEvent.connect(this, &XmppSocketAdapter::OnConnectEvent); + socket->SignalCloseEvent.connect(this, &XmppSocketAdapter::OnCloseEvent); + + // The linux implementation of socket::Connect + // returns an error when the connect didn't complete + // yet. This can be distinguished from a failure + // because socket::IsBlocking is true. Perhaps, + // the linux implementation should be made to + // behave like the windows version which doesn't do this, + // but it seems to be a pattern with these methods + // that they return an error if the operation didn't + // complete in a sync fashion and one has to check IsBlocking + // to tell if was a "real" error. + if (socket->Connect(addr) == SOCKET_ERROR && !socket->IsBlocking()) { + SetWSAError(socket->GetError()); + delete socket; + return false; + } + + socket_ = socket; + state_ = STATE_CONNECTING; + return true; +} + +bool XmppSocketAdapter::Read(char* data, size_t len, size_t* len_read) { + if (len_read) + *len_read = 0; + + if (state_ <= STATE_CLOSING) { + SetError(ERROR_WRONGSTATE); + return false; + } + + ASSERT(socket_ != NULL); + + if (IsOpen()) { + int result = socket_->Recv(data, len); + if (result < 0) { + if (!socket_->IsBlocking()) { + SetWSAError(socket_->GetError()); + return false; + } + + result = 0; + } + + if (len_read) + *len_read = result; + } + + return true; +} + +bool XmppSocketAdapter::Write(const char* data, size_t len) { + if (state_ <= STATE_CLOSING) { + // There may be data in a buffer that gets lost. Too bad! + SetError(ERROR_WRONGSTATE); + return false; + } + + ASSERT(socket_ != NULL); + + size_t sent = 0; + + // try an immediate write when there is no buffer + // and we aren't in SSL mode or opening the connection + if (write_buffer_length_ == 0 && IsOpen()) { + int result = socket_->Send(data, len); + if (result < 0) { + if (!socket_->IsBlocking()) { + SetWSAError(socket_->GetError()); + return false; + } + result = 0; + } + + sent = static_cast<size_t>(result); + } + + // Buffer what we didn't send + if (sent < len) { + QueueWriteData(data + sent, len - sent); + } + + // Service the socket right away to push the written data out in SSL mode + return HandleWritable(); +} + +bool XmppSocketAdapter::Close() { + if (state_ == STATE_CLOSING) { + return false; // avoid recursion, but not unexpected + } + if (state_ == STATE_CLOSED) { + // in theory should not be trying to re-InternalClose. + SetError(ERROR_WRONGSTATE); + return false; + } + + // todo: deal with flushing close + // (flush, don't do reads, clean ssl) + + // If we've gotten to the point where we really do have a socket underneath + // then close it. It should call us back to tell us it is closed, and + // NotifyClose will be called. We indicate "closing" state so that we + // do not recusively try to keep closing the socket. + if (socket_) { + state_ = STATE_CLOSING; + socket_->Close(); + } + + // If we didn't get the callback, then we better make sure we signal + // closed. + if (state_ != STATE_CLOSED) { + // The socket was closed manually, not directly due to error. + if (error_ != ERROR_NONE) { + LOG(LS_INFO) << "XmppSocketAdapter::Close - previous Error: " << error_ + << " WSAError: " << wsa_error_; + error_ = ERROR_NONE; + wsa_error_ = 0; + } + NotifyClose(); + } + return true; +} + +void XmppSocketAdapter::NotifyClose() { + if (state_ == STATE_CLOSED) { + SetError(ERROR_WRONGSTATE); + } else { + LOG(LS_INFO) << "XmppSocketAdapter::NotifyClose - Error: " << error_ + << " WSAError: " << wsa_error_; + state_ = STATE_CLOSED; + SignalClosed(); + FreeState(); + } +} + +void XmppSocketAdapter::OnConnectEvent(talk_base::AsyncSocket *socket) { + if (state_ == STATE_CONNECTING) { + state_ = STATE_OPEN; + LOG(LS_INFO) << "XmppSocketAdapter::OnConnectEvent - STATE_OPEN"; + SignalConnected(); +#if defined(FEATURE_ENABLE_SSL) + } else if (state_ == STATE_TLS_CONNECTING) { + state_ = STATE_TLS_OPEN; + LOG(LS_INFO) << "XmppSocketAdapter::OnConnectEvent - STATE_TLS_OPEN"; + SignalSSLConnected(); + if (write_buffer_length_ > 0) { + HandleWritable(); + } +#endif // defined(FEATURE_ENABLE_SSL) + } else { + LOG(LS_INFO) << "XmppSocketAdapter::OnConnectEvent - state is " << state_; + ASSERT(false); + } +} + +void XmppSocketAdapter::OnReadEvent(talk_base::AsyncSocket *socket) { + HandleReadable(); +} + +void XmppSocketAdapter::OnWriteEvent(talk_base::AsyncSocket *socket) { + HandleWritable(); +} + +void XmppSocketAdapter::OnCloseEvent(talk_base::AsyncSocket *socket, + int error) { + LOG(LS_INFO) << "XmppSocketAdapter::OnCloseEvent(" << error << ")"; + SetWSAError(error); + if (error == SOCKET_EACCES) { + SignalAuthenticationError(); // proxy needs authentication + } + NotifyClose(); +} + +#if defined(FEATURE_ENABLE_SSL) +bool XmppSocketAdapter::StartTls(const std::string& verify_host_name) { + if (state_ != STATE_OPEN) { + SetError(ERROR_WRONGSTATE); + return false; + } + + state_ = STATE_TLS_CONNECTING; + + ASSERT(write_buffer_length_ == 0); + + talk_base::SSLAdapter* ssl_adapter = + static_cast<talk_base::SSLAdapter*>(socket_); + + if (allow_unverified_certs_) { + ssl_adapter->set_ignore_bad_cert(true); + } + + if (ssl_adapter->StartSSL(verify_host_name.c_str(), false) != 0) { + state_ = STATE_OPEN; + SetError(ERROR_SSL); + return false; + } + + return true; +} +#endif // defined(FEATURE_ENABLE_SSL) + +void XmppSocketAdapter::QueueWriteData(const char* data, size_t len) { + // expand buffer if needed + if (write_buffer_length_ + len > write_buffer_capacity_) { + size_t new_capacity = 1024; + while (new_capacity < write_buffer_length_ + len) { + new_capacity = new_capacity * 2; + } + char* new_buffer = new char[new_capacity]; + ASSERT(write_buffer_length_ <= 64000); + memcpy(new_buffer, write_buffer_, write_buffer_length_); + delete[] write_buffer_; + write_buffer_ = new_buffer; + write_buffer_capacity_ = new_capacity; + } + + // copy data into the end of buffer + memcpy(write_buffer_ + write_buffer_length_, data, len); + write_buffer_length_ += len; +} + +void XmppSocketAdapter::FlushWriteQueue(Error* error, int* wsa_error) { + ASSERT(error && wsa_error); + + size_t flushed = 0; + while (flushed < write_buffer_length_) { + int sent = socket_->Send(write_buffer_ + flushed, + static_cast<int>(write_buffer_length_ - flushed)); + if (sent < 0) { + if (!socket_->IsBlocking()) { + *error = ERROR_WINSOCK; + *wsa_error = socket_->GetError(); + } + break; + } + flushed += static_cast<size_t>(sent); + } + + // remove flushed memory + write_buffer_length_ -= flushed; + memmove(write_buffer_, write_buffer_ + flushed, write_buffer_length_); + + // when everything is flushed, deallocate the buffer if it's gotten big + if (write_buffer_length_ == 0) { + if (write_buffer_capacity_ > 8192) { + delete[] write_buffer_; + write_buffer_ = NULL; + write_buffer_capacity_ = 0; + } + } +} + +void XmppSocketAdapter::SetError(Error error) { + if (error_ == ERROR_NONE) { + error_ = error; + } +} + +void XmppSocketAdapter::SetWSAError(int error) { + if (error_ == ERROR_NONE && error != 0) { + error_ = ERROR_WINSOCK; + wsa_error_ = error; + } +} + +bool XmppSocketAdapter::HandleReadable() { + if (!IsOpen()) + return false; + + SignalRead(); + return true; +} + +bool XmppSocketAdapter::HandleWritable() { + if (!IsOpen()) + return false; + + Error error = ERROR_NONE; + int wsa_error = 0; + FlushWriteQueue(&error, &wsa_error); + if (error != ERROR_NONE) { + Close(); + return false; + } + return true; +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.h b/chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.h new file mode 100644 index 0000000..7e42988 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.h @@ -0,0 +1,85 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_SOCKET_ADAPTER_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_SOCKET_ADAPTER_H_ +#include <string> + +#include "talk/base/asyncsocket.h" +#include "talk/xmpp/asyncsocket.h" +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/xmpp/xmppengine.h" + +#ifndef _WIN32 +// Additional errors used by us from Win32 headers +#define SEC_E_CERT_EXPIRED static_cast<int>(0x80090328L) +#define WSA_NOT_ENOUGH_MEMORY ENOMEM +#endif + +namespace notifier { + +class XmppSocketAdapter : public buzz::AsyncSocket, + public sigslot::has_slots<> { + public: + XmppSocketAdapter(const buzz::XmppClientSettings& xcs, + bool allow_unverified_certs); + virtual ~XmppSocketAdapter(); + + virtual State state() { return state_; } + virtual Error error() { return error_; } + virtual int GetError() { return wsa_error_; } + + void set_firewall(bool firewall) { firewall_ = firewall; } + + virtual bool Connect(const talk_base::SocketAddress& addr); + virtual bool Read(char* data, size_t len, size_t* len_read); + virtual bool Write(const char* data, size_t len); + virtual bool Close(); + +#if defined(FEATURE_ENABLE_SSL) + bool StartTls(const std::string& domainname); + bool IsOpen() const { return state_ == STATE_OPEN + || state_ == STATE_TLS_OPEN; } +#else + bool IsOpen() const { return state_ == STATE_OPEN; } +#endif + + sigslot::signal0<> SignalAuthenticationError; + + private: + // return false if the socket is closed + bool HandleReadable(); + bool HandleWritable(); + + State state_; + Error error_; + int wsa_error_; + + talk_base::AsyncSocket* socket_; + cricket::ProtocolType protocol_; + talk_base::ProxyInfo proxy_; + bool firewall_; + char* write_buffer_; + size_t write_buffer_length_; + size_t write_buffer_capacity_; + bool allow_unverified_certs_; + + bool FreeState(); + void NotifyClose(); + + void OnReadEvent(talk_base::AsyncSocket* socket); + void OnWriteEvent(talk_base::AsyncSocket* socket); + void OnConnectEvent(talk_base::AsyncSocket* socket); + void OnCloseEvent(talk_base::AsyncSocket* socket, int error); + + void QueueWriteData(const char* data, size_t len); + void FlushWriteQueue(Error* error, int* wsa_error); + + void SetError(Error error); + void SetWSAError(int error); + DISALLOW_COPY_AND_ASSIGN(XmppSocketAdapter); +}; +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_SOCKET_ADAPTER_H_ |