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