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
ReadBufferResponse(string * buffer_out,HttpResponse * response,bool require_response)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
ReadDownloadResponse(HttpResponse * response,string * buffer_out)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
StripTrailingSlash(const string & s)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.
MakeConnectionURL(const string & sync_server,const string & path,bool use_ssl) const92 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
ReadResponse(string * out_buffer,int length)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
ScopedServerStatusWatcher(ServerConnectionManager * conn_mgr,HttpResponse * response)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
~ScopedServerStatusWatcher()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
ServerConnectionManager(const string & server,int port,bool use_ssl,const string & user_agent)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
~ServerConnectionManager()156 ServerConnectionManager::~ServerConnectionManager() {
157 delete channel_;
158 }
159
NotifyStatusChanged()160 void ServerConnectionManager::NotifyStatusChanged() {
161 listeners_->Notify(&ServerConnectionEventListener::OnServerConnectionEvent,
162 ServerConnectionEvent2(server_status_, server_reachable_));
163 }
164
PostBufferWithCachedAuth(const PostBufferParams * params,ScopedServerStatusWatcher * watcher)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
PostBufferToPath(const PostBufferParams * params,const string & path,const string & auth_token,ScopedServerStatusWatcher * watcher)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
CheckTime(int32 * out_time)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
IsServerReachable()239 bool ServerConnectionManager::IsServerReachable() {
240 int32 time;
241 return CheckTime(&time);
242 }
243
IsUserAuthenticated()244 bool ServerConnectionManager::IsUserAuthenticated() {
245 return IsGoodReplyFromServer(server_status_);
246 }
247
CheckServerReachable()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
kill()257 void ServerConnectionManager::kill() {
258 {
259 base::AutoLock lock(terminate_all_io_mutex_);
260 terminate_all_io_ = true;
261 }
262 }
263
ResetConnection()264 void ServerConnectionManager::ResetConnection() {
265 base::subtle::NoBarrier_AtomicIncrement(&reset_count_, 1);
266 }
267
IncrementErrorCount()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
SetServerParameters(const string & server_url,int port,bool use_ssl)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.
GetServerParameters(string * server_url,int * port,bool * use_ssl) const306 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
GetServerHost() const318 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
AddListener(ServerConnectionEventListener * listener)333 void ServerConnectionManager::AddListener(
334 ServerConnectionEventListener* listener) {
335 listeners_->AddObserver(listener);
336 }
337
RemoveListener(ServerConnectionEventListener * listener)338 void ServerConnectionManager::RemoveListener(
339 ServerConnectionEventListener* listener) {
340 listeners_->RemoveObserver(listener);
341 }
342
MakePost()343 ServerConnectionManager::Post* ServerConnectionManager::MakePost() {
344 return NULL; // For testing.
345 }
346
FillMessageWithShareDetails(sync_pb::ClientToServerMessage * csm,syncable::DirectoryManager * manager,const std::string & share)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
operator <<(std::ostream & s,const struct HttpResponse & hr)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