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, ¶ms->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 ¶ms->buffer_out, ¶ms->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