Home | History | Annotate | Download | only in net
      1 // Copyright (c) 2011 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 "chrome/browser/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/command_line.h"
     14 #include "build/build_config.h"
     15 #include "chrome/browser/sync/engine/net/url_translator.h"
     16 #include "chrome/browser/sync/engine/syncapi.h"
     17 #include "chrome/browser/sync/engine/syncer.h"
     18 #include "chrome/browser/sync/engine/syncproto.h"
     19 #include "chrome/browser/sync/protocol/sync.pb.h"
     20 #include "chrome/browser/sync/syncable/directory_manager.h"
     21 #include "chrome/common/chrome_switches.h"
     22 #include "chrome/common/net/http_return.h"
     23 #include "googleurl/src/gurl.h"
     24 
     25 namespace browser_sync {
     26 
     27 using std::ostream;
     28 using std::string;
     29 using std::vector;
     30 
     31 static const char kSyncServerSyncPath[] = "/command/";
     32 
     33 // At the /time/ path of the sync server, we expect to find a very simple
     34 // time of day service that we can use to synchronize the local clock with
     35 // server time.
     36 static const char kSyncServerGetTimePath[] = "/time";
     37 
     38 static const ServerConnectionEvent shutdown_event =
     39   { ServerConnectionEvent::SHUTDOWN, HttpResponse::CONNECTION_UNAVAILABLE,
     40     false };
     41 
     42 bool ServerConnectionManager::Post::ReadBufferResponse(
     43     string* buffer_out,
     44     HttpResponse* response,
     45     bool require_response) {
     46   if (RC_REQUEST_OK != response->response_code) {
     47     response->server_status = HttpResponse::SYNC_SERVER_ERROR;
     48     return false;
     49   }
     50 
     51   if (require_response && (1 > response->content_length))
     52     return false;
     53 
     54   const int64 bytes_read = ReadResponse(buffer_out,
     55       static_cast<int>(response->content_length));
     56   if (bytes_read != response->content_length) {
     57     response->server_status = HttpResponse::IO_ERROR;
     58     return false;
     59   }
     60   return true;
     61 }
     62 
     63 bool ServerConnectionManager::Post::ReadDownloadResponse(
     64     HttpResponse* response,
     65     string* buffer_out) {
     66   const int64 bytes_read = ReadResponse(buffer_out,
     67       static_cast<int>(response->content_length));
     68 
     69   if (bytes_read != response->content_length) {
     70     LOG(ERROR) << "Mismatched content lengths, server claimed " <<
     71         response->content_length << ", but sent " << bytes_read;
     72     response->server_status = HttpResponse::IO_ERROR;
     73     return false;
     74   }
     75   return true;
     76 }
     77 
     78 namespace {
     79 
     80 string StripTrailingSlash(const string& s) {
     81   int stripped_end_pos = s.size();
     82   if (s.at(stripped_end_pos - 1) == '/') {
     83     stripped_end_pos = stripped_end_pos - 1;
     84   }
     85 
     86   return s.substr(0, stripped_end_pos);
     87 }
     88 
     89 }  // namespace
     90 
     91 // TODO(chron): Use a GURL instead of string concatenation.
     92 string ServerConnectionManager::Post::MakeConnectionURL(
     93     const string& sync_server,
     94     const string& path,
     95     bool use_ssl) const {
     96   string connection_url = (use_ssl ? "https://" : "http://");
     97   connection_url += sync_server;
     98   connection_url = StripTrailingSlash(connection_url);
     99   connection_url += path;
    100 
    101   return connection_url;
    102 }
    103 
    104 int ServerConnectionManager::Post::ReadResponse(string* out_buffer,
    105                                                 int length) {
    106   int bytes_read = buffer_.length();
    107   CHECK(length <= bytes_read);
    108   out_buffer->assign(buffer_);
    109   return bytes_read;
    110 }
    111 
    112 ScopedServerStatusWatcher::ScopedServerStatusWatcher(
    113     ServerConnectionManager* conn_mgr, HttpResponse* response)
    114     : conn_mgr_(conn_mgr),
    115       response_(response),
    116       reset_count_(conn_mgr->reset_count_),
    117       server_reachable_(conn_mgr->server_reachable_) {
    118   response->server_status = conn_mgr->server_status_;
    119 }
    120 
    121 ScopedServerStatusWatcher::~ScopedServerStatusWatcher() {
    122   // Don't update the status of the connection if it has been reset.
    123   // TODO(timsteele): Do we need this? Is this used by multiple threads?
    124   if (reset_count_ != conn_mgr_->reset_count_)
    125     return;
    126   if (conn_mgr_->server_status_ != response_->server_status) {
    127     conn_mgr_->server_status_ = response_->server_status;
    128     conn_mgr_->NotifyStatusChanged();
    129     return;
    130   }
    131   // Notify if we've gone on or offline.
    132   if (server_reachable_ != conn_mgr_->server_reachable_)
    133     conn_mgr_->NotifyStatusChanged();
    134 }
    135 
    136 ServerConnectionManager::ServerConnectionManager(
    137     const string& server,
    138     int port,
    139     bool use_ssl,
    140     const string& user_agent)
    141     : sync_server_(server),
    142       sync_server_port_(port),
    143       user_agent_(user_agent),
    144       use_ssl_(use_ssl),
    145       proto_sync_path_(kSyncServerSyncPath),
    146       get_time_path_(kSyncServerGetTimePath),
    147       error_count_(0),
    148       channel_(new Channel(shutdown_event)),
    149       listeners_(new ObserverListThreadSafe<ServerConnectionEventListener>()),
    150       server_status_(HttpResponse::NONE),
    151       server_reachable_(false),
    152       reset_count_(0),
    153       terminate_all_io_(false) {
    154 }
    155 
    156 ServerConnectionManager::~ServerConnectionManager() {
    157   delete channel_;
    158 }
    159 
    160 void ServerConnectionManager::NotifyStatusChanged() {
    161   listeners_->Notify(&ServerConnectionEventListener::OnServerConnectionEvent,
    162       ServerConnectionEvent2(server_status_, server_reachable_));
    163 }
    164 
    165 bool ServerConnectionManager::PostBufferWithCachedAuth(
    166     const PostBufferParams* params, ScopedServerStatusWatcher* watcher) {
    167   string path =
    168       MakeSyncServerPath(proto_sync_path(), MakeSyncQueryString(client_id_));
    169   return PostBufferToPath(params, path, auth_token(), watcher);
    170 }
    171 
    172 bool ServerConnectionManager::PostBufferToPath(const PostBufferParams* params,
    173     const string& path, const string& auth_token,
    174     ScopedServerStatusWatcher* watcher) {
    175   DCHECK(watcher != NULL);
    176   scoped_ptr<Post> post(MakePost());
    177   post->set_timing_info(params->timing_info);
    178   bool ok = post->Init(path.c_str(), auth_token, params->buffer_in,
    179                        params->response);
    180 
    181   if (!ok || RC_REQUEST_OK != params->response->response_code) {
    182     IncrementErrorCount();
    183     return false;
    184   }
    185 
    186   if (post->ReadBufferResponse(params->buffer_out, params->response, true)) {
    187     params->response->server_status = HttpResponse::SERVER_CONNECTION_OK;
    188     server_reachable_ = true;
    189     return true;
    190   }
    191   return false;
    192 }
    193 
    194 bool ServerConnectionManager::CheckTime(int32* out_time) {
    195   // Verify that the server really is reachable by checking the time. We need
    196   // to do this because of wifi interstitials that intercept messages from the
    197   // client and return HTTP OK instead of a redirect.
    198   HttpResponse response;
    199   ScopedServerStatusWatcher watcher(this, &response);
    200   string post_body = "command=get_time";
    201 
    202   // We only retry the CheckTime call if we were reset during the CheckTime
    203   // attempt. We only try 3 times in case we're in a reset loop elsewhere.
    204   base::subtle::AtomicWord start_reset_count = reset_count_ - 1;
    205   for (int i = 0 ; i < 3 && start_reset_count != reset_count_ ; i++) {
    206     start_reset_count = reset_count_;
    207     scoped_ptr<Post> post(MakePost());
    208 
    209     // Note that the server's get_time path doesn't require authentication.
    210     string get_time_path =
    211         MakeSyncServerPath(kSyncServerGetTimePath, post_body);
    212     VLOG(1) << "Requesting get_time from:" << get_time_path;
    213 
    214     string blank_post_body;
    215     bool ok = post->Init(get_time_path.c_str(), blank_post_body,
    216         blank_post_body, &response);
    217     if (!ok) {
    218       VLOG(1) << "Unable to check the time";
    219       continue;
    220     }
    221     string time_response;
    222     time_response.resize(
    223         static_cast<string::size_type>(response.content_length));
    224     ok = post->ReadDownloadResponse(&response, &time_response);
    225     if (!ok || string::npos !=
    226         time_response.find_first_not_of("0123456789")) {
    227       LOG(ERROR) << "unable to read a non-numeric response from get_time:"
    228             << time_response;
    229       continue;
    230     }
    231     *out_time = atoi(time_response.c_str());
    232     VLOG(1) << "Server was reachable.";
    233     return true;
    234   }
    235   IncrementErrorCount();
    236   return false;
    237 }
    238 
    239 bool ServerConnectionManager::IsServerReachable() {
    240   int32 time;
    241   return CheckTime(&time);
    242 }
    243 
    244 bool ServerConnectionManager::IsUserAuthenticated() {
    245   return IsGoodReplyFromServer(server_status_);
    246 }
    247 
    248 bool ServerConnectionManager::CheckServerReachable() {
    249   const bool server_is_reachable = IsServerReachable();
    250   if (server_reachable_ != server_is_reachable) {
    251     server_reachable_ = server_is_reachable;
    252     NotifyStatusChanged();
    253   }
    254   return server_is_reachable;
    255 }
    256 
    257 void ServerConnectionManager::kill() {
    258   {
    259     base::AutoLock lock(terminate_all_io_mutex_);
    260     terminate_all_io_ = true;
    261   }
    262 }
    263 
    264 void ServerConnectionManager::ResetConnection() {
    265   base::subtle::NoBarrier_AtomicIncrement(&reset_count_, 1);
    266 }
    267 
    268 bool ServerConnectionManager::IncrementErrorCount() {
    269   error_count_mutex_.Acquire();
    270   error_count_++;
    271 
    272   if (error_count_ > kMaxConnectionErrorsBeforeReset) {
    273     error_count_ = 0;
    274 
    275     // Be careful with this mutex because calling out to other methods can
    276     // result in being called back. Unlock it here to prevent any potential
    277     // double-acquisitions.
    278     error_count_mutex_.Release();
    279 
    280     if (!IsServerReachable()) {
    281       LOG(WARNING) << "Too many connection failures, server is not reachable. "
    282                    << "Resetting connections.";
    283       ResetConnection();
    284     } else {
    285       LOG(WARNING) << "Multiple connection failures while server is reachable.";
    286     }
    287     return false;
    288   }
    289 
    290   error_count_mutex_.Release();
    291   return true;
    292 }
    293 
    294 void ServerConnectionManager::SetServerParameters(const string& server_url,
    295                                                   int port,
    296                                                   bool use_ssl) {
    297   {
    298     base::AutoLock lock(server_parameters_mutex_);
    299     sync_server_ = server_url;
    300     sync_server_port_ = port;
    301     use_ssl_ = use_ssl;
    302   }
    303 }
    304 
    305 // Returns the current server parameters in server_url and port.
    306 void ServerConnectionManager::GetServerParameters(string* server_url,
    307                                                   int* port,
    308                                                   bool* use_ssl) const {
    309   base::AutoLock lock(server_parameters_mutex_);
    310   if (server_url != NULL)
    311     *server_url = sync_server_;
    312   if (port != NULL)
    313     *port = sync_server_port_;
    314   if (use_ssl != NULL)
    315     *use_ssl = use_ssl_;
    316 }
    317 
    318 std::string ServerConnectionManager::GetServerHost() const {
    319   string server_url;
    320   int port;
    321   bool use_ssl;
    322   GetServerParameters(&server_url, &port, &use_ssl);
    323   // For unit tests.
    324   if (server_url.empty())
    325     return std::string();
    326   // We just want the hostname, so we don't need to switch on use_ssl.
    327   server_url = "http://" + server_url;
    328   GURL gurl(server_url);
    329   DCHECK(gurl.is_valid()) << gurl;
    330   return gurl.host();
    331 }
    332 
    333 void ServerConnectionManager::AddListener(
    334     ServerConnectionEventListener* listener) {
    335   listeners_->AddObserver(listener);
    336 }
    337 
    338 void ServerConnectionManager::RemoveListener(
    339     ServerConnectionEventListener* listener) {
    340   listeners_->RemoveObserver(listener);
    341 }
    342 
    343 ServerConnectionManager::Post* ServerConnectionManager::MakePost() {
    344   return NULL;  // For testing.
    345 }
    346 
    347 bool FillMessageWithShareDetails(sync_pb::ClientToServerMessage* csm,
    348                                  syncable::DirectoryManager* manager,
    349                                  const std::string& share) {
    350   syncable::ScopedDirLookup dir(manager, share);
    351   if (!dir.good()) {
    352     VLOG(1) << "Dir lookup failed";
    353     return false;
    354   }
    355   string birthday = dir->store_birthday();
    356   if (!birthday.empty())
    357     csm->set_store_birthday(birthday);
    358   csm->set_share(share);
    359   return true;
    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: " << hr.server_status;
    366   return s;
    367 }
    368 
    369 }  // namespace browser_sync
    370