diff options
Diffstat (limited to 'chrome/browser/sync/notifier')
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("&", HtmlEncode("&").c_str()); + EXPECT_STREQ("&amp;", HtmlEncode("&").c_str()); + EXPECT_STREQ("<!>", HtmlEncode("<!>").c_str()); +} + +TEST_F(StringTest, HtmlDecode) { + EXPECT_STREQ("dns", HtmlDecode("dns").c_str()); + EXPECT_STREQ("&", HtmlDecode("&").c_str()); + EXPECT_STREQ("&", HtmlDecode("&amp;").c_str()); + EXPECT_STREQ("<!>", HtmlDecode("<!>").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("&", UrlDecode("&").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[] = ", "; +const char kEllidedSeparator[] = " .."; + +std::string FormatName(const std::string& name, bool bold) { + std::string formatted_name; + if (bold) { + formatted_name.append("<b>"); + } + formatted_name.append(HtmlEncode(name)); + if (bold) { + formatted_name.append("</b>"); + } + return formatted_name; +} + +class SenderFormatterList { + public: + // sender_list must not be deleted while this class is being used. + SenderFormatterList(const MailSenderList& sender_list, + const std::string& me_address) + : state_(INITIAL_STATE), + are_any_read_(false), + index_(-1), + first_unread_index_(-1) { + // Add all read messages. + const MailSender* originator = NULL; + bool any_unread = false; + for (size_t i = 0; i < sender_list.size(); ++i) { + if (sender_list[i].originator()) { + originator = &sender_list[i]; + } + if (sender_list[i].unread()) { + any_unread = true; + continue; + } + are_any_read_ = true; + if (!sender_list[i].originator()) { + senders_.push_back(new SenderFormatter(sender_list[i], + me_address)); + } + } + + // There may not be an orignator (if there no senders). + if (originator) { + senders_.insert(senders_.begin(), new SenderFormatter(*originator, + me_address)); + } + + // Add all unread messages. + if (any_unread) { + // It should be rare, but there may be cases when all of the + // senders appear to have read the message. + first_unread_index_ = is_first_unread() ? 0 : senders_.size(); + for (size_t i = 0; i < sender_list.size(); ++i) { + if (!sender_list[i].unread()) { + continue; + } + // Don't add the originator if it is already at the + // start of the "unread" list. + if (sender_list[i].originator() && is_first_unread()) { + continue; + } + senders_.push_back(new SenderFormatter(sender_list[i], + me_address)); + } + } + } + + ~SenderFormatterList() { + CleanupSequence(&senders_); + } + + std::string GetHtml(int space) { + index_ = -1; + state_ = INITIAL_STATE; + while (!added_.empty()) { + added_.pop(); + } + + // If there is only one sender, let it take up all of the space. + if (senders_.size() == 1) { + senders_[0]->set_space(space); + senders_[0]->set_short_format(false); + } + + int length = 1; + // Add as many senders as we can in the given space. + // Computes the visible length at each iteration, + // but does not construct the actual html. + while (length < space && AddNextSender()) { + int new_length = ConstructHtml(is_first_unread(), NULL); + // Remove names to avoid truncating + // * if there will be at least 2 left or + // * if the spacing <= 2 characters per sender and there + // is at least one left. + if ((new_length > space && + (visible_count() > 2 || + (ComputeSpacePerSender(space) <= 2 && visible_count() > 1)))) { + RemoveLastAddedSender(); + break; + } + length = new_length; + } + + if (length > space) { + int max = ComputeSpacePerSender(space); + for (size_t i = 0; i < senders_.size(); ++i) { + if (senders_[i]->visible()) { + senders_[i]->set_space(max); + } + } + } + + // Now construct the actual html + std::string html_list; + length = ConstructHtml(is_first_unread(), &html_list); + if (length > space) { + LOG(LS_WARNING) << "LENGTH: " << length << " exceeds " + << space << " " << html_list; + } + return html_list; + } + + private: + int ComputeSpacePerSender(int space) const { + // Why the "- 2"? To allow for the " .. " which may occur + // after the names, and no matter what always allow at least + // 2 characters per sender. + return talk_base::_max<int>(space / visible_count() - 2, 2); + } + + // Finds the next sender that should be added to the "from" list + // and sets it to visible. + // + // This method may be called until it returns false or + // until RemoveLastAddedSender is called. + bool AddNextSender() { + // The progression is: + // 1. Add the person who started the thread, which is the first message. + // 2. Add the first unread message (unless it has already been added). + // 3. Add the last message (unless it has already been added). + // 4. Add the message that is just before the last message processed + // (unless it has already been added). + // If there is no message (i.e. at index -1), return false. + // + // Typically, this method is called until it returns false or + // all of the space available is used. + switch (state_) { + case INITIAL_STATE: + state_ = FIRST_MESSAGE; + index_ = 0; + // If the server behaves odd and doesn't send us any senders, + // do something graceful. + if (senders_.size() == 0) { + return false; + } + break; + + case FIRST_MESSAGE: + if (first_unread_index_ >= 0) { + state_ = FIRST_UNREAD_MESSAGE; + index_ = first_unread_index_; + break; + } + // fall through + case FIRST_UNREAD_MESSAGE: + state_ = LAST_MESSAGE; + index_ = senders_.size() - 1; + break; + + case LAST_MESSAGE: + case PREVIOUS_MESSAGE: + state_ = PREVIOUS_MESSAGE; + index_--; + break; + + case REMOVED_MESSAGE: + default: + ASSERT(false); + return false; + } + + if (index_ < 0) { + return false; + } + + if (!senders_[index_]->visible()) { + added_.push(index_); + senders_[index_]->set_visible(true); + } + return true; + } + + // Makes the last added sender not visible. + // + // May be called while visible_count() > 0. + void RemoveLastAddedSender() { + state_ = REMOVED_MESSAGE; + int index = added_.top(); + added_.pop(); + senders_[index]->set_visible(false); + } + + // Constructs the html of the SenderList and returns the length of the + // visible text. + // + // The algorithm simply walks down the list of Senders, appending + // the html for each visible sender, and adding ellipsis or commas + // in between, whichever is appropriate. + // + // html Filled with html. Maybe NULL if the html doesn't + // need to be constructed yet (useful for simply + // determining the length of the visible text). + // + // returns The approximate visible length of the html. + int ConstructHtml(bool first_is_unread, + std::string* html) const { + if (senders_.empty()) { + return 0; + } + + int length = 0; + + // The first is always visible + const SenderFormatter* sender = senders_[0]; + const std::string& originator_name = sender->name(); + length += originator_name.length(); + if (html) { + html->append(FormatName(originator_name, first_is_unread)); + } + + bool elided = false; + const char* between = ""; + for (size_t i = 1; i < senders_.size(); i++) { + sender = senders_[i]; + + if (sender->visible()) { + // Handle the separator + between = elided ? " " : kNormalSeparator; + // Ignore the , for length because it is so narrow, + // so in both cases above the space is the only things + // that counts for spaces. + length++; + + // Handle the name + const std::string name = sender->name(); + length += name.size(); + + // Construct the html + if (html) { + html->append(between); + html->append(FormatName(name, sender->is_unread())); + } + elided = false; + } else if (!elided) { + between = kEllidedSeparator; + length += 2; // ".." is narrow + if (html) { + html->append(between); + } + elided = true; + } + } + return length; + } + + bool is_first_unread() const { + return !are_any_read_; + } + + size_t visible_count() const { + return added_.size(); + } + + enum MessageState { + INITIAL_STATE, + FIRST_MESSAGE, + FIRST_UNREAD_MESSAGE, + LAST_MESSAGE, + PREVIOUS_MESSAGE, + REMOVED_MESSAGE, + }; + + // What state we were in during the last "stateful" function call. + MessageState state_; + bool are_any_read_; + std::vector<SenderFormatter*> senders_; + std::stack<int> added_; + int index_; + int first_unread_index_; + DISALLOW_COPY_AND_ASSIGN(SenderFormatterList); +}; + + +std::string GetSenderHtml(const MailSenderList& sender_list, + int message_count, + const std::string& me_address, + int space) { + // There has to be at least 9 spaces to show something reasonable. + ASSERT(space >= 10); + std::string count_html; + if (message_count > 1) { + std::string count(IntToString(message_count)); + space -= (count.size()); + count_html.append(" ("); + count_html.append(count); + count_html.append(")"); + // Reduce space due to parenthesis and . + space -= 2; + } + + SenderFormatterList senders(sender_list, me_address); + std::string html_list(senders.GetHtml(space)); + html_list.append(count_html); + return html_list; +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/mailbox.h b/chrome/browser/sync/notifier/communicator/mailbox.h new file mode 100644 index 0000000..009de73 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/mailbox.h @@ -0,0 +1,166 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_MAILBOX_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_MAILBOX_H_ + +#include <set> +#include <string> +#include <vector> + +#include "talk/base/basictypes.h" +#include "talk/base/linked_ptr.h" + +namespace buzz { +class XmlElement; +} + +namespace notifier { +// ----------------------------------------------------------------------------- +class MailAddress { + public: + MailAddress(const std::string& name, const std::string& address) + : name_(name), + address_(address) { + } + const std::string& name() const { return name_; } + const std::string& address() const { return address_; } + std::string safe_name() const; // will return *something* + private: + std::string name_; + std::string address_; +}; + +// ----------------------------------------------------------------------------- +class MailSender : public MailAddress { + public: + MailSender(const std::string& name, const std::string& address, bool unread, + bool originator) + : MailAddress(name, address), + unread_(unread), + originator_(originator) { + } + + MailSender(const MailSender& r) + : MailAddress(r.name(), r.address()) { + unread_ = r.unread_; + originator_ = r.originator_; + } + + bool unread() const { return unread_; } + bool originator() const { return originator_; } + + private: + bool unread_; + bool originator_; +}; + +typedef std::vector<MailSender> MailSenderList; + +// ----------------------------------------------------------------------------- +// MessageThread: everything there is to know about a mail thread. +class MessageThread { + public: + MessageThread(const MessageThread& r) { + labels_ = NULL; + senders_ = NULL; + *this = r; + } + + ~MessageThread(); + + // Try to parse the XML to create a MessageThreadInfo. If NULL is returned + // then we either ran out of memory or there was an error in parsing the + // XML + static MessageThread* CreateFromXML(const buzz::XmlElement* src); + + MessageThread& operator=(const MessageThread& r); + + // SameThreadAs : name is self evident + bool SameThreadAs(const MessageThread& r) { + AssertValid(); + r.AssertValid(); + return (thread_id_ == r.thread_id_); + } + + // SameAs : true if thread has same id and messages + // Assumes that messages don't disappear from threads. + bool SameAs(const MessageThread& r) { + AssertValid(); + r.AssertValid(); + return SameThreadAs(r) && + message_count_ == r.message_count_; + } + + typedef std::set<std::string> StringSet; + + int64 thread_id() const { return thread_id_; } + const StringSet* labels() const { return labels_; } + int64 date64() const { return date64_; } + MailSenderList* senders() const { return senders_; } + int personal_level() const { return personal_level_; } + int message_count() const { return message_count_; } + const std::string& subject() const { return subject_; } + const std::string& snippet() const { return snippet_; } + bool starred() const; + bool unread() const; + +#ifdef _DEBUG + void AssertValid() const; +#else + inline void AssertValid() const {} +#endif + + private: + void Clear(); + + private: + MessageThread() : senders_(NULL), labels_(NULL) {} + bool InitFromXml(const buzz::XmlElement* src); + + int64 thread_id_; + int64 date64_; + int message_count_; + int personal_level_; + std::string subject_; + std::string snippet_; + MailSenderList* senders_; + StringSet* labels_; +}; + +typedef talk_base::linked_ptr<MessageThread> MessageThreadPointer; +typedef std::vector<MessageThreadPointer> MessageThreadVector; + +// ----------------------------------------------------------------------------- +class MailBox { + public: + static MailBox* CreateFromXML(const buzz::XmlElement* src); + + const MessageThreadVector& threads() const { return threads_; } + int mailbox_size() const { return mailbox_size_; } + int first_index() const { return first_index_; } + bool estimate() const { return estimate_; } + int64 result_time() const { return result_time_; } + int64 highest_thread_id() const { return highest_thread_id_; } + + private: + MailBox() {} + bool InitFromXml(const buzz::XmlElement* src); + + MessageThreadVector threads_; + + int mailbox_size_; + int first_index_; + bool estimate_; + int64 result_time_; + int64 highest_thread_id_; +}; + +std::string GetSenderHtml(const MailSenderList& sender_list, + int message_count, + const std::string& me_address, + int space); +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_MAILBOX_H_ diff --git a/chrome/browser/sync/notifier/communicator/mailbox_unittest.cc b/chrome/browser/sync/notifier/communicator/mailbox_unittest.cc new file mode 100644 index 0000000..1d498d1 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/mailbox_unittest.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/sync/notifier/communicator/mailbox.h" +#include "notifier/testing/notifier/unittest.h" + +namespace notifier { +TEST_NOTIFIER_F(MailBoxTest); + +TEST_F(MailBoxTest, SingleSenderHtml) { + std::string me_address("random@company.com"); + MailSenderList sender_list; + sender_list.push_back(MailSender("Alex Smith", "a@a.com", true, true)); + std::string sender_html = GetSenderHtml(sender_list, 1, me_address, 25); + ASSERT_STREQ("<b>Alex Smith</b>", sender_html.c_str()); +} + +TEST_F(MailBoxTest, TruncatedSingleSenderHtml) { + std::string me_address("random@company.com"); + MailSenderList sender_list; + sender_list.push_back(MailSender( + "Alex Smith AReallyLongLastNameThatWillBeTruncated", + "a@a.com", + true, + true)); + std::string sender_html = GetSenderHtml(sender_list, 1, me_address, 25); + ASSERT_STREQ("<b>Alex Smith AReallyLongLa.</b>", sender_html.c_str()); +} + +TEST_F(MailBoxTest, SingleSenderManyTimesHtml) { + std::string me_address("random@company.com"); + MailSenderList sender_list; + sender_list.push_back(MailSender("Alex Smith", "a@a.com", true, true)); + std::string sender_html = GetSenderHtml(sender_list, 10, me_address, 25); + ASSERT_STREQ("<b>Alex Smith</b> (10)", sender_html.c_str()); +} + +TEST_F(MailBoxTest, SenderWithMeHtml) { + std::string me_address("randOm@comPany.Com"); + MailSenderList sender_list; + sender_list.push_back( + MailSender("Alex Smith", "alex@jones.com", false, false)); + sender_list.push_back( + MailSender("Your Name Goes Here", "raNdom@coMpany.cOm", true, true)); + std::string sender_html = GetSenderHtml(sender_list, 5, me_address, 25); + ASSERT_STREQ("me, Alex, <b>me</b> (5)", sender_html.c_str()); +} + +TEST_F(MailBoxTest, SenderHtmlWithAllUnread) { + std::string me_address("random@company.com"); + MailSenderList sender_list; + sender_list.push_back( + MailSender("Alex Smith", "alex@jones.com", true, false)); + sender_list.push_back(MailSender( + "Your Name Goes Here", + "foo@company.com", + true, + true)); + sender_list.push_back(MailSender("", "bob@davis.com", true, false)); + std::string sender_html = GetSenderHtml(sender_list, 100, me_address, 25); + ASSERT_STREQ("<b>Your</b>, <b>Alex</b>, <b>bob</b> (100)", + sender_html.c_str()); +} + +TEST_F(MailBoxTest, SenderHtmlWithTruncatedNames) { + std::string me_address("random@company.com"); + MailSenderList sender_list; + sender_list.push_back(MailSender( + "ReallyLongName Smith", + "alex@jones.com", + true, + false)); + sender_list.push_back(MailSender( + "AnotherVeryLongFirstNameIsHere", + "foo@company.com", + true, + true)); + std::string sender_html = GetSenderHtml(sender_list, 2, me_address, 25); + ASSERT_STREQ("<b>AnotherV.</b>, <b>ReallyLo.</b> (2)", + sender_html.c_str()); +} + +TEST_F(MailBoxTest, SenderWithTwoSendersShowing) { + std::string me_address("random@company.com"); + MailSenderList sender_list; + sender_list.push_back( + MailSender("ALongishName Smith", "alex@jones.com", false, false)); + sender_list.push_back( + MailSender("AnotherBigName", "no@company.com", true, false)); + sender_list.push_back( + MailSender("Person1", "no1@company.com", true, false)); + sender_list.push_back( + MailSender("Person2", "no2@company.com", false, true)); + std::string sender_html = GetSenderHtml(sender_list, 6, me_address, 25); + ASSERT_STREQ("Person2 .. <b>AnotherB.</b> .. (6)", + sender_html.c_str()); +} + +TEST_F(MailBoxTest, SenderWithThreeSendersShowing) { + std::string me_address("random@company.com"); + MailSenderList sender_list; + sender_list.push_back( + MailSender("Joe Smith", "alex@jones.com", false, false)); + sender_list.push_back( + MailSender("Bob Other", "no@company.com", true, false)); + sender_list.push_back( + MailSender("Person0", "no0@company.com", true, false)); + sender_list.push_back( + MailSender("Person1", "no1@company.com", true, false)); + sender_list.push_back( + MailSender("ted", "ted@company.com", false, true)); + std::string sender_html = GetSenderHtml(sender_list, 6, me_address, 25); + ASSERT_STREQ( + "ted .. <b>Bob</b> .. <b>Person1</b> (6)", + sender_html.c_str()); +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/product_info.cc b/chrome/browser/sync/notifier/communicator/product_info.cc new file mode 100644 index 0000000..c1deafb --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/product_info.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +namespace notifier { +std::string GetUserAgentString() { + return kXmppProductName; +} + +std::string GetProductSignature() { + return kXmppProductName; +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/product_info.h b/chrome/browser/sync/notifier/communicator/product_info.h new file mode 100644 index 0000000..1da60b0 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/product_info.h @@ -0,0 +1,14 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_ +#include <string> + +namespace notifier { +std::string GetUserAgentString(); +std::string GetProductSignature(); +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_PRODUCT_INFO_H_ diff --git a/chrome/browser/sync/notifier/communicator/single_login_attempt.cc b/chrome/browser/sync/notifier/communicator/single_login_attempt.cc new file mode 100644 index 0000000..68fe272 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/single_login_attempt.cc @@ -0,0 +1,562 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "chrome/browser/sync/notifier/communicator/single_login_attempt.h" + +#include "chrome/browser/sync/notifier/communicator/connection_options.h" +#include "chrome/browser/sync/notifier/communicator/connection_settings.h" +#include "chrome/browser/sync/notifier/communicator/const_communicator.h" +#include "chrome/browser/sync/notifier/communicator/login_failure.h" +#include "chrome/browser/sync/notifier/communicator/login_settings.h" +#include "chrome/browser/sync/notifier/communicator/product_info.h" +#include "chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h" +#include "chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.h" +#include "chrome/browser/sync/notifier/gaia_auth/gaiaauth.h" +#include "talk/base/asynchttprequest.h" +#include "talk/base/firewallsocketserver.h" +#include "talk/base/signalthread.h" +#include "talk/base/taskrunner.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/constants.h" +#include "talk/xmpp/prexmppauth.h" +#include "talk/xmpp/xmppclient.h" +#include "talk/xmpp/xmppclientsettings.h" + +namespace notifier { +static void FillProxyInfo(const buzz::XmppClientSettings& xcs, + talk_base::ProxyInfo* proxy) { + ASSERT(proxy != NULL); + proxy->type = xcs.proxy(); + proxy->address.SetIP(xcs.proxy_host()); + proxy->address.SetPort(xcs.proxy_port()); + if (xcs.use_proxy_auth()) { + proxy->username = xcs.proxy_user(); + proxy->password = xcs.proxy_pass(); + } +} + +static void GetClientErrorInformation( + buzz::XmppClient* client, + buzz::XmppEngine::Error* error, + int* subcode, + buzz::XmlElement** stream_error, + buzz::CaptchaChallenge* captcha_challenge) { + ASSERT(client != NULL); + ASSERT(error && subcode && stream_error && captcha_challenge); + + *error = client->GetError(subcode); + *captcha_challenge = client->GetCaptchaChallenge(); + + *stream_error = NULL; + if (*error == buzz::XmppEngine::ERROR_STREAM) { + const buzz::XmlElement* error_element = client->GetStreamError(); + if (error_element) { + *stream_error = new buzz::XmlElement(*error_element); + } + } +} + +SingleLoginAttempt::SingleLoginAttempt(talk_base::Task* parent, + LoginSettings* login_settings, + bool successful_connection) + : talk_base::Task(parent), + state_(buzz::XmppEngine::STATE_NONE), + code_(buzz::XmppEngine::ERROR_NONE), + subcode_(0), + need_authentication_(false), + certificate_expired_(false), + cookie_refreshed_(false), + successful_connection_(successful_connection), + login_settings_(login_settings), + client_(NULL) { + connection_generator_.reset(new XmppConnectionGenerator( + this, + &login_settings_->connection_options(), + login_settings_->proxy_only(), + login_settings_->server_list(), + login_settings_->server_count())); + + connection_generator_->SignalExhaustedSettings.connect( + this, + &SingleLoginAttempt::OnAttemptedAllConnections); + connection_generator_->SignalNewSettings.connect( + this, + &SingleLoginAttempt::DoLogin); +} + +SingleLoginAttempt::~SingleLoginAttempt() { + // If this assertion goes off, it means that "Stop()" didn't get + // called like it should have been. + ASSERT(client_ == NULL); +} + +bool SingleLoginAttempt::auto_reconnect() const { + return login_settings_->connection_options().auto_reconnect(); +} + +const talk_base::ProxyInfo& SingleLoginAttempt::proxy() const { + ASSERT(connection_generator_.get()); + return connection_generator_->proxy(); +} + +int SingleLoginAttempt::ProcessStart() { + ASSERT(GetState() == talk_base::Task::STATE_START); + connection_generator_->StartGenerating(); + + // After being started, this class is callback driven and does + // signaling from those callbacks (with checks to see if it is + // done if it may be called back from something that isn't a child task). + return talk_base::Task::STATE_BLOCKED; +} + +void SingleLoginAttempt::Stop() { + ClearClient(); + talk_base::Task::Stop(); + + // No more signals should happen after being stopped. + // (This is needed because some of these signals + // happen due to other components doing signaling which + // may continue running even though this task is stopped.) + SignalUnexpectedDisconnect.disconnect_all(); + SignalRedirect.disconnect_all(); + SignalLoginFailure.disconnect_all(); + SignalNeedAutoReconnect.disconnect_all(); + SignalClientStateChange.disconnect_all(); +} + +void SingleLoginAttempt::OnAttemptedAllConnections( + bool successfully_resolved_dns, + int first_dns_error) { + + // Maybe we needed proxy authentication? + if (need_authentication_) { + LoginFailure failure(LoginFailure::PROXY_AUTHENTICATION_ERROR); + SignalLoginFailure(failure); + return; + } + + if (certificate_expired_) { + LoginFailure failure(LoginFailure::CERTIFICATE_EXPIRED_ERROR); + SignalLoginFailure(failure); + return; + } + + if (!successfully_resolved_dns) { + code_ = buzz::XmppEngine::ERROR_SOCKET; + subcode_ = first_dns_error; + } + + LOG(INFO) << "Connection failed with error " << code_; + + // We were connected and we had a problem + if (successful_connection_ && auto_reconnect()) { + SignalNeedAutoReconnect(); + // expect to be deleted at this point + return; + } + + DiagnoseConnectionError(); +} + +void SingleLoginAttempt::UseNextConnection() { + ASSERT(connection_generator_.get() != NULL); + ClearClient(); + connection_generator_->UseNextConnection(); +} + +void SingleLoginAttempt::UseCurrentConnection() { + ASSERT(connection_generator_.get() != NULL); + ClearClient(); + connection_generator_->UseCurrentConnection(); +} + +void SingleLoginAttempt::DoLogin( + const ConnectionSettings& connection_settings) { + if (client_) { + return; + } + + buzz::XmppClientSettings client_settings; + // set the user settings portion + *static_cast<buzz::XmppClientSettings*>(&client_settings) = + login_settings_->user_settings(); + // fill in the rest of the client settings + connection_settings.FillXmppClientSettings(&client_settings); + + client_ = new buzz::XmppClient(this); + SignalLogInput.repeat(client_->SignalLogInput); + SignalLogOutput.repeat(client_->SignalLogOutput); + + // listen for connection progress + client_->SignalStateChange.connect(this, + &SingleLoginAttempt::OnClientStateChange); + + // transition to "start" + OnClientStateChange(buzz::XmppEngine::STATE_START); + // start connecting + client_->Connect(client_settings, login_settings_->lang(), + CreateSocket(client_settings), + CreatePreXmppAuth(client_settings)); + client_->Start(); +} + +void SingleLoginAttempt::OnAuthenticationError() { + // We can check this flag later if all connection options fail + need_authentication_ = true; +} + +void SingleLoginAttempt::OnCertificateExpired() { + // We can check this flag later if all connection options fail + certificate_expired_ = true; +} + + +buzz::AsyncSocket* SingleLoginAttempt::CreateSocket( + const buzz::XmppClientSettings& xcs) { + bool allow_unverified_certs = + login_settings_->connection_options().allow_unverified_certs(); + XmppSocketAdapter* adapter = new XmppSocketAdapter(xcs, + allow_unverified_certs); + adapter->SignalAuthenticationError.connect( + this, + &SingleLoginAttempt::OnAuthenticationError); + if (login_settings_->firewall()) { + adapter->set_firewall(true); + } + return adapter; +} + +buzz::PreXmppAuth* SingleLoginAttempt::CreatePreXmppAuth( + const buzz::XmppClientSettings& xcs) { + if (login_settings_->no_gaia_auth()) + return NULL; + + // For GMail, use Gaia preauthentication over HTTP + buzz::GaiaAuth* auth = new buzz::GaiaAuth(GetUserAgentString(), + GetProductSignature()); + auth->SignalAuthenticationError.connect( + this, + &SingleLoginAttempt::OnAuthenticationError); + auth->SignalCertificateExpired.connect( + this, + &SingleLoginAttempt::OnCertificateExpired); + auth->SignalFreshAuthCookie.connect( + this, + &SingleLoginAttempt::OnFreshAuthCookie); + auth->set_token_service(xcs.token_service()); + + talk_base::ProxyInfo proxy; + FillProxyInfo(xcs, &proxy); + auth->set_proxy(proxy); + auth->set_firewall(login_settings_->firewall()); + return auth; +} + +void SingleLoginAttempt::OnFreshAuthCookie(const std::string& auth_cookie) { + // Remember this is a fresh cookie + cookie_refreshed_ = true; + + // TODO(sync): do the cookie logic (part of which is in the #if 0 below) + + // The following code is what PhoneWindow does for the equivalent method +#if 0 + // Save cookie + AccountInfo current(account_history_.current()); + current.set_auth_cookie(auth_cookie); + account_history_.set_current(current); + + // Calc next time to refresh cookie, between 5 and 10 days + // The cookie has 14 days of life; this gives at least 4 days of retries + // before the current cookie expires, maximizing the chance of + // having a valid cookie next time the connection servers go down. + FTULL now; + + // NOTE: The following line is win32. Address this when implementing this + // code (doing "the cookie logic") + GetSystemTimeAsFileTime(&(now.ft)); + ULONGLONG five_days = (ULONGLONG)10000 * 1000 * 60 * 60 * 24 * 5; // 5 days + ULONGLONG random = (ULONGLONG)10000 * // get to 100 ns units + ((rand() % (5 * 24 * 60)) * (60 * 1000) + // random min. in 5 day period + (rand() % 1000) * 60); // random 1/1000th of a minute + next_cookie_refresh_ = now.ull + five_days + random; // 5-10 days +#endif +} + +void SingleLoginAttempt::DiagnoseConnectionError() { + switch (code_) { + case buzz::XmppEngine::ERROR_MISSING_USERNAME: + case buzz::XmppEngine::ERROR_NETWORK_TIMEOUT: + case buzz::XmppEngine::ERROR_DOCUMENT_CLOSED: + case buzz::XmppEngine::ERROR_BIND: + case buzz::XmppEngine::ERROR_AUTH: + case buzz::XmppEngine::ERROR_TLS: + case buzz::XmppEngine::ERROR_UNAUTHORIZED: + case buzz::XmppEngine::ERROR_VERSION: + case buzz::XmppEngine::ERROR_STREAM: + case buzz::XmppEngine::ERROR_XML: + case buzz::XmppEngine::ERROR_NONE: + default: { + LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_); + SignalLoginFailure(failure); + return; + } + + // The following errors require diagnosistics: + // * spurious close of connection + // * socket errors after auth + case buzz::XmppEngine::ERROR_CONNECTION_CLOSED: + case buzz::XmppEngine::ERROR_SOCKET: + break; + } + + talk_base::AsyncHttpRequest *http_request = + new talk_base::AsyncHttpRequest(GetUserAgentString()); + http_request->set_host("www.google.com"); + http_request->set_port(80); + http_request->set_secure(false); + http_request->request().path = "/"; + http_request->request().verb = talk_base::HV_GET; + + talk_base::ProxyInfo proxy; + ASSERT(connection_generator_.get() != NULL); + if (connection_generator_.get()) { + proxy = connection_generator_->proxy(); + } + http_request->set_proxy(proxy); + http_request->set_firewall(login_settings_->firewall()); + + http_request->SignalWorkDone.connect(this, + &SingleLoginAttempt::OnHttpTestDone); + http_request->Start(); + http_request->Release(); +} + +void SingleLoginAttempt::OnHttpTestDone(talk_base::SignalThread* thread) { + ASSERT(thread != NULL); + + talk_base::AsyncHttpRequest* request = + static_cast<talk_base::AsyncHttpRequest*>(thread); + + if (request->response().scode == 200) { + // We were able to do an HTTP GET of www.google.com:80 + + // + // The original error should be reported + // + LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_); + SignalLoginFailure(failure); + return; + } + + // Otherwise lets transmute the error into ERROR_SOCKET, and put + // the subcode as an indicator of what we think the problem + // might be. + +#if 0 + // TODO(sync): determine if notifier has an analogous situation + + // + // We weren't able to do an HTTP GET of www.google.com:80 + // + GAutoupdater::Version version_logged_in(g_options.version_logged_in()); + GAutoupdater::Version version_installed(GetProductVersion().c_str()); + if (version_logged_in < version_installed) { + // + // Google Talk has been updated and can no longer connect + // to the Google Talk Service. Your firewall is probably + // not allowing the new version of Google Talk to connect + // to the internet. Please adjust your firewall settings + // to allow the new version of Google Talk to connect to + // the internet. + // + // We'll use the "error=1" to help figure this out for now + // + LoginFailure failure(LoginFailure::XMPP_ERROR, + buzz::XmppEngine::ERROR_SOCKET, + 1); + SignalLoginFailure(failure); + return; + } +#endif + + // + // Any other checking we can add here? + // + + // + // Google Talk is unable to use your internet connection. Either your + // network isn't configured or Google Talk is being blocked by + // a local firewall. + // + // We'll use the "error=0" to help figure this out for now + // + LoginFailure failure(LoginFailure::XMPP_ERROR, + buzz::XmppEngine::ERROR_SOCKET, + 0); + SignalLoginFailure(failure); +} + +void SingleLoginAttempt::OnClientStateChange(buzz::XmppEngine::State state) { + if (state_ == state) + return; + + buzz::XmppEngine::State previous_state = state_; + state_ = state; + + switch (state) { + case buzz::XmppEngine::STATE_NONE: + case buzz::XmppEngine::STATE_START: + case buzz::XmppEngine::STATE_OPENING: + // do nothing + break; + case buzz::XmppEngine::STATE_OPEN: + successful_connection_ = true; + break; + case buzz::XmppEngine::STATE_CLOSED: + OnClientStateChangeClosed(previous_state); + break; + } + SignalClientStateChange(state); + if (state_ == buzz::XmppEngine::STATE_CLOSED) { + OnClientStateChange(buzz::XmppEngine::STATE_NONE); + } +} + +void SingleLoginAttempt::ClearClient() { + if (client_ != NULL) { + client_->Disconnect(); + + // If this assertion goes off, it means that the disconnect didn't occur + // properly. See SingleLoginAttempt::OnClientStateChange, + // case XmppEngine::STATE_CLOSED + ASSERT(client_ == NULL); + } +} + +void SingleLoginAttempt::OnClientStateChangeClosed( + buzz::XmppEngine::State previous_state) { + buzz::XmppEngine::Error error = buzz::XmppEngine::ERROR_NONE; + int error_subcode = 0; + buzz::CaptchaChallenge captcha_challenge; + buzz::XmlElement* stream_error_ptr; + GetClientErrorInformation(client_, + &error, + &error_subcode, + &stream_error_ptr, + &captcha_challenge); + scoped_ptr<buzz::XmlElement> stream_error(stream_error_ptr); + + client_->SignalStateChange.disconnect(this); + client_ = NULL; + + if (error == buzz::XmppEngine::ERROR_NONE) { + SignalLogoff(); + return; + } else if (previous_state == buzz::XmppEngine::STATE_OPEN) { + // Handler should attempt reconnect + SignalUnexpectedDisconnect(); + return; + } else { + HandleConnectionError(error, error_subcode, stream_error.get(), + captcha_challenge); + } +} + +void SingleLoginAttempt::HandleConnectionPasswordError( + const buzz::CaptchaChallenge& captcha_challenge) { + LOG(LS_VERBOSE) << "SingleLoginAttempt::HandleConnectionPasswordError"; + + // Clear the auth cookie + std::string current_auth_cookie = + login_settings_->user_settings().auth_cookie(); + login_settings_->modifiable_user_settings()->set_auth_cookie(""); + // If there was an auth cookie and it was the same as the last + // auth cookie, then it is a stale cookie. Retry login. + if (!current_auth_cookie.empty() && !cookie_refreshed_) { + UseCurrentConnection(); + return; + } + + LoginFailure failure(LoginFailure::XMPP_ERROR, code_, subcode_, + captcha_challenge); + SignalLoginFailure(failure); +} + +void SingleLoginAttempt::HandleConnectionError( + buzz::XmppEngine::Error code, + int subcode, + const buzz::XmlElement* stream_error, + const buzz::CaptchaChallenge& captcha_challenge) { + LOG_F(LS_VERBOSE) << "(" << code << ", " << subcode << ")"; + + // Save off the error code information, so we can use it + // to tell the user what went wrong if all else fails + code_ = code; + subcode_ = subcode; + if ((code_ == buzz::XmppEngine::ERROR_UNAUTHORIZED) || + (code_ == buzz::XmppEngine::ERROR_MISSING_USERNAME)) { + // There was a problem with credentials (username/password) + HandleConnectionPasswordError(captcha_challenge); + return; + } + + // Unexpected disconnect, + // Unreachable host, + // Or internal server binding error - + // All these are temporary problems, so continue reconnecting + + // GaiaAuth signals this directly via SignalCertificateExpired, but + // SChannelAdapter propagates the error through SocketWindow as a socket + // error. + if (code_ == buzz::XmppEngine::ERROR_SOCKET && + subcode_ == SEC_E_CERT_EXPIRED) { + certificate_expired_ = true; + } + + login_settings_->modifiable_user_settings()->set_resource(""); + + // Look for stream::error server redirection stanza "see-other-host" + if (stream_error) { + const buzz::XmlElement* other = + stream_error->FirstNamed(buzz::QN_XSTREAM_SEE_OTHER_HOST); + if (other) { + const buzz::XmlElement* text = + stream_error->FirstNamed(buzz::QN_XSTREAM_TEXT); + if (text) { + // Yep, its a "stream:error" with "see-other-host" text, let's + // parse out the server:port, and then reconnect with that. + const std::string& redirect = text->BodyText(); + unsigned int colon = redirect.find(":"); + int redirect_port = kDefaultXmppPort; + std::string redirect_server; + if (colon == std::string::npos) { + redirect_server = redirect; + } else { + redirect_server = redirect.substr(0, colon); + const std::string& port_text = redirect.substr(colon + 1); + std::istringstream ist(port_text); + ist >> redirect_port; + } + // we never allow a redirect to port 0 + if (redirect_port == 0) { + redirect_port = kDefaultXmppPort; + } + SignalRedirect(redirect_server, redirect_port); + // may be deleted at this point + return; + } + } + } + + ASSERT(connection_generator_.get() != NULL); + if (!connection_generator_.get()) { + return; + } + + // Iterate to the next possible connection (still trying to connect) + UseNextConnection(); +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/single_login_attempt.h b/chrome/browser/sync/notifier/communicator/single_login_attempt.h new file mode 100644 index 0000000..ec265ea --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/single_login_attempt.h @@ -0,0 +1,139 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_ +#include <string> + +#include "chrome/browser/sync/notifier/communicator/login.h" +#include "talk/base/scoped_ptr.h" +#include "talk/base/sigslot.h" +#include "talk/base/task.h" +#include "talk/xmpp/xmppengine.h" + +namespace buzz { +class AsyncSocket; +class CaptchaChallenge; +class PreXmppAuth; +class XmppClient; +class XmppClientSettings; +class XmppClientSettings; +} + +namespace talk_base { +class FirewallManager; +struct ProxyInfo; +class SignalThread; +class Task; +} + +namespace notifier { +class ConnectionSettings; +class LoginFailure; +class LoginSettings; +struct ServerInformation; +class XmppConnectionGenerator; + +// Handles all of the aspects of a single login attempt +// (across multiple ip addresses) and maintainence. By containing +// this within one class, when another login attempt is made, +// this class will be disposed and all of the signalling for the +// previous login attempt will be cleaned up immediately. +// +// This is a task to allow for cleaning this up when a signal +// is being fired. Technically, delete this during the firing of +// a signal could work but it is fragile. +class SingleLoginAttempt : public talk_base::Task, public sigslot::has_slots<> { + public: + SingleLoginAttempt(talk_base::Task* parent, + LoginSettings* login_settings, + bool successful_connection); + ~SingleLoginAttempt(); + virtual int ProcessStart(); + void UseNextConnection(); + void UseCurrentConnection(); + + buzz::XmppClient* xmpp_client() { + return client_; + } + + // Returns the proxy that is being used to connect (or + // the default proxy information if all attempted + // connections failed). + const talk_base::ProxyInfo& proxy() const; + + // Typically handled by creating a new SingleLoginAttempt + // and doing StartConnection + sigslot::signal0<> SignalUnexpectedDisconnect; + + // Typically handled by setting storing the redirect for 5 seconds, + // and setting it into LoginSettings, then creating a new SingleLoginAttempt, + // and doing StartConnection. + // + // SignalRedirect(const std::string& redirect_server, int redirect_port); + sigslot::signal2<const std::string&, int> SignalRedirect; + + sigslot::signal0<> SignalNeedAutoReconnect; + + // SignalClientStateChange(buzz::XmppEngine::State new_state); + sigslot::signal1<buzz::XmppEngine::State> SignalClientStateChange; + + // See the LoginFailure for how to handle this. + sigslot::signal1<const LoginFailure&> SignalLoginFailure; + + // Sent when there is a graceful log-off (state goes to closed + // with no error) + sigslot::signal0<> SignalLogoff; + + sigslot::repeater2<const char*, int> SignalLogInput; + sigslot::repeater2<const char*, int> SignalLogOutput; + + protected: + virtual void Stop(); + + private: + void DoLogin(const ConnectionSettings& connection_settings); + buzz::AsyncSocket* CreateSocket(const buzz::XmppClientSettings& xcs); + buzz::PreXmppAuth* CreatePreXmppAuth(const buzz::XmppClientSettings& xcs); + + // cleans up any xmpp client state to get ready for a new one + void ClearClient(); + + void HandleConnectionError( + buzz::XmppEngine::Error code, + int subcode, + const buzz::XmlElement* stream_error, + const buzz::CaptchaChallenge& captcha_challenge); + void HandleConnectionPasswordError( + const buzz::CaptchaChallenge& captcha_challenge); + + void DiagnoseConnectionError(); + void OnHttpTestDone(talk_base::SignalThread* thread); + + void OnAuthenticationError(); + void OnCertificateExpired(); + void OnFreshAuthCookie(const std::string& auth_cookie); + void OnClientStateChange(buzz::XmppEngine::State state); + void OnClientStateChangeClosed(buzz::XmppEngine::State previous_state); + void OnAttemptedAllConnections(bool successfully_resolved_dns, + int first_dns_error); + + bool auto_reconnect() const; + + buzz::XmppEngine::State state_; + buzz::XmppEngine::Error code_; + int subcode_; + bool need_authentication_; + bool certificate_expired_; + bool cookie_refreshed_; + bool successful_connection_; + LoginSettings* login_settings_; + buzz::XmppClient* client_; + scoped_ptr<XmppConnectionGenerator> connection_generator_; + + DISALLOW_COPY_AND_ASSIGN(SingleLoginAttempt); +}; +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_SINGLE_LOGIN_ATTEMPT_H_ diff --git a/chrome/browser/sync/notifier/communicator/talk_auth_task.cc b/chrome/browser/sync/notifier/communicator/talk_auth_task.cc new file mode 100644 index 0000000..a69609c7 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/talk_auth_task.cc @@ -0,0 +1,73 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/sync/notifier/communicator/talk_auth_task.h" + +#include "chrome/browser/sync/notifier/communicator/login.h" +#include "chrome/browser/sync/notifier/communicator/login_settings.h" +#include "chrome/browser/sync/notifier/communicator/product_info.h" +#include "chrome/browser/sync/notifier/gaia_auth/gaiaauth.h" +#include "talk/base/common.h" +#include "talk/base/urlencode.h" +#include "talk/xmpp/xmppclient.h" + +namespace notifier { +const char kTalkGadgetAuthPath[] = "/auth"; + +TalkAuthTask::TalkAuthTask(talk_base::Task* parent, + Login* login, + const char* url) + : talk_base::Task(parent), + login_(login), + url_(url) { + ASSERT(login && !url_.empty()); +} + +int TalkAuthTask::ProcessStart() { + auth_.reset(new buzz::GaiaAuth(GetUserAgentString(), + GetProductSignature())); + auth_->SignalAuthDone.connect( + this, + &TalkAuthTask::OnAuthDone); + auth_->StartAuth(login_->xmpp_client()->jid().BareJid(), + login_->login_settings().user_settings().pass(), + "talk"); + return STATE_RESPONSE; +} + +int TalkAuthTask::ProcessResponse() { + ASSERT(auth_.get()); + if (!auth_->IsAuthDone()) { + return STATE_BLOCKED; + } + SignalAuthDone(*this); + return STATE_DONE; +} + + +void TalkAuthTask::OnAuthDone() { + Wake(); +} + +bool TalkAuthTask::HadError() const { + return auth_->HadError(); +} + +std::string TalkAuthTask::GetAuthenticatedUrl( + const char* talk_base_url) const { + ASSERT(talk_base_url && *talk_base_url && !auth_->HadError()); + + std::string auth_url(talk_base_url); + auth_url.append(kTalkGadgetAuthPath); + auth_url.append("?silent=true&redirect=true&host="); + auth_url.append(UrlEncodeString(url_)); + auth_url.append("&auth="); + auth_url.append(auth_->GetAuth()); + return auth_url; +} + +std::string TalkAuthTask::GetSID() const { + return auth_->GetSID(); +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/talk_auth_task.h b/chrome/browser/sync/notifier/communicator/talk_auth_task.h new file mode 100644 index 0000000..2f690a37 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/talk_auth_task.h @@ -0,0 +1,62 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_TALK_AUTH_TASK_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_TALK_AUTH_TASK_H_ + +#include "talk/base/scoped_ptr.h" +#include "talk/base/sigslot.h" +#include "talk/base/task.h" + +namespace buzz { +class GaiaAuth; +} + +namespace notifier { +class Login; + +// Create an authenticated talk url from an unauthenticated url +class TalkAuthTask : public talk_base::Task, public sigslot::has_slots<> { + public: + TalkAuthTask(talk_base::Task* parent, + Login* login, + const char* url); + + // An abort method which doesn't take any parameters. + // (talk_base::Task::Abort() has a default parameter.) + // + // The primary purpose of this method is to allow a + // signal to be hooked up to abort this task. + void Abort() { + talk_base::Task::Abort(); + } + + const std::string& url() { + return url_; + } + + std::string GetAuthenticatedUrl(const char* talk_base_url) const; + std::string GetSID() const; + + sigslot::signal1<const TalkAuthTask&> SignalAuthDone; + + bool HadError() const; + + // TODO(sync): add captcha support + + protected: + virtual int ProcessStart(); + virtual int ProcessResponse(); + + private: + void OnAuthDone(); + + scoped_ptr<buzz::GaiaAuth> auth_; + Login* login_; + std::string url_; + DISALLOW_COPY_AND_ASSIGN(TalkAuthTask); +}; +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_TALK_AUTH_TASK_H_ diff --git a/chrome/browser/sync/notifier/communicator/xml_parse_helpers-inl.h b/chrome/browser/sync/notifier/communicator/xml_parse_helpers-inl.h new file mode 100644 index 0000000..b400218 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xml_parse_helpers-inl.h @@ -0,0 +1,24 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XML_PARSE_HELPERS_INL_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XML_PARSE_HELPERS_INL_H_ + +#include <sstream> + +#include "chrome/browser/sync/notifier/communicator/xml_parse_helpers.h" +#include "talk/xmllite/xmlelement.h" + +namespace notifier { + +template<class T> +void SetAttr(buzz::XmlElement* xml, const buzz::QName& name, const T& data) { + std::ostringstream ost; + ost << data; + xml->SetAttr(name, ost.str()); +} + +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XML_PARSE_HELPERS_INL_H_ diff --git a/chrome/browser/sync/notifier/communicator/xml_parse_helpers.cc b/chrome/browser/sync/notifier/communicator/xml_parse_helpers.cc new file mode 100644 index 0000000..b05f439 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xml_parse_helpers.cc @@ -0,0 +1,185 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/sync/notifier/communicator/xml_parse_helpers.h" +#include "chrome/browser/sync/notifier/communicator/xml_parse_helpers-inl.h" + +#include <string> + +#include "chrome/browser/sync/notifier/base/string.h" +#include "talk/base/basicdefs.h" +#include "talk/base/stream.h" +#include "talk/xmllite/xmlbuilder.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlparser.h" +#include "talk/xmllite/xmlprinter.h" +#include "talk/xmpp/jid.h" + +namespace notifier { + +buzz::XmlElement* ReadXmlFromStream(talk_base::StreamInterface* stream) { + buzz::XmlBuilder builder; + buzz::XmlParser parser(&builder); + + const int kBufferSize = 4 * 1024; + char buf[kBufferSize]; + + talk_base::StreamResult result = talk_base::SR_SUCCESS; + while(true) { + size_t read = 0; + + // Read a chunk + result = stream->Read(buf, kBufferSize, &read, NULL); + if (result != talk_base::SR_SUCCESS) + break; + + // Pass it to the parser + parser.Parse(buf, read, false); + } + + if (result == talk_base::SR_EOS) { + parser.Parse(NULL, 0, true); + return builder.CreateElement(); + } + + return NULL; +} + +bool ParseInt64Attr(const buzz::XmlElement* element, + const buzz::QName& attribute, int64* result) { + if (!element->HasAttr(attribute)) + return false; + std::string text = element->Attr(attribute); + char* error = NULL; +#ifdef POSIX + *result = atoll(text.c_str()); +#else + *result = _strtoi64(text.c_str(), &error, 10); +#endif + return text.c_str() != error; +} + +bool ParseIntAttr(const buzz::XmlElement* element, const buzz::QName& attribute, + int* result) { + if (!element->HasAttr(attribute)) + return false; + std::string text = element->Attr(attribute); + char* error = NULL; + *result = static_cast<int>(strtol(text.c_str(), &error, 10)); + return text.c_str() != error; +} + +bool ParseBoolAttr(const buzz::XmlElement* element, + const buzz::QName& attribute, bool* result) { + int int_value = 0; + if (!ParseIntAttr(element, attribute, &int_value)) + return false; + *result = int_value != 0; + return true; +} + +bool ParseStringAttr(const buzz::XmlElement* element, + const buzz::QName& attribute, std::string* result) { + if (!element->HasAttr(attribute)) + return false; + *result = element->Attr(attribute); + return true; +} + +void WriteXmlToStream(talk_base::StreamInterface* stream, + const buzz::XmlElement* xml) { + // Save it all to a string and then write that string out to disk. + // + // This is probably really inefficient in multiple ways. We probably + // have an entire string copy of the XML in memory twice -- once in the + // stream and once in the string. There is probably a way to get the data + // directly out of the stream but I don't have the time to decode the stream + // classes right now. + std::ostringstream s; + buzz::XmlPrinter::PrintXml(&s, xml); + std::string output_string = s.str(); + stream->WriteAll(output_string.data(), output_string.length(), NULL, NULL); +} + +bool SetInt64Attr(buzz::XmlElement* element, const buzz::QName& attribute, + int64 value) { + if (!element->HasAttr(attribute)) + return false; + element->AddAttr(attribute, Int64ToString(value).c_str()); + return true; +} + +bool SetIntAttr(buzz::XmlElement* element, const buzz::QName& attribute, + int value) { + if (!element->HasAttr(attribute)) + return false; + element->AddAttr(attribute, IntToString(value).c_str()); + return true; +} + +bool SetBoolAttr(buzz::XmlElement* element, const buzz::QName& attribute, + bool value) { + int int_value = 0; + if (value) { + int_value = 1; + } + return SetIntAttr(element, attribute, int_value); +} + +bool SetStringAttr(buzz::XmlElement* element, const buzz::QName& attribute, + const std::string& value) { + if (!element->HasAttr(attribute)) + return false; + element->AddAttr(attribute, value); + return true; +} + + +// XmlStream + +XmlStream::XmlStream() + : state_(talk_base::SS_OPEN), + builder_(new buzz::XmlBuilder()), + parser_(new buzz::XmlParser(builder_.get())) { +} + +XmlStream::~XmlStream() { +} + +buzz::XmlElement* XmlStream::CreateElement() { + if (talk_base::SS_OPEN == state_) { + Close(); + } + return builder_->CreateElement(); +} + +talk_base::StreamResult XmlStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (error) + *error = -1; + return talk_base::SR_ERROR; +} + +talk_base::StreamResult XmlStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (talk_base::SS_OPEN != state_) { + if (error) + *error = -1; + return talk_base::SR_ERROR; + } + parser_->Parse(static_cast<const char*>(data), data_len, false); + if (written) + *written = data_len; + return talk_base::SR_SUCCESS; +} + +void XmlStream::Close() { + if (talk_base::SS_OPEN != state_) + return; + + parser_->Parse(NULL, 0, true); + state_ = talk_base::SS_CLOSED; +} + +} // namespace buzz diff --git a/chrome/browser/sync/notifier/communicator/xml_parse_helpers.h b/chrome/browser/sync/notifier/communicator/xml_parse_helpers.h new file mode 100644 index 0000000..0c918bd --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xml_parse_helpers.h @@ -0,0 +1,75 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XML_PARSE_HELPERS_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XML_PARSE_HELPERS_H_ + +#include <string> + +#include "talk/base/basictypes.h" +#include "talk/base/scoped_ptr.h" +#include "talk/base/stream.h" + +namespace buzz { +class XmlBuilder; +class XmlElement; +class XmlParser; +class QName; +} + +namespace notifier { +buzz::XmlElement* ReadXmlFromStream(talk_base::StreamInterface* stream); +bool ParseInt64Attr(const buzz::XmlElement* element, + const buzz::QName& attribute, int64* result); +bool ParseIntAttr(const buzz::XmlElement* element, + const buzz::QName& attribute, int* result); +bool ParseBoolAttr(const buzz::XmlElement* element, + const buzz::QName& attribute, bool* result); +bool ParseStringAttr(const buzz::XmlElement* element, + const buzz::QName& attribute, std::string* result); + +void WriteXmlToStream(talk_base::StreamInterface* stream, + const buzz::XmlElement* xml); +bool SetInt64Attr(buzz::XmlElement* element, const buzz::QName& attribute, + int64 result); +bool SetIntAttr(buzz::XmlElement* element, const buzz::QName& attribute, + int result); +bool SetBoolAttr(buzz::XmlElement* element, const buzz::QName& attribute, + bool result); +bool SetStringAttr(buzz::XmlElement* element, const buzz::QName& attribute, + const std::string& result); + +template<class T> +void SetAttr(buzz::XmlElement* xml, const buzz::QName& name, const T& data); + +/////////////////////////////////////////////////////////////////////////////// +// XmlStream +/////////////////////////////////////////////////////////////////////////////// + +class XmlStream : public talk_base::StreamInterface { + public: + XmlStream(); + virtual ~XmlStream(); + + buzz::XmlElement* CreateElement(); + + virtual talk_base::StreamState GetState() const { return state_; } + + virtual talk_base::StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual talk_base::StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + + private: + talk_base::StreamState state_; + scoped_ptr<buzz::XmlBuilder> builder_; + scoped_ptr<buzz::XmlParser> parser_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace buzz + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XML_PARSE_HELPERS_H_ diff --git a/chrome/browser/sync/notifier/communicator/xmpp_connection_generator.cc b/chrome/browser/sync/notifier/communicator/xmpp_connection_generator.cc new file mode 100644 index 0000000..f3a5f4c --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xmpp_connection_generator.cc @@ -0,0 +1,210 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// XmppConnectionGenerator does the following algorithm: +// proxy = ResolveProxyInformation(connection_options) +// for server in server_list +// get dns_addresses for server +// connection_list = (dns_addresses X connection methods X proxy).shuffle() +// for connection in connection_list +// yield connection + +#include "chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h" + +#include <vector> + +#include "chrome/browser/sync/notifier/base/async_dns_lookup.h" +#include "chrome/browser/sync/notifier/base/signal_thread_task.h" +#include "chrome/browser/sync/notifier/communicator/connection_options.h" +#include "chrome/browser/sync/notifier/communicator/connection_settings.h" +#include "chrome/browser/sync/notifier/communicator/product_info.h" +#include "talk/base/autodetectproxy.h" +#include "talk/base/httpcommon.h" +#include "talk/base/logging.h" +#include "talk/base/task.h" +#include "talk/base/thread.h" +#include "talk/xmpp/prexmppauth.h" +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/xmpp/xmppengine.h" + +namespace notifier { + +XmppConnectionGenerator::XmppConnectionGenerator( + talk_base::Task* parent, + const ConnectionOptions* options, + bool proxy_only, + const ServerInformation* server_list, + int server_count) + : settings_list_(new ConnectionSettingsList()), + settings_index_(0), + server_list_(new ServerInformation[server_count]), + server_count_(server_count), + server_index_(-1), + proxy_only_(proxy_only), + successfully_resolved_dns_(false), + first_dns_error_(0), + options_(options), + parent_(parent) { + assert(parent); + assert(options); + assert(server_count_ > 0); + for (int i = 0; i < server_count_; ++i) { + server_list_[i] = server_list[i]; + } +} + +XmppConnectionGenerator::~XmppConnectionGenerator() { + LOG(LS_VERBOSE) << "XmppConnectionGenerator::~XmppConnectionGenerator"; +} + +const talk_base::ProxyInfo& XmppConnectionGenerator::proxy() const { + assert(settings_list_.get()); + if (settings_index_ >= settings_list_->GetCount()) { + return settings_list_->proxy(); + } + + ConnectionSettings* settings = settings_list_->GetSettings(settings_index_); + return settings->proxy(); +} + +// Starts resolving proxy information +void XmppConnectionGenerator::StartGenerating() { + LOG(LS_VERBOSE) << "XmppConnectionGenerator::StartGenerating"; + + talk_base::AutoDetectProxy* proxy_detect = + new talk_base::AutoDetectProxy(GetUserAgentString()); + + if (options_->autodetect_proxy()) { + // Pretend the xmpp server is https, when detecting whether a proxy is + // required to connect. + talk_base::Url<char> host_url("/", + server_list_[0].server.IPAsString().c_str(), + server_list_[0].server.port()); + host_url.set_secure(true); + proxy_detect->set_server_url(host_url.url()); + } else if (options_->proxy_host().length()) { + talk_base::SocketAddress proxy(options_->proxy_host(), + options_->proxy_port()); + proxy_detect->set_proxy(proxy); + } + proxy_detect->set_auth_info(options_->use_proxy_auth(), + options_->auth_user(), + talk_base::CryptString(options_->auth_pass())); + + SignalThreadTask<talk_base::AutoDetectProxy>* wrapper_task = + new SignalThreadTask<talk_base::AutoDetectProxy>(parent_, &proxy_detect); + wrapper_task->SignalWorkDone.connect( + this, + &XmppConnectionGenerator::OnProxyDetect); + wrapper_task->Start(); +} + +void XmppConnectionGenerator::OnProxyDetect( + talk_base::AutoDetectProxy* proxy_detect) { + LOG(LS_VERBOSE) << "XmppConnectionGenerator::OnProxyDetect"; + + ASSERT(settings_list_.get()); + ASSERT(proxy_detect); + settings_list_->SetProxy(proxy_detect->proxy()); + + // Start iterating through the connections (which + // are generated on demand). + UseNextConnection(); +} + +void XmppConnectionGenerator::UseNextConnection() { + // Trying to connect + + // Iterate to the next possible connection + settings_index_++; + if (settings_index_ < settings_list_->GetCount()) { + // We have more connection settings in the settings_list_ to try, kick + // off the next one. + UseCurrentConnection(); + return; + } + + // Iterate to the next possible server + server_index_++; + if (server_index_ < server_count_) { + AsyncDNSLookup* dns_lookup = new AsyncDNSLookup( + server_list_[server_index_].server); + SignalThreadTask<AsyncDNSLookup>* wrapper_task = + new SignalThreadTask<AsyncDNSLookup>(parent_, &dns_lookup); + wrapper_task->SignalWorkDone.connect( + this, + &XmppConnectionGenerator::OnServerDNSResolved); + wrapper_task->Start(); + return; + } + + // All out of possibilities + HandleExhaustedConnections(); +} + +void XmppConnectionGenerator::OnServerDNSResolved( + AsyncDNSLookup* dns_lookup) { + LOG(LS_VERBOSE) << "XmppConnectionGenerator::OnServerDNSResolved"; + + // Print logging info + LOG(LS_VERBOSE) << " server: " << + server_list_[server_index_].server.ToString() << + " error: " << dns_lookup->error(); + if (first_dns_error_ == 0 && dns_lookup->error() != 0) { + first_dns_error_ = dns_lookup->error(); + } + + if (!successfully_resolved_dns_ && dns_lookup->ip_list().size() > 0) { + successfully_resolved_dns_ = true; + } + + for (int i = 0; i < static_cast<int>(dns_lookup->ip_list().size()); ++i) { + LOG(LS_VERBOSE) + << " ip " << i << " : " + << talk_base::SocketAddress::IPToString(dns_lookup->ip_list()[i]); + } + + // Build the ip list + assert(settings_list_.get()); + settings_index_ = -1; + settings_list_->ClearPermutations(); + settings_list_->AddPermutations( + server_list_[server_index_].server.IPAsString(), + dns_lookup->ip_list(), + server_list_[server_index_].server.port(), + server_list_[server_index_].special_port_magic, + proxy_only_); + + UseNextConnection(); +} + +static const char * const PROTO_NAMES[cricket::PROTO_LAST+1] = { + "udp", "tcp", "ssltcp" +}; + +static const char* ProtocolToString(cricket::ProtocolType proto) { + return PROTO_NAMES[proto]; +} + +void XmppConnectionGenerator::UseCurrentConnection() { + LOG(LS_VERBOSE) << "XmppConnectionGenerator::UseCurrentConnection"; + + ConnectionSettings* settings = settings_list_->GetSettings(settings_index_); + LOG(LS_INFO) << "*** Attempting " + << ProtocolToString(settings->protocol()) << " connection to " + << settings->server().IPAsString() << ":" + << settings->server().port() + << " (via " << ProxyToString(settings->proxy().type) + << " proxy @ " << settings->proxy().address.IPAsString() << ":" + << settings->proxy().address.port() << ")"; + + SignalNewSettings(*settings); +} + +void XmppConnectionGenerator::HandleExhaustedConnections() { + LOG_F(LS_VERBOSE) << "(" << buzz::XmppEngine::ERROR_SOCKET + << ", " << first_dns_error_ << ")"; + SignalExhaustedSettings(successfully_resolved_dns_, first_dns_error_); +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h b/chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h new file mode 100644 index 0000000..03a7a8f --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xmpp_connection_generator.h @@ -0,0 +1,81 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_CONNECTION_GENERATOR_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_CONNECTION_GENERATOR_H_ +#include <vector> + +#include "talk/base/scoped_ptr.h" +#include "talk/base/sigslot.h" +#include "talk/base/socketaddress.h" + +namespace talk_base { +class AutoDetectProxy; +struct ProxyInfo; +class SignalThread; +class Task; +} + +namespace notifier { +class AsyncDNSLookup; +class ConnectionOptions; +class ConnectionSettings; +class ConnectionSettingsList; + +struct ServerInformation { + talk_base::SocketAddress server; + bool special_port_magic; +}; + +// Resolves dns names and iterates through the various ip address +// and transport combinations. +class XmppConnectionGenerator : public sigslot::has_slots<> { + public: + // parent is the parent for any tasks needed during this operation + // proxy_only indicates if true connections are only attempted using the proxy + // server_list is the list of connections to attempt in priority order + // server_count is the number of items in the server list + XmppConnectionGenerator(talk_base::Task* parent, + const ConnectionOptions* options, + bool proxy_only, + const ServerInformation* server_list, + int server_count); + ~XmppConnectionGenerator(); + + // Only call this once. Create a new XmppConnectionGenerator and + // delete the current one if the process needs to start again. + void StartGenerating(); + + void UseNextConnection(); + void UseCurrentConnection(); + + const talk_base::ProxyInfo& proxy() const; + + sigslot::signal1<const ConnectionSettings&> SignalNewSettings; + + // SignalExhaustedSettings(bool successfully_resolved_dns, + // int first_dns_error); + sigslot::signal2<bool, int> SignalExhaustedSettings; + + private: + void OnProxyDetect(talk_base::AutoDetectProxy* proxy_detect); + void OnServerDNSResolved(AsyncDNSLookup* dns_lookup); + void HandleExhaustedConnections(); + + talk_base::scoped_ptr<ConnectionSettingsList> settings_list_; + int settings_index_; // the setting that is currently being used + talk_base::scoped_array<ServerInformation> server_list_; + int server_count_; + int server_index_; // the server that is current being used + bool proxy_only_; + bool successfully_resolved_dns_; + int first_dns_error_; + const ConnectionOptions* options_; + + talk_base::Task* parent_; + DISALLOW_COPY_AND_ASSIGN(XmppConnectionGenerator); +}; +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_CONNECTION_GENERATOR_H_ diff --git a/chrome/browser/sync/notifier/communicator/xmpp_log.cc b/chrome/browser/sync/notifier/communicator/xmpp_log.cc new file mode 100644 index 0000000..30b0036 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xmpp_log.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if LOGGING + +#include "chrome/browser/sync/notifier/communicator/xmpp_log.h" + +#include <iomanip> +#include <string> +#include <vector> + +#include "chrome/browser/sync/notifier/base/time.h" +#include "talk/base/common.h" +#include "talk/base/logging.h" + +namespace notifier { + +static bool IsAuthTag(const char* str, size_t len) { + // Beware that str is not NULL terminated + if (str[0] == '<' && + str[1] == 'a' && + str[2] == 'u' && + str[3] == 't' && + str[4] == 'h' && + str[5] <= ' ') { + std::string tag(str, len); + if (tag.find("mechanism") != std::string::npos) + return true; + } + return false; +} + +static bool IsChatText(const char* str, size_t len) { + // Beware that str is not NULL terminated + if (str[0] == '<' && + str[1] == 'm' && + str[2] == 'e' && + str[3] == 's' && + str[4] == 's' && + str[5] == 'a' && + str[6] == 'g' && + str[7] == 'e' && + str[8] <= ' ') { + std::string tag(str, len); + if (tag.find("chat") != std::string::npos) + return true; + } + return false; +} + +void XmppLog::XmppPrint(bool output) { + std::vector<char>* buffer = output ? + &xmpp_output_buffer_ : &xmpp_input_buffer_; + const bool log_chat = LOG_CHECK_LEVEL(LS_SENSITIVE); + if (buffer->size()) { + char* time_string = GetLocalTimeAsString(); + LOG(INFO) << (output ? "SEND >>>>>>>>>>>>>>>>>>>>>>>>>" : + "RECV <<<<<<<<<<<<<<<<<<<<<<<<<") + << " : " << time_string; + + int start = 0; + int nest = 3; + for (int i = 0; i < static_cast<int>(buffer->size()); ++i) { + if ((*buffer)[i] == '>') { + bool indent; + if ((i > 0) && ((*buffer)[i - 1] == '/')) { + indent = false; + } else if ((start + 1 < static_cast<int>(buffer->size())) && + ((*buffer)[start + 1] == '/')) { + indent = false; + nest -= 2; + } else { + indent = true; + } + + // Output a tag + LOG(INFO) << std::setw(nest) << " " + << std::string(&((*buffer)[start]), i + 1 - start); + + if (indent) + nest += 2; + + // Note if it's a PLAIN auth tag + if (IsAuthTag(&((*buffer)[start]), i + 1 - start)) { + censor_password_ = true; + } else if (!log_chat && IsChatText(&((*buffer)[start]), + i + 1 - start)) { + censor_password_ = true; + } + + start = i + 1; + } + + if ((*buffer)[i] == '<' && start < i) { + if (censor_password_) { + LOG(INFO) << std::setw(nest) << " " << "## TEXT REMOVED ##"; + censor_password_ = false; + } else { + LOG(INFO) << std::setw(nest) << " " + << std::string(&((*buffer)[start]), i - start); + } + start = i; + } + } + buffer->erase(buffer->begin(), buffer->begin() + start); + } +} +} // namespace notifier + +#endif // if LOGGING diff --git a/chrome/browser/sync/notifier/communicator/xmpp_log.h b/chrome/browser/sync/notifier/communicator/xmpp_log.h new file mode 100644 index 0000000..a6d12bd --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xmpp_log.h @@ -0,0 +1,45 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_LOG_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_LOG_H_ + +#if LOGGING + +#include <vector> + +#include "talk/base/basictypes.h" +#include "talk/base/sigslot.h" + +namespace notifier { + +// Log the xmpp input and output. +class XmppLog : public sigslot::has_slots<> { + public: + XmppLog() : censor_password_(false) { + } + + void Input(const char* data, int len) { + xmpp_input_buffer_.insert(xmpp_input_buffer_.end(), data, data + len); + XmppPrint(false); + } + + void Output(const char* data, int len) { + xmpp_output_buffer_.insert(xmpp_output_buffer_.end(), data, data + len); + XmppPrint(true); + } + + private: + void XmppPrint(bool output); + + std::vector<char> xmpp_input_buffer_; + std::vector<char> xmpp_output_buffer_; + bool censor_password_; + DISALLOW_COPY_AND_ASSIGN(XmppLog); +}; +} // namespace notifier + +#endif // if LOGGING + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_LOG_H_ diff --git a/chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.cc b/chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.cc new file mode 100644 index 0000000..9bd65db --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.cc @@ -0,0 +1,437 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.h" + +#include <iomanip> +#include <string> + +#include "chrome/browser/sync/notifier/communicator/product_info.h" +#include "talk/base/byteorder.h" +#include "talk/base/common.h" +#include "talk/base/firewallsocketserver.h" +#include "talk/base/logging.h" +#include "talk/base/socketadapters.h" +#include "talk/base/ssladapter.h" +#include "talk/xmpp/xmppengine.h" + +namespace notifier { + +XmppSocketAdapter::XmppSocketAdapter(const buzz::XmppClientSettings& xcs, + bool allow_unverified_certs) + : state_(STATE_CLOSED), + error_(ERROR_NONE), + wsa_error_(0), + socket_(NULL), + protocol_(xcs.protocol()), + firewall_(false), + write_buffer_(NULL), + write_buffer_length_(0), + write_buffer_capacity_(0), + allow_unverified_certs_(allow_unverified_certs) { + proxy_.type = xcs.proxy(); + proxy_.address.SetIP(xcs.proxy_host(), false); + proxy_.address.SetPort(xcs.proxy_port()); + proxy_.username = xcs.proxy_user(); + proxy_.password = xcs.proxy_pass(); +} + +XmppSocketAdapter::~XmppSocketAdapter() { + FreeState(); + + // Clean up any previous socket - cannot delete socket on close because + // close happens during the child socket's stack callback. + if (socket_) { + delete socket_; + socket_ = NULL; + } +} + +bool XmppSocketAdapter::FreeState() { + int code = 0; + + // Clean up the socket + if (socket_ && !(state_ == STATE_CLOSED || state_ == STATE_CLOSING)) { + code = socket_->Close(); + } + + delete[] write_buffer_; + write_buffer_ = NULL; + write_buffer_length_ = 0; + write_buffer_capacity_ = 0; + + if (code) { + SetWSAError(code); + return false; + } + return true; +} + +bool XmppSocketAdapter::Connect(const talk_base::SocketAddress& addr) { + if (state_ != STATE_CLOSED) { + SetError(ERROR_WRONGSTATE); + return false; + } + + LOG(LS_INFO) << "XmppSocketAdapter::Connect(" << addr.ToString() << ")"; + + // Clean up any previous socket - cannot delete socket on close because + // close happens during the child socket's stack callback. + if (socket_) { + delete socket_; + socket_ = NULL; + } + + talk_base::AsyncSocket* socket = + talk_base::Thread::Current()->socketserver() + ->CreateAsyncSocket(SOCK_STREAM); + if (!socket) { + SetWSAError(WSA_NOT_ENOUGH_MEMORY); + return false; + } + + if (firewall_) { + // TODO(sync): Change this to make WSAAsyncSockets support current thread + // socket server + talk_base::FirewallSocketServer* fw = + static_cast<talk_base::FirewallSocketServer*>( + talk_base::Thread::Current()->socketserver()); + socket = fw->WrapSocket(socket, SOCK_STREAM); + } + + if (proxy_.type) { + talk_base::AsyncSocket* proxy_socket = 0; + if (proxy_.type == talk_base::PROXY_SOCKS5) { + proxy_socket = new talk_base::AsyncSocksProxySocket( + socket, proxy_.address, proxy_.username, proxy_.password); + } else { + // Note: we are trying unknown proxies as HTTPS currently + proxy_socket = new talk_base::AsyncHttpsProxySocket(socket, + GetUserAgentString(), proxy_.address, + proxy_.username, proxy_.password); + } + if (!proxy_socket) { + SetWSAError(WSA_NOT_ENOUGH_MEMORY); + delete socket; + return false; + } + socket = proxy_socket; // for our purposes the proxy is now the socket + } + +// #if defined(PRODUCTION) + if (protocol_ == cricket::PROTO_SSLTCP) { + talk_base::AsyncSocket *fake_ssl_socket = + new talk_base::AsyncSSLSocket(socket); + if (!fake_ssl_socket) { + SetWSAError(WSA_NOT_ENOUGH_MEMORY); + delete socket; + return false; + } + socket = fake_ssl_socket; // for our purposes the SSL socket is the socket + } +// #endif // PRODUCTION + +#if defined(FEATURE_ENABLE_SSL) + talk_base::SSLAdapter* ssl = talk_base::SSLAdapter::Create(socket); + socket = ssl; +#endif + +// #if !defined(PRODUCTION) +// if (protocol_ == cricket::PROTO_SSLTCP) { +// ssl->set_ignore_bad_cert(true); +// ssl->StartSSL(addr.hostname().c_str(), true); +// } +// #endif // PRODUCTION + + socket->SignalReadEvent.connect(this, &XmppSocketAdapter::OnReadEvent); + socket->SignalWriteEvent.connect(this, &XmppSocketAdapter::OnWriteEvent); + socket->SignalConnectEvent.connect(this, &XmppSocketAdapter::OnConnectEvent); + socket->SignalCloseEvent.connect(this, &XmppSocketAdapter::OnCloseEvent); + + // The linux implementation of socket::Connect + // returns an error when the connect didn't complete + // yet. This can be distinguished from a failure + // because socket::IsBlocking is true. Perhaps, + // the linux implementation should be made to + // behave like the windows version which doesn't do this, + // but it seems to be a pattern with these methods + // that they return an error if the operation didn't + // complete in a sync fashion and one has to check IsBlocking + // to tell if was a "real" error. + if (socket->Connect(addr) == SOCKET_ERROR && !socket->IsBlocking()) { + SetWSAError(socket->GetError()); + delete socket; + return false; + } + + socket_ = socket; + state_ = STATE_CONNECTING; + return true; +} + +bool XmppSocketAdapter::Read(char* data, size_t len, size_t* len_read) { + if (len_read) + *len_read = 0; + + if (state_ <= STATE_CLOSING) { + SetError(ERROR_WRONGSTATE); + return false; + } + + ASSERT(socket_ != NULL); + + if (IsOpen()) { + int result = socket_->Recv(data, len); + if (result < 0) { + if (!socket_->IsBlocking()) { + SetWSAError(socket_->GetError()); + return false; + } + + result = 0; + } + + if (len_read) + *len_read = result; + } + + return true; +} + +bool XmppSocketAdapter::Write(const char* data, size_t len) { + if (state_ <= STATE_CLOSING) { + // There may be data in a buffer that gets lost. Too bad! + SetError(ERROR_WRONGSTATE); + return false; + } + + ASSERT(socket_ != NULL); + + size_t sent = 0; + + // try an immediate write when there is no buffer + // and we aren't in SSL mode or opening the connection + if (write_buffer_length_ == 0 && IsOpen()) { + int result = socket_->Send(data, len); + if (result < 0) { + if (!socket_->IsBlocking()) { + SetWSAError(socket_->GetError()); + return false; + } + result = 0; + } + + sent = static_cast<size_t>(result); + } + + // Buffer what we didn't send + if (sent < len) { + QueueWriteData(data + sent, len - sent); + } + + // Service the socket right away to push the written data out in SSL mode + return HandleWritable(); +} + +bool XmppSocketAdapter::Close() { + if (state_ == STATE_CLOSING) { + return false; // avoid recursion, but not unexpected + } + if (state_ == STATE_CLOSED) { + // in theory should not be trying to re-InternalClose. + SetError(ERROR_WRONGSTATE); + return false; + } + + // todo: deal with flushing close + // (flush, don't do reads, clean ssl) + + // If we've gotten to the point where we really do have a socket underneath + // then close it. It should call us back to tell us it is closed, and + // NotifyClose will be called. We indicate "closing" state so that we + // do not recusively try to keep closing the socket. + if (socket_) { + state_ = STATE_CLOSING; + socket_->Close(); + } + + // If we didn't get the callback, then we better make sure we signal + // closed. + if (state_ != STATE_CLOSED) { + // The socket was closed manually, not directly due to error. + if (error_ != ERROR_NONE) { + LOG(LS_INFO) << "XmppSocketAdapter::Close - previous Error: " << error_ + << " WSAError: " << wsa_error_; + error_ = ERROR_NONE; + wsa_error_ = 0; + } + NotifyClose(); + } + return true; +} + +void XmppSocketAdapter::NotifyClose() { + if (state_ == STATE_CLOSED) { + SetError(ERROR_WRONGSTATE); + } else { + LOG(LS_INFO) << "XmppSocketAdapter::NotifyClose - Error: " << error_ + << " WSAError: " << wsa_error_; + state_ = STATE_CLOSED; + SignalClosed(); + FreeState(); + } +} + +void XmppSocketAdapter::OnConnectEvent(talk_base::AsyncSocket *socket) { + if (state_ == STATE_CONNECTING) { + state_ = STATE_OPEN; + LOG(LS_INFO) << "XmppSocketAdapter::OnConnectEvent - STATE_OPEN"; + SignalConnected(); +#if defined(FEATURE_ENABLE_SSL) + } else if (state_ == STATE_TLS_CONNECTING) { + state_ = STATE_TLS_OPEN; + LOG(LS_INFO) << "XmppSocketAdapter::OnConnectEvent - STATE_TLS_OPEN"; + SignalSSLConnected(); + if (write_buffer_length_ > 0) { + HandleWritable(); + } +#endif // defined(FEATURE_ENABLE_SSL) + } else { + LOG(LS_INFO) << "XmppSocketAdapter::OnConnectEvent - state is " << state_; + ASSERT(false); + } +} + +void XmppSocketAdapter::OnReadEvent(talk_base::AsyncSocket *socket) { + HandleReadable(); +} + +void XmppSocketAdapter::OnWriteEvent(talk_base::AsyncSocket *socket) { + HandleWritable(); +} + +void XmppSocketAdapter::OnCloseEvent(talk_base::AsyncSocket *socket, + int error) { + LOG(LS_INFO) << "XmppSocketAdapter::OnCloseEvent(" << error << ")"; + SetWSAError(error); + if (error == SOCKET_EACCES) { + SignalAuthenticationError(); // proxy needs authentication + } + NotifyClose(); +} + +#if defined(FEATURE_ENABLE_SSL) +bool XmppSocketAdapter::StartTls(const std::string& verify_host_name) { + if (state_ != STATE_OPEN) { + SetError(ERROR_WRONGSTATE); + return false; + } + + state_ = STATE_TLS_CONNECTING; + + ASSERT(write_buffer_length_ == 0); + + talk_base::SSLAdapter* ssl_adapter = + static_cast<talk_base::SSLAdapter*>(socket_); + + if (allow_unverified_certs_) { + ssl_adapter->set_ignore_bad_cert(true); + } + + if (ssl_adapter->StartSSL(verify_host_name.c_str(), false) != 0) { + state_ = STATE_OPEN; + SetError(ERROR_SSL); + return false; + } + + return true; +} +#endif // defined(FEATURE_ENABLE_SSL) + +void XmppSocketAdapter::QueueWriteData(const char* data, size_t len) { + // expand buffer if needed + if (write_buffer_length_ + len > write_buffer_capacity_) { + size_t new_capacity = 1024; + while (new_capacity < write_buffer_length_ + len) { + new_capacity = new_capacity * 2; + } + char* new_buffer = new char[new_capacity]; + ASSERT(write_buffer_length_ <= 64000); + memcpy(new_buffer, write_buffer_, write_buffer_length_); + delete[] write_buffer_; + write_buffer_ = new_buffer; + write_buffer_capacity_ = new_capacity; + } + + // copy data into the end of buffer + memcpy(write_buffer_ + write_buffer_length_, data, len); + write_buffer_length_ += len; +} + +void XmppSocketAdapter::FlushWriteQueue(Error* error, int* wsa_error) { + ASSERT(error && wsa_error); + + size_t flushed = 0; + while (flushed < write_buffer_length_) { + int sent = socket_->Send(write_buffer_ + flushed, + static_cast<int>(write_buffer_length_ - flushed)); + if (sent < 0) { + if (!socket_->IsBlocking()) { + *error = ERROR_WINSOCK; + *wsa_error = socket_->GetError(); + } + break; + } + flushed += static_cast<size_t>(sent); + } + + // remove flushed memory + write_buffer_length_ -= flushed; + memmove(write_buffer_, write_buffer_ + flushed, write_buffer_length_); + + // when everything is flushed, deallocate the buffer if it's gotten big + if (write_buffer_length_ == 0) { + if (write_buffer_capacity_ > 8192) { + delete[] write_buffer_; + write_buffer_ = NULL; + write_buffer_capacity_ = 0; + } + } +} + +void XmppSocketAdapter::SetError(Error error) { + if (error_ == ERROR_NONE) { + error_ = error; + } +} + +void XmppSocketAdapter::SetWSAError(int error) { + if (error_ == ERROR_NONE && error != 0) { + error_ = ERROR_WINSOCK; + wsa_error_ = error; + } +} + +bool XmppSocketAdapter::HandleReadable() { + if (!IsOpen()) + return false; + + SignalRead(); + return true; +} + +bool XmppSocketAdapter::HandleWritable() { + if (!IsOpen()) + return false; + + Error error = ERROR_NONE; + int wsa_error = 0; + FlushWriteQueue(&error, &wsa_error); + if (error != ERROR_NONE) { + Close(); + return false; + } + return true; +} +} // namespace notifier diff --git a/chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.h b/chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.h new file mode 100644 index 0000000..7e42988 --- /dev/null +++ b/chrome/browser/sync/notifier/communicator/xmpp_socket_adapter.h @@ -0,0 +1,85 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_SOCKET_ADAPTER_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_SOCKET_ADAPTER_H_ +#include <string> + +#include "talk/base/asyncsocket.h" +#include "talk/xmpp/asyncsocket.h" +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/xmpp/xmppengine.h" + +#ifndef _WIN32 +// Additional errors used by us from Win32 headers +#define SEC_E_CERT_EXPIRED static_cast<int>(0x80090328L) +#define WSA_NOT_ENOUGH_MEMORY ENOMEM +#endif + +namespace notifier { + +class XmppSocketAdapter : public buzz::AsyncSocket, + public sigslot::has_slots<> { + public: + XmppSocketAdapter(const buzz::XmppClientSettings& xcs, + bool allow_unverified_certs); + virtual ~XmppSocketAdapter(); + + virtual State state() { return state_; } + virtual Error error() { return error_; } + virtual int GetError() { return wsa_error_; } + + void set_firewall(bool firewall) { firewall_ = firewall; } + + virtual bool Connect(const talk_base::SocketAddress& addr); + virtual bool Read(char* data, size_t len, size_t* len_read); + virtual bool Write(const char* data, size_t len); + virtual bool Close(); + +#if defined(FEATURE_ENABLE_SSL) + bool StartTls(const std::string& domainname); + bool IsOpen() const { return state_ == STATE_OPEN + || state_ == STATE_TLS_OPEN; } +#else + bool IsOpen() const { return state_ == STATE_OPEN; } +#endif + + sigslot::signal0<> SignalAuthenticationError; + + private: + // return false if the socket is closed + bool HandleReadable(); + bool HandleWritable(); + + State state_; + Error error_; + int wsa_error_; + + talk_base::AsyncSocket* socket_; + cricket::ProtocolType protocol_; + talk_base::ProxyInfo proxy_; + bool firewall_; + char* write_buffer_; + size_t write_buffer_length_; + size_t write_buffer_capacity_; + bool allow_unverified_certs_; + + bool FreeState(); + void NotifyClose(); + + void OnReadEvent(talk_base::AsyncSocket* socket); + void OnWriteEvent(talk_base::AsyncSocket* socket); + void OnConnectEvent(talk_base::AsyncSocket* socket); + void OnCloseEvent(talk_base::AsyncSocket* socket, int error); + + void QueueWriteData(const char* data, size_t len); + void FlushWriteQueue(Error* error, int* wsa_error); + + void SetError(Error error); + void SetWSAError(int error); + DISALLOW_COPY_AND_ASSIGN(XmppSocketAdapter); +}; +} // namespace notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_COMMUNICATOR_XMPP_SOCKET_ADAPTER_H_ 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 |