summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync/engine/net
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/sync/engine/net')
-rw-r--r--chrome/browser/sync/engine/net/gaia_authenticator.cc483
-rw-r--r--chrome/browser/sync/engine/net/gaia_authenticator.h304
-rw-r--r--chrome/browser/sync/engine/net/gaia_authenticator_unittest.cc42
-rw-r--r--chrome/browser/sync/engine/net/http_return.h16
-rw-r--r--chrome/browser/sync/engine/net/openssl_init.cc129
-rw-r--r--chrome/browser/sync/engine/net/openssl_init.h20
-rw-r--r--chrome/browser/sync/engine/net/server_connection_manager.cc375
-rw-r--r--chrome/browser/sync/engine/net/server_connection_manager.h345
-rw-r--r--chrome/browser/sync/engine/net/syncapi_server_connection_manager.cc77
-rw-r--r--chrome/browser/sync/engine/net/syncapi_server_connection_manager.h75
-rw-r--r--chrome/browser/sync/engine/net/url_translator.cc50
-rw-r--r--chrome/browser/sync/engine/net/url_translator.h27
12 files changed, 1943 insertions, 0 deletions
diff --git a/chrome/browser/sync/engine/net/gaia_authenticator.cc b/chrome/browser/sync/engine/net/gaia_authenticator.cc
new file mode 100644
index 0000000..7276cec
--- /dev/null
+++ b/chrome/browser/sync/engine/net/gaia_authenticator.cc
@@ -0,0 +1,483 @@
+// 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/engine/net/gaia_authenticator.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/port.h"
+#include "base/string_util.h"
+#include "chrome/browser/sync/engine/all_status.h"
+#include "chrome/browser/sync/engine/net/http_return.h"
+#include "chrome/browser/sync/engine/net/url_translator.h"
+#include "chrome/browser/sync/util/event_sys-inl.h"
+#include "googleurl/src/gurl.h"
+
+using std::pair;
+using std::string;
+using std::vector;
+
+// TODO(timsteele): Integrate the following two functions to string_util.h or
+// somewhere that makes them unit-testable.
+bool SplitStringIntoKeyValues(const string& line,
+ char key_value_delimiter,
+ string* key, vector<string>* values) {
+ key->clear();
+ values->clear();
+
+ // find the key string
+ int end_key_pos = line.find_first_of(key_value_delimiter);
+ if (end_key_pos == string::npos) {
+ DLOG(INFO) << "cannot parse key from line: " << line;
+ return false; // no key
+ }
+ key->assign(line, 0, end_key_pos);
+
+ // find the values string
+ string remains(line, end_key_pos, line.size() - end_key_pos);
+ int begin_values_pos = remains.find_first_not_of(key_value_delimiter);
+ if (begin_values_pos == string::npos) {
+ DLOG(INFO) << "cannot parse value from line: " << line;
+ return false; // no value
+ }
+ string values_string(remains, begin_values_pos,
+ remains.size() - begin_values_pos);
+
+ // construct the values vector
+ values->push_back(values_string);
+ return true;
+}
+
+bool SplitStringIntoKeyValuePairs(const string& line,
+ char key_value_delimiter,
+ char key_value_pair_delimiter,
+ vector<pair<string, string> >* kv_pairs) {
+ kv_pairs->clear();
+
+ vector<string> pairs;
+ SplitString(line, key_value_pair_delimiter, &pairs);
+
+ bool success = true;
+ for (size_t i = 0; i < pairs.size(); ++i) {
+ string key;
+ vector<string> value;
+ if (!SplitStringIntoKeyValues(pairs[i],
+ key_value_delimiter,
+ &key, &value)) {
+ // Don't return here, to allow for keys without associated
+ // values; just record that our split failed.
+ success = false;
+ }
+ DCHECK_LE(value.size(), 1);
+ kv_pairs->push_back(make_pair(key, value.empty()? "" : value[0]));
+ }
+ return success;
+}
+
+namespace browser_sync {
+
+static const char kGaiaV1IssueAuthTokenPath[] = "/accounts/IssueAuthToken";
+
+static const char kGetUserInfoPath[] = "/accounts/GetUserInfo";
+
+// Sole constructor with initializers for all fields.
+GaiaAuthenticator::GaiaAuthenticator(const string& user_agent,
+ const string& service_id,
+ const string& gaia_url)
+ : user_agent_(user_agent),
+ service_id_(service_id),
+ gaia_url_(gaia_url),
+ request_count_(0),
+ early_auth_attempt_count_(0),
+ delay_(0),
+ next_allowed_auth_attempt_time_(0) {
+ GaiaAuthEvent done = { GaiaAuthEvent::GAIA_AUTHENTICATOR_DESTROYED, None,
+ this };
+ channel_ = new Channel(done);
+}
+
+GaiaAuthenticator::~GaiaAuthenticator() {
+ delete channel_;
+}
+
+bool GaiaAuthenticator::LaunchAuthenticate(const AuthParams& params,
+ bool synchronous) {
+ if (synchronous)
+ return AuthenticateImpl(params);
+ AuthParams* copy = new AuthParams;
+ *copy = params;
+ pthread_t thread_id;
+ int result = pthread_create(&thread_id, 0, &GaiaAuthenticator::ThreadMain,
+ copy);
+ if (result)
+ return false;
+ return true;
+}
+
+
+void* GaiaAuthenticator::ThreadMain(void* arg) {
+ NameCurrentThreadForDebugging("SyncEngine_GaiaAuthenticatorThread");
+ AuthParams* const params = reinterpret_cast<AuthParams*>(arg);
+ params->authenticator->AuthenticateImpl(*params);
+ delete params;
+ return 0;
+}
+
+// mutex_ must be entered before calling this function.
+GaiaAuthenticator::AuthParams GaiaAuthenticator::MakeParams(
+ const string& user_name,
+ const string& password,
+ SaveCredentials should_save_credentials,
+ const string& captcha_token,
+ const string& captcha_value,
+ SignIn try_first) {
+ AuthParams params;
+ params.request_id = ++request_count_;
+ params.email = user_name;
+ params.password = password;
+ params.should_save_credentials = should_save_credentials;
+ params.captcha_token = captcha_token;
+ params.captcha_value = captcha_value;
+ params.authenticator = this;
+ params.try_first = try_first;
+ return params;
+}
+
+bool GaiaAuthenticator::Authenticate(const string& user_name,
+ const string& password,
+ SaveCredentials should_save_credentials,
+ bool synchronous,
+ const string& captcha_token,
+ const string& captcha_value,
+ SignIn try_first) {
+ mutex_.Lock();
+ AuthParams const params =
+ MakeParams(user_name, password, should_save_credentials, captcha_token,
+ captcha_value, try_first);
+ mutex_.Unlock();
+ return LaunchAuthenticate(params, synchronous);
+}
+
+bool GaiaAuthenticator::AuthenticateImpl(const AuthParams& params) {
+ AuthResults results;
+ const bool succeeded = AuthenticateImpl(params, &results);
+ mutex_.Lock();
+ if (params.request_id == request_count_) {
+ auth_results_ = results;
+ GaiaAuthEvent event = { succeeded ? GaiaAuthEvent::GAIA_AUTH_SUCCEEDED
+ : GaiaAuthEvent::GAIA_AUTH_FAILED,
+ results.auth_error, this };
+ mutex_.Unlock();
+ channel_->NotifyListeners(event);
+ } else {
+ mutex_.Unlock();
+ }
+ return succeeded;
+}
+
+// This method makes an HTTP request to the Gaia server, and calls other
+// methods to help parse the response. If authentication succeeded, then
+// Gaia-issued cookies are available in the respective variables; if
+// authentication failed, then the exact error is available as an enum. If the
+// client wishes to save the credentials, the last parameter must be true.
+// If a subsequent request is made with fresh credentials, the saved credentials
+// are wiped out; any subsequent request to the zero-parameter overload of this
+// method preserves the saved credentials.
+bool GaiaAuthenticator::AuthenticateImpl(const AuthParams& params,
+ AuthResults* results) {
+ results->credentials_saved = params.should_save_credentials;
+ results->auth_error = ConnectionUnavailable;
+ // Save credentials if so requested.
+ if (params.should_save_credentials != DONT_SAVE_CREDENTIALS) {
+ results->email = params.email.data();
+ results->password = params.password;
+ } else { // Explicitly clear previously-saved credentials.
+ results->email = "";
+ results->password = "";
+ }
+
+ // The aim of this code is to start failing requests if due to a logic error
+ // in the program we're hammering GAIA.
+ time_t now = time(0);
+ if (now > next_allowed_auth_attempt_time_) {
+ next_allowed_auth_attempt_time_ = now + 1;
+ // If we're more than 2 minutes past the allowed time we reset the early
+ // attempt count.
+ if (now - next_allowed_auth_attempt_time_ > 2 * 60) {
+ delay_ = 1;
+ early_auth_attempt_count_ = 0;
+ }
+ } else {
+ ++early_auth_attempt_count_;
+ // Allow 3 attempts, but then limit.
+ if (early_auth_attempt_count_ > 3) {
+ delay_ = AllStatus::GetRecommendedDelaySeconds(delay_);
+ next_allowed_auth_attempt_time_ = now + delay_;
+ return false;
+ }
+ }
+
+ return PerformGaiaRequest(params, results);
+}
+
+bool GaiaAuthenticator::PerformGaiaRequest(const AuthParams& params,
+ AuthResults* results) {
+ GURL gaia_auth_url(gaia_url_);
+
+ string post_body;
+ post_body += "Email=" + CgiEscapeString(params.email);
+ post_body += "&Passwd=" + CgiEscapeString(params.password);
+ post_body += "&source=" + CgiEscapeString(user_agent_);
+ post_body += "&service=" + service_id_;
+ if (!params.captcha_token.empty() && !params.captcha_value.empty()) {
+ post_body += "&logintoken=" + CgiEscapeString(params.captcha_token);
+ post_body += "&logincaptcha=" + CgiEscapeString(params.captcha_value);
+ }
+ post_body += "&PersistentCookie=true";
+ // We set it to GOOGLE (and not HOSTED or HOSTED_OR_GOOGLE) because we only
+ // allow consumer logins.
+ post_body += "&accountType=GOOGLE";
+
+ string message_text;
+ unsigned long server_response_code;
+ if (!Post(gaia_auth_url, post_body, &server_response_code,
+ &message_text)) {
+ results->auth_error = ConnectionUnavailable;
+ return false;
+ }
+
+ // Parse reply in two different ways, depending on if request failed or
+ // succeeded.
+ if (RC_FORBIDDEN == server_response_code) {
+ ExtractAuthErrorFrom(message_text, results);
+ return false;
+ } else if (RC_REQUEST_OK == server_response_code) {
+ ExtractTokensFrom(message_text, results);
+ const bool old_gaia =
+ results->auth_token.empty() && !results->lsid.empty();
+ const bool long_lived_token =
+ params.should_save_credentials == PERSIST_TO_DISK;
+ if ((old_gaia || long_lived_token) &&
+ !IssueAuthToken(results, service_id_, long_lived_token))
+ return false;
+
+ return LookupEmail(results);
+ } else {
+ results->auth_error = Unknown;
+ return false;
+ }
+}
+
+bool GaiaAuthenticator::LookupEmail(AuthResults* results) {
+ // Use the provided Gaia server, but change the path to what V1 expects.
+ GURL url(gaia_url_); // Gaia server
+ GURL::Replacements repl;
+ // Needs to stay in scope till GURL is out of scope
+ string path(kGetUserInfoPath);
+ repl.SetPathStr(path);
+ url = url.ReplaceComponents(repl);
+
+ string post_body;
+ post_body += "LSID=";
+ post_body += CgiEscapeString(results->lsid);
+
+ unsigned long server_response_code;
+ string message_text;
+ if (!Post(url, post_body, &server_response_code, &message_text)) {
+ return false;
+ }
+
+ // Check if we received a valid AuthToken; if not, ignore it.
+ if (RC_FORBIDDEN == server_response_code) {
+ // Server says we're not authenticated.
+ ExtractAuthErrorFrom(message_text, results);
+ return false;
+ } else if (RC_REQUEST_OK == server_response_code) {
+ typedef vector<pair<string, string> > Tokens;
+ Tokens tokens;
+ SplitStringIntoKeyValuePairs(message_text, '=', '\n', &tokens);
+ for (Tokens::iterator i = tokens.begin(); i != tokens.end(); ++i) {
+ if ("accountType" == i->first) {
+ // We never authenticate an email as a hosted account.
+ DCHECK_EQ("GOOGLE", i->second);
+ results->signin = GMAIL_SIGNIN;
+ } else if ("email" == i->first) {
+ results->primary_email = i->second;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+// We need to call this explicitly when we need to obtain a long-lived session
+// token.
+bool GaiaAuthenticator::IssueAuthToken(AuthResults* results,
+ const string& service_id,
+ bool long_lived) {
+ // Use the provided Gaia server, but change the path to what V1 expects.
+ GURL url(gaia_url_); // Gaia server
+ GURL::Replacements repl;
+ // Needs to stay in scope till GURL is out of scope
+ string path(kGaiaV1IssueAuthTokenPath);
+ repl.SetPathStr(path);
+ url = url.ReplaceComponents(repl);
+
+ string post_body;
+ post_body += "LSID=";
+ post_body += CgiEscapeString(results->lsid);
+ post_body += "&service=" + service_id;
+ if (long_lived) {
+ post_body += "&Session=true";
+ }
+
+ unsigned long server_response_code;
+ string message_text;
+ if (!Post(url, post_body,
+ &server_response_code, &message_text)) {
+ return false;
+ }
+
+ // Check if we received a valid AuthToken; if not, ignore it.
+ if (RC_FORBIDDEN == server_response_code) {
+ // Server says we're not authenticated.
+ ExtractAuthErrorFrom(message_text, results);
+ return false;
+ } else if (RC_REQUEST_OK == server_response_code) {
+ // Note that the format of message_text is different from what is returned
+ // in the first request, or to the sole request that is made to Gaia V2.
+ // Specifically, the entire string is the AuthToken, and looks like:
+ // "<token>" rather than "AuthToken=<token>". Thus, we need not use
+ // ExtractTokensFrom(...), but simply assign the token.
+ int last_index = message_text.length() - 1;
+ if ('\n' == message_text[last_index])
+ message_text.erase(last_index);
+ results->auth_token = message_text;
+ return true;
+ }
+ return false;
+}
+
+// TOOD(sync): This passing around of AuthResults makes it really unclear who
+// actually owns the authentication state and when it is valid, but this is
+// endemic to this implementation. We should fix this.
+bool GaiaAuthenticator::AuthenticateService(const string& service_id,
+ const string& sid,
+ const string& lsid,
+ string* other_service_cookie) {
+ // Copy the AuthResults structure and overload the auth_token field
+ // in the copy, local_results, to mean the auth_token for service_id.
+ AuthResults local_results;
+ local_results.sid = sid;
+ local_results.lsid = lsid;
+
+ if (!IssueAuthToken(&local_results, service_id, true)) {
+ LOG(ERROR) << "[AUTH] Failed to obtain cookie for " << service_id;
+ return false;
+ }
+
+ swap(*other_service_cookie, local_results.auth_token);
+ return true;
+}
+
+// Helper method that extracts tokens from a successful reply, and saves them
+// in the right fields.
+void GaiaAuthenticator::ExtractTokensFrom(const string& response,
+ AuthResults* results) {
+ vector<pair<string, string> > tokens;
+ SplitStringIntoKeyValuePairs(response, '=', '\n', &tokens);
+ for (vector<pair<string, string> >::iterator i = tokens.begin();
+ i != tokens.end(); ++i) {
+ if (i->first == "SID") {
+ results->sid = i->second;
+ } else if (i->first == "LSID") {
+ results->lsid = i->second;
+ } else if (i->first == "Auth") {
+ results->auth_token = i->second;
+ }
+ }
+}
+
+// Helper method that extracts tokens from a failure response, and saves them
+// in the right fields.
+void GaiaAuthenticator::ExtractAuthErrorFrom(const string& response,
+ AuthResults* results) {
+ vector<pair<string, string> > tokens;
+ SplitStringIntoKeyValuePairs(response, '=', '\n', &tokens);
+ for (vector<pair<string, string> >::iterator i = tokens.begin();
+ i != tokens.end(); ++i) {
+ if (i->first == "Error") {
+ results->error_msg = i->second;
+ } else if (i->first == "Url") {
+ results->auth_error_url = i->second;
+ } else if (i->first == "CaptchaToken") {
+ results->captcha_token = i->second;
+ } else if (i->first == "CaptchaUrl") {
+ results->captcha_url = i->second;
+ }
+ }
+
+ // Convert string error messages to enum values. Each case has two different
+ // strings; the first one is the most current and the second one is
+ // deprecated, but available.
+ const string& error_msg = results->error_msg;
+ if (error_msg == "BadAuthentication" || error_msg == "badauth") {
+ results->auth_error = BadAuthentication;
+ } else if (error_msg == "NotVerified" || error_msg == "nv") {
+ results->auth_error = NotVerified;
+ } else if (error_msg == "TermsNotAgreed" || error_msg == "tna") {
+ results->auth_error = TermsNotAgreed;
+ } else if (error_msg == "Unknown" || error_msg == "unknown") {
+ results->auth_error = Unknown;
+ } else if (error_msg == "AccountDeleted" || error_msg == "adel") {
+ results->auth_error = AccountDeleted;
+ } else if (error_msg == "AccountDisabled" || error_msg == "adis") {
+ results->auth_error = AccountDisabled;
+ } else if (error_msg == "CaptchaRequired" || error_msg == "cr") {
+ results->auth_error = CaptchaRequired;
+ } else if (error_msg == "ServiceUnavailable" || error_msg == "ire") {
+ results->auth_error = ServiceUnavailable;
+ }
+}
+
+// Reset all stored credentials, perhaps in preparation for letting a different
+// user sign in.
+void GaiaAuthenticator::ResetCredentials() {
+ PThreadScopedLock<PThreadMutex> enter(&mutex_);
+ AuthResults blank;
+ auth_results_ = blank;
+}
+
+void GaiaAuthenticator::SetUsernamePassword(const string& username,
+ const string& password) {
+ PThreadScopedLock<PThreadMutex> enter(&mutex_);
+ auth_results_.password = password;
+ auth_results_.email = username;
+}
+
+void GaiaAuthenticator::SetUsername(const string& username) {
+ PThreadScopedLock<PThreadMutex> enter(&mutex_);
+ auth_results_.email = username;
+}
+
+void GaiaAuthenticator::SetAuthToken(const string& auth_token,
+ SaveCredentials save) {
+ PThreadScopedLock<PThreadMutex> enter(&mutex_);
+ auth_results_.auth_token = auth_token;
+ auth_results_.credentials_saved = save;
+}
+
+bool GaiaAuthenticator::Authenticate(const string& user_name,
+ const string& password,
+ SaveCredentials should_save_credentials,
+ bool synchronous, SignIn try_first) {
+ const string empty;
+ return Authenticate(user_name, password, should_save_credentials, synchronous,
+ empty, empty, try_first);
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/engine/net/gaia_authenticator.h b/chrome/browser/sync/engine/net/gaia_authenticator.h
new file mode 100644
index 0000000..e18984c
--- /dev/null
+++ b/chrome/browser/sync/engine/net/gaia_authenticator.h
@@ -0,0 +1,304 @@
+// 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.
+//
+// Use this class to authenticate users with Gaia and access cookies sent
+// by the Gaia servers.
+//
+// Sample usage:
+// GaiaAuthenticator gaia_auth("User-Agent", SYNC_SERVICE_NAME,
+// browser_sync::kExternalGaiaUrl);
+// if (gaia_auth.Authenticate("email", "passwd", SAVE_IN_MEMORY_ONLY,
+// true)) { // Synchronous
+// // Do something with: gaia_auth.auth_token(), or gaia_auth.sid(),
+// // or gaia_auth.lsid()
+// }
+//
+// Sample asynchonous usage:
+// GaiaAuthenticator gaia_auth("User-Agent", SYNC_SERVICE_NAME,
+// browser_sync::kExternalGaiaUrl);
+// EventListenerHookup* hookup = NewListenerHookup(gaia_auth.channel(),
+// this, &OnAuthenticate);
+// gaia_auth.Authenticate("email", "passwd", true, false);
+// // OnAuthenticate() will get called with result;
+//
+// Credentials can also be preserved for subsequent requests, though these are
+// saved in plain-text in memory, and not very secure on client systems. The
+// email address associated with the Gaia account can be read; the password is
+// write-only.
+
+#ifndef CHROME_BROWSER_SYNC_ENGINE_NET_GAIA_AUTHENTICATOR_H_
+#define CHROME_BROWSER_SYNC_ENGINE_NET_GAIA_AUTHENTICATOR_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "chrome/browser/sync/engine/net/http_return.h"
+#include "chrome/browser/sync/util/event_sys.h"
+#include "chrome/browser/sync/util/pthread_helpers.h"
+#include "chrome/browser/sync/util/signin.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest_prod.h" // For FRIEND_TEST
+
+namespace browser_sync {
+
+static const char kGaiaUrl[] =
+ "https://www.google.com:443/accounts/ClientLogin";
+
+// Use of the following enum is odd. GaiaAuthenticator only looks at
+// and DONT_SAVE_CREDENTIALS and SAVE_IN_MEMORY_ONLY (PERSIST_TO_DISK is == to
+// SAVE_IN_MEMORY_ONLY for GaiaAuthenticator). The sync engine never uses
+// DONT_SAVE_CREDENTIALS. AuthWatcher does look in GaiaAuthenticator's results
+// object to decide if it should save credentials to disk. This currently
+// works so I'm leaving the odd dance alone.
+
+enum SaveCredentials {
+ DONT_SAVE_CREDENTIALS,
+ SAVE_IN_MEMORY_ONLY,
+ PERSIST_TO_DISK // Saved in both memory and disk
+};
+
+// Error codes from Gaia. These will be set correctly for both Gaia V1
+// (/ClientAuth) and V2 (/ClientLogin)
+enum AuthenticationError {
+ None = 0,
+ BadAuthentication = 1,
+ NotVerified = 2,
+ TermsNotAgreed = 3,
+ Unknown = 4,
+ AccountDeleted = 5,
+ AccountDisabled = 6,
+ CaptchaRequired = 7,
+ ServiceUnavailable = 8,
+ // Errors generated by this class not Gaia.
+ CredentialsNotSet = 9,
+ ConnectionUnavailable = 10
+};
+
+class GaiaAuthenticator;
+
+struct GaiaAuthEvent {
+ enum {
+ GAIA_AUTH_FAILED,
+ GAIA_AUTH_SUCCEEDED,
+ GAIA_AUTHENTICATOR_DESTROYED
+ }
+ what_happened;
+ AuthenticationError error;
+ const GaiaAuthenticator* authenticator;
+
+ // Lets us use GaiaAuthEvent as its own traits type in hookups.
+ typedef GaiaAuthEvent EventType;
+ static inline bool IsChannelShutdownEvent(const GaiaAuthEvent& event) {
+ return event.what_happened == GAIA_AUTHENTICATOR_DESTROYED;
+ }
+};
+
+// GaiaAuthenticator can be used to pass user credentials to Gaia and obtain
+// cookies set by the Gaia servers.
+class GaiaAuthenticator {
+ FRIEND_TEST(GaiaAuthenticatorTest, TestNewlineAtEndOfAuthTokenRemoved);
+ public:
+
+ // Since GaiaAuthenticator can be used for any service, or by any client, you
+ // must include a user-agent and a service-id when creating one. The
+ // user_agent is a short string used for simple log analysis. gaia_url is used
+ // to choose the server to authenticate with (e.g.
+ // http://www.google.com/accounts/ClientLogin).
+ GaiaAuthenticator(const std::string& user_agent,
+ const std::string& service_id,
+ const std::string& gaia_url);
+
+ virtual ~GaiaAuthenticator();
+
+ // Pass credentials to authenticate with, or use saved credentials via an
+ // overload. If authentication succeeds, you can retrieve the authentication
+ // token via the respective accessors. Returns a boolean indicating whether
+ // authentication succeeded or not.
+ bool Authenticate(const std::string& user_name, const std::string& password,
+ SaveCredentials should_save_credentials, bool synchronous,
+ const std::string& captcha_token,
+ const std::string& captcha_value,
+ SignIn try_first);
+
+ bool Authenticate(const std::string& user_name, const std::string& password,
+ SaveCredentials should_save_credentials, bool synchronous,
+ SignIn try_first);
+
+ bool AuthenticateService(const std::string& service_id,
+ const std::string& sid,
+ const std::string& lsid,
+ std::string* other_service_cookie);
+
+ // Resets all stored cookies to their default values.
+ void ResetCredentials();
+
+ void SetUsernamePassword(const std::string& username,
+ const std::string& password);
+
+ void SetUsername(const std::string& username);
+
+ void SetAuthToken(const std::string& auth_token, SaveCredentials);
+
+ struct AuthResults {
+ SaveCredentials credentials_saved;
+ std::string email;
+ std::string password;
+
+ // Fields that store various cookies.
+ std::string sid;
+ std::string lsid;
+ std::string auth_token;
+
+ std::string primary_email;
+
+ // Fields for items returned when authentication fails.
+ std::string error_msg;
+ enum AuthenticationError auth_error;
+ std::string auth_error_url;
+ std::string captcha_token;
+ std::string captcha_url;
+ SignIn signin;
+
+ AuthResults () : credentials_saved(DONT_SAVE_CREDENTIALS),
+ auth_error(None) { }
+ };
+
+ protected:
+
+ struct AuthParams {
+ GaiaAuthenticator* authenticator;
+ uint32 request_id;
+ SaveCredentials should_save_credentials;
+ std::string email;
+ std::string password;
+ std::string captcha_token;
+ std::string captcha_value;
+ SignIn try_first;
+ };
+
+ // mutex_ must be entered before calling this function.
+ AuthParams MakeParams(const std::string& user_name,
+ const std::string& password,
+ SaveCredentials should_save_credentials,
+ const std::string& captcha_token,
+ const std::string& captcha_value,
+ SignIn try_first);
+
+ // The real Authenticate implementations.
+ bool AuthenticateImpl(const AuthParams& params);
+ bool AuthenticateImpl(const AuthParams& params, AuthResults* results);
+ bool PerformGaiaRequest(const AuthParams& params, AuthResults* results);
+ bool LaunchAuthenticate(const AuthParams& params, bool synchronous);
+ static void *ThreadMain(void *arg);
+
+ // virtual for testing purposes
+ virtual bool Post(const GURL& url, const std::string& post_body,
+ unsigned long* response_code, std::string* response_body) {
+ return false;
+ }
+
+ // Caller should fill in results->LSID before calling. Result in
+ // results->primary_email.
+ bool LookupEmail(AuthResults* results);
+
+ public:
+ // Retrieve email
+ inline std::string email() const {
+ PThreadScopedLock<PThreadMutex> enter(&mutex_);
+ return auth_results_.email;
+ }
+
+ // Retrieve password
+ inline std::string password() const {
+ PThreadScopedLock<PThreadMutex> enter(&mutex_);
+ return auth_results_.password;
+ }
+
+ // Retrieve AuthToken, if previously authenticated; otherwise returns "".
+ inline std::string auth_token() const {
+ PThreadScopedLock<PThreadMutex> enter(&mutex_);
+ return auth_results_.auth_token;
+ }
+
+ // Retrieve SID cookie. For details, see the Google Accounts documentation.
+ inline std::string sid() const {
+ PThreadScopedLock<PThreadMutex> enter(&mutex_);
+ return auth_results_.sid;
+ }
+
+ // Retrieve LSID cookie. For details, see the Google Accounts documentation.
+ inline std::string lsid() const {
+ PThreadScopedLock<PThreadMutex> enter(&mutex_);
+ return auth_results_.lsid;
+ }
+
+ // Get last authentication error.
+ inline enum AuthenticationError auth_error() const {
+ PThreadScopedLock<PThreadMutex> enter(&mutex_);
+ return auth_results_.auth_error;
+ }
+
+ inline std::string auth_error_url() const {
+ PThreadScopedLock<PThreadMutex> enter(&mutex_);
+ return auth_results_.auth_error_url;
+ }
+
+ inline std::string captcha_token() const {
+ PThreadScopedLock<PThreadMutex> enter(&mutex_);
+ return auth_results_.captcha_token;
+ }
+
+ inline std::string captcha_url() const {
+ PThreadScopedLock<PThreadMutex> enter(&mutex_);
+ return auth_results_.captcha_url;
+ }
+
+ inline AuthResults results() const {
+ PThreadScopedLock<PThreadMutex> enter(&mutex_);
+ return auth_results_;
+ }
+
+ typedef EventChannel<GaiaAuthEvent, PThreadMutex> Channel;
+
+ inline Channel* channel() const {
+ return channel_;
+ }
+
+ private:
+ bool IssueAuthToken(AuthResults* results, const std::string& service_id,
+ bool long_lived_token);
+
+ // Helper method to parse response when authentication succeeds.
+ void ExtractTokensFrom(const std::string& response, AuthResults* results);
+ // Helper method to parse response when authentication fails.
+ void ExtractAuthErrorFrom(const std::string& response, AuthResults* results);
+
+ // Fields for the obvious data items.
+ const std::string user_agent_;
+ const std::string service_id_;
+ const std::string gaia_url_;
+
+ AuthResults auth_results_;
+
+ // When multiple async requests are running, only the one that started most
+ // recently updates the values.
+ //
+ // Note that even though this code was written to handle multiple requests
+ // simultaneously, the sync code issues auth requests one at a time.
+ uint32 request_count_;
+
+ Channel* channel_;
+
+ // Used to compute backoff time for next allowed authentication.
+ int delay_; // In seconds.
+ time_t next_allowed_auth_attempt_time_;
+ int early_auth_attempt_count_;
+
+ // Protects auth_results_, and request_count_.
+ mutable PThreadMutex mutex_;
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_ENGINE_NET_GAIA_AUTHENTICATOR_H_
diff --git a/chrome/browser/sync/engine/net/gaia_authenticator_unittest.cc b/chrome/browser/sync/engine/net/gaia_authenticator_unittest.cc
new file mode 100644
index 0000000..c7c6eb8
--- /dev/null
+++ b/chrome/browser/sync/engine/net/gaia_authenticator_unittest.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/engine/net/gaia_authenticator.h"
+
+#include <string>
+
+#include "chrome/browser/sync/engine/net/http_return.h"
+#include "chrome/browser/sync/util/sync_types.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+
+namespace browser_sync {
+
+class GaiaAuthenticatorTest : public testing::Test { };
+
+class GaiaAuthMock : public GaiaAuthenticator {
+ public:
+ GaiaAuthMock() : GaiaAuthenticator("useragent",
+ "serviceid",
+ "http://gaia_url") {}
+ ~GaiaAuthMock() {}
+ protected:
+ bool Post(const GURL& url, const string& post_body,
+ unsigned long* response_code, string* response_body) {
+ *response_code = RC_REQUEST_OK;
+ response_body->assign("body\n");
+ return true;
+ }
+};
+
+TEST(GaiaAuthenticatorTest, TestNewlineAtEndOfAuthTokenRemoved) {
+ GaiaAuthMock mock_auth;
+ GaiaAuthenticator::AuthResults results;
+ EXPECT_TRUE(mock_auth.IssueAuthToken(&results, "sid", true));
+ EXPECT_EQ(0, results.auth_token.compare("body"));
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/engine/net/http_return.h b/chrome/browser/sync/engine/net/http_return.h
new file mode 100644
index 0000000..fd5167b
--- /dev/null
+++ b/chrome/browser/sync/engine/net/http_return.h
@@ -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.
+
+#ifndef CHROME_BROWSER_SYNC_ENGINE_NET_HTTP_RETURN_H_
+#define CHROME_BROWSER_SYNC_ENGINE_NET_HTTP_RETURN_H_
+
+namespace browser_sync {
+enum HTTPReturnCode {
+ RC_REQUEST_OK = 200,
+ RC_UNAUTHORIZED = 401,
+ RC_FORBIDDEN = 403,
+};
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_ENGINE_NET_HTTP_RETURN_H_
diff --git a/chrome/browser/sync/engine/net/openssl_init.cc b/chrome/browser/sync/engine/net/openssl_init.cc
new file mode 100644
index 0000000..afaf006
--- /dev/null
+++ b/chrome/browser/sync/engine/net/openssl_init.cc
@@ -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.
+//
+// OpenSSL multi-threading initialization
+
+#include "chrome/browser/sync/engine/net/openssl_init.h"
+
+#include <openssl/crypto.h>
+
+#include "base/logging.h"
+#include "chrome/browser/sync/util/compat-pthread.h"
+#include "chrome/browser/sync/util/pthread_helpers.h"
+
+// OpenSSL requires multithreading callbacks to be initialized prior to using
+// the library so that it can manage thread locking as necessary.
+
+// Dynamic lock type
+//
+// This needs to be a struct and in global scope because OpenSSL relies on some
+// macro magic.
+struct CRYPTO_dynlock_value {
+ PThreadMutex mutex;
+ void Lock() {
+ mutex.Lock();
+ }
+ void Unlock() {
+ mutex.Unlock();
+ }
+};
+
+namespace {
+
+// This array stores all of the mutexes available to OpenSSL
+PThreadMutex* mutex_buf = NULL;
+
+// OpenSSL mutex handling callback functions
+
+// OpenSSL Callback - Locks/unlocks the specified mutex held by OpenSSL.
+void OpenSslMutexLockControl(int mode, int n, const char* file, int line) {
+ if (mode & CRYPTO_LOCK) {
+ mutex_buf[n].Lock();
+ } else {
+ mutex_buf[n].Unlock();
+ }
+}
+
+// OpenSSL Callback - Returns the thread ID
+unsigned long OpenSslGetThreadID(void) {
+ return GetCurrentThreadId();
+}
+
+// Dynamic locking functions
+
+// Allocate a new lock
+struct CRYPTO_dynlock_value* dyn_create_function(const char* file, int line) {
+ return new CRYPTO_dynlock_value;
+}
+
+void dyn_lock_function(int mode, struct CRYPTO_dynlock_value* lock,
+ const char* file, int line) {
+ if (mode & CRYPTO_LOCK) {
+ lock->Lock();
+ } else {
+ lock->Unlock();
+ }
+}
+
+void dyn_destroy_function(struct CRYPTO_dynlock_value* lock,
+ const char* file, int line) {
+ delete lock;
+}
+
+} // namespace
+
+// We want to log the version of the OpenSSL library being used, in particular
+// for the case where it's dynamically linked. We want the version from the
+// library, not from the header files. It seems the OpenSSL folks haven't
+// bothered with an accessor for this, so we just pluck it out.
+#ifdef OS_WINDOWS
+// TODO(sync): Figure out how to get the SSL version string on Windows.
+const char* SSL_version_str = "UNKNOWN";
+#else
+extern const char* SSL_version_str;
+#endif
+
+namespace browser_sync {
+
+// Initializes the OpenSSL multithreading callbacks. This isn't thread-safe,
+// but it is called early enough that it doesn't matter.
+void InitOpenSslMultithreading() {
+ LOG(INFO) << "Using OpenSSL headers version " << OPENSSL_VERSION_TEXT
+ << ", lib version " << SSL_version_str;
+
+ if (mutex_buf)
+ return;
+
+ mutex_buf = new PThreadMutex[CRYPTO_num_locks()];
+ CHECK(NULL != mutex_buf);
+
+ // OpenSSL has only one single global set of callbacks, so this
+ // initialization must be done only once, even though the OpenSSL lib may be
+ // used by multiple modules (jingle jabber connections and P2P tunnels).
+ CRYPTO_set_id_callback(OpenSslGetThreadID);
+ CRYPTO_set_locking_callback(OpenSslMutexLockControl);
+
+ CRYPTO_set_dynlock_create_callback(dyn_create_function);
+ CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
+ CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
+}
+
+// Cleans up the OpenSSL multithreading callbacks.
+void CleanupOpenSslMultithreading() {
+ if (!mutex_buf) {
+ return;
+ }
+
+ CRYPTO_set_dynlock_create_callback(NULL);
+ CRYPTO_set_dynlock_lock_callback(NULL);
+ CRYPTO_set_dynlock_destroy_callback(NULL);
+
+ CRYPTO_set_id_callback(NULL);
+ CRYPTO_set_locking_callback(NULL);
+
+ delete [] mutex_buf;
+ mutex_buf = NULL;
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/engine/net/openssl_init.h b/chrome/browser/sync/engine/net/openssl_init.h
new file mode 100644
index 0000000..8cd4558
--- /dev/null
+++ b/chrome/browser/sync/engine/net/openssl_init.h
@@ -0,0 +1,20 @@
+// 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.
+//
+// OpenSSL multi-threading initialization
+
+#ifndef CHROME_BROWSER_SYNC_ENGINE_NET_OPENSSL_INIT_H_
+#define CHROME_BROWSER_SYNC_ENGINE_NET_OPENSSL_INIT_H_
+
+namespace browser_sync {
+
+// Initializes the OpenSSL multithreading callbacks. Returns false on failure.
+void InitOpenSslMultithreading();
+
+// Cleans up the OpenSSL multithreading callbacks.
+void CleanupOpenSslMultithreading();
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_ENGINE_NET_OPENSSL_INIT_H_
diff --git a/chrome/browser/sync/engine/net/server_connection_manager.cc b/chrome/browser/sync/engine/net/server_connection_manager.cc
new file mode 100644
index 0000000..42b380b
--- /dev/null
+++ b/chrome/browser/sync/engine/net/server_connection_manager.cc
@@ -0,0 +1,375 @@
+// 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/engine/net/server_connection_manager.h"
+
+#include <errno.h>
+
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "chrome/browser/sync/engine/net/http_return.h"
+#include "chrome/browser/sync/engine/net/url_translator.h"
+#include "chrome/browser/sync/engine/syncapi.h"
+#include "chrome/browser/sync/engine/syncer.h"
+#include "chrome/browser/sync/engine/syncproto.h"
+#include "chrome/browser/sync/protocol/sync.pb.h"
+#include "chrome/browser/sync/syncable/directory_manager.h"
+#include "chrome/browser/sync/util/character_set_converters.h"
+#include "chrome/browser/sync/util/event_sys-inl.h"
+
+namespace browser_sync {
+
+using std::ostream;
+using std::string;
+using std::vector;
+
+static const char kSyncServerSyncPath[] = "/command/";
+
+// At the /time/ path of the sync server, we expect to find a very simple
+// time of day service that we can use to synchronize the local clock with
+// server time.
+static const char kSyncServerGetTimePath[] = "/time";
+
+static const ServerConnectionEvent shutdown_event =
+ { ServerConnectionEvent::SHUTDOWN, HttpResponse::CONNECTION_UNAVAILABLE,
+ false };
+
+typedef PThreadScopedLock<PThreadMutex> MutexLock;
+
+struct ServerConnectionManager::PlatformMembers {
+ explicit PlatformMembers(const string& user_agent) { }
+ void Kill() { }
+ void Reset() { }
+ void Reset(MutexLock*) { }
+};
+
+bool ServerConnectionManager::Post::ReadBufferResponse(
+ string* buffer_out, HttpResponse* response, bool require_response) {
+ if (RC_REQUEST_OK != response->response_code) {
+ response->server_status = HttpResponse::SYNC_SERVER_ERROR;
+ return false;
+ }
+
+ if (require_response && (1 > response->content_length))
+ return false;
+
+ const int64 bytes_read = ReadResponse(buffer_out, response->content_length);
+ if (bytes_read != response->content_length) {
+ response->server_status = HttpResponse::IO_ERROR;
+ return false;
+ }
+ return true;
+}
+
+bool ServerConnectionManager::Post::ReadDownloadResponse(
+ HttpResponse* response, string* buffer_out) {
+ const int64 bytes_read = ReadResponse(buffer_out, response->content_length);
+
+ if (bytes_read != response->content_length) {
+ LOG(ERROR) << "Mismatched content lengths, server claimed " <<
+ response->content_length << ", but sent " << bytes_read;
+ response->server_status = HttpResponse::IO_ERROR;
+ return false;
+ }
+ return true;
+}
+
+namespace {
+ string StripTrailingSlash(const string& s) {
+ int stripped_end_pos = s.size();
+ if (s.at(stripped_end_pos - 1) == '/') {
+ stripped_end_pos = stripped_end_pos - 1;
+ }
+
+ return s.substr(0, stripped_end_pos);
+ }
+} // namespace
+
+// TODO(chron): Use a GURL instead of string concatenation.
+ string ServerConnectionManager::Post::MakeConnectionURL(
+ const string& sync_server, const string& path,
+ bool use_ssl) const {
+ string connection_url = (use_ssl ? "https://" : "http://");
+ connection_url += sync_server;
+ connection_url = StripTrailingSlash(connection_url);
+ connection_url += path;
+
+ return connection_url;
+}
+
+int ServerConnectionManager::Post::ReadResponse(string* out_buffer,
+ int length) {
+ int bytes_read = buffer_.length();
+ CHECK(length <= bytes_read);
+ out_buffer->assign(buffer_);
+ return bytes_read;
+}
+
+// A helper class that automatically notifies when the status changes:
+struct WatchServerStatus {
+ WatchServerStatus(ServerConnectionManager* conn_mgr, HttpResponse* response)
+ : conn_mgr_(conn_mgr), response_(response),
+ reset_count_(conn_mgr->reset_count_),
+ server_reachable_(conn_mgr->server_reachable_) {
+ response->server_status = conn_mgr->server_status_;
+ }
+ ~WatchServerStatus() {
+ // Don't update the status of the connection if it has been reset.
+ // TODO(timsteele): Do we need this? Is this used by multiple threads?
+ if (reset_count_ != conn_mgr_->reset_count_)
+ return;
+ if (conn_mgr_->server_status_ != response_->server_status) {
+ conn_mgr_->server_status_ = response_->server_status;
+ conn_mgr_->NotifyStatusChanged();
+ return;
+ }
+ // Notify if we've gone on or offline.
+ if (server_reachable_ != conn_mgr_->server_reachable_)
+ conn_mgr_->NotifyStatusChanged();
+ }
+ ServerConnectionManager* const conn_mgr_;
+ HttpResponse* const response_;
+ // TODO(timsteele): Should this be Barrier:AtomicIncrement?
+ base::subtle::AtomicWord reset_count_;
+ bool server_reachable_;
+};
+
+ServerConnectionManager::ServerConnectionManager(
+ const string& server, int port, bool use_ssl, const string& user_agent,
+ const string& client_id)
+ : sync_server_(server), sync_server_port_(port),
+ channel_(new Channel(shutdown_event)),
+ server_status_(HttpResponse::NONE), server_reachable_(false),
+ client_id_(client_id), use_ssl_(use_ssl),
+ user_agent_(user_agent),
+ platform_(new PlatformMembers(user_agent)),
+ reset_count_(0), error_count_(0),
+ terminate_all_io_(false),
+ proto_sync_path_(kSyncServerSyncPath),
+ get_time_path_(kSyncServerGetTimePath) {
+}
+
+ServerConnectionManager::~ServerConnectionManager() {
+ delete channel_;
+ delete platform_;
+ shutdown_event_mutex_.Lock();
+ int result = pthread_cond_broadcast(&shutdown_event_condition_.condvar_);
+ shutdown_event_mutex_.Unlock();
+ if (result) {
+ LOG(ERROR) << "Error signaling shutdown_event_condition_ last error = "
+ << result;
+ }
+}
+
+void ServerConnectionManager::NotifyStatusChanged() {
+ ServerConnectionEvent event = { ServerConnectionEvent::STATUS_CHANGED,
+ server_status_,
+ server_reachable_ };
+ channel_->NotifyListeners(event);
+}
+
+// Uses currently set auth token. Set by AuthWatcher.
+bool ServerConnectionManager::PostBufferWithCachedAuth(
+ const PostBufferParams* params) {
+ string path =
+ MakeSyncServerPath(proto_sync_path(), MakeSyncQueryString(client_id_));
+ return PostBufferToPath(params, path, auth_token_);
+}
+
+bool ServerConnectionManager::PostBufferWithAuth(const PostBufferParams* params,
+ const string& auth_token) {
+ string path = MakeSyncServerPath(proto_sync_path(),
+ MakeSyncQueryString(client_id_));
+
+ return PostBufferToPath(params, path, auth_token);
+}
+
+bool ServerConnectionManager::PostBufferToPath(const PostBufferParams* params,
+ const string& path,
+ const string& auth_token) {
+ WatchServerStatus watcher(this, params->response);
+ scoped_ptr<Post> post(MakePost());
+ post->set_timing_info(params->timing_info);
+ bool ok = post->Init(path.c_str(), auth_token, params->buffer_in,
+ params->response);
+
+ if (!ok || RC_REQUEST_OK != params->response->response_code) {
+ IncrementErrorCount();
+ return false;
+ }
+
+ if (post->ReadBufferResponse(params->buffer_out, params->response, true)) {
+ params->response->server_status = HttpResponse::SERVER_CONNECTION_OK;
+ server_reachable_ = true;
+ return true;
+ }
+ return false;
+}
+
+bool ServerConnectionManager::CheckTime(int32* out_time) {
+ // Verify that the server really is reachable by checking the time. We need
+ // to do this because of wifi interstitials that intercept messages from the
+ // client and return HTTP OK instead of a redirect.
+ HttpResponse response;
+ WatchServerStatus watcher(this, &response);
+ string post_body = "command=get_time";
+
+ // We only retry the CheckTime call if we were reset during the CheckTime
+ // attempt. We only try 3 times in case we're in a reset loop elsewhere.
+ base::subtle::AtomicWord start_reset_count = reset_count_ - 1;
+ for (int i = 0 ; i < 3 && start_reset_count != reset_count_ ; i++) {
+ start_reset_count = reset_count_;
+ scoped_ptr<Post> post(MakePost());
+
+ // Note that the server's get_time path doesn't require authentication.
+ string get_time_path =
+ MakeSyncServerPath(kSyncServerGetTimePath, post_body);
+ LOG(INFO) << "Requesting get_time from:" << get_time_path;
+
+ string blank_post_body;
+ bool ok = post->Init(get_time_path.c_str(), blank_post_body,
+ blank_post_body, &response);
+ if (!ok) {
+ LOG(INFO) << "Unable to check the time";
+ continue;
+ }
+ string time_response;
+ time_response.resize(response.content_length);
+ ok = post->ReadDownloadResponse(&response, &time_response);
+ if (!ok || string::npos !=
+ time_response.find_first_not_of("0123456789")) {
+ LOG(ERROR) << "unable to read a non-numeric response from get_time:"
+ << time_response;
+ continue;
+ }
+ *out_time = atoi(time_response.c_str());
+ LOG(INFO) << "Server was reachable.";
+ return true;
+ }
+ IncrementErrorCount();
+ return false;
+}
+
+bool ServerConnectionManager::IsServerReachable() {
+ int32 time;
+ return CheckTime(&time);
+}
+
+bool ServerConnectionManager::IsUserAuthenticated() {
+ return IsGoodReplyFromServer(server_status_);
+}
+
+bool ServerConnectionManager::CheckServerReachable() {
+ const bool server_is_reachable = IsServerReachable();
+ if (server_reachable_ != server_is_reachable) {
+ server_reachable_ = server_is_reachable;
+ NotifyStatusChanged();
+ }
+ return server_is_reachable;
+}
+
+void ServerConnectionManager::kill() {
+ {
+ MutexLock lock(&terminate_all_io_mutex_);
+ terminate_all_io_ = true;
+ }
+ platform_->Kill();
+ shutdown_event_mutex_.Lock();
+ int result = pthread_cond_broadcast(&shutdown_event_condition_.condvar_);
+ shutdown_event_mutex_.Unlock();
+ if (result) {
+ LOG(ERROR) << "Error signaling shutdown_event_condition_ last error = "
+ << result;
+ }
+}
+
+void ServerConnectionManager::ResetAuthStatus() {
+ ResetConnection();
+ server_status_ = HttpResponse::NONE;
+ NotifyStatusChanged();
+}
+
+void ServerConnectionManager::ResetConnection() {
+ base::subtle::NoBarrier_AtomicIncrement(&reset_count_, 1);
+ platform_->Reset();
+}
+
+bool ServerConnectionManager::IncrementErrorCount() {
+#ifdef OS_WINDOWS
+ error_count_mutex_.Lock();
+ error_count_++;
+
+ if (error_count_ > kMaxConnectionErrorsBeforeReset) {
+ error_count_ = 0;
+
+ // Be careful with this mutex because calling out to other methods can
+ // result in being called back. Unlock it here to prevent any potential
+ // double-acquisitions.
+ error_count_mutex_.Unlock();
+
+ if (!IsServerReachable()) {
+ LOG(WARNING) << "Too many connection failures, server is not reachable. "
+ << "Resetting connections.";
+ ResetConnection();
+ } else {
+ LOG(WARNING) << "Multiple connection failures while server is reachable.";
+ }
+ return false;
+ }
+
+ error_count_mutex_.Unlock();
+ return true;
+#endif
+ return true;
+}
+
+void ServerConnectionManager::SetServerParameters(const string& server_url,
+ int port, bool use_ssl) {
+ {
+ ParametersLock lock(&server_parameters_mutex_);
+ sync_server_ = server_url;
+ sync_server_port_ = port;
+ use_ssl_ = use_ssl;
+ }
+ platform_->Reset();
+}
+
+// Returns the current server parameters in server_url and port.
+void ServerConnectionManager::GetServerParameters(string* server_url,
+ int* port, bool* use_ssl) {
+ ParametersLock lock(&server_parameters_mutex_);
+ if (server_url != NULL)
+ *server_url = sync_server_;
+ if (port != NULL)
+ *port = sync_server_port_;
+ if (use_ssl != NULL)
+ *use_ssl = use_ssl_;
+}
+
+bool FillMessageWithShareDetails(sync_pb::ClientToServerMessage* csm,
+ syncable::DirectoryManager* manager,
+ const PathString &share) {
+ syncable::ScopedDirLookup dir(manager, share);
+ if (!dir.good()) {
+ LOG(INFO) << "Dir lookup failed";
+ return false;
+ }
+ string birthday = dir->store_birthday();
+ if (!birthday.empty())
+ csm->set_store_birthday(birthday);
+ csm->set_share(ToUTF8(share).get_string());
+ return true;
+}
+
+} // namespace browser_sync
+
+std::ostream& operator << (std::ostream& s,
+ const struct browser_sync::HttpResponse& hr) {
+ s << " Response Code (bogus on error): " << hr.response_code;
+ s << " Content-Length (bogus on error): " << hr.content_length;
+ s << " Server Status: " << hr.server_status;
+ return s;
+}
diff --git a/chrome/browser/sync/engine/net/server_connection_manager.h b/chrome/browser/sync/engine/net/server_connection_manager.h
new file mode 100644
index 0000000..8093d45
--- /dev/null
+++ b/chrome/browser/sync/engine/net/server_connection_manager.h
@@ -0,0 +1,345 @@
+// 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_ENGINE_NET_SERVER_CONNECTION_MANAGER_H_
+#define CHROME_BROWSER_SYNC_ENGINE_NET_SERVER_CONNECTION_MANAGER_H_
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+#include "base/atomicops.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "chrome/browser/sync/engine/net/http_return.h"
+#include "chrome/browser/sync/syncable/syncable_id.h"
+#include "chrome/browser/sync/util/event_sys.h"
+#include "chrome/browser/sync/util/pthread_helpers.h"
+#include "chrome/browser/sync/util/signin.h"
+#include "chrome/browser/sync/util/sync_types.h"
+
+namespace syncable {
+class WriteTransaction;
+class DirectoryManager;
+}
+
+namespace sync_pb {
+class ClientToServerMessage;
+};
+
+struct RequestTimingInfo;
+
+namespace browser_sync {
+
+class ClientToServerMessage;
+
+// How many connection errors are accepted before network handles are closed
+// and reopened.
+static const int32 kMaxConnectionErrorsBeforeReset = 10;
+
+// HttpResponse gathers the relevant output properties of an HTTP request.
+// Depending on the value of the server_status code, response_code, and
+// content_length may not be valid.
+struct HttpResponse {
+ enum ServerConnectionCode {
+ // For uninitialized state.
+ NONE,
+
+ // CONNECTION_UNAVAILABLE is returned when InternetConnect() fails.
+ CONNECTION_UNAVAILABLE,
+
+ // IO_ERROR is returned when reading/writing to a buffer has failed.
+ IO_ERROR,
+
+ // SYNC_SERVER_ERROR is returned when the HTTP status code indicates that
+ // a non-auth error has occured.
+ SYNC_SERVER_ERROR,
+
+ // SYNC_AUTH_ERROR is returned when the HTTP status code indicates that an
+ // auth error has occured (i.e. a 401)
+ SYNC_AUTH_ERROR,
+
+ // All the following connection codes are valid responses from the server.
+ // Means the server is up. If you update this list, be sure to also update
+ // IsGoodReplyFromServer().
+
+ // SERVER_CONNECTION_OK is returned when request was handled correctly.
+ SERVER_CONNECTION_OK,
+
+ // RETRY is returned when a Commit request fails with a RETRY response from
+ // the server.
+ //
+ // TODO(idana): the server no longer returns RETRY so we should remove this
+ // value.
+ RETRY,
+ };
+
+ // The HTTP Status code.
+ int64 response_code;
+
+ // The value of the Content-length header.
+ int64 content_length;
+
+ // The size of a download request's payload.
+ int64 payload_length;
+
+ // Identifies the type of failure, if any.
+ ServerConnectionCode server_status;
+};
+
+inline bool IsGoodReplyFromServer(HttpResponse::ServerConnectionCode code) {
+ return code >= HttpResponse::SERVER_CONNECTION_OK;
+}
+
+struct ServerConnectionEvent {
+ enum { SHUTDOWN, STATUS_CHANGED } what_happened;
+ HttpResponse::ServerConnectionCode connection_code;
+ bool server_reachable;
+
+ // Traits.
+ typedef ServerConnectionEvent EventType;
+ static inline bool IsChannelShutdownEvent(const EventType& event) {
+ return SHUTDOWN == event.what_happened;
+ }
+};
+
+struct WatchServerStatus;
+
+// Use this class to interact with the sync server.
+// The ServerConnectionManager currently supports POSTing protocol buffers.
+//
+// *** This class is thread safe. In fact, you should consider creating only
+// one instance for every server that you need to talk to.
+class ServerConnectionManager {
+ friend class Post;
+ friend struct WatchServerStatus;
+ public:
+ typedef EventChannel<ServerConnectionEvent, PThreadMutex> Channel;
+
+ // The lifetime of the GaiaAuthenticator must be longer than the instance
+ // of the ServerConnectionManager that you're creating.
+ ServerConnectionManager(const std::string& server, int port, bool use_ssl,
+ const std::string& user_agent,
+ const std::string& client_id);
+
+ virtual ~ServerConnectionManager();
+
+ // buffer_in - will be POSTed
+ // buffer_out - string will be overwritten with response
+ struct PostBufferParams {
+ const std::string& buffer_in;
+ std::string* buffer_out;
+ HttpResponse* response;
+ RequestTimingInfo* timing_info;
+ };
+
+ // Abstract class providing network-layer functionality to the
+ // ServerConnectionManager. Subclasses implement this using an HTTP stack of
+ // their choice.
+ class Post {
+ public:
+ explicit Post(ServerConnectionManager* scm) : scm_(scm), timing_info_(0) {
+ }
+ virtual ~Post() { }
+
+ // Called to initialize and perform an HTTP POST.
+ virtual bool Init(const char* path, const std::string& auth_token,
+ const std::string& payload,
+ HttpResponse* response) = 0;
+
+ bool ReadBufferResponse(std::string* buffer_out, HttpResponse* response,
+ bool require_response);
+ bool ReadDownloadResponse(HttpResponse* response, std::string* buffer_out);
+
+ void set_timing_info(RequestTimingInfo* timing_info) {
+ timing_info_ = timing_info;
+ }
+ RequestTimingInfo* timing_info() { return timing_info_; }
+
+ protected:
+ std::string MakeConnectionURL(const std::string& sync_server,
+ const std::string& path, bool use_ssl) const;
+
+ void GetServerParams(std::string* server, int* server_port,
+ bool* use_ssl) {
+ ServerConnectionManager::ParametersLock lock(
+ &scm_->server_parameters_mutex_);
+ server->assign(scm_->sync_server_);
+ *server_port = scm_->sync_server_port_;
+ *use_ssl = scm_->use_ssl_;
+ }
+
+ std::string buffer_;
+ ServerConnectionManager* scm_;
+
+ private:
+ int ReadResponse(void* buffer, int length);
+ int ReadResponse(std::string* buffer, int length);
+ RequestTimingInfo* timing_info_;
+ };
+
+ // POSTS buffer_in and reads a response into buffer_out. Uses our currently
+ // set auth token in our headers.
+ //
+ // Returns true if executed successfully.
+ virtual bool PostBufferWithCachedAuth(const PostBufferParams* params);
+
+ // POSTS buffer_in and reads a response into buffer_out. Add a specific auth
+ // token to http headers.
+ //
+ // Returns true if executed successfully.
+ virtual bool PostBufferWithAuth(const PostBufferParams* params,
+ const std::string& auth_token);
+
+ // Checks the time on the server. Returns false if the request failed. |time|
+ // is an out parameter that stores the value returned from the server.
+ virtual bool CheckTime(int32* out_time);
+
+ // Returns true if sync_server_ is reachable. This method verifies that the
+ // server is pingable and that traffic can be sent to and from it.
+ virtual bool IsServerReachable();
+
+ // Returns true if user has been successfully authenticated.
+ virtual bool IsUserAuthenticated();
+
+ // Updates status and broadcasts events on change.
+ bool CheckServerReachable();
+
+ // Signal the shutdown event to notify listeners.
+ virtual void kill();
+
+ inline Channel* channel() const { return channel_; }
+
+ inline std::string user_agent() const { return user_agent_; }
+
+ inline HttpResponse::ServerConnectionCode server_status() const {
+ return server_status_;
+ }
+
+ inline bool server_reachable() const { return server_reachable_; }
+
+ void ResetAuthStatus();
+
+ void ResetConnection();
+
+ void NotifyStatusChanged();
+
+ const std::string client_id() const { return client_id_; }
+
+ void SetDomainFromSignIn(SignIn signin_type, const std::string& signin);
+
+ // This changes the server info used by the connection manager. This allows
+ // a single client instance to talk to different backing servers. This is
+ // typically called during / after authentication so that the server url
+ // can be a function of the user's login id. A side effect of this call is
+ // that ResetConnection is called.
+ void SetServerParameters(const std::string& server_url, int port,
+ bool use_ssl);
+
+ // Returns the current server parameters in server_url, port and use_ssl.
+ void GetServerParameters(std::string* server_url, int* port, bool* use_ssl);
+
+ bool terminate_all_io() const {
+ PThreadScopedLock<PThreadMutex> lock(&terminate_all_io_mutex_);
+ return terminate_all_io_;
+ }
+
+ // Factory method to create a Post object we can use for communication with
+ // the server.
+ virtual Post* MakePost() {
+ return NULL; // For testing.
+ };
+
+ void set_auth_token(const std::string& auth_token) {
+ auth_token_.assign(auth_token);
+ }
+
+ protected:
+
+ PThreadMutex shutdown_event_mutex_;
+ PThreadCondVar shutdown_event_condition_;
+
+ // Protects access to sync_server_, sync_server_port_ and use_ssl_:
+ mutable PThreadMutex server_parameters_mutex_;
+ typedef PThreadScopedLock<PThreadMutex> ParametersLock;
+
+ // The sync_server_ is the server that requests will be made to.
+ std::string sync_server_;
+
+ // The sync_server_port_ is the port that HTTP requests will be made on.
+ int sync_server_port_;
+
+ // The unique id of the user's client.
+ const std::string client_id_;
+
+ // The user-agent string for HTTP.
+ std::string user_agent_;
+
+ // Indicates whether or not requests should be made using HTTPS.
+ bool use_ssl_;
+
+ // The paths we post to.
+ mutable PThreadMutex path_mutex_;
+ typedef PThreadScopedLock<PThreadMutex> ScopedPathLock;
+
+ std::string proto_sync_path_;
+ std::string get_time_path_;
+
+ // The auth token to use in authenticated requests. Set by the AuthWatcher.
+ std::string auth_token_;
+
+ inline std::string proto_sync_path() const {
+ ScopedPathLock lock(&path_mutex_);
+ return proto_sync_path_;
+ }
+ std::string get_time_path() const {
+ ScopedPathLock lock(&path_mutex_);
+ return get_time_path_;
+ }
+
+ // Called wherever a failure should be taken as an indication that we may
+ // be experiencing connection difficulties.
+ virtual bool IncrementErrorCount();
+ mutable PThreadMutex error_count_mutex_; // Protects error_count_
+ int error_count_; // Tracks the number of connection errors.
+
+ protected:
+ Channel* const channel_;
+ // Volatile so various threads can call server_status() without
+ // synchronization.
+ volatile HttpResponse::ServerConnectionCode server_status_;
+ bool server_reachable_;
+
+ struct PlatformMembers; // Contains platform specific member vars.
+ PlatformMembers* const platform_;
+
+ // A counter that is incremented everytime ResetAuthStatus() is called.
+ volatile base::subtle::AtomicWord reset_count_;
+
+ // NOTE: Tests rely on this protected function being virtual.
+ //
+ // Internal PostBuffer base function.
+ virtual bool PostBufferToPath(const PostBufferParams*,
+ const std::string& path,
+ const std::string& auth_token);
+
+ private:
+ mutable PThreadMutex terminate_all_io_mutex_;
+ bool terminate_all_io_; // when set to true, terminate all connections asap
+ DISALLOW_COPY_AND_ASSIGN(ServerConnectionManager);
+};
+
+// Fills a ClientToServerMessage with the appropriate share and birthday
+// settings.
+bool FillMessageWithShareDetails(sync_pb::ClientToServerMessage* csm,
+ syncable::DirectoryManager* manager,
+ const PathString &share);
+
+} // namespace browser_sync
+
+std::ostream& operator<<(std::ostream& s,
+ const struct browser_sync::HttpResponse& hr);
+
+#endif // CHROME_BROWSER_SYNC_ENGINE_NET_SERVER_CONNECTION_MANAGER_H_
diff --git a/chrome/browser/sync/engine/net/syncapi_server_connection_manager.cc b/chrome/browser/sync/engine/net/syncapi_server_connection_manager.cc
new file mode 100644
index 0000000..19981de
--- /dev/null
+++ b/chrome/browser/sync/engine/net/syncapi_server_connection_manager.cc
@@ -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.
+
+#include "chrome/browser/sync/engine/net/syncapi_server_connection_manager.h"
+
+#include "chrome/browser/sync/engine/net/http_return.h"
+#include "chrome/browser/sync/engine/syncapi.h"
+
+using browser_sync::HttpResponse;
+using std::string;
+
+namespace sync_api {
+
+bool SyncAPIBridgedPost::Init(const char* path, const string& auth_token,
+ const string& payload, HttpResponse* response) {
+ string sync_server;
+ int sync_server_port = 0;
+ bool use_ssl = false;
+ GetServerParams(&sync_server, &sync_server_port, &use_ssl);
+ std::string connection_url = MakeConnectionURL(sync_server, path, use_ssl);
+
+ HttpPostProviderInterface* http = factory_->Create();
+ http->SetUserAgent(scm_->user_agent().c_str());
+ http->SetURL(connection_url.c_str(), sync_server_port);
+
+ if (!auth_token.empty()) {
+ string headers = "Authorization: GoogleLogin auth=" + auth_token;
+ http->SetExtraRequestHeaders(headers.c_str());
+ }
+
+ // Must be octet-stream, or the payload may be parsed for a cookie.
+ http->SetPostPayload("application/octet-stream", payload.length(),
+ payload.data());
+
+ // Issue the POST, blocking until it finishes.
+ int os_error_code = 0;
+ int response_code = 0;
+ if (!http->MakeSynchronousPost(&os_error_code, &response_code)) {
+ LOG(INFO) << "Http POST failed, error returns: " << os_error_code;
+ response->server_status = HttpResponse::IO_ERROR;
+ return false;
+ }
+
+ // We got a server response, copy over response codes and content.
+ response->response_code = response_code;
+ response->content_length =
+ static_cast<int64>(http->GetResponseContentLength());
+ response->payload_length =
+ static_cast<int64>(http->GetResponseContentLength());
+ if (response->response_code < 400)
+ response->server_status = HttpResponse::SERVER_CONNECTION_OK;
+ else if (response->response_code == browser_sync::RC_UNAUTHORIZED)
+ response->server_status = HttpResponse::SYNC_AUTH_ERROR;
+ else
+ response->server_status = HttpResponse::SYNC_SERVER_ERROR;
+
+ // Write the content into our buffer.
+ buffer_.assign(http->GetResponseContent(), http->GetResponseContentLength());
+
+ // We're done with the HttpPostProvider.
+ factory_->Destroy(http);
+ return true;
+}
+
+SyncAPIServerConnectionManager::~SyncAPIServerConnectionManager() {
+ delete post_provider_factory_;
+}
+
+void SyncAPIServerConnectionManager::SetHttpPostProviderFactory(
+ HttpPostProviderFactory* factory) {
+ if (post_provider_factory_)
+ delete post_provider_factory_;
+ post_provider_factory_ = factory;
+}
+
+} // namespace sync_api
diff --git a/chrome/browser/sync/engine/net/syncapi_server_connection_manager.h b/chrome/browser/sync/engine/net/syncapi_server_connection_manager.h
new file mode 100644
index 0000000..84a355e
--- /dev/null
+++ b/chrome/browser/sync/engine/net/syncapi_server_connection_manager.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_ENGINE_NET_SYNCAPI_SERVER_CONNECTION_MANAGER_H_
+#define CHROME_BROWSER_SYNC_ENGINE_NET_SYNCAPI_SERVER_CONNECTION_MANAGER_H_
+
+#include <string>
+
+#include "chrome/browser/sync/engine/net/server_connection_manager.h"
+
+namespace sync_api {
+
+class HttpPostProviderFactory;
+
+// This provides HTTP Post functionality through the interface provided
+// to the sync API by the application hosting the syncer backend.
+class SyncAPIBridgedPost :
+ public browser_sync::ServerConnectionManager::Post {
+ public:
+ SyncAPIBridgedPost(browser_sync::ServerConnectionManager* scm,
+ HttpPostProviderFactory* factory)
+ : Post(scm), factory_(factory) {
+ }
+
+ virtual ~SyncAPIBridgedPost() { }
+
+ virtual bool Init(const char* path,
+ const std::string& auth_token,
+ const std::string& payload,
+ browser_sync::HttpResponse* response);
+
+ private:
+ // Pointer to the factory we use for creating HttpPostProviders. We do not
+ // own |factory_|.
+ HttpPostProviderFactory* factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncAPIBridgedPost);
+};
+
+// A ServerConnectionManager subclass used by the syncapi layer. We use a
+// subclass so that we can override MakePost() to generate a POST object using
+// an instance of the HttpPostProviderFactory class.
+class SyncAPIServerConnectionManager :
+ public browser_sync::ServerConnectionManager {
+ public:
+ SyncAPIServerConnectionManager(const std::string& server,
+ int port,
+ bool use_ssl,
+ const std::string& client_version,
+ const std::string& client_id)
+ : ServerConnectionManager(server, port, use_ssl, client_version,
+ client_id),
+ post_provider_factory_(NULL) {
+ }
+
+ virtual ~SyncAPIServerConnectionManager();
+
+ // This method gives ownership of |factory| to |this|.
+ void SetHttpPostProviderFactory(HttpPostProviderFactory* factory);
+ protected:
+ virtual Post* MakePost() {
+ return new SyncAPIBridgedPost(this, post_provider_factory_);
+ }
+ private:
+ // A factory creating concrete HttpPostProviders for use whenever we need to
+ // issue a POST to sync servers.
+ HttpPostProviderFactory* post_provider_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncAPIServerConnectionManager);
+};
+
+} // namespace sync_api
+
+#endif // CHROME_BROWSER_SYNC_ENGINE_NET_SYNCAPI_SERVER_CONNECTION_MANAGER_H_
diff --git a/chrome/browser/sync/engine/net/url_translator.cc b/chrome/browser/sync/engine/net/url_translator.cc
new file mode 100644
index 0000000..0931c36
--- /dev/null
+++ b/chrome/browser/sync/engine/net/url_translator.cc
@@ -0,0 +1,50 @@
+// 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.
+//
+// Contains the definition of a few helper functions used for generating sync
+// URLs.
+
+#include "chrome/browser/sync/engine/net/url_translator.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/port.h"
+#include "chrome/browser/sync/util/character_set_converters.h"
+#include "net/base/escape.h"
+
+using std::string;
+
+namespace browser_sync {
+
+namespace {
+// Parameters that the server understands. (here, a-Z)
+const char kParameterAuthToken[] = "auth";
+const char kParameterClientID[] = "client_id";
+}
+
+// Convenience wrappers around CgiEscapePath().
+string CgiEscapeString(const char* src) {
+ return CgiEscapeString(string(src));
+}
+
+string CgiEscapeString(const string& src) {
+ return EscapePath(src);
+}
+
+// This method appends the query string to the sync server path.
+string MakeSyncServerPath(const string& path, const string& query_string) {
+ string result = path;
+ result.append("?");
+ result.append(query_string);
+ return result;
+}
+
+string MakeSyncQueryString(const string& client_id) {
+ string query;
+ query += kParameterClientID;
+ query += "=" + CgiEscapeString(client_id);
+ return query;
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/engine/net/url_translator.h b/chrome/browser/sync/engine/net/url_translator.h
new file mode 100644
index 0000000..717e15b
--- /dev/null
+++ b/chrome/browser/sync/engine/net/url_translator.h
@@ -0,0 +1,27 @@
+// 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.
+//
+// Contains the declaration of a few helper functions used for generating sync
+// URLs.
+
+#ifndef CHROME_BROWSER_SYNC_ENGINE_NET_URL_TRANSLATOR_H_
+#define CHROME_BROWSER_SYNC_ENGINE_NET_URL_TRANSLATOR_H_
+
+#include <string>
+
+namespace browser_sync {
+
+// Convenience wrappers around CgiEscapePath(), used by gaia_auth.
+std::string CgiEscapeString(const char* src);
+std::string CgiEscapeString(const std::string& src);
+
+// This method appends the query string to the sync server path.
+std::string MakeSyncServerPath(const std::string& path,
+ const std::string& query_string);
+
+std::string MakeSyncQueryString(const std::string& client_id);
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_ENGINE_NET_URL_TRANSLATOR_H_