Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / net / websockets / websocket_stream.cc
index 507ed9d..005b6c5 100644 (file)
@@ -6,45 +6,82 @@
 
 #include "base/logging.h"
 #include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/base/load_flags.h"
 #include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
 #include "net/http/http_status_code.h"
+#include "net/url_request/redirect_info.h"
 #include "net/url_request/url_request.h"
 #include "net/url_request/url_request_context.h"
 #include "net/websockets/websocket_errors.h"
+#include "net/websockets/websocket_event_interface.h"
 #include "net/websockets/websocket_handshake_constants.h"
 #include "net/websockets/websocket_handshake_stream_base.h"
 #include "net/websockets/websocket_handshake_stream_create_helper.h"
 #include "net/websockets/websocket_test_util.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 namespace net {
 namespace {
 
+// The timeout duration of WebSocket handshake.
+// It is defined as the same value as the TCP connection timeout value in
+// net/socket/websocket_transport_client_socket_pool.cc to make it hard for
+// JavaScript programs to recognize the timeout cause.
+const int kHandshakeTimeoutIntervalInSeconds = 240;
+
 class StreamRequestImpl;
 
 class Delegate : public URLRequest::Delegate {
  public:
-  explicit Delegate(StreamRequestImpl* owner) : owner_(owner) {}
-  virtual ~Delegate() {}
+  enum HandshakeResult {
+    INCOMPLETE,
+    CONNECTED,
+    FAILED,
+    NUM_HANDSHAKE_RESULT_TYPES,
+  };
+
+  explicit Delegate(StreamRequestImpl* owner)
+      : owner_(owner), result_(INCOMPLETE) {}
+  ~Delegate() override {
+    UMA_HISTOGRAM_ENUMERATION(
+        "Net.WebSocket.HandshakeResult", result_, NUM_HANDSHAKE_RESULT_TYPES);
+  }
 
   // Implementation of URLRequest::Delegate methods.
-  virtual void OnResponseStarted(URLRequest* request) OVERRIDE;
+  void OnReceivedRedirect(URLRequest* request,
+                          const RedirectInfo& redirect_info,
+                          bool* defer_redirect) override {
+    // HTTP status codes returned by HttpStreamParser are filtered by
+    // WebSocketBasicHandshakeStream, and only 101, 401 and 407 are permitted
+    // back up the stack to HttpNetworkTransaction. In particular, redirect
+    // codes are never allowed, and so URLRequest never sees a redirect on a
+    // WebSocket request.
+    NOTREACHED();
+  }
 
-  virtual void OnAuthRequired(URLRequest* request,
-                              AuthChallengeInfo* auth_info) OVERRIDE;
+  void OnResponseStarted(URLRequest* request) override;
 
-  virtual void OnCertificateRequested(URLRequest* request,
-                                      SSLCertRequestInfo* cert_request_info)
-      OVERRIDE;
+  void OnAuthRequired(URLRequest* request,
+                      AuthChallengeInfo* auth_info) override;
 
-  virtual void OnSSLCertificateError(URLRequest* request,
-                                     const SSLInfo& ssl_info,
-                                     bool fatal) OVERRIDE;
+  void OnCertificateRequested(URLRequest* request,
+                              SSLCertRequestInfo* cert_request_info) override;
 
-  virtual void OnReadCompleted(URLRequest* request, int bytes_read) OVERRIDE;
+  void OnSSLCertificateError(URLRequest* request,
+                             const SSLInfo& ssl_info,
+                             bool fatal) override;
+
+  void OnReadCompleted(URLRequest* request, int bytes_read) override;
 
  private:
   StreamRequestImpl* owner_;
+  HandshakeResult result_;
 };
 
 class StreamRequestImpl : public WebSocketStreamRequest {
@@ -52,45 +89,96 @@ class StreamRequestImpl : public WebSocketStreamRequest {
   StreamRequestImpl(
       const GURL& url,
       const URLRequestContext* context,
+      const url::Origin& origin,
       scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate,
-      WebSocketHandshakeStreamCreateHelper* create_helper)
+      scoped_ptr<WebSocketHandshakeStreamCreateHelper> create_helper)
       : delegate_(new Delegate(this)),
-        url_request_(url, DEFAULT_PRIORITY, delegate_.get(), context),
+        url_request_(context->CreateRequest(url, DEFAULT_PRIORITY,
+                                            delegate_.get(), NULL)),
         connect_delegate_(connect_delegate.Pass()),
-        create_helper_(create_helper) {}
+        create_helper_(create_helper.release()) {
+    create_helper_->set_failure_message(&failure_message_);
+    HttpRequestHeaders headers;
+    headers.SetHeader(websockets::kUpgrade, websockets::kWebSocketLowercase);
+    headers.SetHeader(HttpRequestHeaders::kConnection, websockets::kUpgrade);
+    headers.SetHeader(HttpRequestHeaders::kOrigin, origin.string());
+    headers.SetHeader(websockets::kSecWebSocketVersion,
+                      websockets::kSupportedVersion);
+    url_request_->SetExtraRequestHeaders(headers);
+
+    // This passes the ownership of |create_helper_| to |url_request_|.
+    url_request_->SetUserData(
+        WebSocketHandshakeStreamBase::CreateHelper::DataKey(),
+        create_helper_);
+    url_request_->SetLoadFlags(LOAD_DISABLE_CACHE |
+                               LOAD_BYPASS_CACHE |
+                               LOAD_DO_NOT_PROMPT_FOR_LOGIN);
+  }
 
   // Destroying this object destroys the URLRequest, which cancels the request
   // and so terminates the handshake if it is incomplete.
-  virtual ~StreamRequestImpl() {}
-
-  URLRequest* url_request() { return &url_request_; }
+  ~StreamRequestImpl() override {}
+
+  void Start(scoped_ptr<base::Timer> timer) {
+    DCHECK(timer);
+    TimeDelta timeout(TimeDelta::FromSeconds(
+        kHandshakeTimeoutIntervalInSeconds));
+    timer_ = timer.Pass();
+    timer_->Start(FROM_HERE, timeout,
+                  base::Bind(&StreamRequestImpl::OnTimeout,
+                             base::Unretained(this)));
+    url_request_->Start();
+  }
 
   void PerformUpgrade() {
-    connect_delegate_->OnSuccess(create_helper_->stream()->Upgrade());
+    DCHECK(timer_);
+    timer_->Stop();
+    connect_delegate_->OnSuccess(create_helper_->Upgrade());
   }
 
   void ReportFailure() {
-    std::string failure_message;
-    if (create_helper_->stream()) {
-      failure_message = create_helper_->stream()->GetFailureMessage();
-    } else {
-      switch (url_request_.status().status()) {
+    DCHECK(timer_);
+    timer_->Stop();
+    if (failure_message_.empty()) {
+      switch (url_request_->status().status()) {
         case URLRequestStatus::SUCCESS:
         case URLRequestStatus::IO_PENDING:
           break;
         case URLRequestStatus::CANCELED:
-          failure_message = "WebSocket opening handshake was canceled";
+          if (url_request_->status().error() == ERR_TIMED_OUT)
+            failure_message_ = "WebSocket opening handshake timed out";
+          else
+            failure_message_ = "WebSocket opening handshake was canceled";
           break;
         case URLRequestStatus::FAILED:
-          failure_message =
+          failure_message_ =
               std::string("Error in connection establishment: ") +
-              ErrorToString(url_request_.status().error());
+              ErrorToString(url_request_->status().error());
           break;
       }
     }
+    ReportFailureWithMessage(failure_message_);
+  }
+
+  void ReportFailureWithMessage(const std::string& failure_message) {
     connect_delegate_->OnFailure(failure_message);
   }
 
+  void OnFinishOpeningHandshake() {
+    WebSocketDispatchOnFinishOpeningHandshake(connect_delegate(),
+                                              url_request_->url(),
+                                              url_request_->response_headers(),
+                                              url_request_->response_time());
+  }
+
+  WebSocketStream::ConnectDelegate* connect_delegate() const {
+    return connect_delegate_.get();
+  }
+
+  void OnTimeout() {
+    url_request_->CancelWithError(ERR_TIMED_OUT);
+  }
+
  private:
   // |delegate_| needs to be declared before |url_request_| so that it gets
   // initialised first.
@@ -98,81 +186,109 @@ class StreamRequestImpl : public WebSocketStreamRequest {
 
   // Deleting the StreamRequestImpl object deletes this URLRequest object,
   // cancelling the whole connection.
-  URLRequest url_request_;
+  scoped_ptr<URLRequest> url_request_;
 
   scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate_;
 
   // Owned by the URLRequest.
   WebSocketHandshakeStreamCreateHelper* create_helper_;
+
+  // The failure message supplied by WebSocketBasicHandshakeStream, if any.
+  std::string failure_message_;
+
+  // A timer for handshake timeout.
+  scoped_ptr<base::Timer> timer_;
+};
+
+class SSLErrorCallbacks : public WebSocketEventInterface::SSLErrorCallbacks {
+ public:
+  explicit SSLErrorCallbacks(URLRequest* url_request)
+      : url_request_(url_request) {}
+
+  void CancelSSLRequest(int error, const SSLInfo* ssl_info) override {
+    if (ssl_info) {
+      url_request_->CancelWithSSLError(error, *ssl_info);
+    } else {
+      url_request_->CancelWithError(error);
+    }
+  }
+
+  void ContinueSSLRequest() override {
+    url_request_->ContinueDespiteLastError();
+  }
+
+ private:
+  URLRequest* url_request_;
 };
 
 void Delegate::OnResponseStarted(URLRequest* request) {
-  switch (request->GetResponseCode()) {
+  // All error codes, including OK and ABORTED, as with
+  // Net.ErrorCodesForMainFrame3
+  UMA_HISTOGRAM_SPARSE_SLOWLY("Net.WebSocket.ErrorCodes",
+                              -request->status().error());
+  if (!request->status().is_success()) {
+    DVLOG(3) << "OnResponseStarted (request failed)";
+    owner_->ReportFailure();
+    return;
+  }
+  const int response_code = request->GetResponseCode();
+  DVLOG(3) << "OnResponseStarted (response code " << response_code << ")";
+  switch (response_code) {
     case HTTP_SWITCHING_PROTOCOLS:
+      result_ = CONNECTED;
       owner_->PerformUpgrade();
       return;
 
     case HTTP_UNAUTHORIZED:
+      result_ = FAILED;
+      owner_->OnFinishOpeningHandshake();
+      owner_->ReportFailureWithMessage(
+          "HTTP Authentication failed; no valid credentials available");
+      return;
+
     case HTTP_PROXY_AUTHENTICATION_REQUIRED:
+      result_ = FAILED;
+      owner_->OnFinishOpeningHandshake();
+      owner_->ReportFailureWithMessage("Proxy authentication failed");
       return;
 
     default:
+      result_ = FAILED;
       owner_->ReportFailure();
   }
 }
 
 void Delegate::OnAuthRequired(URLRequest* request,
                               AuthChallengeInfo* auth_info) {
+  // This should only be called if credentials are not already stored.
   request->CancelAuth();
 }
 
 void Delegate::OnCertificateRequested(URLRequest* request,
                                       SSLCertRequestInfo* cert_request_info) {
-  request->ContinueWithCertificate(NULL);
+  // This method is called when a client certificate is requested, and the
+  // request context does not already contain a client certificate selection for
+  // the endpoint. In this case, a main frame resource request would pop-up UI
+  // to permit selection of a client certificate, but since WebSockets are
+  // sub-resources they should not pop-up UI and so there is nothing more we can
+  // do.
+  request->Cancel();
 }
 
 void Delegate::OnSSLCertificateError(URLRequest* request,
                                      const SSLInfo& ssl_info,
                                      bool fatal) {
-  request->Cancel();
+  owner_->connect_delegate()->OnSSLCertificateError(
+      scoped_ptr<WebSocketEventInterface::SSLErrorCallbacks>(
+          new SSLErrorCallbacks(request)),
+      ssl_info,
+      fatal);
 }
 
 void Delegate::OnReadCompleted(URLRequest* request, int bytes_read) {
   NOTREACHED();
 }
 
-// Internal implementation of CreateAndConnectStream and
-// CreateAndConnectStreamForTesting.
-scoped_ptr<WebSocketStreamRequest> CreateAndConnectStreamWithCreateHelper(
-    const GURL& socket_url,
-    scoped_ptr<WebSocketHandshakeStreamCreateHelper> create_helper,
-    const GURL& origin,
-    URLRequestContext* url_request_context,
-    const BoundNetLog& net_log,
-    scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate) {
-  scoped_ptr<StreamRequestImpl> request(
-      new StreamRequestImpl(socket_url,
-                            url_request_context,
-                            connect_delegate.Pass(),
-                            create_helper.get()));
-  HttpRequestHeaders headers;
-  headers.SetHeader(websockets::kUpgrade, websockets::kWebSocketLowercase);
-  headers.SetHeader(HttpRequestHeaders::kConnection, websockets::kUpgrade);
-  headers.SetHeader(HttpRequestHeaders::kOrigin, origin.spec());
-  // TODO(ricea): Move the version number to websocket_handshake_constants.h
-  headers.SetHeader(websockets::kSecWebSocketVersion,
-                    websockets::kSupportedVersion);
-  request->url_request()->SetExtraRequestHeaders(headers);
-  request->url_request()->SetUserData(
-      WebSocketHandshakeStreamBase::CreateHelper::DataKey(),
-      create_helper.release());
-  request->url_request()->SetLoadFlags(LOAD_DISABLE_CACHE |
-                                       LOAD_BYPASS_CACHE |
-                                       LOAD_DO_NOT_PROMPT_FOR_LOGIN);
-  request->url_request()->Start();
-  return request.PassAs<WebSocketStreamRequest>();
-}
-
 }  // namespace
 
 WebSocketStreamRequest::~WebSocketStreamRequest() {}
@@ -185,35 +301,56 @@ WebSocketStream::ConnectDelegate::~ConnectDelegate() {}
 scoped_ptr<WebSocketStreamRequest> WebSocketStream::CreateAndConnectStream(
     const GURL& socket_url,
     const std::vector<std::string>& requested_subprotocols,
-    const GURL& origin,
+    const url::Origin& origin,
     URLRequestContext* url_request_context,
     const BoundNetLog& net_log,
     scoped_ptr<ConnectDelegate> connect_delegate) {
   scoped_ptr<WebSocketHandshakeStreamCreateHelper> create_helper(
       new WebSocketHandshakeStreamCreateHelper(connect_delegate.get(),
                                                requested_subprotocols));
-  return CreateAndConnectStreamWithCreateHelper(socket_url,
-                                                create_helper.Pass(),
-                                                origin,
-                                                url_request_context,
-                                                net_log,
-                                                connect_delegate.Pass());
+  scoped_ptr<StreamRequestImpl> request(
+      new StreamRequestImpl(socket_url,
+                            url_request_context,
+                            origin,
+                            connect_delegate.Pass(),
+                            create_helper.Pass()));
+  request->Start(scoped_ptr<base::Timer>(new base::Timer(false, false)));
+  return request.Pass();
 }
 
 // This is declared in websocket_test_util.h.
 scoped_ptr<WebSocketStreamRequest> CreateAndConnectStreamForTesting(
-      const GURL& socket_url,
-      scoped_ptr<WebSocketHandshakeStreamCreateHelper> create_helper,
-      const GURL& origin,
-      URLRequestContext* url_request_context,
-      const BoundNetLog& net_log,
-      scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate) {
-  return CreateAndConnectStreamWithCreateHelper(socket_url,
-                                                create_helper.Pass(),
-                                                origin,
-                                                url_request_context,
-                                                net_log,
-                                                connect_delegate.Pass());
+    const GURL& socket_url,
+    scoped_ptr<WebSocketHandshakeStreamCreateHelper> create_helper,
+    const url::Origin& origin,
+    URLRequestContext* url_request_context,
+    const BoundNetLog& net_log,
+    scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate,
+    scoped_ptr<base::Timer> timer) {
+  scoped_ptr<StreamRequestImpl> request(
+      new StreamRequestImpl(socket_url,
+                            url_request_context,
+                            origin,
+                            connect_delegate.Pass(),
+                            create_helper.Pass()));
+  request->Start(timer.Pass());
+  return request.Pass();
+}
+
+void WebSocketDispatchOnFinishOpeningHandshake(
+    WebSocketStream::ConnectDelegate* connect_delegate,
+    const GURL& url,
+    const scoped_refptr<HttpResponseHeaders>& headers,
+    base::Time response_time) {
+  DCHECK(connect_delegate);
+  if (headers.get()) {
+    connect_delegate->OnFinishOpeningHandshake(make_scoped_ptr(
+        new WebSocketHandshakeResponseInfo(url,
+                                           headers->response_code(),
+                                           headers->GetStatusText(),
+                                           headers,
+                                           response_time)));
+  }
 }
 
 }  // namespace net