Home | History | Annotate | Download | only in net
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "sync/engine/net/server_connection_manager.h"
      6 
      7 #include <errno.h>
      8 
      9 #include <ostream>
     10 #include <string>
     11 #include <vector>
     12 
     13 #include "base/metrics/histogram.h"
     14 #include "build/build_config.h"
     15 #include "net/base/net_errors.h"
     16 #include "net/http/http_status_code.h"
     17 #include "sync/engine/net/url_translator.h"
     18 #include "sync/engine/syncer.h"
     19 #include "sync/protocol/sync.pb.h"
     20 #include "sync/syncable/directory.h"
     21 #include "url/gurl.h"
     22 
     23 namespace syncer {
     24 
     25 using std::ostream;
     26 using std::string;
     27 using std::vector;
     28 
     29 static const char kSyncServerSyncPath[] = "/command/";
     30 
     31 HttpResponse::HttpResponse()
     32     : response_code(kUnsetResponseCode),
     33       content_length(kUnsetContentLength),
     34       payload_length(kUnsetPayloadLength),
     35       server_status(NONE) {}
     36 
     37 #define ENUM_CASE(x) case x: return #x; break
     38 
     39 const char* HttpResponse::GetServerConnectionCodeString(
     40     ServerConnectionCode code) {
     41   switch (code) {
     42     ENUM_CASE(NONE);
     43     ENUM_CASE(CONNECTION_UNAVAILABLE);
     44     ENUM_CASE(IO_ERROR);
     45     ENUM_CASE(SYNC_SERVER_ERROR);
     46     ENUM_CASE(SYNC_AUTH_ERROR);
     47     ENUM_CASE(SERVER_CONNECTION_OK);
     48     ENUM_CASE(RETRY);
     49   }
     50   NOTREACHED();
     51   return "";
     52 }
     53 
     54 #undef ENUM_CASE
     55 
     56 // TODO(clamy): check if all errors are in the right category.
     57 HttpResponse::ServerConnectionCode
     58 HttpResponse::ServerConnectionCodeFromNetError(int error_code) {
     59   switch (error_code) {
     60     case net::ERR_ABORTED:
     61     case net::ERR_SOCKET_NOT_CONNECTED:
     62     case net::ERR_NETWORK_CHANGED:
     63     case net::ERR_CONNECTION_FAILED:
     64     case net::ERR_NAME_NOT_RESOLVED:
     65     case net::ERR_INTERNET_DISCONNECTED:
     66     case net::ERR_NETWORK_ACCESS_DENIED:
     67     case net::ERR_NETWORK_IO_SUSPENDED:
     68       return CONNECTION_UNAVAILABLE;
     69   }
     70   return IO_ERROR;
     71 }
     72 
     73 ServerConnectionManager::Connection::Connection(
     74     ServerConnectionManager* scm) : scm_(scm) {
     75 }
     76 
     77 ServerConnectionManager::Connection::~Connection() {
     78 }
     79 
     80 bool ServerConnectionManager::Connection::ReadBufferResponse(
     81     string* buffer_out,
     82     HttpResponse* response,
     83     bool require_response) {
     84   if (net::HTTP_OK != response->response_code) {
     85     response->server_status = HttpResponse::SYNC_SERVER_ERROR;
     86     return false;
     87   }
     88 
     89   if (require_response && (1 > response->content_length))
     90     return false;
     91 
     92   const int64 bytes_read = ReadResponse(buffer_out,
     93       static_cast<int>(response->content_length));
     94   if (bytes_read != response->content_length) {
     95     response->server_status = HttpResponse::IO_ERROR;
     96     return false;
     97   }
     98   return true;
     99 }
    100 
    101 bool ServerConnectionManager::Connection::ReadDownloadResponse(
    102     HttpResponse* response,
    103     string* buffer_out) {
    104   const int64 bytes_read = ReadResponse(buffer_out,
    105       static_cast<int>(response->content_length));
    106 
    107   if (bytes_read != response->content_length) {
    108     LOG(ERROR) << "Mismatched content lengths, server claimed " <<
    109         response->content_length << ", but sent " << bytes_read;
    110     response->server_status = HttpResponse::IO_ERROR;
    111     return false;
    112   }
    113   return true;
    114 }
    115 
    116 ServerConnectionManager::ScopedConnectionHelper::ScopedConnectionHelper(
    117     ServerConnectionManager* manager, Connection* connection)
    118     : manager_(manager), connection_(connection) {}
    119 
    120 ServerConnectionManager::ScopedConnectionHelper::~ScopedConnectionHelper() {
    121   if (connection_)
    122     manager_->OnConnectionDestroyed(connection_.get());
    123   connection_.reset();
    124 }
    125 
    126 ServerConnectionManager::Connection*
    127 ServerConnectionManager::ScopedConnectionHelper::get() {
    128   return connection_.get();
    129 }
    130 
    131 namespace {
    132 
    133 string StripTrailingSlash(const string& s) {
    134   int stripped_end_pos = s.size();
    135   if (s.at(stripped_end_pos - 1) == '/') {
    136     stripped_end_pos = stripped_end_pos - 1;
    137   }
    138 
    139   return s.substr(0, stripped_end_pos);
    140 }
    141 
    142 }  // namespace
    143 
    144 // TODO(chron): Use a GURL instead of string concatenation.
    145 string ServerConnectionManager::Connection::MakeConnectionURL(
    146     const string& sync_server,
    147     const string& path,
    148     bool use_ssl) const {
    149   string connection_url = (use_ssl ? "https://" : "http://");
    150   connection_url += sync_server;
    151   connection_url = StripTrailingSlash(connection_url);
    152   connection_url += path;
    153 
    154   return connection_url;
    155 }
    156 
    157 int ServerConnectionManager::Connection::ReadResponse(string* out_buffer,
    158                                                       int length) {
    159   int bytes_read = buffer_.length();
    160   CHECK(length <= bytes_read);
    161   out_buffer->assign(buffer_);
    162   return bytes_read;
    163 }
    164 
    165 ScopedServerStatusWatcher::ScopedServerStatusWatcher(
    166     ServerConnectionManager* conn_mgr, HttpResponse* response)
    167     : conn_mgr_(conn_mgr),
    168       response_(response) {
    169   response->server_status = conn_mgr->server_status_;
    170 }
    171 
    172 ScopedServerStatusWatcher::~ScopedServerStatusWatcher() {
    173   conn_mgr_->SetServerStatus(response_->server_status);
    174 }
    175 
    176 ServerConnectionManager::ServerConnectionManager(
    177     const string& server,
    178     int port,
    179     bool use_ssl,
    180     bool use_oauth2_token)
    181     : sync_server_(server),
    182       sync_server_port_(port),
    183       use_ssl_(use_ssl),
    184       use_oauth2_token_(use_oauth2_token),
    185       proto_sync_path_(kSyncServerSyncPath),
    186       server_status_(HttpResponse::NONE),
    187       terminated_(false),
    188       active_connection_(NULL) {
    189 }
    190 
    191 ServerConnectionManager::~ServerConnectionManager() {
    192 }
    193 
    194 ServerConnectionManager::Connection*
    195 ServerConnectionManager::MakeActiveConnection() {
    196   base::AutoLock lock(terminate_connection_lock_);
    197   DCHECK(!active_connection_);
    198   if (terminated_)
    199     return NULL;
    200 
    201   active_connection_ = MakeConnection();
    202   return active_connection_;
    203 }
    204 
    205 void ServerConnectionManager::OnConnectionDestroyed(Connection* connection) {
    206   DCHECK(connection);
    207   base::AutoLock lock(terminate_connection_lock_);
    208   // |active_connection_| can be NULL already if it was aborted. Also,
    209   // it can legitimately be a different Connection object if a new Connection
    210   // was created after a previous one was Aborted and destroyed.
    211   if (active_connection_ != connection)
    212     return;
    213 
    214   active_connection_ = NULL;
    215 }
    216 
    217 bool ServerConnectionManager::SetAuthToken(const std::string& auth_token) {
    218   DCHECK(thread_checker_.CalledOnValidThread());
    219   if (previously_invalidated_token != auth_token) {
    220     auth_token_.assign(auth_token);
    221     previously_invalidated_token = std::string();
    222     return true;
    223   }
    224   return false;
    225 }
    226 
    227 void ServerConnectionManager::OnInvalidationCredentialsRejected() {
    228   InvalidateAndClearAuthToken();
    229   SetServerStatus(HttpResponse::SYNC_AUTH_ERROR);
    230 }
    231 
    232 void ServerConnectionManager::InvalidateAndClearAuthToken() {
    233   DCHECK(thread_checker_.CalledOnValidThread());
    234   // Copy over the token to previous invalid token.
    235   if (!auth_token_.empty()) {
    236     previously_invalidated_token.assign(auth_token_);
    237     auth_token_ = std::string();
    238   }
    239 }
    240 
    241 void ServerConnectionManager::SetServerStatus(
    242     HttpResponse::ServerConnectionCode server_status) {
    243   if (server_status_ == server_status)
    244     return;
    245   server_status_ = server_status;
    246   NotifyStatusChanged();
    247 }
    248 
    249 void ServerConnectionManager::NotifyStatusChanged() {
    250   DCHECK(thread_checker_.CalledOnValidThread());
    251   FOR_EACH_OBSERVER(ServerConnectionEventListener, listeners_,
    252      OnServerConnectionEvent(
    253          ServerConnectionEvent(server_status_)));
    254 }
    255 
    256 bool ServerConnectionManager::PostBufferWithCachedAuth(
    257     PostBufferParams* params, ScopedServerStatusWatcher* watcher) {
    258   DCHECK(thread_checker_.CalledOnValidThread());
    259   string path =
    260       MakeSyncServerPath(proto_sync_path(), MakeSyncQueryString(client_id_));
    261   return PostBufferToPath(params, path, auth_token(), watcher);
    262 }
    263 
    264 bool ServerConnectionManager::PostBufferToPath(PostBufferParams* params,
    265     const string& path, const string& auth_token,
    266     ScopedServerStatusWatcher* watcher) {
    267   DCHECK(thread_checker_.CalledOnValidThread());
    268   DCHECK(watcher != NULL);
    269 
    270   // TODO(pavely): crbug.com/273096. Check for "credentials_lost" is added as
    271   // workaround for M29 blocker to avoid sending RPC to sync with known invalid
    272   // token but instead to trigger refreshing token in ProfileSyncService. Need
    273   // to clean it.
    274   if (auth_token.empty() || auth_token == "credentials_lost") {
    275     params->response.server_status = HttpResponse::SYNC_AUTH_ERROR;
    276     return false;
    277   }
    278 
    279   // When our connection object falls out of scope, it clears itself from
    280   // active_connection_.
    281   ScopedConnectionHelper post(this, MakeActiveConnection());
    282   if (!post.get()) {
    283     params->response.server_status = HttpResponse::CONNECTION_UNAVAILABLE;
    284     return false;
    285   }
    286 
    287   // Note that |post| may be aborted by now, which will just cause Init to fail
    288   // with CONNECTION_UNAVAILABLE.
    289   bool ok = post.get()->Init(
    290       path.c_str(), auth_token, params->buffer_in, &params->response);
    291 
    292   if (params->response.server_status == HttpResponse::SYNC_AUTH_ERROR) {
    293     InvalidateAndClearAuthToken();
    294   }
    295 
    296   if (!ok || net::HTTP_OK != params->response.response_code)
    297     return false;
    298 
    299   if (post.get()->ReadBufferResponse(
    300       &params->buffer_out, &params->response, true)) {
    301     params->response.server_status = HttpResponse::SERVER_CONNECTION_OK;
    302     return true;
    303   }
    304   return false;
    305 }
    306 
    307 // Returns the current server parameters in server_url and port.
    308 void ServerConnectionManager::GetServerParameters(string* server_url,
    309                                                   int* port,
    310                                                   bool* use_ssl) const {
    311   if (server_url != NULL)
    312     *server_url = sync_server_;
    313   if (port != NULL)
    314     *port = sync_server_port_;
    315   if (use_ssl != NULL)
    316     *use_ssl = use_ssl_;
    317 }
    318 
    319 std::string ServerConnectionManager::GetServerHost() const {
    320   string server_url;
    321   int port;
    322   bool use_ssl;
    323   GetServerParameters(&server_url, &port, &use_ssl);
    324   // For unit tests.
    325   if (server_url.empty())
    326     return std::string();
    327   // We just want the hostname, so we don't need to switch on use_ssl.
    328   server_url = "http://" + server_url;
    329   GURL gurl(server_url);
    330   DCHECK(gurl.is_valid()) << gurl;
    331   return gurl.host();
    332 }
    333 
    334 void ServerConnectionManager::AddListener(
    335     ServerConnectionEventListener* listener) {
    336   DCHECK(thread_checker_.CalledOnValidThread());
    337   listeners_.AddObserver(listener);
    338 }
    339 
    340 void ServerConnectionManager::RemoveListener(
    341     ServerConnectionEventListener* listener) {
    342   DCHECK(thread_checker_.CalledOnValidThread());
    343   listeners_.RemoveObserver(listener);
    344 }
    345 
    346 ServerConnectionManager::Connection* ServerConnectionManager::MakeConnection()
    347 {
    348   return NULL;  // For testing.
    349 }
    350 
    351 void ServerConnectionManager::TerminateAllIO() {
    352   base::AutoLock lock(terminate_connection_lock_);
    353   terminated_ = true;
    354   if (active_connection_)
    355     active_connection_->Abort();
    356 
    357   // Sever our ties to this connection object. Note that it still may exist,
    358   // since we don't own it, but it has been neutered.
    359   active_connection_ = NULL;
    360 }
    361 
    362 std::ostream& operator << (std::ostream& s, const struct HttpResponse& hr) {
    363   s << " Response Code (bogus on error): " << hr.response_code;
    364   s << " Content-Length (bogus on error): " << hr.content_length;
    365   s << " Server Status: "
    366     << HttpResponse::GetServerConnectionCodeString(hr.server_status);
    367   return s;
    368 }
    369 
    370 }  // namespace syncer
    371