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
24namespace syncer {
25
26using std::ostream;
27using std::string;
28using std::vector;
29
30static const char kSyncServerSyncPath[] = "/command/";
31
32HttpResponse::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
40const 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.
58HttpResponse::ServerConnectionCode
59HttpResponse::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
74ServerConnectionManager::Connection::Connection(
75    ServerConnectionManager* scm) : scm_(scm) {
76}
77
78ServerConnectionManager::Connection::~Connection() {
79}
80
81bool 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
102bool 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
117ServerConnectionManager::ScopedConnectionHelper::ScopedConnectionHelper(
118    ServerConnectionManager* manager, Connection* connection)
119    : manager_(manager), connection_(connection) {}
120
121ServerConnectionManager::ScopedConnectionHelper::~ScopedConnectionHelper() {
122  if (connection_)
123    manager_->OnConnectionDestroyed(connection_.get());
124  connection_.reset();
125}
126
127ServerConnectionManager::Connection*
128ServerConnectionManager::ScopedConnectionHelper::get() {
129  return connection_.get();
130}
131
132namespace {
133
134string 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.
146string 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
158int 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
166ScopedServerStatusWatcher::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
173ScopedServerStatusWatcher::~ScopedServerStatusWatcher() {
174  conn_mgr_->SetServerStatus(response_->server_status);
175}
176
177ServerConnectionManager::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
200ServerConnectionManager::~ServerConnectionManager() {
201  if (signal_handler_registered_) {
202    cancelation_signal_->UnregisterHandler(this);
203  }
204}
205
206ServerConnectionManager::Connection*
207ServerConnectionManager::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
217void 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
229bool 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
247void 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
256void 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
268void ServerConnectionManager::NotifyStatusChanged() {
269  DCHECK(thread_checker_.CalledOnValidThread());
270  FOR_EACH_OBSERVER(ServerConnectionEventListener, listeners_,
271     OnServerConnectionEvent(
272         ServerConnectionEvent(server_status_)));
273}
274
275bool 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
283bool 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, &params->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      &params->buffer_out, &params->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.
329void 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
340std::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
355void ServerConnectionManager::AddListener(
356    ServerConnectionEventListener* listener) {
357  DCHECK(thread_checker_.CalledOnValidThread());
358  listeners_.AddObserver(listener);
359}
360
361void ServerConnectionManager::RemoveListener(
362    ServerConnectionEventListener* listener) {
363  DCHECK(thread_checker_.CalledOnValidThread());
364  listeners_.RemoveObserver(listener);
365}
366
367ServerConnectionManager::Connection* ServerConnectionManager::MakeConnection()
368{
369  return NULL;  // For testing.
370}
371
372void 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
383std::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