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::OnInvalidationCredentialsRejected() {
    248   InvalidateAndClearAuthToken();
    249   SetServerStatus(HttpResponse::SYNC_AUTH_ERROR);
    250 }
    251 
    252 void ServerConnectionManager::InvalidateAndClearAuthToken() {
    253   DCHECK(thread_checker_.CalledOnValidThread());
    254   // Copy over the token to previous invalid token.
    255   if (!auth_token_.empty()) {
    256     previously_invalidated_token.assign(auth_token_);
    257     auth_token_ = std::string();
    258   }
    259 }
    260 
    261 void ServerConnectionManager::SetServerStatus(
    262     HttpResponse::ServerConnectionCode server_status) {
    263   // SYNC_AUTH_ERROR is permanent error. Need to notify observer to take
    264   // action externally to resolve.
    265   if (server_status != HttpResponse::SYNC_AUTH_ERROR &&
    266       server_status_ == server_status) {
    267     return;
    268   }
    269   server_status_ = server_status;
    270   NotifyStatusChanged();
    271 }
    272 
    273 void ServerConnectionManager::NotifyStatusChanged() {
    274   DCHECK(thread_checker_.CalledOnValidThread());
    275   FOR_EACH_OBSERVER(ServerConnectionEventListener, listeners_,
    276      OnServerConnectionEvent(
    277          ServerConnectionEvent(server_status_)));
    278 }
    279 
    280 bool ServerConnectionManager::PostBufferWithCachedAuth(
    281     PostBufferParams* params, ScopedServerStatusWatcher* watcher) {
    282   DCHECK(thread_checker_.CalledOnValidThread());
    283   string path =
    284       MakeSyncServerPath(proto_sync_path(), MakeSyncQueryString(client_id_));
    285   return PostBufferToPath(params, path, auth_token(), watcher);
    286 }
    287 
    288 bool ServerConnectionManager::PostBufferToPath(PostBufferParams* params,
    289     const string& path, const string& auth_token,
    290     ScopedServerStatusWatcher* watcher) {
    291   DCHECK(thread_checker_.CalledOnValidThread());
    292   DCHECK(watcher != NULL);
    293 
    294   // TODO(pavely): crbug.com/273096. Check for "credentials_lost" is added as
    295   // workaround for M29 blocker to avoid sending RPC to sync with known invalid
    296   // token but instead to trigger refreshing token in ProfileSyncService. Need
    297   // to clean it.
    298   if (auth_token.empty() || auth_token == "credentials_lost") {
    299     params->response.server_status = HttpResponse::SYNC_AUTH_ERROR;
    300     // Print a log to distinguish this "known failure" from others.
    301     LOG(WARNING) << "ServerConnectionManager forcing SYNC_AUTH_ERROR";
    302     return false;
    303   }
    304 
    305   // When our connection object falls out of scope, it clears itself from
    306   // active_connection_.
    307   ScopedConnectionHelper post(this, MakeActiveConnection());
    308   if (!post.get()) {
    309     params->response.server_status = HttpResponse::CONNECTION_UNAVAILABLE;
    310     return false;
    311   }
    312 
    313   // Note that |post| may be aborted by now, which will just cause Init to fail
    314   // with CONNECTION_UNAVAILABLE.
    315   bool ok = post.get()->Init(
    316       path.c_str(), auth_token, params->buffer_in, &params->response);
    317 
    318   if (params->response.server_status == HttpResponse::SYNC_AUTH_ERROR) {
    319     InvalidateAndClearAuthToken();
    320   }
    321 
    322   if (!ok || net::HTTP_OK != params->response.response_code)
    323     return false;
    324 
    325   if (post.get()->ReadBufferResponse(
    326       &params->buffer_out, &params->response, true)) {
    327     params->response.server_status = HttpResponse::SERVER_CONNECTION_OK;
    328     return true;
    329   }
    330   return false;
    331 }
    332 
    333 // Returns the current server parameters in server_url and port.
    334 void ServerConnectionManager::GetServerParameters(string* server_url,
    335                                                   int* port,
    336                                                   bool* use_ssl) const {
    337   if (server_url != NULL)
    338     *server_url = sync_server_;
    339   if (port != NULL)
    340     *port = sync_server_port_;
    341   if (use_ssl != NULL)
    342     *use_ssl = use_ssl_;
    343 }
    344 
    345 std::string ServerConnectionManager::GetServerHost() const {
    346   string server_url;
    347   int port;
    348   bool use_ssl;
    349   GetServerParameters(&server_url, &port, &use_ssl);
    350   // For unit tests.
    351   if (server_url.empty())
    352     return std::string();
    353   // We just want the hostname, so we don't need to switch on use_ssl.
    354   server_url = "http://" + server_url;
    355   GURL gurl(server_url);
    356   DCHECK(gurl.is_valid()) << gurl;
    357   return gurl.host();
    358 }
    359 
    360 void ServerConnectionManager::AddListener(
    361     ServerConnectionEventListener* listener) {
    362   DCHECK(thread_checker_.CalledOnValidThread());
    363   listeners_.AddObserver(listener);
    364 }
    365 
    366 void ServerConnectionManager::RemoveListener(
    367     ServerConnectionEventListener* listener) {
    368   DCHECK(thread_checker_.CalledOnValidThread());
    369   listeners_.RemoveObserver(listener);
    370 }
    371 
    372 ServerConnectionManager::Connection* ServerConnectionManager::MakeConnection()
    373 {
    374   return NULL;  // For testing.
    375 }
    376 
    377 void ServerConnectionManager::OnSignalReceived() {
    378   base::AutoLock lock(terminate_connection_lock_);
    379   terminated_ = true;
    380   if (active_connection_)
    381     active_connection_->Abort();
    382 
    383   // Sever our ties to this connection object. Note that it still may exist,
    384   // since we don't own it, but it has been neutered.
    385   active_connection_ = NULL;
    386 }
    387 
    388 std::ostream& operator << (std::ostream& s, const struct HttpResponse& hr) {
    389   s << " Response Code (bogus on error): " << hr.response_code;
    390   s << " Content-Length (bogus on error): " << hr.content_length;
    391   s << " Server Status: "
    392     << HttpResponse::GetServerConnectionCodeString(hr.server_status);
    393   return s;
    394 }
    395 
    396 }  // namespace syncer
    397