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