summaryrefslogtreecommitdiffstats
path: root/net/websockets/websocket.cc
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2010-07-29 17:14:53 +0100
committerBen Murdoch <benm@google.com>2010-08-04 14:29:45 +0100
commitc407dc5cd9bdc5668497f21b26b09d988ab439de (patch)
tree7eaf8707c0309516bdb042ad976feedaf72b0bb1 /net/websockets/websocket.cc
parent0998b1cdac5733f299c12d88bc31ef9c8035b8fa (diff)
downloadexternal_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.zip
external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.gz
external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.bz2
Merge Chromium src@r53293
Change-Id: Ia79acf8670f385cee48c45b0a75371d8e950af34
Diffstat (limited to 'net/websockets/websocket.cc')
-rw-r--r--net/websockets/websocket.cc408
1 files changed, 198 insertions, 210 deletions
diff --git a/net/websockets/websocket.cc b/net/websockets/websocket.cc
index ad7acaf..fa6f180 100644
--- a/net/websockets/websocket.cc
+++ b/net/websockets/websocket.cc
@@ -8,40 +8,30 @@
#include "net/websockets/websocket.h"
#include "base/message_loop.h"
-#include "net/http/http_response_headers.h"
-#include "net/http/http_util.h"
+#include "net/websockets/websocket_handshake.h"
+#include "net/websockets/websocket_handshake_draft75.h"
namespace net {
-static const int kWebSocketPort = 80;
-static const int kSecureWebSocketPort = 443;
-
-static const char kServerHandshakeHeader[] =
- "HTTP/1.1 101 Web Socket Protocol Handshake\r\n";
-static const size_t kServerHandshakeHeaderLength =
- sizeof(kServerHandshakeHeader) - 1;
-
-static const char kUpgradeHeader[] = "Upgrade: WebSocket\r\n";
-static const size_t kUpgradeHeaderLength = sizeof(kUpgradeHeader) - 1;
-
-static const char kConnectionHeader[] = "Connection: Upgrade\r\n";
-static const size_t kConnectionHeaderLength = sizeof(kConnectionHeader) - 1;
-
-bool WebSocket::Request::is_secure() const {
- return url_.SchemeIs("wss");
-}
+static const char kClosingFrame[2] = {'\xff', '\x00'};
+static int64 kClosingHandshakeTimeout = 1000; // msec.
WebSocket::WebSocket(Request* request, WebSocketDelegate* delegate)
: ready_state_(INITIALIZED),
- mode_(MODE_INCOMPLETE),
request_(request),
+ handshake_(NULL),
delegate_(delegate),
origin_loop_(MessageLoop::current()),
socket_stream_(NULL),
max_pending_send_allowed_(0),
current_read_buf_(NULL),
read_consumed_len_(0),
- current_write_buf_(NULL) {
+ current_write_buf_(NULL),
+ server_closing_handshake_(false),
+ client_closing_handshake_(false),
+ closing_handshake_started_(false),
+ force_close_task_(NULL),
+ closing_handshake_timeout_(kClosingHandshakeTimeout) {
DCHECK(request_.get());
DCHECK(delegate_);
DCHECK(origin_loop_);
@@ -74,6 +64,13 @@ void WebSocket::Connect() {
}
void WebSocket::Send(const std::string& msg) {
+ if (ready_state_ == CLOSING || ready_state_ == CLOSED) {
+ return;
+ }
+ if (client_closing_handshake_) {
+ // We must not send any data after we start the WebSocket closing handshake.
+ return;
+ }
DCHECK(ready_state_ == OPEN);
DCHECK(MessageLoop::current() == origin_loop_);
@@ -89,23 +86,59 @@ void WebSocket::Send(const std::string& msg) {
void WebSocket::Close() {
DCHECK(MessageLoop::current() == origin_loop_);
+ // If connection has not yet started, do nothing.
if (ready_state_ == INITIALIZED) {
DCHECK(!socket_stream_);
ready_state_ = CLOSED;
return;
}
- if (ready_state_ != CLOSED) {
+
+ // If the readyState attribute is in the CLOSING or CLOSED state, do nothing
+ if (ready_state_ == CLOSING || ready_state_ == CLOSED)
+ return;
+
+ if (request_->version() == DRAFT75) {
DCHECK(socket_stream_);
socket_stream_->Close();
return;
}
+
+ // If the WebSocket connection is not yet established, fail the WebSocket
+ // connection and set the readyState attribute's value to CLOSING.
+ if (ready_state_ == CONNECTING) {
+ ready_state_ = CLOSING;
+ origin_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &WebSocket::FailConnection));
+ }
+
+ // If the WebSocket closing handshake has not yet been started, start
+ // the WebSocket closing handshake and set the readyState attribute's value
+ // to CLOSING.
+ if (!closing_handshake_started_) {
+ ready_state_ = CLOSING;
+ origin_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &WebSocket::StartClosingHandshake));
+ }
+
+ // Otherwise, set the readyState attribute's value to CLOSING.
+ ready_state_ = CLOSING;
}
void WebSocket::DetachDelegate() {
if (!delegate_)
return;
delegate_ = NULL;
- Close();
+ if (ready_state_ == INITIALIZED) {
+ DCHECK(!socket_stream_);
+ ready_state_ = CLOSED;
+ return;
+ }
+ if (ready_state_ != CLOSED) {
+ DCHECK(socket_stream_);
+ socket_stream_->Close();
+ }
}
void WebSocket::OnConnected(SocketStream* socket_stream,
@@ -119,7 +152,23 @@ void WebSocket::OnConnected(SocketStream* socket_stream,
read_consumed_len_ = 0;
DCHECK(!current_write_buf_);
- const std::string msg = request_->CreateClientHandshakeMessage();
+ DCHECK(!handshake_.get());
+ switch (request_->version()) {
+ case DEFAULT_VERSION:
+ handshake_.reset(new WebSocketHandshake(
+ request_->url(), request_->origin(), request_->location(),
+ request_->protocol()));
+ break;
+ case DRAFT75:
+ handshake_.reset(new WebSocketHandshakeDraft75(
+ request_->url(), request_->origin(), request_->location(),
+ request_->protocol()));
+ break;
+ default:
+ NOTREACHED() << "Unexpected protocol version:" << request_->version();
+ }
+
+ const std::string msg = handshake_->CreateClientHandshakeMessage();
IOBufferWithSize* buf = new IOBufferWithSize(msg.size());
memcpy(buf->data(), msg.data(), msg.size());
pending_write_bufs_.push_back(buf);
@@ -154,191 +203,38 @@ void WebSocket::OnClose(SocketStream* socket_stream) {
}
void WebSocket::OnError(const SocketStream* socket_stream, int error) {
- origin_loop_->PostTask(FROM_HERE,
- NewRunnableMethod(this, &WebSocket::DoError, error));
-}
-
-std::string WebSocket::Request::CreateClientHandshakeMessage() const {
- std::string msg;
- msg = "GET ";
- msg += url_.path();
- if (url_.has_query()) {
- msg += "?";
- msg += url_.query();
- }
- msg += " HTTP/1.1\r\n";
- msg += kUpgradeHeader;
- msg += kConnectionHeader;
- msg += "Host: ";
- msg += StringToLowerASCII(url_.host());
- if (url_.has_port()) {
- bool secure = is_secure();
- int port = url_.EffectiveIntPort();
- if ((!secure &&
- port != kWebSocketPort && port != url_parse::PORT_UNSPECIFIED) ||
- (secure &&
- port != kSecureWebSocketPort && port != url_parse::PORT_UNSPECIFIED)) {
- msg += ":";
- msg += IntToString(port);
- }
- }
- msg += "\r\n";
- msg += "Origin: ";
- // It's OK to lowercase the origin as the Origin header does not contain
- // the path or query portions, as per
- // http://tools.ietf.org/html/draft-abarth-origin-00.
- //
- // TODO(satorux): Should we trim the port portion here if it's 80 for
- // http:// or 443 for https:// ? Or can we assume it's done by the
- // client of the library?
- msg += StringToLowerASCII(origin_);
- msg += "\r\n";
- if (!protocol_.empty()) {
- msg += "WebSocket-Protocol: ";
- msg += protocol_;
- msg += "\r\n";
- }
- // TODO(ukai): Add cookie if necessary.
- msg += "\r\n";
- return msg;
-}
-
-int WebSocket::CheckHandshake() {
- DCHECK(current_read_buf_);
- DCHECK(ready_state_ == CONNECTING);
- mode_ = MODE_INCOMPLETE;
- const char *start = current_read_buf_->StartOfBuffer() + read_consumed_len_;
- const char *p = start;
- size_t len = current_read_buf_->offset() - read_consumed_len_;
- if (len < kServerHandshakeHeaderLength) {
- return -1;
- }
- if (!memcmp(p, kServerHandshakeHeader, kServerHandshakeHeaderLength)) {
- mode_ = MODE_NORMAL;
- } else {
- int eoh = HttpUtil::LocateEndOfHeaders(p, len);
- if (eoh < 0)
- return -1;
- scoped_refptr<HttpResponseHeaders> headers(
- new HttpResponseHeaders(HttpUtil::AssembleRawHeaders(p, eoh)));
- if (headers->response_code() == 407) {
- mode_ = MODE_AUTHENTICATE;
- // TODO(ukai): Implement authentication handlers.
- }
- DLOG(INFO) << "non-normal websocket connection. "
- << "response_code=" << headers->response_code()
- << " mode=" << mode_;
- // Invalid response code.
- ready_state_ = CLOSED;
- return eoh;
- }
- const char* end = p + len + 1;
- p += kServerHandshakeHeaderLength;
-
- if (mode_ == MODE_NORMAL) {
- size_t header_size = end - p;
- if (header_size < kUpgradeHeaderLength)
- return -1;
- if (memcmp(p, kUpgradeHeader, kUpgradeHeaderLength)) {
- DLOG(INFO) << "Bad Upgrade Header "
- << std::string(p, kUpgradeHeaderLength);
- ready_state_ = CLOSED;
- return p - start;
- }
- p += kUpgradeHeaderLength;
-
- header_size = end - p;
- if (header_size < kConnectionHeaderLength)
- return -1;
- if (memcmp(p, kConnectionHeader, kConnectionHeaderLength)) {
- DLOG(INFO) << "Bad Connection Header "
- << std::string(p, kConnectionHeaderLength);
- ready_state_ = CLOSED;
- return p - start;
- }
- p += kConnectionHeaderLength;
- }
- int eoh = HttpUtil::LocateEndOfHeaders(start, len);
- if (eoh == -1)
- return eoh;
- scoped_refptr<HttpResponseHeaders> headers(
- new HttpResponseHeaders(HttpUtil::AssembleRawHeaders(start, eoh)));
- if (!ProcessHeaders(*headers)) {
- DLOG(INFO) << "Process Headers failed: "
- << std::string(start, eoh);
- ready_state_ = CLOSED;
- return eoh;
- }
- switch (mode_) {
- case MODE_NORMAL:
- if (CheckResponseHeaders()) {
- ready_state_ = OPEN;
- } else {
- ready_state_ = CLOSED;
- }
- break;
- default:
- ready_state_ = CLOSED;
- break;
- }
- if (ready_state_ == CLOSED)
- DLOG(INFO) << "CheckHandshake mode=" << mode_
- << " " << std::string(start, eoh);
- return eoh;
-}
-
-// Gets the value of the specified header.
-// It assures only one header of |name| in |headers|.
-// Returns true iff single header of |name| is found in |headers|
-// and |value| is filled with the value.
-// Returns false otherwise.
-static bool GetSingleHeader(const HttpResponseHeaders& headers,
- const std::string& name,
- std::string* value) {
- std::string first_value;
- void* iter = NULL;
- if (!headers.EnumerateHeader(&iter, name, &first_value))
- return false;
-
- // Checks no more |name| found in |headers|.
- // Second call of EnumerateHeader() must return false.
- std::string second_value;
- if (headers.EnumerateHeader(&iter, name, &second_value))
- return false;
- *value = first_value;
- return true;
-}
-
-bool WebSocket::ProcessHeaders(const HttpResponseHeaders& headers) {
- if (!GetSingleHeader(headers, "websocket-origin", &ws_origin_))
- return false;
-
- if (!GetSingleHeader(headers, "websocket-location", &ws_location_))
- return false;
-
- if (!request_->protocol().empty()
- && !GetSingleHeader(headers, "websocket-protocol", &ws_protocol_))
- return false;
- return true;
-}
-
-bool WebSocket::CheckResponseHeaders() const {
- DCHECK(mode_ == MODE_NORMAL);
- if (!LowerCaseEqualsASCII(request_->origin(), ws_origin_.c_str()))
- return false;
- if (request_->location() != ws_location_)
- return false;
- if (request_->protocol() != ws_protocol_)
- return false;
- return true;
+ origin_loop_->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &WebSocket::DoSocketError, error));
}
void WebSocket::SendPending() {
DCHECK(MessageLoop::current() == origin_loop_);
- DCHECK(socket_stream_);
+ if (!socket_stream_) {
+ DCHECK_EQ(CLOSED, ready_state_);
+ return;
+ }
if (!current_write_buf_) {
- if (pending_write_bufs_.empty())
+ if (pending_write_bufs_.empty()) {
+ if (client_closing_handshake_) {
+ // Already sent 0xFF and 0x00 bytes.
+ // *The WebSocket closing handshake has started.*
+ closing_handshake_started_ = true;
+ if (server_closing_handshake_) {
+ // 4.2 3-8-3 If the WebSocket connection is not already closed,
+ // then close the WebSocket connection.
+ // *The WebSocket closing handshake has finished*
+ socket_stream_->Close();
+ } else {
+ // 5. Wait a user-agent-determined length of time, or until the
+ // WebSocket connection is closed.
+ force_close_task_ =
+ NewRunnableMethod(this, &WebSocket::DoForceCloseConnection);
+ origin_loop_->PostDelayedTask(
+ FROM_HERE, force_close_task_, closing_handshake_timeout_);
+ }
+ }
return;
+ }
current_write_buf_ = new DrainableIOBuffer(
pending_write_bufs_.front(), pending_write_bufs_.front()->size());
}
@@ -352,21 +248,28 @@ void WebSocket::SendPending() {
void WebSocket::DoReceivedData() {
DCHECK(MessageLoop::current() == origin_loop_);
+ scoped_refptr<WebSocket> protect(this);
switch (ready_state_) {
case CONNECTING:
{
- int eoh = CheckHandshake();
+ DCHECK(handshake_.get());
+ DCHECK(current_read_buf_);
+ const char* data =
+ current_read_buf_->StartOfBuffer() + read_consumed_len_;
+ size_t len = current_read_buf_->offset() - read_consumed_len_;
+ int eoh = handshake_->ReadServerHandshake(data, len);
if (eoh < 0) {
// Not enough data, Retry when more data is available.
return;
}
SkipReadBuffer(eoh);
}
- if (ready_state_ != OPEN) {
+ if (handshake_->mode() != WebSocketHandshake::MODE_CONNECTED) {
// Handshake failed.
socket_stream_->Close();
return;
}
+ ready_state_ = OPEN;
if (delegate_)
delegate_->OnOpen(this);
if (current_read_buf_->offset() == read_consumed_len_) {
@@ -375,6 +278,7 @@ void WebSocket::DoReceivedData() {
}
// FALL THROUGH
case OPEN:
+ case CLOSING: // need to process closing-frame from server.
ProcessFrameData();
break;
@@ -389,6 +293,11 @@ void WebSocket::DoReceivedData() {
void WebSocket::ProcessFrameData() {
DCHECK(current_read_buf_);
+ if (server_closing_handshake_) {
+ // Any data on the connection after the 0xFF frame is discarded.
+ return;
+ }
+ scoped_refptr<WebSocket> protect(this);
const char* start_frame =
current_read_buf_->StartOfBuffer() + read_consumed_len_;
const char* next_frame = start_frame;
@@ -396,6 +305,10 @@ void WebSocket::ProcessFrameData() {
const char* end =
current_read_buf_->StartOfBuffer() + current_read_buf_->offset();
while (p < end) {
+ // Let /error/ be false.
+ bool error = false;
+
+ // Handle the /frame type/ byte as follows.
unsigned char frame_byte = static_cast<unsigned char>(*p++);
if ((frame_byte & 0x80) == 0x80) {
int length = 0;
@@ -417,7 +330,30 @@ void WebSocket::ProcessFrameData() {
if (p + length < end) {
p += length;
next_frame = p;
+ if (request_->version() != DRAFT75 &&
+ frame_byte == 0xFF && length == 0) {
+ // 4.2 Data framing 3. Handle the /frame type/ byte.
+ // 8. If the /frame type/ is 0xFF and the /length/ was 0, then
+ // run the following substeps:
+ // 1. If the WebSocket closing handshake has not yet started, then
+ // start the WebSocket closing handshake.
+ server_closing_handshake_ = true;
+ if (!closing_handshake_started_) {
+ origin_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &WebSocket::StartClosingHandshake));
+ } else {
+ // If the WebSocket closing handshake has been started and
+ // the WebSocket connection is not already closed, then close
+ // the WebSocket connection.
+ socket_stream_->Close();
+ }
+ return;
+ }
+ // 4.2 3-8 Otherwise, let /error/ be true.
+ error = true;
} else {
+ // Not enough data in buffer.
break;
}
} else {
@@ -425,12 +361,21 @@ void WebSocket::ProcessFrameData() {
while (p < end && *p != '\xff')
++p;
if (p < end && *p == '\xff') {
- if (frame_byte == 0x00 && delegate_)
- delegate_->OnMessage(this, std::string(msg_start, p - msg_start));
+ if (frame_byte == 0x00) {
+ if (delegate_) {
+ delegate_->OnMessage(this, std::string(msg_start, p - msg_start));
+ }
+ } else {
+ // Otherwise, discard the data and let /error/ to be true.
+ error = true;
+ }
++p;
next_frame = p;
}
}
+ // If /error/ is true, then *a WebSocket error has been detected.*
+ if (error && delegate_)
+ delegate_->OnError(this);
}
SkipReadBuffer(next_frame - start_frame);
}
@@ -473,8 +418,50 @@ void WebSocket::SkipReadBuffer(int len) {
}
}
+void WebSocket::StartClosingHandshake() {
+ // 4.2 *start the WebSocket closing handshake*.
+ if (closing_handshake_started_ || client_closing_handshake_) {
+ // 1. If the WebSocket closing handshake has started, then abort these
+ // steps.
+ return;
+ }
+ // 2.,3. Send a 0xFF and 0x00 byte to the server.
+ client_closing_handshake_ = true;
+ IOBufferWithSize* buf = new IOBufferWithSize(2);
+ memcpy(buf->data(), kClosingFrame, 2);
+ pending_write_bufs_.push_back(buf);
+ SendPending();
+}
+
+void WebSocket::DoForceCloseConnection() {
+ // 4.2 *start the WebSocket closing handshake*
+ // 6. If the WebSocket connection is not already closed, then close the
+ // WebSocket connection. (If this happens, then the closing handshake
+ // doesn't finish.)
+ DCHECK(MessageLoop::current() == origin_loop_);
+ force_close_task_ = NULL;
+ FailConnection();
+}
+
+void WebSocket::FailConnection() {
+ DCHECK(MessageLoop::current() == origin_loop_);
+ // 6.1 Client-initiated closure.
+ // *fail the WebSocket connection*.
+ // the user agent must close the WebSocket connection, and may report the
+ // problem to the user.
+ if (!socket_stream_)
+ return;
+ socket_stream_->Close();
+}
+
void WebSocket::DoClose() {
DCHECK(MessageLoop::current() == origin_loop_);
+ if (force_close_task_) {
+ // WebSocket connection is closed while waiting a user-agent-determined
+ // length of time after *The WebSocket closing handshake has started*.
+ force_close_task_->Cancel();
+ force_close_task_ = NULL;
+ }
WebSocketDelegate* delegate = delegate_;
delegate_ = NULL;
ready_state_ = CLOSED;
@@ -482,14 +469,15 @@ void WebSocket::DoClose() {
return;
socket_stream_ = NULL;
if (delegate)
- delegate->OnClose(this);
+ delegate->OnClose(this,
+ server_closing_handshake_ && closing_handshake_started_);
Release();
}
-void WebSocket::DoError(int error) {
+void WebSocket::DoSocketError(int error) {
DCHECK(MessageLoop::current() == origin_loop_);
if (delegate_)
- delegate_->OnError(this, error);
+ delegate_->OnSocketError(this, error);
}
} // namespace net