summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync/notifier
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/sync/notifier')
-rw-r--r--chrome/browser/sync/notifier/base/async_dns_lookup.cc133
-rw-r--r--chrome/browser/sync/notifier/base/async_dns_lookup.h49
-rw-r--r--chrome/browser/sync/notifier/base/async_network_alive.h52
-rw-r--r--chrome/browser/sync/notifier/base/fastalloc.h59
-rw-r--r--chrome/browser/sync/notifier/base/linux/network_status_detector_task_linux.cc15
-rw-r--r--chrome/browser/sync/notifier/base/linux/time_linux.cc7
-rw-r--r--chrome/browser/sync/notifier/base/nethelpers.cc42
-rw-r--r--chrome/browser/sync/notifier/base/nethelpers.h25
-rw-r--r--chrome/browser/sync/notifier/base/network_status_detector_task.cc30
-rw-r--r--chrome/browser/sync/notifier/base/network_status_detector_task.h55
-rw-r--r--chrome/browser/sync/notifier/base/network_status_detector_task_mt.cc48
-rw-r--r--chrome/browser/sync/notifier/base/network_status_detector_task_mt.h34
-rw-r--r--chrome/browser/sync/notifier/base/posix/time_posix.cc54
-rw-r--r--chrome/browser/sync/notifier/base/signal_thread_task.h92
-rw-r--r--chrome/browser/sync/notifier/base/static_assert.h19
-rw-r--r--chrome/browser/sync/notifier/base/string.cc403
-rw-r--r--chrome/browser/sync/notifier/base/string.h381
-rw-r--r--chrome/browser/sync/notifier/base/string_unittest.cc362
-rw-r--r--chrome/browser/sync/notifier/base/task_pump.cc42
-rw-r--r--chrome/browser/sync/notifier/base/task_pump.h34
-rw-r--r--chrome/browser/sync/notifier/base/time.cc360
-rw-r--r--chrome/browser/sync/notifier/base/time.h114
-rw-r--r--chrome/browser/sync/notifier/base/time_unittest.cc73
-rw-r--r--chrome/browser/sync/notifier/base/timer.cc33
-rw-r--r--chrome/browser/sync/notifier/base/timer.h40
-rw-r--r--chrome/browser/sync/notifier/base/utils.h91
-rw-r--r--chrome/browser/sync/notifier/base/win32/async_network_alive_win32.cc233
-rw-r--r--chrome/browser/sync/notifier/base/win32/time_win32.cc158
-rw-r--r--chrome/browser/sync/notifier/communicator/auth_task.cc69
-rw-r--r--chrome/browser/sync/notifier/communicator/auth_task.h77
-rw-r--r--chrome/browser/sync/notifier/communicator/auto_reconnect.cc155
-rw-r--r--chrome/browser/sync/notifier/communicator/auto_reconnect.h71
-rw-r--r--chrome/browser/sync/notifier/communicator/connection_options.cc16
-rw-r--r--chrome/browser/sync/notifier/communicator/connection_options.h55
-rw-r--r--chrome/browser/sync/notifier/communicator/connection_settings.cc126
-rw-r--r--chrome/browser/sync/notifier/communicator/connection_settings.h78
-rw-r--r--chrome/browser/sync/notifier/communicator/const_communicator.h11
-rw-r--r--chrome/browser/sync/notifier/communicator/login.cc361
-rw-r--r--chrome/browser/sync/notifier/communicator/login.h155
-rw-r--r--chrome/browser/sync/notifier/communicator/login_failure.cc45
-rw-r--r--chrome/browser/sync/notifier/communicator/login_failure.h69
-rw-r--r--chrome/browser/sync/notifier/communicator/login_settings.cc57
-rw-r--r--chrome/browser/sync/notifier/communicator/login_settings.h97
-rw-r--r--chrome/browser/sync/notifier/communicator/mailbox.cc682
-rw-r--r--chrome/browser/sync/notifier/communicator/mailbox.h166
-rw-r--r--chrome/browser/sync/notifier/communicator/mailbox_unittest.cc118
-rw-r--r--chrome/browser/sync/notifier/communicator/product_info.cc15
-rw-r--r--chrome/browser/sync/notifier/communicator/product_info.h14
-rw-r--r--chrome/browser/sync/notifier/communicator/single_login_attempt.cc562
-rw-r--r--chrome/browser/sync/notifier/communicator/single_login_attempt.h139
-rw-r--r--chrome/browser/sync/notifier/communicator/talk_auth_task.cc73
-rw-r--r--chrome/browser/sync/notifier/communicator/talk_auth_task.h62
-rw-r--r--chrome/browser/sync/notifier/communicator/xml_parse_helpers-inl.h24
-rw-r--r--chrome/browser/sync/notifier/communicator/xml_parse_helpers.cc185
-rw-r--r--chrome/browser/sync/notifier/communicator/xml_parse_helpers.h75
-rw-r--r--chrome/browser/sync/notifier/communicator/xmpp_connection_generator.cc210
-rw-r--r--chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h81
-rw-r--r--chrome/browser/sync/notifier/communicator/xmpp_log.cc111
-rw-r--r--chrome/browser/sync/notifier/communicator/xmpp_log.h45
-rw-r--r--chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.cc437
-rw-r--r--chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.h85
-rw-r--r--chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc442
-rw-r--r--chrome/browser/sync/notifier/gaia_auth/gaiaauth.h129
-rw-r--r--chrome/browser/sync/notifier/gaia_auth/gaiahelper.cc236
-rw-r--r--chrome/browser/sync/notifier/gaia_auth/gaiahelper.h87
-rw-r--r--chrome/browser/sync/notifier/gaia_auth/inet_aton.h14
-rw-r--r--chrome/browser/sync/notifier/gaia_auth/sigslotrepeater.h86
-rw-r--r--chrome/browser/sync/notifier/gaia_auth/win32window.cc115
-rw-r--r--chrome/browser/sync/notifier/listener/listen_task.cc72
-rw-r--r--chrome/browser/sync/notifier/listener/listen_task.h47
-rw-r--r--chrome/browser/sync/notifier/listener/listener_unittest.cc10
-rw-r--r--chrome/browser/sync/notifier/listener/mediator_thread.h43
-rw-r--r--chrome/browser/sync/notifier/listener/mediator_thread_impl.cc278
-rw-r--r--chrome/browser/sync/notifier/listener/mediator_thread_impl.h120
-rw-r--r--chrome/browser/sync/notifier/listener/mediator_thread_mock.h74
-rw-r--r--chrome/browser/sync/notifier/listener/send_update_task.cc96
-rw-r--r--chrome/browser/sync/notifier/listener/send_update_task.h37
-rw-r--r--chrome/browser/sync/notifier/listener/subscribe_task.cc90
-rw-r--r--chrome/browser/sync/notifier/listener/subscribe_task.h39
-rw-r--r--chrome/browser/sync/notifier/listener/talk_mediator.h71
-rw-r--r--chrome/browser/sync/notifier/listener/talk_mediator_impl.cc275
-rw-r--r--chrome/browser/sync/notifier/listener/talk_mediator_impl.h117
-rw-r--r--chrome/browser/sync/notifier/listener/talk_mediator_unittest.cc176
83 files changed, 10218 insertions, 0 deletions
diff --git a/chrome/browser/sync/notifier/base/async_dns_lookup.cc b/chrome/browser/sync/notifier/base/async_dns_lookup.cc
new file mode 100644
index 0000000..0d3ce87
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/async_dns_lookup.cc
@@ -0,0 +1,133 @@
+// 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/base/async_dns_lookup.h"
+
+#ifdef POSIX
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#endif // POSIX
+
+#include <vector>
+
+#include "chrome/browser/sync/notifier/base/nethelpers.h"
+#include "chrome/browser/sync/notifier/gaia_auth/inet_aton.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+
+enum { MSG_TIMEOUT = talk_base::SignalThread::ST_MSG_FIRST_AVAILABLE };
+
+#ifndef WIN32
+const int WSAHOST_NOT_FOUND = 11001; // follows the format in winsock2.h
+#endif // WIN32
+
+namespace notifier {
+
+AsyncDNSLookup::AsyncDNSLookup(const talk_base::SocketAddress& server)
+ : server_(new talk_base::SocketAddress(server)),
+ error_(0) {
+ // Timeout after 5 seconds.
+ talk_base::Thread::Current()->PostDelayed(5000, this, MSG_TIMEOUT);
+}
+
+AsyncDNSLookup::~AsyncDNSLookup() {
+}
+
+void AsyncDNSLookup::DoWork() {
+ std::string hostname(server_->IPAsString());
+
+ in_addr addr;
+ if (inet_aton(hostname.c_str(), &addr)) {
+ talk_base::CritScope scope(&cs_);
+ ip_list_.push_back(talk_base::NetworkToHost32(addr.s_addr));
+ } else {
+ LOG_F(LS_VERBOSE) << "(" << hostname << ")";
+ hostent ent;
+ char buffer[8192];
+ int errcode = 0;
+ hostent* host = SafeGetHostByName(hostname.c_str(), &ent,
+ buffer, sizeof(buffer),
+ &errcode);
+ talk_base::Thread::Current()->Clear(this, MSG_TIMEOUT);
+ if (host) {
+ talk_base::CritScope scope(&cs_);
+
+ // Check to see if this already timed out.
+ if (error_ == 0) {
+ for (int index = 0; true; ++index) {
+ uint32* addr = reinterpret_cast<uint32*>(host->h_addr_list[index]);
+ if (addr == 0) { // 0 = end of list
+ break;
+ }
+ uint32 ip = talk_base::NetworkToHost32(*addr);
+ LOG_F(LS_VERBOSE) << "(" << hostname << ") resolved to: "
+ << talk_base::SocketAddress::IPToString(ip);
+ ip_list_.push_back(ip);
+ }
+ // Maintain the invariant that either the list is not empty
+ // or the error is non zero when we are done with processing
+ // the dnslookup.
+ if (ip_list_.empty() && error_ == 0) {
+ error_ = WSAHOST_NOT_FOUND;
+ }
+ }
+ FreeHostEnt(host);
+ } else {
+ { // Scoping for the critical section.
+ talk_base::CritScope scope(&cs_);
+
+ // Check to see if this already timed out.
+ if (error_ == 0) {
+ error_ = errcode;
+ }
+ }
+ LOG_F(LS_ERROR) << "(" << hostname << ") error: " << error_;
+ }
+ }
+}
+
+void AsyncDNSLookup::OnMessage(talk_base::Message* message) {
+ ASSERT(message);
+ if (message->message_id == MSG_TIMEOUT) {
+ OnTimeout();
+ } else {
+ talk_base::SignalThread::OnMessage(message);
+ }
+}
+
+void AsyncDNSLookup::OnTimeout() {
+ // Allow the scope for the critical section to be the whole
+ // method, just to be sure that the worker thread can't exit
+ // while we are doing SignalWorkDone (because that could possibly
+ // cause the class to be deleted).
+ talk_base::CritScope scope(&cs_);
+
+ // Check to see if the ip list was already filled (or errored out).
+ if (!ip_list_.empty() || error_ != 0) {
+ return;
+ }
+
+ // Worker thread is taking too long so timeout.
+ error_ = WSAHOST_NOT_FOUND;
+
+ // Rely on the caller to do the Release/Destroy.
+ //
+ // Doing this signal while holding cs_ won't cause a deadlock because
+ // the AsyncDNSLookup::DoWork thread doesn't have any locks at this point,
+ // and it is the only thread being held up by this.
+ SignalWorkDone(this);
+
+ // Ensure that no more "WorkDone" signaling is done.
+ // Don't call Release or Destroy since that was already done
+ // by the callback.
+ SignalWorkDone.disconnect_all();
+}
+} // namespace notifier
diff --git a/chrome/browser/sync/notifier/base/async_dns_lookup.h b/chrome/browser/sync/notifier/base/async_dns_lookup.h
new file mode 100644
index 0000000..123d311
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/async_dns_lookup.h
@@ -0,0 +1,49 @@
+// 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_BASE_ASYNC_DNS_LOOKUP_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_BASE_ASYNC_DNS_LOOKUP_H_
+
+#include <vector>
+
+#include "base/scoped_ptr.h"
+#include "talk/base/signalthread.h"
+
+namespace talk_base {
+class SocketAddress;
+class Task;
+}
+
+namespace notifier {
+
+class AsyncDNSLookup : public talk_base::SignalThread {
+ public:
+ explicit AsyncDNSLookup(const talk_base::SocketAddress& server);
+ virtual ~AsyncDNSLookup();
+
+ const int error() const {
+ return error_;
+ }
+
+ const std::vector<uint32>& ip_list() const {
+ return ip_list_;
+ }
+
+ protected:
+ // SignalThread Interface
+ virtual void DoWork();
+ virtual void OnMessage(talk_base::Message* message);
+
+ private:
+ void OnTimeout();
+
+ scoped_ptr<talk_base::SocketAddress> server_;
+ talk_base::CriticalSection cs_;
+ int error_;
+ std::vector<uint32> ip_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(AsyncDNSLookup);
+};
+} // namespace notifier
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_BASE_ASYNC_DNS_LOOKUP_H_
diff --git a/chrome/browser/sync/notifier/base/async_network_alive.h b/chrome/browser/sync/notifier/base/async_network_alive.h
new file mode 100644
index 0000000..330348d
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/async_network_alive.h
@@ -0,0 +1,52 @@
+// 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_BASE_ASYNC_NETWORK_ALIVE_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_BASE_ASYNC_NETWORK_ALIVE_H_
+
+#include "talk/base/signalthread.h"
+
+namespace notifier {
+
+// System specific info needed for changes
+class PlatformNetworkInfo;
+
+class AsyncNetworkAlive : public talk_base::SignalThread {
+ public:
+ static AsyncNetworkAlive* Create();
+
+ virtual ~AsyncNetworkAlive() {}
+
+ bool alive() const {
+ return alive_;
+ }
+
+ bool error() const {
+ return error_;
+ }
+
+ void SetWaitForNetworkChange(PlatformNetworkInfo* previous_info) {
+ network_info_ = previous_info;
+ }
+
+ PlatformNetworkInfo* ReleaseInfo() {
+ PlatformNetworkInfo* info = network_info_;
+ network_info_ = NULL;
+ return info;
+ }
+
+ protected:
+ AsyncNetworkAlive() : network_info_(NULL), alive_(false), error_(false) {
+ }
+
+ protected:
+ PlatformNetworkInfo* network_info_;
+ bool alive_;
+ bool error_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AsyncNetworkAlive);
+};
+} // namespace notifier
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_BASE_ASYNC_NETWORK_ALIVE_H_
diff --git a/chrome/browser/sync/notifier/base/fastalloc.h b/chrome/browser/sync/notifier/base/fastalloc.h
new file mode 100644
index 0000000..ed19a53
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/fastalloc.h
@@ -0,0 +1,59 @@
+// 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_BASE_FASTALLOC_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_BASE_FASTALLOC_H_
+
+#include <assert.h>
+
+namespace notifier {
+
+template<class T, size_t Size>
+class FastAlloc {
+ public:
+ FastAlloc() : buffer_(NULL), size_(0) { };
+ ~FastAlloc() { freeBuffer(); }
+ T* get_buffer(size_t size) {
+ if (size_ != 0) {
+ // We only allow one call to get_buffer. This makes the logic here
+ // simpler, and the user has to worry less about someone else calling
+ // get_buffer again on the same FastAlloc object and invalidating the
+ // memory they were using.
+ assert(false && "get_buffer may one be called once");
+ return NULL;
+ }
+
+ if (size <= Size) {
+ buffer_ = internal_buffer_;
+ } else {
+ buffer_ = new T[size];
+ }
+
+ if (buffer_ != NULL) {
+ size_ = size;
+ }
+ return buffer_;
+ }
+
+ private:
+ void freeBuffer() {
+#ifdef DEBUG
+ memset(buffer_, 0xCC, size_ * sizeof(T));
+#endif
+
+ if (buffer_ != NULL && buffer_ != internal_buffer_) {
+ delete[] buffer_;
+ }
+ buffer_ = NULL;
+ size_ = 0;
+ }
+
+ T* buffer_;
+ T internal_buffer_[Size];
+ size_t size_;
+};
+
+}
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_BASE_FASTALLOC_H_
diff --git a/chrome/browser/sync/notifier/base/linux/network_status_detector_task_linux.cc b/chrome/browser/sync/notifier/base/linux/network_status_detector_task_linux.cc
new file mode 100644
index 0000000..e232bcb
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/linux/network_status_detector_task_linux.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 "chrome/browser/sync/notifier/base/network_status_detector_task.h"
+
+namespace notifier {
+
+NetworkStatusDetectorTask* NetworkStatusDetectorTask::Create(
+ talk_base::Task* parent) {
+ // TODO(sync): No implementation for linux
+ return NULL;
+}
+
+} // namespace notifier
diff --git a/chrome/browser/sync/notifier/base/linux/time_linux.cc b/chrome/browser/sync/notifier/base/linux/time_linux.cc
new file mode 100644
index 0000000..ea2acf5
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/linux/time_linux.cc
@@ -0,0 +1,7 @@
+// 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.
+//
+// Time functions
+
+#include "chrome/browser/sync/notifier/base/posix/time_posix.cc"
diff --git a/chrome/browser/sync/notifier/base/nethelpers.cc b/chrome/browser/sync/notifier/base/nethelpers.cc
new file mode 100644
index 0000000..23fc8d2
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/nethelpers.cc
@@ -0,0 +1,42 @@
+// 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/base/nethelpers.h"
+
+namespace notifier {
+
+hostent* SafeGetHostByName(const char* hostname, hostent* host,
+ char* buffer, size_t buffer_len,
+ int* herrno) {
+ hostent* result = NULL;
+#if WIN32
+ result = gethostbyname(hostname);
+ if (!result) {
+ *herrno = WSAGetLastError();
+ }
+#elif OS_LINUX
+ gethostbyname_r(hostname, host, buffer, buffer_len, &result, herrno);
+#elif OSX
+ result = getipnodebyname(hostname, AF_INET, AI_DEFAULT, herrno);
+#else
+#error "I don't know how to do gethostbyname safely on your system."
+#endif
+ return result;
+}
+
+// This function should mirror the above function, and free any resources
+// allocated by the above.
+void FreeHostEnt(hostent* host) {
+#if WIN32
+ // No need to free anything, struct returned is static memory.
+#elif OS_LINUX
+ // No need to free anything, we pass in a pointer to a struct.
+#elif OSX
+ freehostent(host);
+#else
+#error "I don't know how to free a hostent on your system."
+#endif
+}
+
+} // namespace notifier
diff --git a/chrome/browser/sync/notifier/base/nethelpers.h b/chrome/browser/sync/notifier/base/nethelpers.h
new file mode 100644
index 0000000..d2b9fd4
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/nethelpers.h
@@ -0,0 +1,25 @@
+// 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_BASE_NETHELPERS_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_BASE_NETHELPERS_H_
+
+#ifdef POSIX
+#include <netdb.h>
+#include <cstddef>
+#elif WIN32
+#include <winsock2.h>
+#endif
+
+namespace notifier {
+
+hostent* SafeGetHostByName(const char* hostname, hostent* host,
+ char* buffer, size_t buffer_len,
+ int* herrno);
+
+void FreeHostEnt(hostent* host);
+
+} // namespace notifier
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_BASE_NETHELPERS_H_
diff --git a/chrome/browser/sync/notifier/base/network_status_detector_task.cc b/chrome/browser/sync/notifier/base/network_status_detector_task.cc
new file mode 100644
index 0000000..f9acd88
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/network_status_detector_task.cc
@@ -0,0 +1,30 @@
+// 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/base/network_status_detector_task.h"
+
+namespace notifier {
+
+void NetworkStatusDetectorTask::DetectNetworkState() {
+ // If the detection has been finished, then just broadcast the current
+ // state. Otherwise, allow the signal to be sent when the initial
+ // detection is finished.
+ if (initial_detection_done_) {
+ SignalNetworkStateDetected(is_alive_, is_alive_);
+ }
+}
+
+void NetworkStatusDetectorTask::SetNetworkAlive(bool is_alive) {
+ bool was_alive = is_alive_;
+ is_alive_ = is_alive;
+
+ if (!initial_detection_done_ || was_alive != is_alive_) {
+ initial_detection_done_ = true;
+
+ // Tell everyone about the network state change.
+ SignalNetworkStateDetected(was_alive, is_alive_);
+ }
+}
+
+} // namespace notifier
diff --git a/chrome/browser/sync/notifier/base/network_status_detector_task.h b/chrome/browser/sync/notifier/base/network_status_detector_task.h
new file mode 100644
index 0000000..4cf190e
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/network_status_detector_task.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_BASE_NETWORK_STATUS_DETECTOR_TASK_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_BASE_NETWORK_STATUS_DETECTOR_TASK_H_
+
+#include "chrome/browser/sync/notifier/base/time.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/task.h"
+
+namespace notifier {
+class AsyncNetworkAlive;
+
+// Detects the current network state and any changes to that.
+class NetworkStatusDetectorTask : public talk_base::Task,
+ public sigslot::has_slots<> {
+ public:
+ // Create an instance of (a subclass of) this class.
+ static NetworkStatusDetectorTask* Create(talk_base::Task* parent);
+
+ // Determines the current network state and
+ // then calls SignalNetworkStateDetected.
+ void DetectNetworkState();
+
+ // Fires whenever the network state is detected.
+ // SignalNetworkStateDetected(was_alive, is_alive);
+ sigslot::signal2<bool, bool> SignalNetworkStateDetected;
+
+ protected:
+ explicit NetworkStatusDetectorTask(talk_base::Task* parent)
+ : talk_base::Task(parent),
+ initial_detection_done_(false),
+ is_alive_(false) {
+ }
+
+ virtual ~NetworkStatusDetectorTask() { }
+
+ virtual int ProcessStart() = 0;
+
+ // Stay around until aborted.
+ virtual int ProcessResponse() {
+ return STATE_BLOCKED;
+ }
+
+ void SetNetworkAlive(bool is_alive);
+
+ private:
+ bool initial_detection_done_;
+ bool is_alive_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkStatusDetectorTask);
+};
+} // namespace notifier
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_BASE_NETWORK_STATUS_DETECTOR_TASK_H_
diff --git a/chrome/browser/sync/notifier/base/network_status_detector_task_mt.cc b/chrome/browser/sync/notifier/base/network_status_detector_task_mt.cc
new file mode 100644
index 0000000..d4e406c
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/network_status_detector_task_mt.cc
@@ -0,0 +1,48 @@
+// 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/base/network_status_detector_task_mt.h"
+
+#include "chrome/browser/sync/notifier/base/async_network_alive.h"
+#include "chrome/browser/sync/notifier/base/signal_thread_task.h"
+
+#include "talk/base/common.h"
+
+namespace notifier {
+
+void NetworkStatusDetectorTaskMT::OnNetworkAliveDone(
+ AsyncNetworkAlive* network_alive) {
+ ASSERT(network_alive);
+ SetNetworkAlive(network_alive->alive());
+ // If we got an error from detecting the network alive state,
+ // then stop retrying the detection.
+ if (network_alive->error()) {
+ return;
+ }
+ StartAsyncDetection(network_alive->ReleaseInfo());
+}
+
+void NetworkStatusDetectorTaskMT::StartAsyncDetection(
+ PlatformNetworkInfo* previous_info) {
+ // Use the AsyncNetworkAlive to determine the network state (and
+ // changes in the network state).
+ AsyncNetworkAlive* network_alive = AsyncNetworkAlive::Create();
+
+ if (previous_info) {
+ network_alive->SetWaitForNetworkChange(previous_info);
+ }
+ SignalThreadTask<AsyncNetworkAlive>* task =
+ new SignalThreadTask<AsyncNetworkAlive>(this, &network_alive);
+ task->SignalWorkDone.connect(
+ this, &NetworkStatusDetectorTaskMT::OnNetworkAliveDone);
+ task->Start();
+}
+
+NetworkStatusDetectorTask* NetworkStatusDetectorTask::Create(
+ talk_base::Task* parent) {
+ ASSERT(parent);
+ return new NetworkStatusDetectorTaskMT(parent);
+}
+
+} // namespace notifier
diff --git a/chrome/browser/sync/notifier/base/network_status_detector_task_mt.h b/chrome/browser/sync/notifier/base/network_status_detector_task_mt.h
new file mode 100644
index 0000000..e1812f2
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/network_status_detector_task_mt.h
@@ -0,0 +1,34 @@
+// 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_BASE_NETWORK_STATUS_DETECTOR_TASK_MT_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_BASE_NETWORK_STATUS_DETECTOR_TASK_MT_H_
+
+#include "chrome/browser/sync/notifier/base/network_status_detector_task.h"
+
+namespace notifier {
+
+class AsyncNetworkAlive;
+class PlatformNetworkInfo;
+
+class NetworkStatusDetectorTaskMT : public NetworkStatusDetectorTask {
+ public:
+ explicit NetworkStatusDetectorTaskMT(talk_base::Task* parent)
+ : NetworkStatusDetectorTask(parent) {
+ }
+
+ protected:
+ virtual int ProcessStart() {
+ StartAsyncDetection(NULL);
+ return STATE_RESPONSE;
+ }
+
+ private:
+ void OnNetworkAliveDone(AsyncNetworkAlive* network_alive);
+ void StartAsyncDetection(PlatformNetworkInfo* network_info);
+};
+
+} // namespace notifier
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_BASE_NETWORK_STATUS_DETECTOR_TASK_MT_H_
diff --git a/chrome/browser/sync/notifier/base/posix/time_posix.cc b/chrome/browser/sync/notifier/base/posix/time_posix.cc
new file mode 100644
index 0000000..849f802
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/posix/time_posix.cc
@@ -0,0 +1,54 @@
+// 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 <assert.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "chrome/browser/sync/notifier/base/time.h"
+
+namespace notifier {
+
+time64 GetCurrent100NSTime() {
+ struct timeval tv;
+ struct timezone tz;
+
+ gettimeofday(&tv, &tz);
+
+ time64 retval = tv.tv_sec * kSecsTo100ns;
+ retval += tv.tv_usec * kMicrosecsTo100ns;
+ retval += kStart100NsTimeToEpoch;
+ return retval;
+}
+
+time64 TmToTime64(const struct tm& tm) {
+ struct tm tm_temp;
+ memcpy(&tm_temp, &tm, sizeof(struct tm));
+ time_t t = timegm(&tm_temp);
+ return t * kSecsTo100ns;
+}
+
+bool Time64ToTm(time64 t, struct tm* tm) {
+ assert(tm != NULL);
+ time_t secs = t / kSecsTo100ns;
+ gmtime_r(&secs, tm);
+ return true;
+}
+
+bool UtcTimeToLocalTime(struct tm* tm) {
+ assert(tm != NULL);
+ time_t t = timegm(tm);
+ localtime_r(&t, tm);
+ return true;
+}
+
+bool LocalTimeToUtcTime(struct tm* tm) {
+ assert(tm != NULL);
+ time_t t = mktime(tm);
+ gmtime_r(&t, tm);
+ return true;
+}
+
+} // namespace notifier
diff --git a/chrome/browser/sync/notifier/base/signal_thread_task.h b/chrome/browser/sync/notifier/base/signal_thread_task.h
new file mode 100644
index 0000000..93059d8
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/signal_thread_task.h
@@ -0,0 +1,92 @@
+// 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_BASE_SIGNAL_THREAD_TASK_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_BASE_SIGNAL_THREAD_TASK_H_
+
+#include "talk/base/common.h"
+#include "talk/base/signalthread.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/task.h"
+
+namespace notifier {
+
+template<class T>
+class SignalThreadTask : public talk_base::Task,
+ public sigslot::has_slots<> {
+ public:
+ // Takes ownership of signal_thread.
+ SignalThreadTask(talk_base::Task* task_parent, T** signal_thread)
+ : talk_base::Task(task_parent),
+ signal_thread_(NULL),
+ finished_(false) {
+ SetSignalThread(signal_thread);
+ }
+
+ virtual ~SignalThreadTask() {
+ ClearSignalThread();
+ }
+
+ virtual void Stop() {
+ Task::Stop();
+ ClearSignalThread();
+ }
+
+ virtual int ProcessStart() {
+ ASSERT(GetState() == talk_base::Task::STATE_START);
+ signal_thread_->SignalWorkDone.connect(
+ this,
+ &SignalThreadTask<T>::OnWorkDone);
+ signal_thread_->Start();
+ return talk_base::Task::STATE_RESPONSE;
+ }
+
+ int ProcessResponse() {
+ if (!finished_) {
+ return talk_base::Task::STATE_BLOCKED;
+ }
+ SignalWorkDone(signal_thread_);
+ ClearSignalThread();
+ return talk_base::Task::STATE_DONE;
+ }
+
+ sigslot::signal1<T*> SignalWorkDone;
+
+ private:
+ // Takes ownership of signal_thread
+ void SetSignalThread(T** signal_thread) {
+ ASSERT(!signal_thread_ && signal_thread && *signal_thread);
+ // Verify that no one is listening to the signal thread
+ // for work done. They should be using this class instead.
+ ASSERT((*signal_thread)->SignalWorkDone.is_empty());
+
+ signal_thread_ = *signal_thread;
+
+ // Helps callers not to use signal thread after this point
+ // since this class has taken ownership (and avoid the
+ // error of doing signal_thread->Start()).
+ *signal_thread = NULL;
+ }
+
+ void OnWorkDone(talk_base::SignalThread* signal_thread) {
+ ASSERT(signal_thread == signal_thread_);
+ finished_ = true;
+ Wake();
+ }
+
+ void ClearSignalThread() {
+ if (signal_thread_) {
+ signal_thread_->Destroy();
+ signal_thread_ = NULL;
+ }
+ }
+
+ T* signal_thread_;
+ bool finished_;
+ DISALLOW_COPY_AND_ASSIGN(SignalThreadTask);
+};
+
+} // namespace notifier
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_BASE_SIGNAL_THREAD_TASK_H_
diff --git a/chrome/browser/sync/notifier/base/static_assert.h b/chrome/browser/sync/notifier/base/static_assert.h
new file mode 100644
index 0000000..78e6ac3
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/static_assert.h
@@ -0,0 +1,19 @@
+// 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_BASE_STATIC_ASSERT_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_BASE_STATIC_ASSERT_H_
+
+template <bool> struct STATIC_ASSERTION_FAILURE;
+
+template <> struct STATIC_ASSERTION_FAILURE<true> { enum { value = 1 }; };
+
+template<int> struct static_assert_test{};
+
+#define STATIC_ASSERT(B) \
+typedef static_assert_test<\
+ sizeof(STATIC_ASSERTION_FAILURE< (bool)( B ) >)>\
+ static_assert_typedef_ ## __LINE__
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_BASE_STATIC_ASSERT_H_
diff --git a/chrome/browser/sync/notifier/base/string.cc b/chrome/browser/sync/notifier/base/string.cc
new file mode 100644
index 0000000..c3ef54d
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/string.cc
@@ -0,0 +1,403 @@
+// 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.
+
+#ifdef OS_MACOSX
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#include <float.h>
+#include <string.h>
+
+#include "base/format_macros.h"
+#include "base/string_util.h"
+#include "chrome/browser/sync/notifier/base/string.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringencode.h"
+
+using base::snprintf;
+
+namespace notifier {
+
+std::string HtmlEncode(const std::string& src) {
+ size_t max_length = src.length() * 6 + 1;
+ std::string dest;
+ dest.resize(max_length);
+ size_t new_size = talk_base::html_encode(&dest[0], max_length,
+ src.data(), src.length());
+ dest.resize(new_size);
+ return dest;
+}
+
+std::string HtmlDecode(const std::string& src) {
+ size_t max_length = src.length() + 1;
+ std::string dest;
+ dest.resize(max_length);
+ size_t new_size = talk_base::html_decode(&dest[0], max_length,
+ src.data(), src.length());
+ dest.resize(new_size);
+ return dest;
+}
+
+std::string UrlEncode(const std::string& src) {
+ size_t max_length = src.length() * 6 + 1;
+ std::string dest;
+ dest.resize(max_length);
+ size_t new_size = talk_base::url_encode(&dest[0], max_length,
+ src.data(), src.length());
+ dest.resize(new_size);
+ return dest;
+}
+
+std::string UrlDecode(const std::string& src) {
+ size_t max_length = src.length() + 1;
+ std::string dest;
+ dest.resize(max_length);
+ size_t new_size = talk_base::url_decode(&dest[0], max_length,
+ src.data(), src.length());
+ dest.resize(new_size);
+ return dest;
+}
+
+int CharToHexValue(char hex) {
+ if (hex >= '0' && hex <= '9') {
+ return hex - '0';
+ } else if (hex >= 'A' && hex <= 'F') {
+ return hex - 'A' + 10;
+ } else if (hex >= 'a' && hex <= 'f') {
+ return hex - 'a' + 10;
+ } else {
+ return -1;
+ }
+}
+
+// Template function to convert a string to an int/int64
+// If strict is true, check for the validity and overflow
+template<typename T>
+bool ParseStringToIntTemplate(const char* str,
+ T* value,
+ bool strict,
+ T min_value) {
+ ASSERT(str);
+ ASSERT(value);
+
+ // Skip spaces
+ while (*str == ' ') {
+ ++str;
+ }
+
+ // Process sign
+ int c = static_cast<int>(*str++); // current char
+ int possible_sign = c; // save sign indication
+ if (c == '-' || c == '+') {
+ c = static_cast<int>(*str++);
+ }
+
+ // Process numbers
+ T total = 0;
+ while (c && (c = CharToDigit(static_cast<char>(c))) != -1) {
+ // Check for overflow
+ if (strict && (total < min_value / 10 ||
+ (total == min_value / 10 &&
+ c > ((-(min_value + 10)) % 10)))) {
+ return false;
+ }
+
+ // Accumulate digit
+ // Note that we accumulate in the negative direction so that we will not
+ // blow away with the largest negative number
+ total = 10 * total - c;
+
+ // Get next char
+ c = static_cast<int>(*str++);
+ }
+
+ // Fail if encountering non-numeric character
+ if (strict && c == -1) {
+ return false;
+ }
+
+ // Negate the number if needed
+ if (possible_sign == '-') {
+ *value = total;
+ } else {
+ // Check for overflow
+ if (strict && total == min_value) {
+ return false;
+ }
+
+ *value = -total;
+ }
+
+ return true;
+}
+
+// Convert a string to an int
+// If strict is true, check for the validity and overflow
+bool ParseStringToInt(const char* str, int* value, bool strict) {
+ return ParseStringToIntTemplate<int>(str, value, strict, kint32min);
+}
+
+// Convert a string to an int
+// This version does not check for the validity and overflow
+int StringToInt(const char* str) {
+ int value = 0;
+ ParseStringToInt(str, &value, false);
+ return value;
+}
+
+// Convert a string to an unsigned int.
+// If strict is true, check for the validity and overflow
+bool ParseStringToUint(const char* str, uint32* value, bool strict) {
+ ASSERT(str);
+ ASSERT(value);
+
+ int64 int64_value;
+ if (!ParseStringToInt64(str, &int64_value, strict)) {
+ return false;
+ }
+ if (int64_value < 0 || int64_value > kuint32max) {
+ return false;
+ }
+
+ *value = static_cast<uint32>(int64_value);
+ return true;
+}
+
+// Convert a string to an int
+// This version does not check for the validity and overflow
+uint32 StringToUint(const char* str) {
+ uint32 value = 0;
+ ParseStringToUint(str, &value, false);
+ return value;
+}
+
+// Convert a string to an int64
+// If strict is true, check for the validity and overflow
+bool ParseStringToInt64(const char* str, int64* value, bool strict) {
+ return ParseStringToIntTemplate<int64>(str, value, strict, kint64min);
+}
+
+// Convert a string to an int64
+// This version does not check for the validity and overflow
+int64 StringToInt64(const char* str) {
+ int64 value = 0;
+ ParseStringToInt64(str, &value, false);
+ return value;
+}
+
+// Convert a string to a double
+// If strict is true, check for the validity and overflow
+bool ParseStringToDouble(const char* str, double* value, bool strict) {
+ ASSERT(str);
+ ASSERT(value);
+
+ // Skip spaces
+ while (*str == ' ') {
+ ++str;
+ }
+
+ // Process sign
+ int c = static_cast<int>(*str++); // current char
+ int sign = c; // save sign indication
+ if (c == '-' || c == '+') {
+ c = static_cast<int>(*str++);
+ }
+
+ // Process numbers before "."
+ double total = 0.0;
+ while (c && (c != '.') && (c = CharToDigit(static_cast<char>(c))) != -1) {
+ // Check for overflow
+ if (strict && total >= DBL_MAX / 10) {
+ return false;
+ }
+
+ // Accumulate digit
+ total = 10.0 * total + c;
+
+ // Get next char
+ c = static_cast<int>(*str++);
+ }
+
+ // Process "."
+ if (c == '.') {
+ c = static_cast<int>(*str++);
+ } else {
+ // Fail if encountering non-numeric character
+ if (strict && c == -1) {
+ return false;
+ }
+ }
+
+ // Process numbers after "."
+ double power = 1.0;
+ while ((c = CharToDigit(static_cast<char>(c))) != -1) {
+ // Check for overflow
+ if (strict && total >= DBL_MAX / 10) {
+ return false;
+ }
+
+ // Accumulate digit
+ total = 10.0 * total + c;
+ power *= 10.0;
+
+ // Get next char
+ c = static_cast<int>(*str++);
+ }
+
+ // Get the final number
+ *value = total / power;
+ if (sign == '-') {
+ *value = -(*value);
+ }
+
+ return true;
+}
+
+// Convert a string to a double
+// This version does not check for the validity and overflow
+double StringToDouble(const char* str) {
+ double value = 0;
+ ParseStringToDouble(str, &value, false);
+ return value;
+}
+
+// Convert a float to a string
+std::string FloatToString(float f) {
+ char buf[80];
+ snprintf(buf, sizeof(buf), "%f", f);
+ return std::string(buf);
+}
+
+std::string DoubleToString(double d) {
+ char buf[160];
+ snprintf(buf, sizeof(buf), "%.17g", d);
+ return std::string(buf);
+}
+
+std::string UIntToString(uint32 i) {
+ char buf[80];
+ snprintf(buf, sizeof(buf), "%lu", i);
+ return std::string(buf);
+}
+
+// Convert an int to a string
+std::string IntToString(int i) {
+ char buf[80];
+ snprintf(buf, sizeof(buf), "%d", i);
+ return std::string(buf);
+}
+
+// Convert an int64 to a string
+std::string Int64ToString(int64 i64) {
+ char buf[80];
+ snprintf(buf, sizeof(buf), "%" PRId64 "d", i64);
+ return std::string(buf);
+}
+
+std::string UInt64ToString(uint64 i64) {
+ char buf[80];
+ snprintf(buf, sizeof(buf), "%" PRId64 "u", i64);
+ return std::string(buf);
+}
+
+std::string Int64ToHexString(int64 i64) {
+ char buf[80];
+ snprintf(buf, sizeof(buf), "%" PRId64 "x", i64);
+ return std::string(buf);
+}
+
+// Parse a single "delim" delimited string from "*source"
+// Modify *source to point after the delimiter.
+// If no delimiter is present after the string, set *source to NULL.
+//
+// Mainly a stringified wrapper around strpbrk()
+std::string SplitOneStringToken(const char** source, const char* delim) {
+ ASSERT(source);
+ ASSERT(delim);
+
+ if (!*source) {
+ return std::string();
+ }
+ const char* begin = *source;
+ *source = strpbrk(*source, delim);
+ if (*source) {
+ return std::string(begin, (*source)++);
+ } else {
+ return std::string(begin);
+ }
+}
+
+std::string LowerWithUnderToPascalCase(const char* lower_with_under) {
+ ASSERT(lower_with_under);
+
+ std::string pascal_case;
+ bool make_upper = true;
+ for (; *lower_with_under != '\0'; lower_with_under++) {
+ char current_char = *lower_with_under;
+ if (current_char == '_') {
+ ASSERT(!make_upper);
+ make_upper = true;
+ continue;
+ }
+ if (make_upper) {
+ current_char = toupper(current_char);
+ make_upper = false;
+ }
+ pascal_case.append(1, current_char);
+ }
+ return pascal_case;
+}
+
+std::string PascalCaseToLowerWithUnder(const char* pascal_case) {
+ ASSERT(pascal_case);
+
+ std::string lower_with_under;
+ bool previous_was_upper = true;
+ for(; *pascal_case != '\0'; pascal_case++) {
+ char current_char = *pascal_case;
+ if (isupper(current_char)) {
+ // DNSName should be dns_name
+ if ((islower(pascal_case[1]) && !lower_with_under.empty()) ||
+ !previous_was_upper) {
+ lower_with_under.append(1, '_');
+ }
+ current_char = tolower(current_char);
+ } else if (previous_was_upper) {
+ previous_was_upper = false;
+ }
+ lower_with_under.append(1, current_char);
+ }
+ return lower_with_under;
+}
+void StringReplace(std::string* s,
+ const char* old_sub,
+ const char* new_sub,
+ bool replace_all) {
+ ASSERT(s);
+
+ // If old_sub is empty, nothing to do
+ if (!old_sub || !*old_sub) {
+ return;
+ }
+
+ int old_sub_size = strlen(old_sub);
+ std::string res;
+ std::string::size_type start_pos = 0;
+
+ do {
+ std::string::size_type pos = s->find(old_sub, start_pos);
+ if (pos == std::string::npos) {
+ break;
+ }
+ res.append(*s, start_pos, pos - start_pos);
+ res.append(new_sub);
+ start_pos = pos + old_sub_size; // start searching again after the "old"
+ } while (replace_all);
+ res.append(*s, start_pos, s->length() - start_pos);
+
+ *s = res;
+}
+
+} // namespace notifier
diff --git a/chrome/browser/sync/notifier/base/string.h b/chrome/browser/sync/notifier/base/string.h
new file mode 100644
index 0000000..725cc66
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/string.h
@@ -0,0 +1,381 @@
+// 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_BASE_STRING_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_BASE_STRING_H_
+
+#ifdef COMPILER_MSVC
+#include <xhash>
+#elif defined(__GNUC__)
+#include <ext/hash_map>
+#endif
+
+#include <ctype.h>
+#include <string>
+
+#include "chrome/browser/sync/notifier/base/fastalloc.h"
+#include "talk/base/basictypes.h"
+
+namespace notifier {
+
+// Does html encoding of strings.
+std::string HtmlEncode(const std::string& src);
+
+// Does html decoding of strings.
+std::string HtmlDecode(const std::string& src);
+
+// Does utl encoding of strings.
+std::string UrlEncode(const std::string& src);
+
+// Does url decoding of strings.
+std::string UrlDecode(const std::string& src);
+
+// Convert a character to a digit
+// if the character is not a digit return -1 (same as CRT)
+inline int CharToDigit(char c) {
+ return ((c) >= '0' && (c) <= '9' ? (c) - '0' : -1);
+}
+
+int CharToHexValue(char hex);
+
+// ----------------------------------------------------------------------
+// ParseStringToInt()
+// ParseStringToUint()
+// ParseStringToInt64()
+// ParseStringToDouble()
+// Convert a string to an int/int64/double
+// If strict is true, check for the validity and overflow
+// ----------------------------------------------------------------------
+
+bool ParseStringToInt(const char* str, int* value, bool strict);
+
+bool ParseStringToUint(const char* str, uint32* value, bool strict);
+
+bool ParseStringToInt64(const char* str, int64* value, bool strict);
+
+bool ParseStringToDouble(const char* str, double* value, bool strict);
+
+// ----------------------------------------------------------------------
+// StringToInt()
+// StringToUint()
+// StringToInt64()
+// StringToDouble()
+// Convert a string to an int/int64/double
+// Note that these functions do not check for the validity or overflow
+// ----------------------------------------------------------------------
+
+int StringToInt(const char* str);
+
+uint32 StringToUint(const char* str);
+
+int64 StringToInt64(const char* str);
+
+double StringToDouble(const char* str);
+
+// ----------------------------------------------------------------------
+// FloatToString()
+// DoubleToString()
+// IntToString()
+// UIntToString()
+// Int64ToString()
+// UInt64ToString()
+// Convert various types to their string representation. These
+// all do the obvious, trivial thing.
+// ----------------------------------------------------------------------
+
+std::string FloatToString(float f);
+std::string DoubleToString(double d);
+
+std::string IntToString(int i);
+std::string UIntToString(uint32 i);
+
+std::string Int64ToString(int64 i64);
+std::string UInt64ToString(uint64 i64);
+
+std::string Int64ToHexString(int64 i64);
+
+// ----------------------------------------------------------------------
+// StringStartsWith()
+// StringEndsWith()
+// Check if a string starts or ends with a pattern
+// ----------------------------------------------------------------------
+
+inline bool StringStartsWith(const std::string& s, const char* p) {
+ return s.find(p) == 0;
+}
+
+inline bool StringEndsWith(const std::string& s, const char* p) {
+ return s.rfind(p) == (s.length() - strlen(p));
+}
+
+// ----------------------------------------------------------------------
+// MakeStringEndWith()
+// If the string does not end with a pattern, make it end with it
+// ----------------------------------------------------------------------
+
+inline std::string MakeStringEndWith(const std::string& s, const char* p) {
+ if (StringEndsWith(s, p)) {
+ return s;
+ } else {
+ std::string ns(s);
+ ns += p;
+ return ns;
+ }
+}
+
+// Convert a lower_case_string to LowerCaseString
+std::string LowerWithUnderToPascalCase(const char* lower_with_under);
+
+// Convert a PascalCaseString to pascal_case_string
+std::string PascalCaseToLowerWithUnder(const char* pascal_case);
+
+// ----------------------------------------------------------------------
+// LowerString()
+// LowerStringToBuf()
+// Convert the characters in "s" to lowercase.
+// Changes contents of "s". LowerStringToBuf copies at most
+// "n" characters (including the terminating '\0') from "s"
+// to another buffer.
+// ----------------------------------------------------------------------
+
+inline void LowerString(char* s) {
+ for (; *s; ++s) {
+ *s = tolower(*s);
+ }
+}
+
+inline void LowerString(std::string* s) {
+ std::string::iterator end = s->end();
+ for (std::string::iterator i = s->begin(); i != end; ++i) {
+ *i = tolower(*i);
+ }
+}
+
+inline void LowerStringToBuf(const char* s, char* buf, int n) {
+ for (int i = 0; i < n - 1; ++i) {
+ char c = s[i];
+ buf[i] = tolower(c);
+ if (c == '\0') {
+ return;
+ }
+ }
+ buf[n - 1] = '\0';
+}
+
+// ----------------------------------------------------------------------
+// UpperString()
+// UpperStringToBuf()
+// Convert the characters in "s" to uppercase.
+// UpperString changes "s". UpperStringToBuf copies at most
+// "n" characters (including the terminating '\0') from "s"
+// to another buffer.
+// ----------------------------------------------------------------------
+
+inline void UpperString(char* s) {
+ for (; *s; ++s) {
+ *s = toupper(*s);
+ }
+}
+
+inline void UpperString(std::string* s) {
+ for (std::string::iterator iter = s->begin(); iter != s->end(); ++iter) {
+ *iter = toupper(*iter);
+ }
+}
+
+inline void UpperStringToBuf(const char* s, char* buf, int n) {
+ for (int i = 0; i < n - 1; ++i) {
+ char c = s[i];
+ buf[i] = toupper(c);
+ if (c == '\0') {
+ return;
+ }
+ }
+ buf[n - 1] = '\0';
+}
+
+// ----------------------------------------------------------------------
+// TrimStringLeft
+// Removes any occurrences of the characters in 'remove' from the start
+// of the string. Returns the number of chars trimmed.
+// ----------------------------------------------------------------------
+inline int TrimStringLeft(std::string* s, const char* remove) {
+ int i = 0;
+ for (; i < static_cast<int>(s->size()) && strchr(remove, (*s)[i]); ++i);
+ if (i > 0) s->erase(0, i);
+ return i;
+}
+
+// ----------------------------------------------------------------------
+// TrimStringRight
+// Removes any occurrences of the characters in 'remove' from the end
+// of the string. Returns the number of chars trimmed.
+// ----------------------------------------------------------------------
+inline int TrimStringRight(std::string* s, const char* remove) {
+ int size = static_cast<int>(s->size());
+ int i = size;
+ for (; i > 0 && strchr(remove, (*s)[i - 1]); --i);
+ if (i < size) {
+ s->erase(i);
+ }
+ return size - i;
+}
+
+// ----------------------------------------------------------------------
+// TrimString
+// Removes any occurrences of the characters in 'remove' from either
+// end of the string.
+// ----------------------------------------------------------------------
+inline int TrimString(std::string* s, const char* remove) {
+ return TrimStringRight(s, remove) + TrimStringLeft(s, remove);
+}
+
+// ----------------------------------------------------------------------
+// StringReplace()
+// Replace the "old" pattern with the "new" pattern in a string. If
+// replace_all is false, it only replaces the first instance of "old."
+// ----------------------------------------------------------------------
+
+void StringReplace(std::string* s,
+ const char* old_sub,
+ const char* new_sub,
+ bool replace_all);
+
+inline size_t HashString(const std::string &value) {
+#ifdef COMPILER_MSVC
+ return stdext::hash_value(value);
+#elif defined(__GNUC__)
+ __gnu_cxx::hash<const char*> h;
+ return h(value.c_str());
+#else
+ // Compile time error because we don't return a value
+#endif
+}
+
+// ----------------------------------------------------------------------
+// SplitOneStringToken()
+// Parse a single "delim" delimited string from "*source"
+// Modify *source to point after the delimiter.
+// If no delimiter is present after the string, set *source to NULL.
+//
+// If the start of *source is a delimiter, return an empty string.
+// If *source is NULL, return an empty string.
+// ----------------------------------------------------------------------
+std::string SplitOneStringToken(const char** source, const char* delim);
+
+//----------------------------------------------------------------------
+// CharTraits provides wrappers with common function names for char/wchar_t
+// specific CRT functions
+//----------------------------------------------------------------------
+
+template <class CharT> struct CharTraits {
+};
+
+template <>
+struct CharTraits<char> {
+ static inline size_t length(const char* s) {
+ return strlen(s);
+ }
+ static inline bool copy(char* dst, size_t dst_size, const char* s) {
+ if (s == NULL || dst == NULL)
+ return false;
+ else
+ return copy_num(dst, dst_size, s, strlen(s));
+ }
+ static inline bool copy_num(char* dst, size_t dst_size, const char* s,
+ size_t s_len) {
+ if (dst_size < (s_len + 1))
+ return false;
+ memcpy(dst, s, s_len);
+ dst[s_len] = '\0';
+ return true;
+ }
+};
+
+template <>
+struct CharTraits<wchar_t> {
+ static inline size_t length(const wchar_t* s) {
+ return wcslen(s);
+ }
+ static inline bool copy(wchar_t* dst, size_t dst_size, const wchar_t* s) {
+ if (s == NULL || dst == NULL)
+ return false;
+ else
+ return copy_num(dst, dst_size, s, wcslen(s));
+ }
+ static inline bool copy_num(wchar_t* dst, size_t dst_size, const wchar_t* s,
+ size_t s_len) {
+ if (dst_size < (s_len + 1)) {
+ return false;
+ }
+ memcpy(dst, s, s_len * sizeof(wchar_t));
+ dst[s_len] = '\0';
+ return true;
+ }
+};
+
+//----------------------------------------------------------------------
+// This class manages a fixed-size, null-terminated string buffer. It is
+// meant to be allocated on the stack, and it makes no use of the heap
+// internally. In most cases you'll just want to use a std::(w)string, but
+// when you need to avoid the heap, you can use this class instead.
+//
+// Methods are provided to read the null-terminated buffer and to append
+// data to the buffer, and once the buffer fills-up, it simply discards any
+// extra append calls.
+//----------------------------------------------------------------------
+
+template <class CharT, int MaxSize>
+class FixedString {
+ public:
+ typedef CharTraits<CharT> char_traits;
+
+ FixedString() : index_(0), truncated_(false) {
+ buf_[0] = CharT(0);
+ }
+
+ ~FixedString() {
+ memset(buf_, 0xCC, sizeof(buf_));
+ }
+
+ // Returns true if the Append ever failed.
+ bool was_truncated() const { return truncated_; }
+
+ // Returns the number of characters in the string, excluding the null
+ // terminator.
+ size_t size() const { return index_; }
+
+ // Returns the null-terminated string.
+ const CharT* get() const { return buf_; }
+ CharT* get() { return buf_; }
+
+ // Append an array of characters. The operation is bounds checked, and if
+ // there is insufficient room, then the was_truncated() flag is set to true.
+ void Append(const CharT* s, size_t n) {
+ if (char_traits::copy_num(buf_ + index_, arraysize(buf_) - index_, s, n)) {
+ index_ += n;
+ } else {
+ truncated_ = true;
+ }
+ }
+
+ // Append a null-terminated string.
+ void Append(const CharT* s) {
+ Append(s, char_traits::length(s));
+ }
+
+ // Append a single character.
+ void Append(CharT c) {
+ Append(&c, 1);
+ }
+
+ private:
+ CharT buf_[MaxSize];
+ size_t index_;
+ bool truncated_;
+};
+
+} // namespace notifier
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_BASE_STRING_H_
diff --git a/chrome/browser/sync/notifier/base/string_unittest.cc b/chrome/browser/sync/notifier/base/string_unittest.cc
new file mode 100644
index 0000000..954315a
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/string_unittest.cc
@@ -0,0 +1,362 @@
+// 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/base/string.h"
+#include "notifier/testing/notifier/unittest.h"
+
+namespace notifier {
+
+TEST_NOTIFIER_F(StringTest);
+
+TEST_F(StringTest, StringToInt) {
+ ASSERT_EQ(StringToInt("625"), 625);
+ ASSERT_EQ(StringToInt("6"), 6);
+ ASSERT_EQ(StringToInt("0"), 0);
+ ASSERT_EQ(StringToInt(" 122"), 122);
+ ASSERT_EQ(StringToInt("a"), 0);
+ ASSERT_EQ(StringToInt(" a"), 0);
+ ASSERT_EQ(StringToInt("2147483647"), 2147483647);
+ ASSERT_EQ(StringToInt("-2147483648"),
+ static_cast<int>(0x80000000)); // Hex constant avoids gcc warning.
+
+ int value = 0;
+ ASSERT_FALSE(ParseStringToInt("62.5", &value, true));
+ ASSERT_FALSE(ParseStringToInt("625e", &value, true));
+ ASSERT_FALSE(ParseStringToInt("2147483648", &value, true));
+ ASSERT_FALSE(ParseStringToInt("-2147483649", &value, true));
+ ASSERT_FALSE(ParseStringToInt("-4857004031", &value, true));
+}
+
+TEST_F(StringTest, StringToUint) {
+ ASSERT_EQ(StringToUint("625"), 625);
+ ASSERT_EQ(StringToUint("6"), 6);
+ ASSERT_EQ(StringToUint("0"), 0);
+ ASSERT_EQ(StringToUint(" 122"), 122);
+ ASSERT_EQ(StringToUint("a"), 0);
+ ASSERT_EQ(StringToUint(" a"), 0);
+ ASSERT_EQ(StringToUint("4294967295"), static_cast<uint32>(0xffffffff));
+
+ uint32 value = 0;
+ ASSERT_FALSE(ParseStringToUint("62.5", &value, true));
+ ASSERT_FALSE(ParseStringToUint("625e", &value, true));
+ ASSERT_FALSE(ParseStringToUint("4294967296", &value, true));
+ ASSERT_FALSE(ParseStringToUint("-1", &value, true));
+}
+
+TEST_F(StringTest, StringToInt64) {
+ ASSERT_EQ(StringToInt64("119600064000000000"),
+ INT64_C(119600064000000000));
+ ASSERT_EQ(StringToInt64(" 119600064000000000"),
+ INT64_C(119600064000000000));
+ ASSERT_EQ(StringToInt64("625"), 625);
+ ASSERT_EQ(StringToInt64("6"), 6);
+ ASSERT_EQ(StringToInt64("0"), 0);
+ ASSERT_EQ(StringToInt64(" 122"), 122);
+ ASSERT_EQ(StringToInt64("a"), 0);
+ ASSERT_EQ(StringToInt64(" a"), 0);
+ ASSERT_EQ(StringToInt64("9223372036854775807"), INT64_C(9223372036854775807));
+ ASSERT_EQ(StringToInt64("-9223372036854775808I64"),
+ static_cast<int64>(INT64_C(0x8000000000000000)));
+
+ int64 value = 0;
+ ASSERT_FALSE(ParseStringToInt64("62.5", &value, true));
+ ASSERT_FALSE(ParseStringToInt64("625e", &value, true));
+ ASSERT_FALSE(ParseStringToInt64("9223372036854775808", &value, true));
+ ASSERT_FALSE(ParseStringToInt64("-9223372036854775809", &value, true));
+}
+
+TEST_F(StringTest, StringToDouble) {
+ ASSERT_DOUBLE_EQ(StringToDouble("625"), 625);
+ ASSERT_DOUBLE_EQ(StringToDouble("-625"), -625);
+ ASSERT_DOUBLE_EQ(StringToDouble("-6.25"), -6.25);
+ ASSERT_DOUBLE_EQ(StringToDouble("6.25"), 6.25);
+ ASSERT_DOUBLE_EQ(StringToDouble("0.00"), 0);
+ ASSERT_DOUBLE_EQ(StringToDouble(" 55.1"), 55.1);
+ ASSERT_DOUBLE_EQ(StringToDouble(" 55.001"), 55.001);
+ ASSERT_DOUBLE_EQ(StringToDouble(" 1.001"), 1.001);
+
+ double value = 0.0;
+ ASSERT_FALSE(ParseStringToDouble("62*5", &value, true));
+}
+
+TEST_F(StringTest, Int64ToHexString) {
+ ASSERT_STREQ("1a8e79fe1d58000",
+ Int64ToHexString(INT64_C(119600064000000000)).c_str());
+ ASSERT_STREQ("271", Int64ToHexString(625).c_str());
+ ASSERT_STREQ("0", Int64ToHexString(0).c_str());
+}
+
+TEST_F(StringTest, StringStartsWith) {
+ { std::string s(""); ASSERT_TRUE(StringStartsWith(s, "")); }
+ { std::string s("abc"); ASSERT_TRUE(StringStartsWith(s, "ab")); }
+ { std::string s("abc"); ASSERT_FALSE(StringStartsWith(s, "bc")); }
+}
+
+TEST_F(StringTest, StringEndsWith) {
+ { std::string s(""); ASSERT_TRUE(StringEndsWith(s, "")); }
+ { std::string s("abc"); ASSERT_TRUE(StringEndsWith(s, "bc")); }
+ { std::string s("abc"); ASSERT_FALSE(StringEndsWith(s, "ab")); }
+}
+
+TEST_F(StringTest, MakeStringEndWith) {
+ {
+ std::string s("");
+ std::string t(MakeStringEndWith(s, ""));
+ ASSERT_STREQ(t.c_str(), "");
+ }
+ {
+ std::string s("abc");
+ std::string t(MakeStringEndWith(s, "def"));
+ ASSERT_STREQ(t.c_str(), "abcdef");
+ }
+ {
+ std::string s("abc");
+ std::string t(MakeStringEndWith(s, "bc"));
+ ASSERT_STREQ(t.c_str(), "abc");
+ }
+}
+
+TEST_F(StringTest, LowerString) {
+ { std::string s(""); LowerString(&s); ASSERT_STREQ(s.c_str(), ""); }
+ { std::string s("a"); LowerString(&s); ASSERT_STREQ(s.c_str(), "a"); }
+ { std::string s("A"); LowerString(&s); ASSERT_STREQ(s.c_str(), "a"); }
+ { std::string s("abc"); LowerString(&s); ASSERT_STREQ(s.c_str(), "abc"); }
+ { std::string s("ABC"); LowerString(&s); ASSERT_STREQ(s.c_str(), "abc"); }
+}
+
+TEST_F(StringTest, UpperString) {
+ { std::string s(""); UpperString(&s); ASSERT_STREQ(s.c_str(), ""); }
+ { std::string s("A"); UpperString(&s); ASSERT_STREQ(s.c_str(), "A"); }
+ { std::string s("a"); UpperString(&s); ASSERT_STREQ(s.c_str(), "A"); }
+ { std::string s("ABC"); UpperString(&s); ASSERT_STREQ(s.c_str(), "ABC"); }
+ { std::string s("abc"); UpperString(&s); ASSERT_STREQ(s.c_str(), "ABC"); }
+}
+
+TEST_F(StringTest, TrimString) {
+ const char* white = " \n\t";
+ std::string s, c;
+
+ // TrimStringLeft
+ s = ""; // empty
+ c = "";
+ ASSERT_EQ(TrimStringLeft(&s, white), 0);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+
+ s = " \n\t"; // all bad
+ c = "";
+ ASSERT_EQ(TrimStringLeft(&s, white), 3);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+
+ s = "dog"; // nothing bad
+ c = "dog";
+ ASSERT_EQ(TrimStringLeft(&s, white), 0);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+
+ s = " dog "; // some bad
+ c = "dog ";
+ ASSERT_EQ(TrimStringLeft(&s, white), 1);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+
+ s = " \n\t\t I love my little dog \n\t ";
+ c = "I love my little dog \n\t ";
+ ASSERT_EQ(TrimStringLeft(&s, white), 5);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+
+ // TrimStringRight
+ s = "";
+ c = "";
+ ASSERT_EQ(TrimStringRight(&s, white), 0);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+
+ s = " \n\t";
+ c = "";
+ ASSERT_EQ(TrimStringRight(&s, white), 3);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+
+ s = "dog";
+ c = "dog";
+ ASSERT_EQ(TrimStringRight(&s, white), 0);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+
+ s = " dog ";
+ c = " dog";
+ ASSERT_EQ(TrimStringRight(&s, white), 1);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+
+ s = " \n\t\t I love my little dog \n\t ";
+ c = " \n\t\t I love my little dog";
+ ASSERT_EQ(TrimStringRight(&s, white), 4);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+
+ // TrimString
+ s = "";
+ c = "";
+ ASSERT_EQ(TrimString(&s, white), 0);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+
+ s = " \n\t";
+ c = "";
+ ASSERT_EQ(TrimString(&s, white), 3);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+
+ s = "dog";
+ c = "dog";
+ ASSERT_EQ(TrimString(&s, white), 0);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+
+ s = " dog ";
+ c = "dog";
+ ASSERT_EQ(TrimString(&s, white), 2);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+
+ s = " \n\t\t I love my little dog \n\t ";
+ c = "I love my little dog";
+ ASSERT_EQ(TrimString(&s, white), 9);
+ ASSERT_STREQ(s.c_str(), c.c_str());
+}
+
+TEST_F(StringTest, SplitOneStringToken) {
+ const char* teststrings[] = {
+ "alongword",
+ "alongword ",
+ "alongword ",
+ "alongword anotherword",
+ " alongword",
+ "",
+ };
+ const char* source = NULL;
+
+ source = teststrings[0];
+ ASSERT_STREQ(SplitOneStringToken(&source, " ").c_str(), "alongword");
+ ASSERT_STREQ(source, NULL);
+
+ source = teststrings[1];
+ ASSERT_STREQ(SplitOneStringToken(&source, " ").c_str(), "alongword");
+ ASSERT_STREQ(source, teststrings[1] + strlen("alongword") + 1);
+
+ source = teststrings[2];
+ ASSERT_STREQ(SplitOneStringToken(&source, " ").c_str(), "alongword");
+ ASSERT_STREQ(source, teststrings[2] + strlen("alongword") + 1);
+
+ source = teststrings[3];
+ ASSERT_STREQ(SplitOneStringToken(&source, " ").c_str(), "alongword");
+ ASSERT_STREQ(source, teststrings[3] + strlen("alongword") + 1);
+
+ source = teststrings[4];
+ ASSERT_STREQ(SplitOneStringToken(&source, " ").c_str(), "");
+ ASSERT_STREQ(source, teststrings[4] + 1);
+
+ source = teststrings[5];
+ ASSERT_STREQ(SplitOneStringToken(&source, " ").c_str(), "");
+ ASSERT_STREQ(source, NULL);
+}
+
+TEST_F(StringTest, FixedString) {
+ // Test basic operation.
+ const wchar_t kData[] = L"hello world";
+ FixedString<wchar_t, 40> buf;
+
+ buf.Append(kData);
+ EXPECT_EQ(arraysize(kData)-1, buf.size());
+ EXPECT_EQ(0, wcscmp(kData, buf.get()));
+
+ buf.Append(' ');
+ buf.Append(kData);
+ const wchar_t kExpected[] = L"hello world hello world";
+ EXPECT_EQ(arraysize(kExpected)-1, buf.size());
+ EXPECT_EQ(0, wcscmp(kExpected, buf.get()));
+ EXPECT_EQ(false, buf.was_truncated());
+
+ // Test overflow.
+ FixedString<wchar_t, 5> buf2;
+ buf2.Append(L"hello world");
+ EXPECT_EQ(static_cast<size_t>(0), buf2.size());
+ EXPECT_EQ(0, buf2.get()[0]);
+ EXPECT_EQ(true, buf2.was_truncated());
+}
+
+TEST_F(StringTest, LowerToPascalCase) {
+ EXPECT_STREQ("", LowerWithUnderToPascalCase("").c_str());
+ EXPECT_STREQ("A", LowerWithUnderToPascalCase("a").c_str());
+ EXPECT_STREQ("TestS", LowerWithUnderToPascalCase("test_s").c_str());
+ EXPECT_STREQ("XQ", LowerWithUnderToPascalCase("x_q").c_str());
+ EXPECT_STREQ("XQDNS", LowerWithUnderToPascalCase("x_qDNS").c_str());
+}
+
+TEST_F(StringTest, PascalCaseToLower) {
+ EXPECT_STREQ("", PascalCaseToLowerWithUnder("").c_str());
+ EXPECT_STREQ("a", PascalCaseToLowerWithUnder("A").c_str());
+ EXPECT_STREQ("test_s", PascalCaseToLowerWithUnder("TestS").c_str());
+ EXPECT_STREQ("xq", PascalCaseToLowerWithUnder("XQ").c_str());
+ EXPECT_STREQ("dns_name", PascalCaseToLowerWithUnder("DNSName").c_str());
+ EXPECT_STREQ("xqdns", PascalCaseToLowerWithUnder("XQDNS").c_str());
+ EXPECT_STREQ("xqdn_sa", PascalCaseToLowerWithUnder("XQDNSa").c_str());
+ EXPECT_STREQ("dns1", PascalCaseToLowerWithUnder("DNS1").c_str());
+}
+
+TEST_F(StringTest, HtmlEncode) {
+ EXPECT_STREQ("dns", HtmlEncode("dns").c_str());
+ EXPECT_STREQ("&amp;", HtmlEncode("&").c_str());
+ EXPECT_STREQ("&amp;amp;", HtmlEncode("&amp;").c_str());
+ EXPECT_STREQ("&lt;!&gt;", HtmlEncode("<!>").c_str());
+}
+
+TEST_F(StringTest, HtmlDecode) {
+ EXPECT_STREQ("dns", HtmlDecode("dns").c_str());
+ EXPECT_STREQ("&", HtmlDecode("&amp;").c_str());
+ EXPECT_STREQ("&amp;", HtmlDecode("&amp;amp;").c_str());
+ EXPECT_STREQ("<!>", HtmlDecode("&lt;!&gt;").c_str());
+}
+
+TEST_F(StringTest, UrlEncode) {
+ EXPECT_STREQ("%26", UrlEncode("&").c_str());
+ EXPECT_STREQ("%3f%20", UrlEncode("? ").c_str());
+ EXPECT_STREQ("as%20dfdsa", UrlEncode("as dfdsa").c_str());
+ EXPECT_STREQ("%3c!%3e", UrlEncode("<!>").c_str());
+ EXPECT_STREQ("!%23!", UrlEncode("!#!").c_str());
+ EXPECT_STREQ("!!", UrlEncode("!!").c_str());
+}
+
+TEST_F(StringTest, UrlDecode) {
+ EXPECT_STREQ("&", UrlDecode("%26").c_str());
+ EXPECT_STREQ("? ", UrlDecode("%3f%20").c_str());
+ EXPECT_STREQ("as dfdsa", UrlDecode("as%20dfdsa").c_str());
+ EXPECT_STREQ("<!>", UrlDecode("%3c!%3e").c_str());
+ EXPECT_STREQ("&amp;", UrlDecode("&amp;").c_str());
+}
+
+TEST_F(StringTest, StringReplace) {
+ // Test StringReplace core functionality.
+ std::string s = "<attribute name=abcd/>";
+ StringReplace(&s, "=", " = ", false);
+ EXPECT_STREQ(s.c_str(), "<attribute name = abcd/>");
+
+ // Test for negative case.
+ s = "<attribute name=abcd/>";
+ StringReplace(&s, "-", "=", false);
+ EXPECT_STREQ(s.c_str(), "<attribute name=abcd/>");
+
+ // Test StringReplace core functionality with replace_all flag set.
+ s = "<attribute name==abcd/>";
+ StringReplace(&s, "=", " = ", true);
+ EXPECT_STREQ(s.c_str(), "<attribute name = = abcd/>");
+
+ // Input is an empty string.
+ s = "";
+ StringReplace(&s, "=", " = ", false);
+ EXPECT_STREQ(s.c_str(), "");
+
+ // Input is an empty string and this is a request for repeated
+ // string replaces.
+ s = "";
+ StringReplace(&s, "=", " = ", true);
+ EXPECT_STREQ(s.c_str(), "");
+
+ // Input and string to replace is an empty string.
+ s = "";
+ StringReplace(&s, "", " = ", false);
+ EXPECT_STREQ(s.c_str(), "");
+}
+
+} // namespace notifier
diff --git a/chrome/browser/sync/notifier/base/task_pump.cc b/chrome/browser/sync/notifier/base/task_pump.cc
new file mode 100644
index 0000000..7e99fc1
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/task_pump.cc
@@ -0,0 +1,42 @@
+// 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/base/task_pump.h"
+
+#include "chrome/browser/sync/notifier/base/time.h"
+#include "talk/base/common.h"
+#include "talk/base/thread.h"
+
+namespace notifier {
+
+// Don't add any messages because there are cleared and thrown away.
+enum { MSG_WAKE_UP = 1, MSG_TIMED_WAKE_UP };
+
+TaskPump::TaskPump() : timeout_change_count_(0), posted_(false) {
+}
+
+void TaskPump::OnMessage(talk_base::Message* msg) {
+ posted_ = false;
+ int initial_count = timeout_change_count_;
+
+ // If a task timed out, ensure that it is not blocked, so it will be deleted.
+ // This may result in a WakeTasks if a task is timed out.
+ PollTasks();
+
+ // Run tasks and handle timeouts.
+ RunTasks();
+}
+
+void TaskPump::WakeTasks() {
+ if (!posted_) {
+ // Do the requested wake up
+ talk_base::Thread::Current()->Post(this, MSG_WAKE_UP);
+ posted_ = true;
+ }
+}
+
+int64 TaskPump::CurrentTime() {
+ return GetCurrent100NSTime();
+}
+} // namespace notifier
diff --git a/chrome/browser/sync/notifier/base/task_pump.h b/chrome/browser/sync/notifier/base/task_pump.h
new file mode 100644
index 0000000..b6c00ec
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/task_pump.h
@@ -0,0 +1,34 @@
+// 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_BASE_TASK_PUMP_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_BASE_TASK_PUMP_H_
+
+#include "talk/base/messagequeue.h"
+#include "talk/base/taskrunner.h"
+
+namespace notifier {
+
+class TaskPump : public talk_base::MessageHandler,
+ public talk_base::TaskRunner {
+ public:
+ TaskPump();
+
+ // MessageHandler interface.
+ virtual void OnMessage(talk_base::Message* msg);
+
+ // TaskRunner interface
+ virtual void WakeTasks();
+ virtual int64 CurrentTime();
+
+ private:
+ int timeout_change_count_;
+ bool posted_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskPump);
+};
+
+} // namespace notifier
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_BASE_TASK_PUMP_H_
diff --git a/chrome/browser/sync/notifier/base/time.cc b/chrome/browser/sync/notifier/base/time.cc
new file mode 100644
index 0000000..ed3c414
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/time.cc
@@ -0,0 +1,360 @@
+// 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/base/time.h"
+
+#include <string>
+#include <time.h>
+
+#include "chrome/browser/sync/notifier/base/string.h"
+#include "chrome/browser/sync/notifier/base/utils.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+namespace notifier {
+
+// Get the current time represented in 100NS granularity since epoch
+// (Jan 1, 1970)
+time64 GetCurrent100NSTimeSinceEpoch() {
+ return GetCurrent100NSTime() - kStart100NsTimeToEpoch;
+}
+
+char* GetLocalTimeAsString() {
+ time64 long_time = GetCurrent100NSTime();
+ struct tm now;
+ Time64ToTm(long_time, &now);
+ char* time_string = asctime(&now);
+ if (time_string) {
+ int time_len = strlen(time_string);
+ if (time_len > 0) {
+ time_string[time_len - 1] = 0; // trim off terminating \n
+ }
+ }
+ return time_string;
+}
+
+// Parses RFC 822 Date/Time format
+// 5. DATE AND TIME SPECIFICATION
+// 5.1. SYNTAX
+//
+// date-time = [ day "," ] date time ; dd mm yy
+// ; hh:mm:ss zzz
+// day = "Mon" / "Tue" / "Wed" / "Thu"
+// / "Fri" / "Sat" / "Sun"
+//
+// date = 1*2DIGIT month 2DIGIT ; day month year
+// ; e.g. 20 Jun 82
+//
+// month = "Jan" / "Feb" / "Mar" / "Apr"
+// / "May" / "Jun" / "Jul" / "Aug"
+// / "Sep" / "Oct" / "Nov" / "Dec"
+//
+// time = hour zone ; ANSI and Military
+//
+// hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT]
+// ; 00:00:00 - 23:59:59
+//
+// zone = "UT" / "GMT" ; Universal Time
+// ; North American : UT
+// / "EST" / "EDT" ; Eastern: - 5/ - 4
+// / "CST" / "CDT" ; Central: - 6/ - 5
+// / "MST" / "MDT" ; Mountain: - 7/ - 6
+// / "PST" / "PDT" ; Pacific: - 8/ - 7
+// / 1ALPHA ; Military: Z = UT;
+// ; A:-1; (J not used)
+// ; M:-12; N:+1; Y:+12
+// / ( ("+" / "-") 4DIGIT ) ; Local differential
+// ; hours+min. (HHMM)
+// Return local time if ret_local_time == true, return UTC time otherwise
+const int kNumOfDays = 7;
+const int kNumOfMonth = 12;
+// Note: RFC822 does not include '-' as a separator, but Http Cookies use
+// it in the date field, like this: Wdy, DD-Mon-YYYY HH:MM:SS GMT
+// This differs from RFC822 only by those dashes. It is legacy quirk from
+// old Netscape cookie specification. So it makes sense to expand this
+// parser rather then add another one.
+// See http://wp.netscape.com/newsref/std/cookie_spec.html
+const char kRFC822_DateDelimiters[] = " ,:-";
+
+const char kRFC822_TimeDelimiter[] = ": ";
+const char* kRFC822_Day[] = {
+ "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
+};
+const char* kRFC822_Month[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+struct TimeZoneInfo {
+ const char* zone_name;
+ int hour_dif;
+};
+
+const TimeZoneInfo kRFC822_TimeZone[] = {
+ { "UT", 0 },
+ { "GMT", 0 },
+ { "EST", -5 },
+ { "EDT", -4 },
+ { "CST", -6 },
+ { "CDT", -5 },
+ { "MST", -7 },
+ { "MDT", -6 },
+ { "PST", -8 },
+ { "PDT", -7 },
+ { "A", -1 }, // Military time zones
+ { "B", -2 },
+ { "C", -3 },
+ { "D", -4 },
+ { "E", -5 },
+ { "F", -6 },
+ { "G", -7 },
+ { "H", -8 },
+ { "I", -9 },
+ { "K", -10 },
+ { "L", -11 },
+ { "M", -12 },
+ { "N", 1 },
+ { "O", 2 },
+ { "P", 3 },
+ { "Q", 4 },
+ { "R", 5 },
+ { "S", 6 },
+ { "T", 7 },
+ { "U", 8 },
+ { "V", 9 },
+ { "W", 10 },
+ { "X", 11 },
+ { "Y", 12 },
+ { "Z", 0 },
+};
+
+bool ParseRFC822DateTime(const char* str, struct tm* time,
+ bool ret_local_time) {
+ ASSERT(str && *str);
+ ASSERT(time);
+
+ std::string str_date(str);
+ std::string str_token;
+ const char* str_curr = str_date.c_str();
+
+ str_token = SplitOneStringToken(&str_curr, kRFC822_DateDelimiters);
+ if (str_token == "") {
+ return false;
+ }
+
+ for (int i = 0; i < kNumOfDays; ++i) {
+ if (str_token == kRFC822_Day[i]) {
+ // Skip spaces after ','
+ while (*str_curr == ' ' && *str_curr != '\0') {
+ str_curr++;
+ }
+
+ str_token = SplitOneStringToken(&str_curr, kRFC822_DateDelimiters);
+ if (str_token == "") {
+ return false;
+ }
+ break;
+ }
+ }
+
+ int day = 0;
+ if (!ParseStringToInt(str_token.c_str(), &day, true) || day < 0 || day > 31) {
+ return false;
+ }
+
+ str_token = SplitOneStringToken(&str_curr, kRFC822_DateDelimiters);
+ if (str_token == "") {
+ return false;
+ }
+
+ int month = -1;
+ for (int i = 0; i < kNumOfMonth; ++i) {
+ if (str_token == kRFC822_Month[i]) {
+ month = i; // month is 0 based number
+ break;
+ }
+ }
+ if (month == -1) { // month not found
+ return false;
+ }
+
+ str_token = SplitOneStringToken(&str_curr, kRFC822_DateDelimiters);
+ if (str_token == "") {
+ return false;
+ }
+
+ int year = 0;
+ if (!ParseStringToInt(str_token.c_str(), &year, true)) {
+ return false;
+ }
+ if (year < 100) { // two digit year format, convert to 1950 - 2050 range
+ if (year < 50) {
+ year += 2000;
+ } else {
+ year += 1900;
+ }
+ }
+
+ str_token = SplitOneStringToken(&str_curr, kRFC822_TimeDelimiter);
+ if (str_token == "") {
+ return false;
+ }
+
+ int hour = 0;
+ if (!ParseStringToInt(str_token.c_str(), &hour, true) ||
+ hour < 0 || hour > 23) {
+ return false;
+ }
+
+ str_token = SplitOneStringToken(&str_curr, kRFC822_TimeDelimiter);
+ if (str_token == "") {
+ return false;
+ }
+
+ int minute = 0;
+ if (!ParseStringToInt(str_token.c_str(), &minute, true) ||
+ minute < 0 || minute > 59) {
+ return false;
+ }
+
+ str_token = SplitOneStringToken(&str_curr, kRFC822_TimeDelimiter);
+ if (str_token == "") {
+ return false;
+ }
+
+ int second = 0;
+ // distingushed between XX:XX and XX:XX:XX time formats
+ if (str_token.size() == 2 && isdigit(str_token[0]) && isdigit(str_token[1])) {
+ second = 0;
+ if (!ParseStringToInt(str_token.c_str(), &second, true) ||
+ second < 0 || second > 59) {
+ return false;
+ }
+
+ str_token = SplitOneStringToken(&str_curr, kRFC822_TimeDelimiter);
+ if (str_token == "") {
+ return false;
+ }
+ }
+
+ int bias = 0;
+ if (str_token[0] == '+' || str_token[0] == '-' || isdigit(str_token[0])) {
+ // numeric format
+ int zone = 0;
+ if (!ParseStringToInt(str_token.c_str(), &zone, true)) {
+ return false;
+ }
+
+ // zone is in HHMM format, need to convert to the number of minutes
+ bias = (zone / 100) * 60 + (zone % 100);
+ } else { // text format
+ for (size_t i = 0; i < sizeof(kRFC822_TimeZone) / sizeof(TimeZoneInfo);
+ ++i) {
+ if (str_token == kRFC822_TimeZone[i].zone_name) {
+ bias = kRFC822_TimeZone[i].hour_dif * 60;
+ break;
+ }
+ }
+ }
+
+ SetZero(*time);
+ time->tm_year = year - 1900;
+ time->tm_mon = month;
+ time->tm_mday = day;
+ time->tm_hour = hour;
+ time->tm_min = minute;
+ time->tm_sec = second;
+
+ time64 time_64 = TmToTime64(*time);
+ time_64 = time_64 - bias * kMinsTo100ns;
+
+ if (!Time64ToTm(time_64, time)) {
+ return false;
+ }
+
+ if (ret_local_time) {
+ if (!UtcTimeToLocalTime(time)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Parse a string to time span
+//
+// A TimeSpan value can be represented as
+// [d.]hh:mm:ss
+//
+// d = days (optional)
+// hh = hours as measured on a 24-hour clock
+// mm = minutes
+// ss = seconds
+bool ParseStringToTimeSpan(const char* str, time64* time_span) {
+ ASSERT(str);
+ ASSERT(time_span);
+
+ const char kColonDelimitor[] = ":";
+ const char kDotDelimitor = '.';
+
+ std::string str_span(str);
+ time64 span = 0;
+
+ int idx = str_span.find(kDotDelimitor);
+ if (idx != -1) {
+ std::string str_day = str_span.substr(0, idx);
+ int day = 0;
+ if (!ParseStringToInt(str_day.c_str(), &day, true) ||
+ day < 0 || day > 365) {
+ return false;
+ }
+ span = day;
+
+ str_span = str_span.substr(idx + 1);
+ }
+
+ const char* str_curr = str_span.c_str();
+ std::string str_token;
+
+ str_token = SplitOneStringToken(&str_curr, kColonDelimitor);
+ if (str_token == "") {
+ return false;
+ }
+
+ int hour = 0;
+ if (!ParseStringToInt(str_token.c_str(), &hour, true) ||
+ hour < 0 || hour > 23) {
+ return false;
+ }
+ span = span * 24 + hour;
+
+ str_token = SplitOneStringToken(&str_curr, kColonDelimitor);
+ if (str_token == "") {
+ return false;
+ }
+
+ int minute = 0;
+ if (!ParseStringToInt(str_token.c_str(), &minute, true) ||
+ minute < 0 || minute > 59) {
+ return false;
+ }
+ span = span * 60 + minute;
+
+ str_token = SplitOneStringToken(&str_curr, kColonDelimitor);
+ if (str_token == "") {
+ return false;
+ }
+
+ int second = 0;
+ if (!ParseStringToInt(str_token.c_str(), &second, true) ||
+ second < 0 || second > 59) {
+ return false;
+ }
+
+ *time_span = (span * 60 + second) * kSecsTo100ns;
+
+ return true;
+}
+
+} // namespace notifier
diff --git a/chrome/browser/sync/notifier/base/time.h b/chrome/browser/sync/notifier/base/time.h
new file mode 100644
index 0000000..9bdb6f6
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/time.h
@@ -0,0 +1,114 @@
+// 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_BASE_TIME_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_BASE_TIME_H_
+
+#include <time.h>
+
+#include "talk/base/basictypes.h"
+
+typedef uint64 time64;
+
+#define kMicrosecsTo100ns (static_cast<time64>(10))
+#define kMillisecsTo100ns (static_cast<time64>(10000))
+#define kSecsTo100ns (1000 * kMillisecsTo100ns)
+#define kMinsTo100ns (60 * kSecsTo100ns)
+#define kHoursTo100ns (60 * kMinsTo100ns)
+#define kDaysTo100ns (24 * kHoursTo100ns)
+const time64 kMaxTime100ns = UINT64_C(9223372036854775807);
+
+// Time difference in 100NS granularity between platform-dependent starting
+// time and Jan 1, 1970.
+#ifdef WIN32
+// On Windows time64 is seconds since Jan 1, 1601.
+#define kStart100NsTimeToEpoch (116444736000000000uI64) // Jan 1, 1970 in time64
+#else
+// On Unix time64 is seconds since Jan 1, 1970.
+#define kStart100NsTimeToEpoch (0) // Jan 1, 1970 in time64
+#endif
+
+// Time difference in 100NS granularity between platform-dependent starting
+// time and Jan 1, 1980.
+#define kStart100NsTimeTo1980 \
+ kStart100NsTimeToEpoch + UINT64_C(3155328000000000)
+
+#define kTimeGranularity (kDaysTo100ns)
+
+namespace notifier {
+
+// Get the current time represented in 100NS granularity
+// Different platform might return the value since different starting time.
+// Win32 platform returns the value since Jan 1, 1601.
+time64 GetCurrent100NSTime();
+
+// Get the current time represented in 100NS granularity since epoch
+// (Jan 1, 1970).
+time64 GetCurrent100NSTimeSinceEpoch();
+
+// Convert from struct tm to time64.
+time64 TmToTime64(const struct tm& tm);
+
+// Convert from time64 to struct tm.
+bool Time64ToTm(time64 t, struct tm* tm);
+
+// Convert from UTC time to local time.
+bool UtcTimeToLocalTime(struct tm* tm);
+
+// Convert from local time to UTC time.
+bool LocalTimeToUtcTime(struct tm* tm);
+
+// Returns the local time as a string suitable for logging
+// Note: This is *not* threadsafe, so only call it from the main thread.
+char* GetLocalTimeAsString();
+
+// Parses RFC 822 Date/Time format
+// 5. DATE AND TIME SPECIFICATION
+// 5.1. SYNTAX
+//
+// date-time = [ day "," ] date time ; dd mm yy
+// ; hh:mm:ss zzz
+// day = "Mon" / "Tue" / "Wed" / "Thu"
+// / "Fri" / "Sat" / "Sun"
+//
+// date = 1*2DIGIT month 2DIGIT ; day month year
+// ; e.g. 20 Jun 82
+//
+// month = "Jan" / "Feb" / "Mar" / "Apr"
+// / "May" / "Jun" / "Jul" / "Aug"
+// / "Sep" / "Oct" / "Nov" / "Dec"
+//
+// time = hour zone ; ANSI and Military
+//
+// hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT]
+// ; 00:00:00 - 23:59:59
+//
+// zone = "UT" / "GMT" ; Universal Time
+// ; North American : UT
+// / "EST" / "EDT" ; Eastern: - 5/ - 4
+// / "CST" / "CDT" ; Central: - 6/ - 5
+// / "MST" / "MDT" ; Mountain: - 7/ - 6
+// / "PST" / "PDT" ; Pacific: - 8/ - 7
+// / 1ALPHA ; Military: Z = UT;
+// ; A:-1; (J not used)
+// ; M:-12; N:+1; Y:+12
+// / ( ("+" / "-") 4DIGIT ) ; Local differential
+// ; hours+min. (HHMM)
+// Return local time if ret_local_time == true, return UTC time otherwise
+bool ParseRFC822DateTime(const char* str, struct tm* time, bool ret_local_time);
+
+// Parse a string to time span.
+//
+// A TimeSpan value can be represented as
+// [d.]hh:mm:ss
+//
+// d = days (optional)
+// hh = hours as measured on a 24-hour clock
+// mm = minutes
+// ss = seconds
+bool ParseStringToTimeSpan(const char* str, time64* time_span);
+
+} // namespace notifier
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_BASE_TIME_H_
diff --git a/chrome/browser/sync/notifier/base/time_unittest.cc b/chrome/browser/sync/notifier/base/time_unittest.cc
new file mode 100644
index 0000000..0a34b0a
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/time_unittest.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/base/time.h"
+#include "notifier/testing/notifier/unittest.h"
+
+namespace notifier {
+
+TEST_NOTIFIER_F(TimeTest);
+
+TEST_F(TimeTest, ParseRFC822DateTime) {
+ struct tm t = {0};
+
+ EXPECT_TRUE(ParseRFC822DateTime("Mon, 16 May 2005 15:44:18 -0700",
+ &t, false));
+ EXPECT_EQ(t.tm_year, 2005 - 1900);
+ EXPECT_EQ(t.tm_mon, 4);
+ EXPECT_EQ(t.tm_mday, 16);
+ EXPECT_EQ(t.tm_hour, 22);
+ EXPECT_EQ(t.tm_min, 44);
+ EXPECT_EQ(t.tm_sec, 18);
+
+ EXPECT_TRUE(ParseRFC822DateTime("Mon, 16 May 2005 15:44:18 -0700", &t, true));
+ EXPECT_EQ(t.tm_year, 2005 - 1900);
+ EXPECT_EQ(t.tm_mon, 4);
+ EXPECT_EQ(t.tm_mday, 16);
+ EXPECT_TRUE(t.tm_hour == 15 || t.tm_hour == 14); // daylight saving time
+ EXPECT_EQ(t.tm_min, 44);
+ EXPECT_EQ(t.tm_sec , 18);
+
+ EXPECT_TRUE(ParseRFC822DateTime("Tue, 17 May 2005 02:56:18 +0400",
+ &t, false));
+ EXPECT_EQ(t.tm_year, 2005 - 1900);
+ EXPECT_EQ(t.tm_mon, 4);
+ EXPECT_EQ(t.tm_mday, 16);
+ EXPECT_EQ(t.tm_hour, 22);
+ EXPECT_EQ(t.tm_min, 56);
+ EXPECT_EQ(t.tm_sec , 18);
+
+ EXPECT_TRUE(ParseRFC822DateTime("Tue, 17 May 2005 02:56:18 +0400", &t, true));
+ EXPECT_EQ(t.tm_year, 2005 - 1900);
+ EXPECT_EQ(t.tm_mon, 4);
+ EXPECT_EQ(t.tm_mday, 16);
+ EXPECT_TRUE(t.tm_hour == 15 || t.tm_hour == 14); // daylight saving time
+ EXPECT_EQ(t.tm_min, 56);
+ EXPECT_EQ(t.tm_sec, 18);
+}
+
+TEST_F(TimeTest, ParseStringToTimeSpan) {
+ time64 time_span = 0;
+
+ EXPECT_TRUE(ParseStringToTimeSpan("0:0:4", &time_span));
+ EXPECT_EQ(time_span, 4 * kSecsTo100ns);
+
+ EXPECT_TRUE(ParseStringToTimeSpan("0:3:4", &time_span));
+ EXPECT_EQ(time_span, (3 * 60 + 4) * kSecsTo100ns);
+
+ EXPECT_TRUE(ParseStringToTimeSpan("2:3:4", &time_span));
+ EXPECT_EQ(time_span, (2 * 3600 + 3 * 60 + 4) * kSecsTo100ns);
+
+ EXPECT_TRUE(ParseStringToTimeSpan("1.2:3:4", &time_span));
+ EXPECT_EQ(time_span, (1 * 86400 + 2 * 60 * 60 + 3 * 60 + 4) * kSecsTo100ns);
+
+ EXPECT_FALSE(ParseStringToTimeSpan("2:invalid:4", &time_span));
+}
+
+TEST_F(TimeTest, UseLocalTimeAsString) {
+ // Just call it to ensure that it doesn't assert.
+ GetLocalTimeAsString();
+}
+
+} // namespace notifier
diff --git a/chrome/browser/sync/notifier/base/timer.cc b/chrome/browser/sync/notifier/base/timer.cc
new file mode 100644
index 0000000..7fa20b4
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/timer.cc
@@ -0,0 +1,33 @@
+// 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/base/timer.h"
+
+namespace notifier {
+
+Timer::Timer(talk_base::Task* parent, int timeout_seconds, bool repeat)
+ : Task(parent),
+ repeat_(repeat) {
+
+ set_timeout_seconds(timeout_seconds);
+ Start();
+ ResumeTimeout();
+}
+
+Timer::~Timer() {
+}
+
+int Timer::OnTimeout() {
+ if (!repeat_) {
+ return STATE_DONE;
+ }
+ ResetTimeout();
+ return STATE_BLOCKED;
+}
+
+int Timer::ProcessStart() {
+ return STATE_BLOCKED;
+}
+
+} // namespace notifier
diff --git a/chrome/browser/sync/notifier/base/timer.h b/chrome/browser/sync/notifier/base/timer.h
new file mode 100644
index 0000000..dd68c73
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/timer.h
@@ -0,0 +1,40 @@
+// 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_BASE_TIMER_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_BASE_TIMER_H_
+
+#include "talk/base/task.h"
+
+namespace notifier {
+
+class Timer : private talk_base::Task {
+ public:
+ Timer(talk_base::Task* parent, int timeout_seconds, bool repeat);
+ ~Timer();
+
+ // Call Abort() to stop the timer.
+ using talk_base::Task::Abort;
+
+ // Call to find out when the timer is set to go off
+ // Returns int64
+ using talk_base::Task::get_timeout_time;
+
+ // Call to set the timeout interval.
+ using talk_base::Task::set_timeout_seconds;
+
+ using talk_base::Task::SignalTimeout;
+
+ private:
+ virtual int OnTimeout();
+ virtual int ProcessStart();
+
+ bool repeat_;
+
+ DISALLOW_COPY_AND_ASSIGN(Timer);
+};
+
+} // namespace notifier
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_BASE_TIMER_H_
diff --git a/chrome/browser/sync/notifier/base/utils.h b/chrome/browser/sync/notifier/base/utils.h
new file mode 100644
index 0000000..2105233
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/utils.h
@@ -0,0 +1,91 @@
+// 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.
+//
+// Utility functions
+
+#ifndef CHROME_BROWSER_SYNC_NOTIFIER_BASE_UTILS_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_BASE_UTILS_H_
+
+#include <map>
+#include <string>
+
+#include "chrome/browser/sync/notifier/base/static_assert.h"
+
+// return error if the first argument evaluates to false
+#define RET_IF_FALSE(x) do { if (!(x)) return false; } while (false)
+
+// Protocol constants
+const char kHttpProto[] = "http://";
+const char kHttpsProto[] = "https://";
+
+// Initialize a POD to zero.
+// Using this function requires discipline. Don't use for types that have a
+// v-table or virtual bases.
+template <typename T>
+inline void SetZero(T& p) {
+ // Guard against the easy mistake of
+ // foo(int *p) { SetZero(p); } instead of
+ // SetZero(*p);
+ // which it should be.
+ STATIC_ASSERT(sizeof(T) != sizeof(void*));
+
+ // A POD (plain old data) object has one of these data types:
+ // a fundamental type, union, struct, array,
+ // or class--with no constructor. PODs don't have virtual functions or
+ // virtual bases.
+
+ // Test to see if the type has constructors.
+ union CtorTest {
+ T t;
+ int i;
+ };
+
+ // TODO(sync) There might be a way to test if the type has virtuals
+ // For now, if we zero a type with virtuals by mistake, it is going to crash
+ // predictable at run-time when the virtuals are called.
+ memset(&p, 0, sizeof(T));
+}
+
+// Used to delete each element in a vector<T*>/deque<T*>
+// (and then empty the sequence).
+template <class T>
+void CleanupSequence(T* items) {
+ for (typename T::iterator it(items->begin()); it != items->end(); ++it) {
+ delete (*it);
+ }
+ items->clear();
+}
+
+// Typically used to clean up values used in a hash_map
+// that had Type* as values.
+//
+// WARNING: This function assumes that T::clear will not access the values
+// (or the keys if they are the same as the values). This is true
+// for hash_map.
+template <class T>
+void CleanupMap(T* items) {
+ // This is likely slower than a for loop, but we have to do it this way. In
+ // some of the maps we use, deleting it->second causes it->first to be deleted
+ // as well, and that seems to send the iterator in a tizzy.
+ typename T::iterator it = items->begin();
+ while (it != items->end()) {
+ items->erase(it->first);
+ delete it->second;
+ it = items->begin();
+ }
+}
+
+// Get the value of an element in the map with the specified name
+template <class T>
+void GetMapElement(const std::map<const std::string, const T>& m,
+ const char* name,
+ T* value) {
+ typename std::map<const std::string, const T>::const_iterator iter(
+ m.find(name));
+ if (iter != m.end()) {
+ *value = iter->second;
+ }
+}
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_BASE_UTILS_H_
diff --git a/chrome/browser/sync/notifier/base/win32/async_network_alive_win32.cc b/chrome/browser/sync/notifier/base/win32/async_network_alive_win32.cc
new file mode 100644
index 0000000..b344817
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/win32/async_network_alive_win32.cc
@@ -0,0 +1,233 @@
+// 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 <winsock2.h>
+
+#include "chrome/browser/sync/notifier/base/async_network_alive.h"
+#include "chrome/browser/sync/notifier/base/utils.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/common.h"
+#include "third_party/smartany/scoped_any.h"
+
+namespace notifier {
+class PlatformNetworkInfo {
+ public:
+ PlatformNetworkInfo() : ws_handle_(NULL), event_handle_(NULL) {
+ }
+
+ ~PlatformNetworkInfo() {
+ Close();
+ }
+
+ void Close() {
+ talk_base::CritScope crit_scope(&crit_sect_);
+ if (ws_handle_) {
+ if (event_handle_) // unblock any waiting for network changes
+ SetEvent(get(event_handle_));
+ // finishes the iteration.
+ VERIFY(WSALookupServiceEnd(ws_handle_) == 0);
+ ws_handle_ = NULL;
+ LOG_F(LS_INFO) << "WSACleanup 1";
+ ::WSACleanup();
+ }
+ }
+
+ bool IsAlive(bool* error) {
+ ASSERT(error);
+ *error = false;
+
+ // If IsAlive was previously called, we need a new handle.
+ // Why? If we use the same handle, we only get diffs on what changed
+ // which isn't what we want.
+ Close();
+ int result = Initialize();
+ if (result != 0) {
+ LOG_F(LS_ERROR) << "failed:" << result;
+ // Default to alive on error.
+ *error = true;
+ return true;
+ }
+
+ bool alive = false;
+
+ // Retrieve network info and move to next one. In this function, we only
+ // need to know whether or not there is network connection.
+ // allocate 256 bytes for name, it should be enough for most cases.
+ // If the name is longer, it is OK as we will check the code returned and
+ // set correct network status.
+ char result_buffer[sizeof(WSAQUERYSET) + 256] = {0};
+ bool flush_previous_result = false;
+ do {
+ DWORD control_flags = LUP_RETURN_NAME;
+ if (flush_previous_result) {
+ control_flags |= LUP_FLUSHPREVIOUS;
+ }
+ DWORD length = sizeof(result_buffer);
+ reinterpret_cast<WSAQUERYSET*>(&result_buffer[0])->dwSize =
+ sizeof(WSAQUERYSET);
+ // ws_handle_ may be NULL (if exiting), but the call will simply fail
+ int result = ::WSALookupServiceNext(
+ ws_handle_,
+ control_flags,
+ &length,
+ reinterpret_cast<WSAQUERYSET*>(&result_buffer[0]));
+
+ if (result == 0) {
+ // get at least one connection, return "connected".
+ alive = true;
+ } else {
+ ASSERT(result == SOCKET_ERROR);
+ result = ::WSAGetLastError();
+ if (result == WSA_E_NO_MORE || result == WSAENOMORE) {
+ break;
+ }
+
+ // Error code WSAEFAULT means there is a network connection but the
+ // result_buffer size is too small to contain the results. The
+ // variable "length" returned from WSALookupServiceNext is the minimum
+ // number of bytes required. We do not need to retrieve detail info.
+ // Return "alive" in this case.
+ if (result == WSAEFAULT) {
+ alive = true;
+ flush_previous_result = true;
+ } else {
+ LOG_F(LS_WARNING) << "failed:" << result;
+ *error = true;
+ break;
+ }
+ }
+ } while (true);
+ LOG_F(LS_INFO) << "alive: " << alive;
+ return alive;
+ }
+
+ bool WaitForChange() {
+ // IsAlive must be called first.
+ int junk1 = 0, junk2 = 0;
+ DWORD bytes_returned = 0;
+ int result = SOCKET_ERROR;
+ {
+ talk_base::CritScope crit_scope(&crit_sect_);
+ if (!ws_handle_)
+ return false;
+ ASSERT(!event_handle_);
+ reset(event_handle_, ::CreateEvent(NULL, FALSE, FALSE, NULL));
+ if (!event_handle_) {
+ LOG_F(LS_WARNING) << "failed to CreateEvent";
+ return false;
+ }
+ WSAOVERLAPPED overlapped = {0};
+ overlapped.hEvent = get(event_handle_);
+ WSACOMPLETION completion;
+ ::SetZero(completion);
+ completion.Type = NSP_NOTIFY_EVENT;
+ completion.Parameters.Event.lpOverlapped = &overlapped;
+
+ LOG_F(LS_INFO) << "calling WSANSPIoctl";
+ // Do a non-blocking request for change notification. event_handle_
+ // will get signaled when there is a change, so we wait on it later.
+ // It can also be signaled by Close() in order allow clean termination.
+ result = ::WSANSPIoctl(ws_handle_,
+ SIO_NSP_NOTIFY_CHANGE,
+ &junk1,
+ 0,
+ &junk2,
+ 0,
+ &bytes_returned,
+ &completion);
+ }
+ if (NO_ERROR != result) {
+ result = ::WSAGetLastError();
+ if (WSA_IO_PENDING != result) {
+ LOG_F(LS_WARNING) << "failed: " << result;
+ reset(event_handle_);
+ return false;
+ }
+ }
+ LOG_F(LS_INFO) << "waiting";
+ WaitForSingleObject(get(event_handle_), INFINITE);
+ reset(event_handle_);
+ LOG_F(LS_INFO) << "changed";
+ return true;
+ }
+
+ private:
+ int Initialize() {
+ WSADATA wsa_data;
+ LOG_F(LS_INFO) << "calling WSAStartup";
+ int result = ::WSAStartup(MAKEWORD(2, 2), &wsa_data);
+ if (result != ERROR_SUCCESS) {
+ LOG_F(LS_ERROR) << "failed:" << result;
+ return result;
+ }
+
+ WSAQUERYSET query_set = {0};
+ query_set.dwSize = sizeof(WSAQUERYSET);
+ query_set.dwNameSpace = NS_NLA;
+ // Initiate a client query to iterate through the
+ // currently connected networks.
+ if (0 != ::WSALookupServiceBegin(&query_set, LUP_RETURN_ALL,
+ &ws_handle_)) {
+ result = ::WSAGetLastError();
+ LOG_F(LS_INFO) << "WSACleanup 2";
+ ::WSACleanup();
+ ASSERT(ws_handle_ == NULL);
+ ws_handle_ = NULL;
+ return result;
+ }
+ return 0;
+ }
+ talk_base::CriticalSection crit_sect_;
+ HANDLE ws_handle_;
+ scoped_event event_handle_;
+ DISALLOW_COPY_AND_ASSIGN(PlatformNetworkInfo);
+};
+
+class AsyncNetworkAliveWin32 : public AsyncNetworkAlive {
+ public:
+ AsyncNetworkAliveWin32() {
+ }
+
+ virtual ~AsyncNetworkAliveWin32() {
+ if (network_info_) {
+ delete network_info_;
+ network_info_ = NULL;
+ }
+ }
+
+ protected:
+ // SignalThread Interface
+ virtual void DoWork() {
+ if (!network_info_) {
+ network_info_ = new PlatformNetworkInfo();
+ } else {
+ // Since network_info is set, it means that
+ // we are suppose to wait for network state changes.
+ if (!network_info_->WaitForChange()) {
+ // The wait was aborted so we must be shutting down.
+ alive_ = false;
+ error_ = true;
+ return;
+ }
+ }
+ alive_ = network_info_->IsAlive(&error_);
+ }
+
+ virtual void OnWorkStop() {
+ if (network_info_) {
+ network_info_->Close();
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AsyncNetworkAliveWin32);
+};
+
+AsyncNetworkAlive* AsyncNetworkAlive::Create() {
+ return new AsyncNetworkAliveWin32();
+}
+
+} // namespace notifier
diff --git a/chrome/browser/sync/notifier/base/win32/time_win32.cc b/chrome/browser/sync/notifier/base/win32/time_win32.cc
new file mode 100644
index 0000000..34a53fe
--- /dev/null
+++ b/chrome/browser/sync/notifier/base/win32/time_win32.cc
@@ -0,0 +1,158 @@
+// 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.
+//
+// Time functions
+
+#include <time.h>
+#include <windows.h>
+
+#include "chrome/browser/sync/notifier/base/time.h"
+
+#include "chrome/browser/sync/notifier/base/utils.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+namespace notifier {
+
+time64 FileTimeToTime64(const FILETIME& file_time) {
+ return static_cast<time64>(file_time.dwHighDateTime) << 32 |
+ file_time.dwLowDateTime;
+}
+
+void Time64ToFileTime(const time64& time, FILETIME* ft) {
+ ASSERT(ft);
+
+ ft->dwHighDateTime = static_cast<DWORD>(time >> 32);
+ ft->dwLowDateTime = static_cast<DWORD>(time & 0xffffffff);
+}
+
+void TmTimeToSystemTime(const struct tm& tm, SYSTEMTIME* sys_time) {
+ ASSERT(sys_time);
+
+ SetZero(*sys_time);
+ // tm's year is 1900 based, systemtime's year is absolute
+ sys_time->wYear = tm.tm_year + 1900;
+ // tm's month is 0 based, but systemtime's month is 1 based
+ sys_time->wMonth = tm.tm_mon + 1;
+ sys_time->wDay = tm.tm_mday;
+ sys_time->wDayOfWeek = tm.tm_wday;
+ sys_time->wHour = tm.tm_hour;
+ sys_time->wMinute = tm.tm_min;
+ sys_time->wSecond = tm.tm_sec;
+}
+
+void SystemTimeToTmTime(const SYSTEMTIME& sys_time, struct tm* tm) {
+ ASSERT(tm);
+
+ SetZero(*tm);
+ // tm's year is 1900 based, systemtime's year is absolute
+ tm->tm_year = sys_time.wYear - 1900;
+ // tm's month is 0 based, but systemtime's month is 1 based
+ tm->tm_mon = sys_time.wMonth - 1;
+ tm->tm_mday = sys_time.wDay;
+ tm->tm_wday = sys_time.wDayOfWeek;
+ tm->tm_hour = sys_time.wHour;
+ tm->tm_min = sys_time.wMinute;
+ tm->tm_sec = sys_time.wSecond;
+}
+
+time64 GetCurrent100NSTime() {
+ // In order to get the 100ns time we shouldn't use SystemTime
+ // as it's granularity is 1 ms. Below is the correct implementation.
+ // On the other hand the system clock granularity is 15 ms, so we
+ // are not gaining much by having the timestamp in nano-sec
+ // If we decise to go with ms, divide "time64 time" by 10000
+
+ FILETIME file_time;
+ ::GetSystemTimeAsFileTime(&file_time);
+
+ time64 time = FileTimeToTime64(file_time);
+ return time;
+}
+
+time64 TmToTime64(const struct tm& tm) {
+ SYSTEMTIME sys_time;
+ TmTimeToSystemTime(tm, &sys_time);
+
+ FILETIME file_time;
+ SetZero(file_time);
+ if (!::SystemTimeToFileTime(&sys_time, &file_time)) {
+ return 0;
+ }
+
+ return FileTimeToTime64(file_time);
+}
+
+bool Time64ToTm(time64 t, struct tm* tm) {
+ ASSERT(tm);
+
+ FILETIME file_time;
+ SetZero(file_time);
+ Time64ToFileTime(t, &file_time);
+
+ SYSTEMTIME sys_time;
+ SetZero(sys_time);
+ if (!::FileTimeToSystemTime(&file_time, &sys_time)) {
+ return false;
+ }
+
+ SystemTimeToTmTime(sys_time, tm);
+
+ return true;
+}
+
+bool UtcTimeToLocalTime(struct tm* tm) {
+ ASSERT(tm);
+
+ SYSTEMTIME utc_time;
+ TmTimeToSystemTime(*tm, &utc_time);
+
+ TIME_ZONE_INFORMATION time_zone;
+ if (::GetTimeZoneInformation(&time_zone) == TIME_ZONE_ID_INVALID) {
+ return false;
+ }
+
+ SYSTEMTIME local_time;
+ if (!::SystemTimeToTzSpecificLocalTime(&time_zone, &utc_time, &local_time)) {
+ return false;
+ }
+
+ SystemTimeToTmTime(local_time, tm);
+
+ return true;
+}
+
+bool LocalTimeToUtcTime(struct tm* tm) {
+ ASSERT(tm);
+
+ SYSTEMTIME local_time;
+ TmTimeToSystemTime(*tm, &local_time);
+
+ // Get the bias, which when added to local, gives UTC
+ TIME_ZONE_INFORMATION time_zone;
+ if (::GetTimeZoneInformation(&time_zone) == TIME_ZONE_ID_INVALID) {
+ return false;
+ }
+
+ // By negating the biases, we can get translation from UTC to local
+ time_zone.Bias *= -1;
+ time_zone.DaylightBias *= -1;
+ time_zone.StandardBias *= -1; // this is 0 but negating for completness
+
+ // We'll tell SystemTimeToTzSpecificLocalTime that the local time is actually
+ // UTC. With the negated bias, the "local" time that the API returns will
+ // actually be UTC. Casting the const off because
+ // SystemTimeToTzSpecificLocalTime's definition requires it, although the
+ // value is not modified.
+ SYSTEMTIME utc_time;
+ if (!::SystemTimeToTzSpecificLocalTime(&time_zone, &local_time, &utc_time)) {
+ return false;
+ }
+
+ SystemTimeToTmTime(utc_time, tm);
+
+ return true;
+}
+
+} // namespace notifier
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[] = ",&nbsp;";
+const char kEllidedSeparator[] = "&nbsp;..";
+
+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 ? "&nbsp;" : 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("&nbsp;(");
+ count_html.append(count);
+ count_html.append(")");
+ // Reduce space due to parenthesis and &nbsp;.
+ 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>&nbsp;(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,&nbsp;Alex,&nbsp;<b>me</b>&nbsp;(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>,&nbsp;<b>Alex</b>,&nbsp;<b>bob</b>&nbsp;(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>,&nbsp;<b>ReallyLo.</b>&nbsp;(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&nbsp;..&nbsp;<b>AnotherB.</b>&nbsp;..&nbsp;(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&nbsp;..&nbsp;<b>Bob</b>&nbsp;..&nbsp;<b>Person1</b>&nbsp;(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_
diff --git a/chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc b/chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc
new file mode 100644
index 0000000..3bc6550
--- /dev/null
+++ b/chrome/browser/sync/notifier/gaia_auth/gaiaauth.cc
@@ -0,0 +1,442 @@
+// 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/gaia_auth/gaiaauth.h"
+#include "talk/base/asynchttprequest.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/httpclient.h"
+#include "talk/base/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/signalthread.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/socketpool.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/urlencode.h"
+#include "talk/xmpp/saslcookiemechanism.h"
+#include "talk/xmpp/saslplainmechanism.h"
+
+namespace buzz {
+
+static const int kGaiaAuthTimeoutMs = 30 * 1000; // 30 sec
+
+// Warning, this is externed.
+GaiaServer buzz::g_gaia_server;
+
+///////////////////////////////////////////////////////////////////////////////
+// GaiaAuth::WorkerThread
+///////////////////////////////////////////////////////////////////////////////
+
+// GaiaAuth is NOT invoked during SASL authenticatioin, but
+// it is invoked even before XMPP login begins. As a PreXmppAuth
+// object, it is driven by XmppClient before the XMPP socket is
+// opened. The job of GaiaAuth is to goes out using HTTPS
+// POST to grab cookies from GAIA.
+
+// It is used by XmppClient.
+// It grabs a SaslAuthenticator which knows how to play the cookie login.
+
+class GaiaAuth::WorkerThread : public talk_base::SignalThread {
+ public:
+ WorkerThread(const std::string& username,
+ const talk_base::CryptString& pass,
+ const std::string& token,
+ const std::string& service,
+ const std::string& user_agent,
+ const std::string& signature,
+ bool obtain_auth,
+ const std::string& token_service) :
+ username_(username),
+ pass_(pass),
+ service_(service),
+ firewall_(0),
+ done_(false),
+ success_(false),
+ error_(true),
+ error_code_(0),
+ proxy_auth_required_(false),
+ certificate_expired_(false),
+ auth_token_(token),
+ fresh_auth_token_(false),
+ obtain_auth_(obtain_auth),
+ agent_(user_agent),
+ signature_(signature),
+ token_service_(token_service) {}
+
+ void set_proxy(const talk_base::ProxyInfo& proxy) { proxy_ = proxy; }
+ void set_firewall(talk_base::FirewallManager * firewall) {
+ firewall_ = firewall;
+ }
+ void set_captcha_answer(const CaptchaAnswer& captcha_answer) {
+ captcha_answer_ = captcha_answer;
+ }
+
+ virtual void DoWork() {
+ LOG(INFO) << "GaiaAuth Begin";
+ // Maybe we already have an auth token, then there is nothing to do.
+ if (!auth_token_.empty()) {
+ LOG(INFO) << "Reusing auth token:" << auth_token_;
+ success_ = true;
+ error_ = false;
+ } else {
+ talk_base::PhysicalSocketServer physical;
+ talk_base::SocketServer * ss = &physical;
+ if (firewall_) {
+ ss = new talk_base::FirewallSocketServer(ss, firewall_);
+ }
+
+ talk_base::SslSocketFactory factory(ss, agent_);
+ factory.SetProxy(proxy_);
+ if (g_gaia_server.use_ssl()) {
+ factory.SetIgnoreBadCert(true);
+ factory.UseSSL(g_gaia_server.hostname().c_str());
+ }
+ factory.SetLogging(talk_base::LS_VERBOSE, "GaiaAuth");
+
+ talk_base::ReuseSocketPool pool(&factory);
+ talk_base::HttpClient http(agent_, &pool);
+
+ talk_base::HttpMonitor monitor(ss);
+ monitor.Connect(&http);
+
+ // If we do not already have a SID, let's get one using our password.
+ if (sid_.empty() || (auth_.empty() && obtain_auth_)) {
+ GaiaRequestSid(&http, username_, pass_, signature_,
+ obtain_auth_ ? service_ : "", captcha_answer_,
+ g_gaia_server);
+ ss->Wait(kGaiaAuthTimeoutMs, true);
+
+ error_code_ = monitor.error(); // save off the error code
+
+ if (!monitor.done()) {
+ LOG(INFO) << "GaiaAuth request timed out";
+ goto Cleanup;
+ } else if (monitor.error()) {
+ LOG(INFO) << "GaiaAuth request error: " << monitor.error();
+ if (monitor.error() == talk_base::HE_AUTH) {
+ success_ = false;
+ proxy_auth_required_ = true;
+ } else if (monitor.error() == talk_base::HE_CERTIFICATE_EXPIRED) {
+ success_ = false;
+ certificate_expired_ = true;
+ }
+ goto Cleanup;
+ } else {
+ std::string captcha_token, captcha_url;
+ switch (GaiaParseSidResponse(http, g_gaia_server,
+ &captcha_token, &captcha_url,
+ &sid_, &lsid_, &auth_)) {
+ case GR_ERROR:
+ goto Cleanup;
+
+ case GR_UNAUTHORIZED:
+ if (!captcha_url.empty()) {
+ captcha_challenge_ = buzz::CaptchaChallenge(captcha_token,
+ captcha_url);
+ }
+ // We had no "error" - we were just unauthorized
+ error_ = false;
+ error_code_ = 0;
+ goto Cleanup;
+
+ case GR_SUCCESS:
+ break;
+ }
+ }
+ }
+
+ // If all we need is a SID, then we are done now.
+ if (service_.empty() || obtain_auth_) {
+ success_ = true;
+ error_ = false;
+ error_code_ = 0;
+ goto Cleanup;
+ }
+
+ monitor.reset();
+ GaiaRequestAuthToken(&http, sid_, lsid_, service_, g_gaia_server);
+ ss->Wait(kGaiaAuthTimeoutMs, true);
+
+ error_code_ = monitor.error(); // save off the error code
+
+ if (!monitor.done()) {
+ LOG(INFO) << "GaiaAuth request timed out";
+ } else if (monitor.error()) {
+ LOG(INFO) << "GaiaAuth request error: " << monitor.error();
+ if (monitor.error() == talk_base::HE_AUTH) {
+ success_ = false;
+ proxy_auth_required_ = true;
+ } else if (monitor.error() == talk_base::HE_CERTIFICATE_EXPIRED) {
+ success_ = false;
+ certificate_expired_ = true;
+ }
+ } else {
+ if (GR_SUCCESS == GaiaParseAuthTokenResponse(http, &auth_token_)) {
+ fresh_auth_token_ = true;
+ success_ = true;
+ error_ = false;
+ error_code_ = 0;
+ }
+ }
+ }
+
+ // done authenticating
+
+ Cleanup:
+ done_ = true;
+ }
+
+ bool IsDone() const { return done_; }
+ bool Succeeded() const { return success_; }
+ bool HadError() const { return error_; }
+ int GetError() const { return error_code_; }
+ bool ProxyAuthRequired() const { return proxy_auth_required_; }
+ bool CertificateExpired() const { return certificate_expired_; }
+ const buzz::CaptchaChallenge& GetCaptchaChallenge() {
+ return captcha_challenge_;
+ }
+ bool fresh_auth_token() const { return fresh_auth_token_; }
+
+ talk_base::CryptString GetPassword() const { return pass_; }
+ std::string GetSID() const { return sid_; }
+ std::string GetAuth() const { return auth_; }
+ std::string GetToken() const { return auth_token_; }
+ std::string GetUsername() const { return username_; }
+ std::string GetTokenService() const { return token_service_; }
+
+ private:
+ std::string username_;
+ talk_base::CryptString pass_;
+ std::string service_;
+ talk_base::ProxyInfo proxy_;
+ talk_base::FirewallManager * firewall_;
+ bool done_;
+ bool success_;
+ bool error_;
+ int error_code_;
+ bool proxy_auth_required_;
+ bool certificate_expired_;
+ std::string sid_;
+ std::string lsid_;
+ std::string auth_;
+ std::string auth_token_;
+ buzz::CaptchaChallenge captcha_challenge_;
+ CaptchaAnswer captcha_answer_;
+ bool fresh_auth_token_;
+ bool obtain_auth_;
+ std::string agent_;
+ std::string signature_;
+ std::string token_service_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// GaiaAuth
+///////////////////////////////////////////////////////////////////////////////
+
+GaiaAuth::GaiaAuth(const std::string &user_agent, const std::string &sig)
+ : agent_(user_agent), signature_(sig),
+ firewall_(0), worker_(NULL), done_(false) {
+}
+
+GaiaAuth::~GaiaAuth() {
+ if (worker_) {
+ worker_->Release();
+ worker_ = NULL;
+ }
+}
+
+void GaiaAuth::StartPreXmppAuth(const buzz::Jid& jid,
+ const talk_base::SocketAddress& server,
+ const talk_base::CryptString& pass,
+ const std::string & auth_cookie) {
+ InternalStartGaiaAuth(jid, server, pass, auth_cookie, "mail", false);
+}
+
+void GaiaAuth::StartTokenAuth(const buzz::Jid& jid,
+ const talk_base::CryptString& pass,
+ const std::string& service) {
+ InternalStartGaiaAuth(jid, talk_base::SocketAddress(),
+ pass, "", service, false);
+}
+
+void GaiaAuth::StartAuth(const buzz::Jid& jid,
+ const talk_base::CryptString& pass,
+ const std::string & service) {
+ InternalStartGaiaAuth(jid, talk_base::SocketAddress(),
+ pass, "", service, true);
+}
+
+void GaiaAuth::StartAuthFromSid(const buzz::Jid& jid,
+ const std::string& sid,
+ const std::string& service) {
+ InternalStartGaiaAuth(jid, talk_base::SocketAddress(),
+ talk_base::CryptString(), sid, service, false);
+}
+
+void GaiaAuth::InternalStartGaiaAuth(const buzz::Jid& jid,
+ const talk_base::SocketAddress& server,
+ const talk_base::CryptString& pass,
+ const std::string& token,
+ const std::string& service,
+ bool obtain_auth) {
+ worker_ = new WorkerThread(jid.Str(), pass, token,
+ service, agent_, signature_,
+ obtain_auth, token_service_);
+ worker_->set_proxy(proxy_);
+ worker_->set_firewall(firewall_);
+ worker_->set_captcha_answer(captcha_answer_);
+ worker_->SignalWorkDone.connect(this, &GaiaAuth::OnAuthDone);
+ worker_->Start();
+}
+
+void GaiaAuth::OnAuthDone(talk_base::SignalThread* worker) {
+ if (!worker_->IsDone())
+ return;
+ done_ = true;
+
+ if (worker_->fresh_auth_token()) {
+ SignalFreshAuthCookie(worker_->GetToken());
+ }
+ if (worker_->ProxyAuthRequired()) {
+ SignalAuthenticationError();
+ }
+ if (worker_->CertificateExpired()) {
+ SignalCertificateExpired();
+ }
+ SignalAuthDone();
+}
+
+std::string GaiaAuth::ChooseBestSaslMechanism(
+ const std::vector<std::string> & mechanisms, bool encrypted) {
+ if (!done_)
+ return "";
+
+ std::vector<std::string>::const_iterator it;
+
+ // a token is the weakest auth - 15s, service-limited, so prefer it.
+ it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-TOKEN");
+ if (it != mechanisms.end())
+ return "X-GOOGLE-TOKEN";
+
+ // a cookie is the next weakest - 14 days
+ it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-COOKIE");
+ if (it != mechanisms.end())
+ return "X-GOOGLE-COOKIE";
+
+ // never pass @google.com passwords without encryption!!
+ if (!encrypted &&
+ buzz::Jid(worker_->GetUsername()).domain() == "google.com") {
+ return "";
+ }
+
+ // as a last resort, use plain authentication
+ if (buzz::Jid(worker_->GetUsername()).domain() != "google.com") {
+ it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN");
+ if (it != mechanisms.end())
+ return "PLAIN";
+ }
+
+ // No good mechanism found
+ return "";
+}
+
+buzz::SaslMechanism* GaiaAuth::CreateSaslMechanism(
+ const std::string& mechanism) {
+
+ if (!done_) {
+ return NULL;
+ }
+
+ if (mechanism == "X-GOOGLE-TOKEN") {
+ return new buzz::SaslCookieMechanism(
+ mechanism,
+ worker_->GetUsername(),
+ worker_->GetToken(),
+ worker_->GetTokenService());
+ }
+
+ if (mechanism == "X-GOOGLE-COOKIE") {
+ return new buzz::SaslCookieMechanism(
+ "X-GOOGLE-COOKIE",
+ worker_->GetUsername(),
+ worker_->GetSID(),
+ worker_->GetTokenService());
+ }
+
+ if (mechanism == "PLAIN") {
+ return new buzz::SaslPlainMechanism(buzz::Jid(worker_->GetUsername()),
+ worker_->GetPassword());
+ }
+
+ // oh well - none of the above
+ return NULL;
+}
+
+std::string GaiaAuth::CreateAuthenticatedUrl(
+ const std::string & continue_url, const std::string & service) {
+ if (!done_ || worker_->GetToken().empty())
+ return "";
+
+ std::string url;
+ // Note that http_prefix always ends with a "/"
+ url += g_gaia_server.http_prefix()
+ + "accounts/TokenAuth?auth="
+ + worker_->GetToken(); // Do not URL encode - GAIA doesn't like that
+ url += "&service=" + service;
+ url += "&continue=" + UrlEncodeString(continue_url);
+ url += "&source=" + signature_;
+ return url;
+}
+
+std::string GaiaAuth::GetAuthCookie() {
+ assert(IsAuthDone() && IsAuthorized());
+ if (!done_ || !worker_->Succeeded()) {
+ return "";
+ }
+ return worker_->GetToken();
+}
+
+std::string GaiaAuth::GetAuth() {
+ assert(IsAuthDone() && IsAuthorized());
+ if (!done_ || !worker_->Succeeded()) {
+ return "";
+ }
+ return worker_->GetAuth();
+}
+
+std::string GaiaAuth::GetSID() {
+ assert(IsAuthDone() && IsAuthorized());
+ if (!done_ || !worker_->Succeeded()) {
+ return "";
+ }
+ return worker_->GetSID();
+}
+
+bool GaiaAuth::IsAuthDone() {
+ return done_;
+}
+
+bool GaiaAuth::IsAuthorized() {
+ return done_ && worker_ != NULL && worker_->Succeeded();
+}
+
+bool GaiaAuth::HadError() {
+ return done_ && worker_ != NULL && worker_->HadError();
+}
+
+int GaiaAuth::GetError() {
+ if (done_ && worker_ != NULL) {
+ return worker_->GetError();
+ }
+ return 0;
+}
+
+buzz::CaptchaChallenge GaiaAuth::GetCaptchaChallenge() {
+ if (!done_ || worker_->Succeeded()) {
+ return buzz::CaptchaChallenge();
+ }
+ return worker_->GetCaptchaChallenge();
+}
+} // namespace buzz
diff --git a/chrome/browser/sync/notifier/gaia_auth/gaiaauth.h b/chrome/browser/sync/notifier/gaia_auth/gaiaauth.h
new file mode 100644
index 0000000..8919bbc
--- /dev/null
+++ b/chrome/browser/sync/notifier/gaia_auth/gaiaauth.h
@@ -0,0 +1,129 @@
+// 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.
+//
+// Gaia auth code for XMPP notifier support. This should be merged with
+// the other gaia auth file when we have time.
+
+#ifndef CHROME_BROWSER_SYNC_NOTIFIER_GAIA_AUTH_GAIAAUTH_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_GAIA_AUTH_GAIAAUTH_H_
+
+#include <string>
+#include <vector>
+
+#include "chrome/browser/sync/notifier/gaia_auth/gaiahelper.h"
+#include "talk/base/cryptstring.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/xmpp/prexmppauth.h"
+
+namespace talk_base {
+class FirewallManager;
+class SignalThread;
+}
+
+namespace buzz {
+
+///////////////////////////////////////////////////////////////////////////////
+// GaiaAuth
+///////////////////////////////////////////////////////////////////////////////
+
+class GaiaAuth : public PreXmppAuth, public sigslot::has_slots<> {
+ public:
+ GaiaAuth(const std::string& user_agent, const std::string& signature);
+ virtual ~GaiaAuth();
+
+ void set_proxy(const talk_base::ProxyInfo& proxy) {
+ proxy_ = proxy;
+ }
+ void set_firewall(talk_base::FirewallManager* firewall) {
+ firewall_ = firewall;
+ }
+ void set_captcha_answer(const CaptchaAnswer& captcha_answer) {
+ captcha_answer_ = captcha_answer;
+ }
+
+ // From inside XMPP login, this is called
+ virtual void StartPreXmppAuth(const buzz::Jid& jid,
+ const talk_base::SocketAddress& server,
+ const talk_base::CryptString& pass,
+ const std::string& auth_cookie);
+
+ void StartTokenAuth(const buzz::Jid& jid,
+ const talk_base::CryptString& pass,
+ const std::string& service);
+
+ // This is used when calling GetAuth()
+ void StartAuth(const buzz::Jid& jid,
+ const talk_base::CryptString& pass,
+ const std::string& service);
+
+ // This is used when bootstrapping from a download page
+ void StartAuthFromSid(const buzz::Jid& jid,
+ const std::string& sid,
+ const std::string& service);
+
+ virtual bool IsAuthDone();
+ virtual bool IsAuthorized();
+ virtual bool HadError();
+ virtual int GetError();
+ virtual buzz::CaptchaChallenge GetCaptchaChallenge();
+ // Returns the auth token that can be sent in an url param to gaia in order
+ // to generate an auth cookie.
+ virtual std::string GetAuthCookie();
+
+ // Returns the auth cookie for gaia.
+ std::string GetAuth();
+ std::string GetSID();
+
+ // Sets / gets the token service to use.
+ std::string token_service() const { return token_service_; }
+ void set_token_service(const std::string& token_service) {
+ token_service_ = token_service;
+ }
+
+ virtual std::string ChooseBestSaslMechanism(
+ const std::vector<std::string>& mechanisms, bool encrypted);
+ virtual buzz::SaslMechanism* CreateSaslMechanism(
+ const std::string& mechanism);
+
+ std::string CreateAuthenticatedUrl(const std::string& continue_url,
+ const std::string& service);
+
+ sigslot::signal0<> SignalAuthenticationError;
+ sigslot::signal0<> SignalCertificateExpired;
+ sigslot::signal1<const std::string&> SignalFreshAuthCookie;
+
+ private:
+ void OnAuthDone(talk_base::SignalThread* worker);
+
+ void InternalStartGaiaAuth(const buzz::Jid& jid,
+ const talk_base::SocketAddress& server,
+ const talk_base::CryptString& pass,
+ const std::string& sid,
+ const std::string& service,
+ bool obtain_auth);
+
+ std::string agent_;
+ std::string signature_;
+ talk_base::ProxyInfo proxy_;
+ talk_base::FirewallManager* firewall_;
+ class WorkerThread;
+ WorkerThread* worker_;
+ bool done_;
+
+ CaptchaAnswer captcha_answer_;
+ std::string token_service_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Globals
+///////////////////////////////////////////////////////////////////////////////
+
+extern GaiaServer g_gaia_server;
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace buzz
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_GAIA_AUTH_GAIAAUTH_H_
diff --git a/chrome/browser/sync/notifier/gaia_auth/gaiahelper.cc b/chrome/browser/sync/notifier/gaia_auth/gaiahelper.cc
new file mode 100644
index 0000000..3e0683c
--- /dev/null
+++ b/chrome/browser/sync/notifier/gaia_auth/gaiahelper.cc
@@ -0,0 +1,236 @@
+// 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/gaia_auth/gaiahelper.h"
+#include "talk/base/common.h"
+#include "talk/base/cryptstring.h"
+#include "talk/base/httpclient.h"
+#include "talk/base/httpcommon-inl.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/urlencode.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+std::string GetValueForKey(const std::string & key, const std::string & nvp) {
+ size_t start_of_line = 0;
+ size_t end_of_line = 0;
+ for (;;) { // for each line
+ start_of_line = nvp.find_first_not_of("\r\n", end_of_line);
+ if (start_of_line == std::string::npos)
+ break;
+ end_of_line = nvp.find_first_of("\r\n", start_of_line);
+ if (end_of_line == std::string::npos) {
+ end_of_line = nvp.length();
+ }
+ size_t equals = nvp.find('=', start_of_line);
+ if (equals >= end_of_line ||
+ equals == std::string::npos ||
+ equals - start_of_line != key.length()) {
+ continue;
+ }
+
+ if (nvp.find(key, start_of_line) == start_of_line) {
+ return std::string(nvp, equals + 1, end_of_line - equals - 1);
+ }
+ }
+ return "";
+}
+
+} // anonymous namespace
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace buzz {
+
+GaiaServer::GaiaServer()
+ : hostname_("www.google.com"),
+ port_(443),
+ use_ssl_(true) {
+}
+
+bool GaiaServer::SetServer(const char* url) {
+ talk_base::Url<char> parsed(url);
+ hostname_ = parsed.server();
+ port_ = parsed.port();
+ use_ssl_ = parsed.secure();
+ return true; // parsed.valid();
+}
+
+bool GaiaServer::SetDebugServer(const char* server) {
+ const char* colon = strchr(server, ':');
+ if (colon) {
+ hostname_ = std::string(server, colon - server);
+ port_ = atoi(colon+1);
+ use_ssl_ = false;
+ return true;
+ }
+ return false;
+}
+
+std::string GaiaServer::http_prefix() const {
+ talk_base::Url<char> parsed("", hostname_, port_);
+ parsed.set_secure(use_ssl_);
+ return parsed.url();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool GaiaRequestSid(talk_base::HttpClient* client,
+ const std::string& username,
+ const talk_base::CryptString& password,
+ const std::string& signature,
+ const std::string& service,
+ const CaptchaAnswer& captcha_answer,
+ const GaiaServer& gaia_server) {
+ buzz::Jid jid(username);
+ std::string usable_name = username;
+ if (jid.domain() == buzz::STR_DEFAULT_DOMAIN) {
+ // The default domain (default.talk.google.com) is not usable
+ // for Gaia auth. But both gmail.com and googlemain.com will
+ // work, because the gaia server doesn't check to make sure the
+ // appropriate one is being used. So we just slam on gmail.com
+ usable_name = jid.node() + "@" + buzz::STR_GMAIL_COM;
+ }
+
+ std::string post_data;
+ post_data += "Email=" + UrlEncodeString(usable_name);
+ post_data += "&Passwd=" + password.UrlEncode();
+ post_data += "&PersistentCookie=false";
+ post_data += "&source=" + signature;
+ // TODO(chron): This behavior is not the same as in the other gaia auth
+ // loader. We should make it the same. Probably GOOGLE is enough, we don't
+ // want to auth against hosted accounts.
+ post_data += "&accountType=HOSTED_OR_GOOGLE";
+ post_data += "&skipvpage=true";
+ if (!service.empty()) {
+ post_data += "&service=" + service;
+ }
+
+ if (!captcha_answer.captcha_token().empty()) {
+ post_data += "&logintoken=" + captcha_answer.captcha_token();
+ post_data += "&logincaptcha="
+ + UrlEncodeString(captcha_answer.captcha_answer());
+ }
+
+ client->reset();
+ client->set_server(talk_base::SocketAddress(gaia_server.hostname(),
+ gaia_server.port(), false));
+ client->request().verb = talk_base::HV_POST;
+ client->request().path = "/accounts/ClientAuth";
+ client->request().setContent("application/x-www-form-urlencoded",
+ new talk_base::MemoryStream(post_data.data(), post_data.size()));
+ client->response().document.reset(new talk_base::MemoryStream);
+ client->start();
+ return true;
+}
+
+GaiaResponse GaiaParseSidResponse(const talk_base::HttpClient& client,
+ const GaiaServer& gaia_server,
+ std::string* captcha_token,
+ std::string* captcha_url,
+ std::string* sid,
+ std::string* lsid,
+ std::string* auth) {
+ uint32 status_code = client.response().scode;
+ const talk_base::MemoryStream* stream =
+ static_cast<const talk_base::MemoryStream*>(
+ client.response().document.get());
+ size_t length;
+ stream->GetPosition(&length);
+ std::string response;
+ if (length > 0) {
+ response.assign(stream->GetBuffer(), length);
+ }
+
+ LOG(LS_INFO) << "GaiaAuth request to " << client.request().path;
+ LOG(LS_INFO) << "GaiaAuth Status Code: " << status_code;
+ LOG(LS_INFO) << response;
+
+ if (status_code == talk_base::HC_FORBIDDEN) {
+ // The error URL may be the relative path to the captcha jpg.
+ std::string image_url = GetValueForKey("CaptchaUrl", response);
+ if (!image_url.empty()) {
+ // We should activate this "full url code" once we have a better ways
+ // to crack the URL for later download. Right now we are too
+ // dependent on what Gaia returns.
+#if 0
+ if (image_url.find("http://") != 0 &&
+ image_url.find("https://") != 0) {
+ if (image_url.find("/") == 0) {
+ *captcha_url = gaia_server.http_prefix() + image_url;
+ } else {
+ *captcha_url = Utf8(gaia_server.http_prefix()).AsString()
+ + "/accounts/" + image_url;
+ }
+ }
+#else
+ *captcha_url = "/accounts/" + image_url;
+#endif
+
+ *captcha_token = GetValueForKey("CaptchaToken", response);
+ }
+ return GR_UNAUTHORIZED;
+ }
+
+ if (status_code != talk_base::HC_OK) {
+ return GR_ERROR;
+ }
+
+ *sid = GetValueForKey("SID", response);
+ *lsid = GetValueForKey("LSID", response);
+ if (auth) {
+ *auth = GetValueForKey("Auth", response);
+ }
+ if (sid->empty() || lsid->empty()) {
+ return GR_ERROR;
+ }
+
+ return GR_SUCCESS;
+}
+
+bool GaiaRequestAuthToken(talk_base::HttpClient* client,
+ const std::string& sid,
+ const std::string& lsid,
+ const std::string& service,
+ const GaiaServer& gaia_server) {
+ std::string post_data;
+ post_data += "SID=" + UrlEncodeString(sid);
+ post_data += "&LSID=" + UrlEncodeString(lsid);
+ post_data += "&service=" + service;
+ post_data += "&Session=true"; // creates two week cookie
+
+ client->reset();
+ client->set_server(talk_base::SocketAddress(gaia_server.hostname(),
+ gaia_server.port(), false));
+ client->request().verb = talk_base::HV_POST;
+ client->request().path = "/accounts/IssueAuthToken";
+ client->request().setContent("application/x-www-form-urlencoded",
+ new talk_base::MemoryStream(post_data.data(), post_data.size()));
+ client->response().document.reset(new talk_base::MemoryStream);
+ client->start();
+ return true;
+}
+
+GaiaResponse GaiaParseAuthTokenResponse(const talk_base::HttpClient& client,
+ std::string* auth_token) {
+ if (client.response().scode != talk_base::HC_OK) {
+ return GR_ERROR;
+ }
+
+ const talk_base::MemoryStream* stream =
+ static_cast<const talk_base::MemoryStream*>(
+ client.response().document.get());
+ size_t length;
+ stream->GetPosition(&length);
+ while ((length > 0) && isspace(stream->GetBuffer()[length-1]))
+ --length;
+ auth_token->assign(stream->GetBuffer(), length);
+ return auth_token->empty() ? GR_ERROR : GR_SUCCESS;
+}
+
+} // namespace buzz
diff --git a/chrome/browser/sync/notifier/gaia_auth/gaiahelper.h b/chrome/browser/sync/notifier/gaia_auth/gaiahelper.h
new file mode 100644
index 0000000..e0303d0
--- /dev/null
+++ b/chrome/browser/sync/notifier/gaia_auth/gaiahelper.h
@@ -0,0 +1,87 @@
+// 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_GAIA_AUTH_GAIAHELPER_H__
+#define CHROME_BROWSER_SYNC_NOTIFIER_GAIA_AUTH_GAIAHELPER_H__
+
+#include <string>
+
+namespace talk_base {
+class CryptString;
+class HttpClient;
+}
+
+namespace buzz {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class CaptchaAnswer {
+ public:
+ CaptchaAnswer() {}
+ CaptchaAnswer(const std::string& token, const std::string& answer)
+ : captcha_token_(token), captcha_answer_(answer) {
+ }
+ const std::string& captcha_token() const { return captcha_token_; }
+ const std::string& captcha_answer() const { return captcha_answer_; }
+
+ private:
+ std::string captcha_token_;
+ std::string captcha_answer_;
+};
+
+class GaiaServer {
+ public:
+ GaiaServer();
+
+ bool SetServer(const char* url); // protocol://server:port
+ bool SetDebugServer(const char* server); // server:port
+
+ const std::string& hostname() const { return hostname_; }
+ int port() const { return port_; }
+ bool use_ssl() const { return use_ssl_; }
+
+ std::string http_prefix() const;
+
+ private:
+ std::string hostname_;
+ int port_;
+ bool use_ssl_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Gaia Authentication Helper Functions
+///////////////////////////////////////////////////////////////////////////////
+
+enum GaiaResponse { GR_ERROR, GR_UNAUTHORIZED, GR_SUCCESS };
+
+bool GaiaRequestSid(talk_base::HttpClient* client,
+ const std::string& username,
+ const talk_base::CryptString& password,
+ const std::string& signature,
+ const std::string& service,
+ const CaptchaAnswer& captcha_answer,
+ const GaiaServer& gaia_server);
+
+GaiaResponse GaiaParseSidResponse(const talk_base::HttpClient& client,
+ const GaiaServer& gaia_server,
+ std::string* captcha_token,
+ std::string* captcha_url,
+ std::string* sid,
+ std::string* lsid,
+ std::string* auth);
+
+bool GaiaRequestAuthToken(talk_base::HttpClient* client,
+ const std::string& sid,
+ const std::string& lsid,
+ const std::string& service,
+ const GaiaServer& gaia_server);
+
+GaiaResponse GaiaParseAuthTokenResponse(const talk_base::HttpClient& client,
+ std::string* auth_token);
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace buzz
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_GAIA_AUTH_GAIAHELPER_H__
diff --git a/chrome/browser/sync/notifier/gaia_auth/inet_aton.h b/chrome/browser/sync/notifier/gaia_auth/inet_aton.h
new file mode 100644
index 0000000..a10d6cf
--- /dev/null
+++ b/chrome/browser/sync/notifier/gaia_auth/inet_aton.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.
+//
+// Define inet_aton alone so it's easier to include.
+
+#ifndef CHROME_BROWSER_SYNC_NOTIFIER_GAIA_AUTH_INET_ATON_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_GAIA_AUTH_INET_ATON_H_
+
+#ifdef WIN32
+int inet_aton(const char * cp, struct in_addr* inp);
+#endif
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_GAIA_AUTH_INET_ATON_H_
diff --git a/chrome/browser/sync/notifier/gaia_auth/sigslotrepeater.h b/chrome/browser/sync/notifier/gaia_auth/sigslotrepeater.h
new file mode 100644
index 0000000..3e223b9
--- /dev/null
+++ b/chrome/browser/sync/notifier/gaia_auth/sigslotrepeater.h
@@ -0,0 +1,86 @@
+// 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_GAIA_AUTH_INET_ATON_H_SIGSLOTREPEATER_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_GAIA_AUTH_INET_ATON_H_SIGSLOTREPEATER_H_
+
+// Repeaters are both signals and slots, which are designed as intermediate
+// pass-throughs for signals and slots which don't know about each other (for
+// modularity or encapsulation). This eliminates the need to declare a signal
+// handler whose sole purpose is to fire another signal. The repeater connects
+// to the originating signal using the 'repeat' method. When the repeated
+// signal fires, the repeater will also fire.
+
+#include "talk/base/sigslot.h"
+
+namespace sigslot {
+
+ template<class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class repeater0 : public signal0<mt_policy>,
+ public has_slots<mt_policy> {
+ public:
+ typedef signal0<mt_policy> base_type;
+ typedef repeater0<mt_policy> this_type;
+
+ repeater0() { }
+ explicit repeater0(const this_type& s) : base_type(s) { }
+
+ void reemit() { signal0<mt_policy>::emit(); }
+ void repeat(base_type &s) { s.connect(this, &this_type::reemit); }
+ };
+
+ template<class arg1_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class repeater1 : public signal1<arg1_type, mt_policy>,
+ public has_slots<mt_policy>
+ {
+ public:
+ typedef signal1<arg1_type, mt_policy> base_type;
+ typedef repeater1<arg1_type, mt_policy> this_type;
+
+ repeater1() { }
+ repeater1(const this_type& s) : base_type(s) { }
+
+ void reemit(arg1_type a1) { signal1<arg1_type, mt_policy>::emit(a1); }
+ void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+ };
+
+ template<class arg1_type, class arg2_type,
+ class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class repeater2 : public signal2<arg1_type, arg2_type, mt_policy>,
+ public has_slots<mt_policy>
+ {
+ public:
+ typedef signal2<arg1_type, arg2_type, mt_policy> base_type;
+ typedef repeater2<arg1_type, arg2_type, mt_policy> this_type;
+
+ repeater2() { }
+ repeater2(const this_type& s) : base_type(s) { }
+
+ void reemit(arg1_type a1, arg2_type a2) {
+ signal2<arg1_type, arg2_type, mt_policy>::emit(a1, a2);
+ }
+ void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type,
+ class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class repeater3 : public signal3<arg1_type, arg2_type, arg3_type, mt_policy>,
+ public has_slots<mt_policy>
+ {
+ public:
+ typedef signal3<arg1_type, arg2_type, arg3_type, mt_policy> base_type;
+ typedef repeater3<arg1_type, arg2_type, arg3_type, mt_policy> this_type;
+
+ repeater3() { }
+ repeater3(const this_type& s) : base_type(s) { }
+
+ void reemit(arg1_type a1, arg2_type a2, arg3_type a3) {
+ signal3<arg1_type, arg2_type, arg3_type, mt_policy>::emit(a1, a2, a3);
+ }
+ void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+ };
+
+} // namespace sigslot
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_GAIA_AUTH_INET_ATON_H_SIGSLOTREPEATER_H_
diff --git a/chrome/browser/sync/notifier/gaia_auth/win32window.cc b/chrome/browser/sync/notifier/gaia_auth/win32window.cc
new file mode 100644
index 0000000..f1eb8bf
--- /dev/null
+++ b/chrome/browser/sync/notifier/gaia_auth/win32window.cc
@@ -0,0 +1,115 @@
+// 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.
+
+// Originally from libjingle. Minor alterations to compile it in Chrome.
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/win32window.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Window
+///////////////////////////////////////////////////////////////////////////////
+
+static const wchar_t kWindowBaseClassName[] = L"WindowBaseClass";
+HINSTANCE instance_ = GetModuleHandle(NULL);
+ATOM window_class_ = 0;
+
+Win32Window::Win32Window() : wnd_(NULL) {
+}
+
+Win32Window::~Win32Window() {
+ ASSERT(NULL == wnd_);
+}
+
+bool Win32Window::Create(HWND parent, const wchar_t* title, DWORD style,
+ DWORD exstyle, int x, int y, int cx, int cy) {
+ if (wnd_) {
+ // Window already exists.
+ return false;
+ }
+
+ if (!window_class_) {
+ // Class not registered, register it.
+ WNDCLASSEX wcex;
+ memset(&wcex, 0, sizeof(wcex));
+ wcex.cbSize = sizeof(wcex);
+ wcex.hInstance = instance_;
+ wcex.lpfnWndProc = &Win32Window::WndProc;
+ wcex.lpszClassName = kWindowBaseClassName;
+ window_class_ = ::RegisterClassEx(&wcex);
+ if (!window_class_) {
+ LOG_GLE(LS_ERROR) << "RegisterClassEx failed";
+ return false;
+ }
+ }
+ wnd_ = ::CreateWindowEx(exstyle, kWindowBaseClassName, title, style,
+ x, y, cx, cy, parent, NULL, instance_, this);
+ return (NULL != wnd_);
+}
+
+void Win32Window::Destroy() {
+ VERIFY(::DestroyWindow(wnd_) != FALSE);
+}
+
+#if 0
+void Win32Window::SetInstance(HINSTANCE instance) {
+ instance_ = instance;
+}
+
+void Win32Window::Shutdown() {
+ if (window_class_) {
+ ::UnregisterClass(MAKEINTATOM(window_class_), instance_);
+ window_class_ = 0;
+ }
+}
+#endif
+
+bool Win32Window::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+ LRESULT& result) {
+ switch (uMsg) {
+ case WM_CLOSE:
+ if (!OnClose()) {
+ result = 0;
+ return true;
+ }
+ break;
+ }
+ return false;
+}
+
+LRESULT Win32Window::WndProc(HWND hwnd, UINT uMsg,
+ WPARAM wParam, LPARAM lParam) {
+ Win32Window* that = reinterpret_cast<Win32Window*>(
+ ::GetWindowLongPtr(hwnd, GWL_USERDATA));
+ if (!that && (WM_CREATE == uMsg)) {
+ CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam);
+ that = static_cast<Win32Window*>(cs->lpCreateParams);
+ that->wnd_ = hwnd;
+ ::SetWindowLongPtr(hwnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>(that));
+ }
+ if (that) {
+ LRESULT result;
+ bool handled = that->OnMessage(uMsg, wParam, lParam, result);
+ if (WM_DESTROY == uMsg) {
+ for (HWND child = ::GetWindow(hwnd, GW_CHILD); child;
+ child = ::GetWindow(child, GW_HWNDNEXT)) {
+ LOG(LS_INFO) << "Child window: " << static_cast<void*>(child);
+ }
+ }
+ if (WM_NCDESTROY == uMsg) {
+ ::SetWindowLongPtr(hwnd, GWL_USERDATA, NULL);
+ that->wnd_ = NULL;
+ that->OnDestroyed();
+ }
+ if (handled) {
+ return result;
+ }
+ }
+ return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+} // namespace talk_base
diff --git a/chrome/browser/sync/notifier/listener/listen_task.cc b/chrome/browser/sync/notifier/listener/listen_task.cc
new file mode 100644
index 0000000..ff43df1
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/listen_task.cc
@@ -0,0 +1,72 @@
+// 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/listener/listen_task.h"
+
+#include "base/logging.h"
+#include "talk/base/task.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace browser_sync {
+
+ListenTask::ListenTask(Task* parent)
+ : buzz::XmppTask(parent, buzz::XmppEngine::HL_TYPE) {
+}
+
+ListenTask::~ListenTask() {
+}
+
+int ListenTask::ProcessStart() {
+ LOG(INFO) << "P2P: Listener task started.";
+ return STATE_RESPONSE;
+}
+
+int ListenTask::ProcessResponse() {
+ LOG(INFO) << "P2P: Listener response received.";
+ const buzz::XmlElement* stanza = NextStanza();
+ if (stanza == NULL) {
+ return STATE_BLOCKED;
+ }
+ // Acknowledge receipt of the notificaiton to the buzz server.
+ scoped_ptr<buzz::XmlElement> response_stanza(MakeIqResult(stanza));
+ SendStanza(response_stanza.get());
+
+ // Inform listeners that a notification has been received.
+ SignalUpdateAvailable();
+ return STATE_RESPONSE;
+}
+
+bool ListenTask::HandleStanza(const buzz::XmlElement* stanza) {
+ if (IsValidNotification(stanza)) {
+ QueueStanza(stanza);
+ return true;
+ }
+ return false;
+}
+
+bool ListenTask::IsValidNotification(const buzz::XmlElement* stanza) {
+ static const std::string kNSNotifier("google:notifier");
+ static const buzz::QName kQnNotifierGetAll(true, kNSNotifier, "getAll");
+ // An update notificaiton has the following form.
+ // <cli:iq from="{bare_jid}" to="{full_jid}"
+ // id="#" type="set" xmlns:cli="jabber:client">
+ // <not:getAll xmlns:not="google:notifier">
+ // <Timestamp long="#" xmlns=""/>
+ // </not:getAll>
+ // </cli:iq>
+ if (MatchRequestIq(stanza, buzz::STR_SET, kQnNotifierGetAll) &&
+ !stricmp(stanza->Attr(buzz::QN_TO).c_str(),
+ GetClient()->jid().Str().c_str()) &&
+ !stricmp(stanza->Attr(buzz::QN_FROM).c_str(),
+ GetClient()->jid().BareJid().Str().c_str())) {
+ return true;
+ }
+ return false;
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/notifier/listener/listen_task.h b/chrome/browser/sync/notifier/listener/listen_task.h
new file mode 100644
index 0000000..ab1da3e
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/listen_task.h
@@ -0,0 +1,47 @@
+// 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.
+
+// This class listens for notifications from the talk service, and signals when
+// they arrive. It checks all incoming stanza's to see if they look like
+// notifications, and filters out those which are not valid.
+//
+// The task is deleted automatically by the buzz::XmppClient. This occurs in the
+// destructor of TaskRunner, which is a superclass of buzz::XmppClient.
+
+#ifndef CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_LISTEN_TASK_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_LISTEN_TASK_H_
+
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+class buzz::XmlElement;
+class Jid;
+}
+
+namespace browser_sync {
+
+class ListenTask : public buzz::XmppTask {
+ public:
+ explicit ListenTask(Task* parent);
+ virtual ~ListenTask();
+
+ // Overriden from buzz::XmppTask.
+ virtual int ProcessStart();
+ virtual int ProcessResponse();
+ virtual bool HandleStanza(const buzz::XmlElement* stanza);
+
+ // Signal callback upon receipt of a notification.
+ sigslot::signal0<> SignalUpdateAvailable;
+
+ private:
+ // Decide whether a notification should start a sync. We only validate that
+ // this notification came from our own Jid().
+ bool IsValidNotification(const buzz::XmlElement* stanza);
+
+ DISALLOW_COPY_AND_ASSIGN(ListenTask);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_LISTEN_TASK_H_
diff --git a/chrome/browser/sync/notifier/listener/listener_unittest.cc b/chrome/browser/sync/notifier/listener/listener_unittest.cc
new file mode 100644
index 0000000..26697d6
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/listener_unittest.cc
@@ -0,0 +1,10 @@
+// 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 "testing/gtest/include/gtest/gtest.h"
+
+int main(int argc, char **argv) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/chrome/browser/sync/notifier/listener/mediator_thread.h b/chrome/browser/sync/notifier/listener/mediator_thread.h
new file mode 100644
index 0000000..7626b41
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/mediator_thread.h
@@ -0,0 +1,43 @@
+// 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.
+
+//
+// These methods should post messages to a queue which a different thread will
+// later come back and read from.
+
+#ifndef CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_MEDIATOR_THREAD_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_MEDIATOR_THREAD_H_
+
+#include "talk/xmpp/xmppclientsettings.h"
+
+namespace browser_sync {
+
+class MediatorThread {
+ public:
+ enum MediatorMessage {
+ MSG_LOGGED_IN,
+ MSG_LOGGED_OUT,
+ MSG_SUBSCRIPTION_SUCCESS,
+ MSG_SUBSCRIPTION_FAILURE,
+ MSG_NOTIFICATION_RECEIVED,
+ MSG_NOTIFICATION_SENT
+ };
+
+ MediatorThread() {}
+ virtual ~MediatorThread() {}
+
+ virtual void Login(const buzz::XmppClientSettings& settings) = 0;
+ virtual void Logout() = 0;
+ virtual void Start() = 0;
+ virtual void SubscribeForUpdates() = 0;
+ virtual void ListenForUpdates() = 0;
+ virtual void SendNotification() = 0;
+
+ // Connect to this for messages about talk events.
+ sigslot::signal1<MediatorMessage> SignalStateChange;
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_MEDIATOR_THREAD_H_
diff --git a/chrome/browser/sync/notifier/listener/mediator_thread_impl.cc b/chrome/browser/sync/notifier/listener/mediator_thread_impl.cc
new file mode 100644
index 0000000..04a536d
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/mediator_thread_impl.cc
@@ -0,0 +1,278 @@
+// 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/listener/mediator_thread_impl.h"
+
+#include "base/logging.h"
+#include "chrome/browser/sync/engine/net/gaia_authenticator.h"
+#include "chrome/browser/sync/notifier/base/async_dns_lookup.h"
+#include "chrome/browser/sync/notifier/base/task_pump.h"
+#include "chrome/browser/sync/notifier/communicator/connection_options.h"
+#include "chrome/browser/sync/notifier/communicator/const_communicator.h"
+#include "chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h"
+#include "chrome/browser/sync/notifier/listener/listen_task.h"
+#include "chrome/browser/sync/notifier/listener/send_update_task.h"
+#include "chrome/browser/sync/notifier/listener/subscribe_task.h"
+#include "chrome/browser/sync/protocol/service_constants.h"
+#include "chrome/browser/sync/util/pthread_helpers.h"
+#include "talk/base/thread.h"
+#ifdef WIN32
+#include "talk/base/win32socketserver.h"
+#endif
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+using std::string;
+
+namespace browser_sync {
+
+MediatorThreadImpl::MediatorThreadImpl() {
+}
+
+MediatorThreadImpl::~MediatorThreadImpl() {
+}
+
+void MediatorThreadImpl::Start() {
+ talk_base::Thread::Start();
+}
+
+void MediatorThreadImpl::Run() {
+ NameCurrentThreadForDebugging("SyncEngine_MediatorThread");
+ // For win32, this sets up the win32socketserver.
+ // Note that it needs to dispatch windows messages
+ // since that is what the win32 socket server uses.
+#ifdef WIN32
+ scoped_ptr<talk_base::SocketServer> socket_server(
+ new talk_base::Win32SocketServer(this));
+ talk_base::SocketServer* old_socket_server = socketserver();
+ set_socketserver(socket_server.get());
+
+ // Since we just changed the socket server,
+ // ensure that any queued up messages are processed.
+ socket_server->WakeUp();
+ ::MSG message;
+ while (::GetMessage(&message, NULL, 0, 0)) {
+ ::TranslateMessage(&message);
+ ::DispatchMessage(&message);
+ if (IsStopping()) {
+ break;
+ }
+ }
+#endif
+
+ ProcessMessages(talk_base::kForever);
+
+#ifdef WIN32
+ set_socketserver(old_socket_server);
+ socket_server.reset();
+#endif
+}
+
+void MediatorThreadImpl::Login(const buzz::XmppClientSettings& settings) {
+ Post(this, CMD_LOGIN, new LoginData(settings));
+}
+
+void MediatorThreadImpl::Logout() {
+ Post(this, CMD_DISCONNECT);
+ Stop();
+}
+
+void MediatorThreadImpl::ListenForUpdates() {
+ Post(this, CMD_LISTEN_FOR_UPDATES);
+}
+
+void MediatorThreadImpl::SubscribeForUpdates() {
+ Post(this, CMD_SUBSCRIBE_FOR_UPDATES);
+}
+
+void MediatorThreadImpl::SendNotification() {
+ Post(this, CMD_SEND_NOTIFICATION);
+}
+
+void MediatorThreadImpl::ProcessMessages(int milliseconds) {
+ talk_base::Thread::ProcessMessages(milliseconds);
+}
+
+void MediatorThreadImpl::OnMessage(talk_base::Message* msg) {
+ scoped_ptr<LoginData> data;
+ switch (msg->message_id) {
+ case CMD_LOGIN:
+ DCHECK(msg->pdata);
+ data.reset(reinterpret_cast<LoginData*>(msg->pdata));
+ DoLogin(data.get());
+ break;
+ case CMD_DISCONNECT:
+ DoDisconnect();
+ break;
+ case CMD_LISTEN_FOR_UPDATES:
+ DoListenForUpdates();
+ break;
+ case CMD_SEND_NOTIFICATION:
+ DoSendNotification();
+ break;
+ case CMD_SUBSCRIBE_FOR_UPDATES:
+ DoSubscribeForUpdates();
+ break;
+ default:
+ LOG(ERROR) << "P2P: Someone passed a bad message to the thread.";
+ break;
+ }
+}
+
+void MediatorThreadImpl::DoLogin(LoginData* login_data) {
+ LOG(INFO) << "P2P: Thread logging into talk network.";
+ buzz::XmppClientSettings& user_settings = login_data->user_settings;
+
+ // Set our service id.
+ user_settings.set_token_service(SYNC_SERVICE_NAME);
+
+ // Start a new pump for the login.
+ login_.reset();
+ pump_.reset(new notifier::TaskPump());
+
+ notifier::ServerInformation server_list[2];
+ int server_list_count = 2;
+
+ // The default servers know how to serve over port 443 (that's the magic)
+ server_list[0].server = talk_base::SocketAddress("talk.google.com",
+ notifier::kDefaultXmppPort,
+ true); // Use DNS
+ server_list[0].special_port_magic = true;
+ server_list[1].server = talk_base::SocketAddress("talkx.l.google.com",
+ notifier::kDefaultXmppPort,
+ true); // Use DNS
+ server_list[1].special_port_magic = true;
+
+ // Autodetect proxy is on by default.
+ notifier::ConnectionOptions options;
+
+ // Language is not used in the stanza so we default to |en|.
+ std::string lang = "en";
+ login_.reset(new notifier::Login(pump_.get(),
+ user_settings,
+ options,
+ lang,
+ server_list,
+ server_list_count,
+ // NetworkStatusDetectionTask will be
+ // created for you if NULL is passed in.
+ // It helps shorten the autoreconnect
+ // time after going offline and coming
+ // back online.
+ NULL,
+ // talk_base::FirewallManager* is NULL.
+ NULL,
+ false,
+ // Both the proxy and a non-proxy route
+ // will be attempted.
+ false,
+ // |previous_login_successful| is true
+ // because we have already done a
+ // successful gaia login at this point
+ // through another mechanism.
+ true));
+
+ login_->SignalClientStateChange.connect(
+ this, &MediatorThreadImpl::OnClientStateChangeMessage);
+ login_->SignalLoginFailure.connect(
+ this, &MediatorThreadImpl::OnLoginFailureMessage);
+ login_->StartConnection();
+}
+
+void MediatorThreadImpl::OnInputDebug(const char* msg, int length) {
+ string output(msg, length);
+ LOG(INFO) << "P2P: OnInputDebug:" << output << ".";
+}
+
+void MediatorThreadImpl::OnOutputDebug(const char* msg, int length) {
+ string output(msg, length);
+ LOG(INFO) << "P2P: OnOutputDebug:" << output << ".";
+}
+
+void MediatorThreadImpl::DoDisconnect() {
+ LOG(INFO) << "P2P: Thread logging out of talk network.";
+ login_.reset();
+ // Delete the old pump while on the thread to ensure that
+ // everything is cleaned-up in a predicatable manner.
+ pump_.reset();
+}
+
+void MediatorThreadImpl::DoSubscribeForUpdates() {
+ SubscribeTask* subscription = new SubscribeTask(xmpp_client());
+ subscription->SignalStatusUpdate.connect(
+ this,
+ &MediatorThreadImpl::OnSubscriptionStateChange);
+ subscription->Start();
+}
+
+void MediatorThreadImpl::DoListenForUpdates() {
+ ListenTask* listener = new ListenTask(xmpp_client());
+ listener->SignalUpdateAvailable.connect(
+ this,
+ &MediatorThreadImpl::OnUpdateListenerMessage);
+ listener->Start();
+}
+
+void MediatorThreadImpl::DoSendNotification() {
+ SendUpdateTask* task = new SendUpdateTask(xmpp_client());
+ task->SignalStatusUpdate.connect(
+ this,
+ &MediatorThreadImpl::OnUpdateNotificationSent);
+ task->Start();
+}
+
+void MediatorThreadImpl::OnUpdateListenerMessage() {
+ SignalStateChange(MSG_NOTIFICATION_RECEIVED);
+}
+
+void MediatorThreadImpl::OnUpdateNotificationSent(bool success) {
+ if (success) {
+ SignalStateChange(MSG_NOTIFICATION_SENT);
+ }
+}
+
+void MediatorThreadImpl::OnLoginFailureMessage(
+ const notifier::LoginFailure& failure) {
+ SignalStateChange(MSG_LOGGED_OUT);
+}
+
+void MediatorThreadImpl::OnClientStateChangeMessage(
+ notifier::Login::ConnectionState state) {
+ switch (state) {
+ case notifier::Login::STATE_CLOSED:
+ SignalStateChange(MSG_LOGGED_OUT);
+ break;
+ case notifier::Login::STATE_RETRYING:
+ case notifier::Login::STATE_OPENING:
+ LOG(INFO) << "P2P: Thread trying to connect.";
+ // Maybe first time logon, maybe intermediate network disruption
+ // Assume the server went down, and lost our subscription for updates.
+ SignalStateChange(MSG_SUBSCRIPTION_FAILURE);
+ break;
+ case notifier::Login::STATE_OPENED:
+ SignalStateChange(MSG_LOGGED_IN);
+ break;
+ default:
+ LOG(WARNING) << "P2P: Unknown client state change.";
+ break;
+ }
+}
+
+void MediatorThreadImpl::OnSubscriptionStateChange(bool success) {
+ if (success) {
+ SignalStateChange(MSG_SUBSCRIPTION_SUCCESS);
+ } else {
+ SignalStateChange(MSG_SUBSCRIPTION_FAILURE);
+ }
+}
+
+buzz::XmppClient* MediatorThreadImpl::xmpp_client() {
+ if (!login_.get()) {
+ return NULL;
+ }
+ return login_->xmpp_client();
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/notifier/listener/mediator_thread_impl.h b/chrome/browser/sync/notifier/listener/mediator_thread_impl.h
new file mode 100644
index 0000000..684952c
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/mediator_thread_impl.h
@@ -0,0 +1,120 @@
+// 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.
+
+// This object runs on a thread and knows how to interpret messages sent by
+// the talk mediator. The mediator posts messages to a queue which the thread
+// polls (in a super class).
+//
+// Example usage:
+// MediatorThread m = new MediatorThreadImpl(pass in stuff);
+// m.start(); // Start the thread
+// // Once the thread is started, you can do server stuff
+// m.Login(loginInformation);
+// // events happen, the mediator finds out through its pump
+// // more messages are dispatched to the thread
+// // eventually we want to log out
+// m.Logout();
+// delete m; // Also stops the thread
+
+#ifndef CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_MEDIATOR_THREAD_IMPL_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_MEDIATOR_THREAD_IMPL_H_
+
+#include "base/scoped_ptr.h"
+#include "chrome/browser/sync/notifier/communicator/login.h"
+#include "chrome/browser/sync/notifier/communicator/login_failure.h"
+#include "chrome/browser/sync/notifier/listener/mediator_thread.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+namespace notifier {
+class TaskPump;
+} // namespace notifier
+
+namespace buzz {
+class buzz::XmppClient;
+} // namespace buzz
+
+namespace talk_base {
+class talk_base::SocketServer;
+} // namespace talk_base
+
+namespace browser_sync {
+
+enum MEDIATOR_CMD {
+ CMD_LOGIN,
+ CMD_DISCONNECT,
+ CMD_LISTEN_FOR_UPDATES,
+ CMD_SEND_NOTIFICATION,
+ CMD_SUBSCRIBE_FOR_UPDATES
+};
+
+// Used to pass authentication information from the mediator to the thread
+// Use new to allocate it on the heap, the thread will delete it for you.
+
+struct LoginData : public talk_base::MessageData {
+ explicit LoginData(const buzz::XmppClientSettings& settings)
+ : user_settings(settings) {
+ }
+ virtual ~LoginData() {}
+
+ buzz::XmppClientSettings user_settings;
+};
+
+class MediatorThreadImpl
+ : public MediatorThread,
+ public sigslot::has_slots<>,
+ public talk_base::MessageHandler,
+ public talk_base::Thread {
+ public:
+ MediatorThreadImpl();
+ virtual ~MediatorThreadImpl();
+
+ // Start the thread
+ virtual void Start();
+ virtual void Run();
+
+ // These are called from outside threads, by the talk mediator object.
+ // They add messages to a queue which we poll in this thread.
+ void Login(const buzz::XmppClientSettings& settings);
+ void Logout();
+ void ListenForUpdates();
+ void SubscribeForUpdates();
+ void SendNotification();
+ void LogStanzas();
+
+ private:
+ // Called from within the thread on internal events.
+ void ProcessMessages(int cms);
+ void OnMessage(talk_base::Message* msg);
+ void DoLogin(LoginData* login_data);
+ void DoDisconnect();
+ void DoSubscribeForUpdates();
+ void DoListenForUpdates();
+ void DoSendNotification();
+ void DoStanzaLogging();
+
+ // These handle messages indicating an event happened in the outside world.
+ void OnUpdateListenerMessage();
+ void OnUpdateNotificationSent(bool success);
+ void OnLoginFailureMessage(const notifier::LoginFailure& failure);
+ void OnClientStateChangeMessage(notifier::Login::ConnectionState state);
+ void OnSubscriptionStateChange(bool success);
+ void OnInputDebug(const char* msg, int length);
+ void OnOutputDebug(const char* msg, int length);
+
+ buzz::XmppClient* xmpp_client();
+
+ // All buzz::XmppClients are owned by their parent. The root parent is the
+ // SingleLoginTask created by the notifier::Login object. This in turn is
+ // owned by the TaskPump. They are destroyed either when processing is
+ // complete or the pump shuts down.
+ scoped_ptr<notifier::TaskPump> pump_;
+ scoped_ptr<notifier::Login> login_;
+ DISALLOW_COPY_AND_ASSIGN(MediatorThreadImpl);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_MEDIATOR_THREAD_IMPL_H_
diff --git a/chrome/browser/sync/notifier/listener/mediator_thread_mock.h b/chrome/browser/sync/notifier/listener/mediator_thread_mock.h
new file mode 100644
index 0000000..dea8a8e
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/mediator_thread_mock.h
@@ -0,0 +1,74 @@
+// 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.
+
+// This is mock for delicious testing.
+// It's very primitive, and it would have been better to use gmock, except
+// that gmock is only for linux.
+
+#ifndef CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_MEDIATOR_THREAD_MOCK_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_MEDIATOR_THREAD_MOCK_H_
+
+#include "chrome/browser/sync/notifier/listener/mediator_thread.h"
+#include "talk/xmpp/xmppclientsettings.h"
+
+namespace browser_sync {
+
+class MockMediatorThread : public MediatorThread {
+ public:
+ MockMediatorThread() {
+ Reset();
+ }
+ ~MockMediatorThread() {}
+
+ void Reset() {
+ login_calls = 0;
+ logout_calls = 0;
+ start_calls = 0;
+ subscribe_calls = 0;
+ listen_calls = 0;
+ send_calls = 0;
+ }
+
+ // Overridden from MediatorThread
+ void Login(const buzz::XmppClientSettings& settings) {
+ login_calls++;
+ }
+
+ void Logout() {
+ logout_calls++;
+ }
+
+ void Start() {
+ start_calls++;
+ }
+
+ virtual void SubscribeForUpdates() {
+ subscribe_calls++;
+ }
+
+ virtual void ListenForUpdates() {
+ listen_calls++;
+ }
+
+ virtual void SendNotification() {
+ send_calls++;
+ }
+
+ // Callback control
+ void ChangeState(MediatorThread::MediatorMessage message) {
+ SignalStateChange(message);
+ }
+
+ // Intneral State
+ int login_calls;
+ int logout_calls;
+ int start_calls;
+ int subscribe_calls;
+ int listen_calls;
+ int send_calls;
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_MEDIATOR_THREAD_MOCK_H_
diff --git a/chrome/browser/sync/notifier/listener/send_update_task.cc b/chrome/browser/sync/notifier/listener/send_update_task.cc
new file mode 100644
index 0000000..79fffed
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/send_update_task.cc
@@ -0,0 +1,96 @@
+// 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/listener/send_update_task.h"
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+
+namespace browser_sync {
+
+SendUpdateTask::SendUpdateTask(Task* parent)
+ : XmppTask(parent, buzz::XmppEngine::HL_SINGLE) { // Watch for one reply.
+}
+
+SendUpdateTask::~SendUpdateTask() {
+}
+
+bool SendUpdateTask::HandleStanza(const buzz::XmlElement* stanza) {
+ if (!MatchResponseIq(stanza, GetClient()->jid().BareJid(), task_id()))
+ return false;
+ QueueStanza(stanza);
+ return true;
+}
+
+int SendUpdateTask::ProcessStart() {
+ LOG(INFO) << "P2P: Notification task started.";
+ scoped_ptr<buzz::XmlElement> stanza(NewUpdateMessage());
+ if (SendStanza(stanza.get()) != buzz::XMPP_RETURN_OK) {
+ // TODO(brg) : Retry on error.
+ SignalStatusUpdate(false);
+ return STATE_DONE;
+ }
+ return STATE_RESPONSE;
+}
+
+int SendUpdateTask::ProcessResponse() {
+ LOG(INFO) << "P2P: Notification response received.";
+ const buzz::XmlElement* stanza = NextStanza();
+ if (stanza == NULL) {
+ return STATE_BLOCKED;
+ }
+ if (stanza->HasAttr(buzz::QN_TYPE) &&
+ stanza->Attr(buzz::QN_TYPE) == buzz::STR_RESULT) {
+ // Notify listeners of success.
+ SignalStatusUpdate(true);
+ return STATE_DONE;
+ }
+
+ // An error response was received.
+ // TODO(brg) : Error handling.
+ SignalStatusUpdate(false);
+ return STATE_DONE;
+}
+
+buzz::XmlElement* SendUpdateTask::NewUpdateMessage() {
+ static const std::string kNSNotifier = "google:notifier";
+ static const buzz::QName kQnNotifierSet(true, kNSNotifier, "set");
+ static const buzz::QName kQnId(true, buzz::STR_EMPTY, "Id");
+ static const buzz::QName kQnServiceUrl(true, buzz::STR_EMPTY, "ServiceUrl");
+ static const buzz::QName kQnData(true, buzz::STR_EMPTY, "data");
+ static const buzz::QName kQnServiceId(true, buzz::STR_EMPTY, "ServiceId");
+
+ // Create our update stanza. In the future this may include the revision id,
+ // but at the moment simply does a p2p ping. The message is constructed as:
+ // <iq type='get' from='{fullJid}' to='{bareJid}' id='{#}'>
+ // <set xmlns="google:notifier">
+ // <Id xmlns="">
+ // <ServiceUrl xmlns="" data="google:notifier"/>
+ // <ServiceId xmlns="" data="notification"/>
+ // </Id>
+ // </set>
+ // </iq>
+ buzz::XmlElement* stanza = MakeIq(buzz::STR_GET, GetClient()->jid().BareJid(),
+ task_id());
+
+ buzz::XmlElement* notifier_set = new buzz::XmlElement(kQnNotifierSet, true);
+ stanza->AddElement(notifier_set);
+
+ buzz::XmlElement* id = new buzz::XmlElement(kQnId, true);
+ notifier_set->AddElement(id);
+
+ buzz::XmlElement* service_url = new buzz::XmlElement(kQnServiceUrl, true);
+ service_url->AddAttr(kQnData, kNSNotifier);
+ id->AddElement(service_url);
+
+ buzz::XmlElement* service_id = new buzz::XmlElement(kQnServiceId, true);
+ service_id->AddAttr(kQnData, "notification");
+ id->AddElement(service_id);
+ return stanza;
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/notifier/listener/send_update_task.h b/chrome/browser/sync/notifier/listener/send_update_task.h
new file mode 100644
index 0000000..056703e
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/send_update_task.h
@@ -0,0 +1,37 @@
+// 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.
+
+// Methods for sending the update stanza to notify peers via xmpp.
+
+#ifndef CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_SEND_UPDATE_TASK_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_SEND_UPDATE_TASK_H_
+
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace browser_sync {
+
+class SendUpdateTask : public buzz::XmppTask {
+ public:
+ explicit SendUpdateTask(Task* parent);
+ virtual ~SendUpdateTask();
+
+ // Overridden from buzz::XmppTask
+ virtual int ProcessStart();
+ virtual int ProcessResponse();
+ virtual bool HandleStanza(const buzz::XmlElement* stanza);
+
+ // Signal callback upon subscription success.
+ sigslot::signal1<bool> SignalStatusUpdate;
+
+ private:
+ // Allocates and constructs an buzz::XmlElement containing the update stanza.
+ buzz::XmlElement* NewUpdateMessage();
+
+ DISALLOW_COPY_AND_ASSIGN(SendUpdateTask);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_SEND_UPDATE_TASK_H_
diff --git a/chrome/browser/sync/notifier/listener/subscribe_task.cc b/chrome/browser/sync/notifier/listener/subscribe_task.cc
new file mode 100644
index 0000000..8d8a3ea
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/subscribe_task.cc
@@ -0,0 +1,90 @@
+// 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/listener/subscribe_task.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "talk/base/task.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace browser_sync {
+
+SubscribeTask::SubscribeTask(Task* parent)
+ : XmppTask(parent, buzz::XmppEngine::HL_SINGLE) {
+}
+
+SubscribeTask::~SubscribeTask() {
+}
+
+bool SubscribeTask::HandleStanza(const buzz::XmlElement* stanza) {
+ if (!MatchResponseIq(stanza, GetClient()->jid().BareJid(), task_id()))
+ return false;
+ QueueStanza(stanza);
+ return true;
+}
+
+int SubscribeTask::ProcessStart() {
+ LOG(INFO) << "P2P: Subscription task started.";
+ scoped_ptr<buzz::XmlElement> iq_stanza(NewSubscriptionMessage());
+
+ if (SendStanza(iq_stanza.get()) != buzz::XMPP_RETURN_OK) {
+ SignalStatusUpdate(false);
+ return STATE_DONE;
+ }
+ return STATE_RESPONSE;
+}
+
+int SubscribeTask::ProcessResponse() {
+ LOG(INFO) << "P2P: Subscription response received.";
+ const buzz::XmlElement* stanza = NextStanza();
+ if (stanza == NULL) {
+ return STATE_BLOCKED;
+ }
+ // We've receieved a response to our subscription request.
+ if (stanza->HasAttr(buzz::QN_TYPE) &&
+ stanza->Attr(buzz::QN_TYPE) == buzz::STR_RESULT) {
+ SignalStatusUpdate(true);
+ return STATE_DONE;
+ }
+ // An error response was received.
+ // TODO(brg) : Error handling.
+ SignalStatusUpdate(false);
+ return STATE_DONE;
+}
+
+buzz::XmlElement* SubscribeTask::NewSubscriptionMessage() {
+ static const buzz::QName kQnNotifierGetAll(true, "google:notifier", "getAll");
+ static const buzz::QName kQnNotifierClientActive(true, buzz::STR_EMPTY,
+ "ClientActive");
+ static const buzz::QName kQnBool(true, buzz::STR_EMPTY, "bool");
+ static const std::string kTrueString("true");
+
+ // Create the subscription stanza using the notificaitons protocol.
+ // <iq type='get' from='{fullJid}' to='{bareJid}' id='{#}'>
+ // <gn:getAll xmlns:gn='google:notifier' xmlns=''>
+ // <ClientActive bool='true'/>
+ // </gn:getAll>
+ // </iq>
+ buzz::XmlElement* get_all_request =
+ MakeIq(buzz::STR_GET, GetClient()->jid().BareJid(), task_id());
+
+ buzz::XmlElement* notifier_get =
+ new buzz::XmlElement(kQnNotifierGetAll, true);
+ get_all_request->AddElement(notifier_get);
+
+ buzz::XmlElement* client_active =
+ new buzz::XmlElement(kQnNotifierClientActive, true);
+ client_active->AddAttr(kQnBool, kTrueString);
+ notifier_get->AddElement(client_active);
+
+ return get_all_request;
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/notifier/listener/subscribe_task.h b/chrome/browser/sync/notifier/listener/subscribe_task.h
new file mode 100644
index 0000000..4b96f38
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/subscribe_task.h
@@ -0,0 +1,39 @@
+// 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.
+
+// This class handles subscribing to talk notifications. It does the getAll
+// iq stanza which establishes the endpoint and directs future notifications to
+// be pushed.
+
+#ifndef CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_SUBSCRIBE_TASK_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_SUBSCRIBE_TASK_H_
+
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace browser_sync {
+
+class SubscribeTask : public buzz::XmppTask {
+ public:
+ explicit SubscribeTask(Task* parent);
+ virtual ~SubscribeTask();
+
+ // Overridden from XmppTask.
+ virtual int ProcessStart();
+ virtual int ProcessResponse();
+ virtual bool HandleStanza(const buzz::XmlElement* stanza);
+
+ // Signal callback upon subscription success.
+ sigslot::signal1<bool> SignalStatusUpdate;
+
+ private:
+ // Assembles an Xmpp stanza which can be sent to subscribe to notifications.
+ buzz::XmlElement* NewSubscriptionMessage();
+
+ DISALLOW_COPY_AND_ASSIGN(SubscribeTask);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_SUBSCRIBE_TASK_H_
diff --git a/chrome/browser/sync/notifier/listener/talk_mediator.h b/chrome/browser/sync/notifier/listener/talk_mediator.h
new file mode 100644
index 0000000..c651c10
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/talk_mediator.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.
+
+// Interface to the code which handles talk logic. Used to initialize SSL
+// before the underlying talk login occurs
+// Example usage:
+//
+// TalkMediator mediator();
+// mediator.SetCredentials("email", "pass", false);
+// mediator.WatchAuthWatcher(auth_watcher_);
+// AuthWatcher eventually sends AUTH_SUCCEEDED which triggers:
+// mediator.Login();
+// ...
+// mediator.Logout();
+
+#ifndef CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_TALK_MEDIATOR_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_TALK_MEDIATOR_H_
+
+#include <string>
+
+namespace browser_sync {
+class AuthWatcher;
+class SyncerThread;
+
+struct TalkMediatorEvent {
+ enum WhatHappened {
+ LOGIN_SUCCEEDED,
+ LOGOUT_SUCCEEDED,
+ SUBSCRIPTIONS_ON,
+ SUBSCRIPTIONS_OFF,
+ NOTIFICATION_RECEIVED,
+ NOTIFICATION_SENT,
+ TALKMEDIATOR_DESTROYED,
+ };
+
+ // Required by EventChannel
+ typedef TalkMediatorEvent EventType;
+
+ static inline bool IsChannelShutdownEvent(const TalkMediatorEvent& event) {
+ return event.what_happened == TALKMEDIATOR_DESTROYED;
+ }
+
+ WhatHappened what_happened;
+};
+
+typedef EventChannel<TalkMediatorEvent, PThreadMutex> TalkMediatorChannel;
+
+class TalkMediator {
+ public:
+ TalkMediator() {}
+ virtual ~TalkMediator() {}
+
+ // The following methods are for authorizaiton of the xmpp client.
+ virtual void WatchAuthWatcher(browser_sync::AuthWatcher* auth_watcher) = 0;
+ virtual bool SetAuthToken(const std::string& email,
+ const std::string& token) = 0;
+ virtual bool Login() = 0;
+ virtual bool Logout() = 0;
+
+ // Method for the owner of this object to notify peers that an update
+ // has occurred.
+ virtual bool SendNotification() = 0;
+
+ // Channel by which talk mediator events are signaled.
+ virtual TalkMediatorChannel* channel() const = 0;
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_TALK_MEDIATOR_H_
diff --git a/chrome/browser/sync/notifier/listener/talk_mediator_impl.cc b/chrome/browser/sync/notifier/listener/talk_mediator_impl.cc
new file mode 100644
index 0000000..9d83d67
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/talk_mediator_impl.cc
@@ -0,0 +1,275 @@
+// 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/listener/talk_mediator_impl.h"
+
+#include "base/logging.h"
+#include "chrome/browser/sync/engine/auth_watcher.h"
+#include "chrome/browser/sync/engine/syncer_thread.h"
+#include "chrome/browser/sync/notifier/listener/mediator_thread_impl.h"
+#include "chrome/browser/sync/util/event_sys-inl.h"
+#include "talk/base/cryptstring.h"
+#include "talk/base/ssladapter.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace browser_sync {
+
+// Before any authorization event from TalkMediatorImpl, we need to initialize
+// the SSL library.
+class SslInitializationSingleton {
+ public:
+ virtual ~SslInitializationSingleton() {
+ talk_base::CleanupSSL();
+ };
+
+ void RegisterClient() {}
+
+ static SslInitializationSingleton* GetInstance() {
+ MutexLock lock(&mutex_);
+ if (!instance_.get()) {
+ instance_.reset(new SslInitializationSingleton());
+ }
+ return instance_.get();
+ }
+
+ private:
+ typedef PThreadScopedLock<PThreadMutex> MutexLock;
+
+ SslInitializationSingleton() {
+ talk_base::InitializeSSL();
+ };
+
+ // The single instance of this class.
+ static PThreadMutex mutex_;
+ static scoped_ptr<SslInitializationSingleton> instance_;
+
+ DISALLOW_COPY_AND_ASSIGN(SslInitializationSingleton);
+};
+
+// Declaration of class scoped static variables.
+PThreadMutex SslInitializationSingleton::mutex_;
+scoped_ptr<SslInitializationSingleton> SslInitializationSingleton::instance_;
+
+TalkMediatorImpl::TalkMediatorImpl()
+ : mediator_thread_(new MediatorThreadImpl()) {
+ // Ensure the SSL library is initialized.
+ SslInitializationSingleton::GetInstance()->RegisterClient();
+
+ // Construct the callback channel with the shutdown event.
+ TalkMediatorInitialization(false);
+}
+
+TalkMediatorImpl::TalkMediatorImpl(MediatorThread *thread)
+ : mediator_thread_(thread) {
+ // When testing we do not initialize the SSL library.
+ TalkMediatorInitialization(true);
+}
+
+void TalkMediatorImpl::TalkMediatorInitialization(bool should_connect) {
+ TalkMediatorEvent done = { TalkMediatorEvent::TALKMEDIATOR_DESTROYED };
+ channel_.reset(new TalkMediatorChannel(done));
+ if (should_connect) {
+ mediator_thread_->SignalStateChange.connect(
+ this,
+ &TalkMediatorImpl::MediatorThreadMessageHandler);
+ state_.connected = 1;
+ }
+ mediator_thread_->Start();
+ state_.started = 1;
+}
+
+TalkMediatorImpl::~TalkMediatorImpl() {
+ if (state_.started) {
+ Logout();
+ }
+}
+
+void TalkMediatorImpl::AuthWatcherEventHandler(
+ const AuthWatcherEvent& auth_event) {
+ MutexLock lock(&mutex_);
+ switch (auth_event.what_happened) {
+ case AuthWatcherEvent::AUTHWATCHER_DESTROYED:
+ case AuthWatcherEvent::GAIA_AUTH_FAILED:
+ case AuthWatcherEvent::SERVICE_AUTH_FAILED:
+ case AuthWatcherEvent::SERVICE_CONNECTION_FAILED:
+ // We have failed to connect to the buzz server, and we maintain a
+ // decreased polling interval and stay in a flaky connection mode.
+ // Note that the failure is on the authwatcher's side and can not be
+ // resolved without manual retry.
+ break;
+ case AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START:
+ // TODO(brg) : We are restarting the authentication attempt. We need to
+ // insure this code path is stable.
+ break;
+ case AuthWatcherEvent::AUTH_SUCCEEDED:
+ if (!state_.logged_in) {
+ DoLogin();
+ }
+ break;
+ default:
+ // Do nothing
+ break;
+ }
+}
+
+void TalkMediatorImpl::WatchAuthWatcher(AuthWatcher* watcher) {
+ auth_hookup_.reset(NewEventListenerHookup(
+ watcher->channel(),
+ this,
+ &TalkMediatorImpl::AuthWatcherEventHandler));
+}
+
+bool TalkMediatorImpl::Login() {
+ MutexLock lock(&mutex_);
+ return DoLogin();
+}
+
+bool TalkMediatorImpl::DoLogin() {
+ // Connect to the mediator thread and start it processing messages.
+ if (!state_.connected) {
+ mediator_thread_->SignalStateChange.connect(
+ this,
+ &TalkMediatorImpl::MediatorThreadMessageHandler);
+ state_.connected = 1;
+ }
+ if (state_.initialized && !state_.logged_in) {
+ mediator_thread_->Login(xmpp_settings_);
+ state_.logged_in = 1;
+ return true;
+ }
+ return false;
+}
+
+bool TalkMediatorImpl::Logout() {
+ MutexLock lock(&mutex_);
+ // We do not want to be called back during logout since we may be closing.
+ if (state_.connected) {
+ mediator_thread_->SignalStateChange.disconnect(this);
+ state_.connected = 0;
+ }
+ if (state_.started) {
+ mediator_thread_->Logout();
+ state_.started = 0;
+ state_.logged_in = 0;
+ state_.subscribed = 0;
+ return true;
+ }
+ return false;
+}
+
+bool TalkMediatorImpl::SendNotification() {
+ MutexLock lock(&mutex_);
+ if (state_.logged_in && state_.subscribed) {
+ mediator_thread_->SendNotification();
+ return true;
+ }
+ return false;
+}
+
+TalkMediatorChannel* TalkMediatorImpl::channel() const {
+ return channel_.get();
+}
+
+bool TalkMediatorImpl::SetAuthToken(const std::string& email,
+ const std::string& token) {
+ MutexLock lock(&mutex_);
+
+ // Verify that we can create a JID from the email provided.
+ buzz::Jid jid = buzz::Jid(email);
+ if (jid.node().empty() || !jid.IsValid()) {
+ return false;
+ }
+
+ // Construct the XmppSettings object for login to buzz.
+ xmpp_settings_.set_user(jid.node());
+ xmpp_settings_.set_resource("chrome-sync");
+ xmpp_settings_.set_host(jid.domain());
+ xmpp_settings_.set_use_tls(true);
+ xmpp_settings_.set_auth_cookie(token);
+
+ state_.initialized = 1;
+ return true;
+}
+
+void TalkMediatorImpl::MediatorThreadMessageHandler(
+ MediatorThread::MediatorMessage message) {
+ LOG(INFO) << "P2P: MediatorThread has passed a message";
+ switch (message) {
+ case MediatorThread::MSG_LOGGED_IN:
+ OnLogin();
+ break;
+ case MediatorThread::MSG_LOGGED_OUT:
+ OnLogout();
+ break;
+ case MediatorThread::MSG_SUBSCRIPTION_SUCCESS:
+ OnSubscriptionSuccess();
+ break;
+ case MediatorThread::MSG_SUBSCRIPTION_FAILURE:
+ OnSubscriptionFailure();
+ break;
+ case MediatorThread::MSG_NOTIFICATION_RECEIVED:
+ OnNotificationReceived();
+ break;
+ case MediatorThread::MSG_NOTIFICATION_SENT:
+ OnNotificationSent();
+ break;
+ default:
+ LOG(WARNING) << "P2P: Unknown message returned from mediator thread.";
+ break;
+ }
+}
+
+void TalkMediatorImpl::OnLogin() {
+ LOG(INFO) << "P2P: Logged in.";
+ MutexLock lock(&mutex_);
+ // ListenForUpdates enables the ListenTask. This is done before
+ // SubscribeForUpdates.
+ mediator_thread_->ListenForUpdates();
+ mediator_thread_->SubscribeForUpdates();
+ TalkMediatorEvent event = { TalkMediatorEvent::LOGIN_SUCCEEDED };
+ channel_->NotifyListeners(event);
+}
+
+void TalkMediatorImpl::OnLogout() {
+ LOG(INFO) << "P2P: Logged off.";
+ OnSubscriptionFailure();
+ MutexLock lock(&mutex_);
+ state_.logged_in = 0;
+ TalkMediatorEvent event = { TalkMediatorEvent::LOGOUT_SUCCEEDED };
+ channel_->NotifyListeners(event);
+}
+
+void TalkMediatorImpl::OnSubscriptionSuccess() {
+ LOG(INFO) << "P2P: Update subscription active.";
+ MutexLock lock(&mutex_);
+ state_.subscribed = 1;
+ TalkMediatorEvent event = { TalkMediatorEvent::SUBSCRIPTIONS_ON };
+ channel_->NotifyListeners(event);
+}
+
+void TalkMediatorImpl::OnSubscriptionFailure() {
+ LOG(INFO) << "P2P: Update subscription failure.";
+ MutexLock lock(&mutex_);
+ state_.subscribed = 0;
+ TalkMediatorEvent event = { TalkMediatorEvent::SUBSCRIPTIONS_OFF };
+ channel_->NotifyListeners(event);
+}
+
+void TalkMediatorImpl::OnNotificationReceived() {
+ LOG(INFO) << "P2P: Updates are available on the server.";
+ MutexLock lock(&mutex_);
+ TalkMediatorEvent event = { TalkMediatorEvent::NOTIFICATION_RECEIVED };
+ channel_->NotifyListeners(event);
+}
+
+void TalkMediatorImpl::OnNotificationSent() {
+ LOG(INFO) <<
+ "P2P: Peers were notified that updates are available on the server.";
+ MutexLock lock(&mutex_);
+ TalkMediatorEvent event = { TalkMediatorEvent::NOTIFICATION_SENT };
+ channel_->NotifyListeners(event);
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/notifier/listener/talk_mediator_impl.h b/chrome/browser/sync/notifier/listener/talk_mediator_impl.h
new file mode 100644
index 0000000..33bb94a
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/talk_mediator_impl.h
@@ -0,0 +1,117 @@
+// 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.
+
+// This class is the interface between talk code and the client code proper
+// It will manage all aspects of the connection and call back into the client
+// when it needs attention (for instance if updates are available for syncing).
+
+#ifndef CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_TALK_MEDIATOR_IMPL_H_
+#define CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_TALK_MEDIATOR_IMPL_H_
+
+#include <string>
+
+#include "base/scoped_ptr.h"
+#include "chrome/browser/sync/engine/auth_watcher.h"
+#include "chrome/browser/sync/notifier/listener/mediator_thread.h"
+#include "chrome/browser/sync/notifier/listener/talk_mediator.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "testing/gtest/include/gtest/gtest_prod.h" // For FRIEND_TEST
+
+class EventListenerHookup;
+
+namespace browser_sync {
+class AuthWatcher;
+struct AuthWatcherEvent;
+class SyncerThread;
+
+class TalkMediatorImpl
+ : public TalkMediator,
+ public sigslot::has_slots<> {
+ public:
+ TalkMediatorImpl();
+ explicit TalkMediatorImpl(MediatorThread* thread);
+ virtual ~TalkMediatorImpl();
+
+ // Overriden from TalkMediator.
+ virtual void WatchAuthWatcher(AuthWatcher* auth_watcher);
+ virtual bool SetAuthToken(const std::string& email,
+ const std::string& token);
+ virtual bool Login();
+ virtual bool Logout();
+
+ virtual bool SendNotification();
+
+ TalkMediatorChannel* channel() const;
+
+ private:
+ struct TalkMediatorState {
+ TalkMediatorState()
+ : started(0), connected(0), initialized(0), logged_in(0),
+ subscribed(0) {
+ }
+
+ unsigned int started : 1; // Background thread has started.
+ unsigned int connected : 1; // Connected to the mediator thread signal.
+ unsigned int initialized : 1; // Initialized with login information.
+ unsigned int logged_in : 1; // Logged in the mediator's authenticator.
+ unsigned int subscribed : 1; // Subscribed to the xmpp receiving channel.
+ };
+
+ typedef PThreadScopedLock<PThreadMutex> MutexLock;
+
+ // Completes common initialization between the constructors. Set should
+ // connect to true if the talk mediator should connect to the controlled
+ // mediator thread's SignalStateChange object.
+ void TalkMediatorInitialization(bool should_connect);
+
+ // Called from the authwatcher after authentication completes. Signals this
+ // class to push listening and subscription events to the mediator thread.
+ void AuthWatcherEventHandler(const AuthWatcherEvent& auth_event);
+
+ // Callback for the mediator thread.
+ void MediatorThreadMessageHandler(MediatorThread::MediatorMessage message);
+
+ // Responses to messages from the MediatorThread.
+ void OnNotificationReceived();
+ void OnNotificationSent();
+ void OnLogin();
+ void OnLogout();
+ void OnSubscriptionFailure();
+ void OnSubscriptionSuccess();
+
+ // Does the actual login funcationality, called from Login() and the
+ // AuthWatcher event handler.
+ bool DoLogin();
+
+ // Mutex for synchronizing event access. This class listens to two event
+ // sources, Authwatcher and MediatorThread. It can also be called by through
+ // the TalkMediatorInteface. All these access points are serialized by
+ // this mutex.
+ PThreadMutex mutex_;
+
+ // Internal state.
+ TalkMediatorState state_;
+
+ // Cached and verfied from the SetAuthToken method.
+ buzz::XmppClientSettings xmpp_settings_;
+
+ // Interface to listen to authentication events.
+ scoped_ptr<EventListenerHookup> auth_hookup_;
+
+ // The worker thread through which talk events are posted and received.
+ scoped_ptr<MediatorThread> mediator_thread_;
+
+ // Channel through which to broadcast events.
+ scoped_ptr<TalkMediatorChannel> channel_;
+
+ FRIEND_TEST(TalkMediatorImplTest, SetAuthTokenWithBadInput);
+ FRIEND_TEST(TalkMediatorImplTest, SetAuthTokenWithGoodInput);
+ FRIEND_TEST(TalkMediatorImplTest, SendNotification);
+ FRIEND_TEST(TalkMediatorImplTest, MediatorThreadCallbacks);
+ DISALLOW_COPY_AND_ASSIGN(TalkMediatorImpl);
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_NOTIFIER_LISTENER_TALK_MEDIATOR_IMPL_H_
diff --git a/chrome/browser/sync/notifier/listener/talk_mediator_unittest.cc b/chrome/browser/sync/notifier/listener/talk_mediator_unittest.cc
new file mode 100644
index 0000000..6d26cdb
--- /dev/null
+++ b/chrome/browser/sync/notifier/listener/talk_mediator_unittest.cc
@@ -0,0 +1,176 @@
+// 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 "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "chrome/browser/sync/notifier/listener/mediator_thread_mock.h"
+#include "chrome/browser/sync/notifier/listener/talk_mediator_impl.h"
+#include "chrome/browser/sync/util/event_sys-inl.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace browser_sync {
+
+class TalkMediatorImplTest : public testing::Test {
+ public:
+ void HandleTalkMediatorEvent(
+ const browser_sync::TalkMediatorEvent& event) {
+ last_message_ = event.what_happened;
+ }
+
+ protected:
+ TalkMediatorImplTest() {}
+ ~TalkMediatorImplTest() {}
+
+ virtual void SetUp() {
+ last_message_ = -1;
+ }
+
+ virtual void TearDown() {
+ }
+
+ int last_message_;
+};
+
+TEST_F(TalkMediatorImplTest, ConstructionOfTheClass) {
+ // Constructing a single talk mediator enables SSL through the singleton.
+ scoped_ptr<TalkMediatorImpl> talk1(new TalkMediatorImpl());
+ talk1.reset(NULL);
+}
+
+TEST_F(TalkMediatorImplTest, SetAuthTokenWithBadInput) {
+ scoped_ptr<TalkMediatorImpl> talk1(new TalkMediatorImpl(
+ new MockMediatorThread()));
+ ASSERT_FALSE(talk1->SetAuthToken("@missinguser.com", ""));
+ ASSERT_EQ(talk1->state_.initialized, 0);
+
+ scoped_ptr<TalkMediatorImpl> talk2(new TalkMediatorImpl(
+ new MockMediatorThread()));
+ ASSERT_FALSE(talk2->SetAuthToken("", "1234567890"));
+ ASSERT_EQ(talk2->state_.initialized, 0);
+
+ scoped_ptr<TalkMediatorImpl> talk3(new TalkMediatorImpl(
+ new MockMediatorThread()));
+ ASSERT_FALSE(talk3->SetAuthToken("missingdomain", "abcde"));
+ ASSERT_EQ(talk3->state_.initialized, 0);
+}
+
+TEST_F(TalkMediatorImplTest, SetAuthTokenWithGoodInput) {
+ scoped_ptr<TalkMediatorImpl> talk1(new TalkMediatorImpl(
+ new MockMediatorThread()));
+ ASSERT_TRUE(talk1->SetAuthToken("chromium@gmail.com", "token"));
+ ASSERT_EQ(talk1->state_.initialized, 1);
+
+ scoped_ptr<TalkMediatorImpl> talk2(new TalkMediatorImpl(
+ new MockMediatorThread()));
+ ASSERT_TRUE(talk2->SetAuthToken("chromium@mail.google.com", "token"));
+ ASSERT_EQ(talk2->state_.initialized, 1);
+
+ scoped_ptr<TalkMediatorImpl> talk3(new TalkMediatorImpl(
+ new MockMediatorThread()));
+ ASSERT_TRUE(talk3->SetAuthToken("chromium@chromium.org", "token"));
+ ASSERT_EQ(talk3->state_.initialized, 1);
+}
+
+TEST_F(TalkMediatorImplTest, LoginWiring) {
+ // The TalkMediatorImpl owns the mock.
+ MockMediatorThread* mock = new MockMediatorThread();
+ scoped_ptr<TalkMediatorImpl> talk1(new TalkMediatorImpl(mock));
+
+ // Login checks states for initialization.
+ ASSERT_EQ(talk1->Login(), false);
+ ASSERT_EQ(mock->login_calls, 0);
+
+ ASSERT_EQ(talk1->SetAuthToken("chromium@gmail.com", "token"), true);
+ ASSERT_EQ(talk1->Login(), true);
+ ASSERT_EQ(mock->login_calls, 1);
+
+ // Successive calls to login will fail. One needs to create a new talk
+ // mediator object.
+ ASSERT_EQ(talk1->Login(), false);
+ ASSERT_EQ(mock->login_calls, 1);
+
+ ASSERT_EQ(talk1->Logout(), true);
+ ASSERT_EQ(mock->logout_calls, 1);
+
+ // Successive logout calls do nothing.
+ ASSERT_EQ(talk1->Logout(), false);
+ ASSERT_EQ(mock->logout_calls, 1);
+}
+
+TEST_F(TalkMediatorImplTest, SendNotification) {
+ // The TalkMediatorImpl owns the mock.
+ MockMediatorThread* mock = new MockMediatorThread();
+ scoped_ptr<TalkMediatorImpl> talk1(new TalkMediatorImpl(mock));
+
+ // Failure due to not being logged in.
+ ASSERT_EQ(talk1->SendNotification(), false);
+ ASSERT_EQ(mock->send_calls, 0);
+
+ ASSERT_EQ(talk1->SetAuthToken("chromium@gmail.com", "token"), true);
+ ASSERT_EQ(talk1->Login(), true);
+ ASSERT_EQ(mock->login_calls, 1);
+
+ // Failure due to not being subscribed.
+ ASSERT_EQ(talk1->SendNotification(), false);
+ ASSERT_EQ(mock->send_calls, 0);
+
+ // Fake subscription
+ talk1->OnSubscriptionSuccess();
+ ASSERT_EQ(talk1->state_.subscribed, 1);
+ ASSERT_EQ(talk1->SendNotification(), true);
+ ASSERT_EQ(mock->send_calls, 1);
+ ASSERT_EQ(talk1->SendNotification(), true);
+ ASSERT_EQ(mock->send_calls, 2);
+
+ ASSERT_EQ(talk1->Logout(), true);
+ ASSERT_EQ(mock->logout_calls, 1);
+
+ // Failure due to being logged out.
+ ASSERT_EQ(talk1->SendNotification(), false);
+ ASSERT_EQ(mock->send_calls, 2);
+}
+
+TEST_F(TalkMediatorImplTest, MediatorThreadCallbacks) {
+ // The TalkMediatorImpl owns the mock.
+ MockMediatorThread* mock = new MockMediatorThread();
+ scoped_ptr<TalkMediatorImpl> talk1(new TalkMediatorImpl(mock));
+
+ scoped_ptr<EventListenerHookup> callback(NewEventListenerHookup(
+ talk1->channel(), this, &TalkMediatorImplTest::HandleTalkMediatorEvent));
+
+ ASSERT_EQ(talk1->SetAuthToken("chromium@gmail.com", "token"), true);
+ ASSERT_EQ(talk1->Login(), true);
+ ASSERT_EQ(mock->login_calls, 1);
+
+ mock->ChangeState(MediatorThread::MSG_LOGGED_IN);
+ ASSERT_EQ(last_message_, TalkMediatorEvent::LOGIN_SUCCEEDED);
+
+ // The message triggers calls to listen and subscribe.
+ ASSERT_EQ(mock->listen_calls, 1);
+ ASSERT_EQ(mock->subscribe_calls, 1);
+ ASSERT_EQ(talk1->state_.subscribed, 0);
+
+ mock->ChangeState(MediatorThread::MSG_SUBSCRIPTION_SUCCESS);
+ ASSERT_EQ(last_message_, TalkMediatorEvent::SUBSCRIPTIONS_ON);
+ ASSERT_EQ(talk1->state_.subscribed, 1);
+
+ // After subscription success is receieved, the talk mediator will allow
+ // sending of notifications.
+ ASSERT_EQ(talk1->SendNotification(), true);
+ ASSERT_EQ(mock->send_calls, 1);
+
+ // |MSG_NOTIFICATION_RECEIVED| from the MediatorThread triggers a callback
+ // of type |NOTIFICATION_RECEIVED|.
+ mock->ChangeState(MediatorThread::MSG_NOTIFICATION_RECEIVED);
+ ASSERT_EQ(last_message_, TalkMediatorEvent::NOTIFICATION_RECEIVED);
+
+ // A |TALKMEDIATOR_DESTROYED| message is received during tear down.
+ talk1.reset();
+ ASSERT_EQ(last_message_, TalkMediatorEvent::TALKMEDIATOR_DESTROYED);
+}
+
+} // namespace browser_sync