diff options
author | Ben Murdoch <benm@google.com> | 2010-07-29 17:14:53 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2010-08-04 14:29:45 +0100 |
commit | c407dc5cd9bdc5668497f21b26b09d988ab439de (patch) | |
tree | 7eaf8707c0309516bdb042ad976feedaf72b0bb1 /net/websockets/websocket.cc | |
parent | 0998b1cdac5733f299c12d88bc31ef9c8035b8fa (diff) | |
download | external_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.cc | 408 |
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 |