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