summaryrefslogtreecommitdiffstats
path: root/net/websockets
diff options
context:
space:
mode:
authorukai@chromium.org <ukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-01 03:11:16 +0000
committerukai@chromium.org <ukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-01 03:11:16 +0000
commit3985966ad82dc8ac7094698bc1e598a6d072c93e (patch)
treec5e22e38883a3d7ba8eb0708d959f5e64cffb70b /net/websockets
parent1bdaa60c309250932259402cc8abe0a1bb3f5b2d (diff)
downloadchromium_src-3985966ad82dc8ac7094698bc1e598a6d072c93e.zip
chromium_src-3985966ad82dc8ac7094698bc1e598a6d072c93e.tar.gz
chromium_src-3985966ad82dc8ac7094698bc1e598a6d072c93e.tar.bz2
Factor out WebSocket frame handler out of WebSocketJob.
WebSocket frame handler might be used to count number of frames, variance of type or length of frame. It might be also used to compress/decompress message in websocket frame. BUG=none TEST=none Review URL: http://codereview.chromium.org/2078002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@48615 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/websockets')
-rw-r--r--net/websockets/websocket_frame_handler.cc132
-rw-r--r--net/websockets/websocket_frame_handler.h79
-rw-r--r--net/websockets/websocket_frame_handler_unittest.cc125
-rw-r--r--net/websockets/websocket_job.cc174
-rw-r--r--net/websockets/websocket_job.h12
-rw-r--r--net/websockets/websocket_job_unittest.cc175
-rw-r--r--net/websockets/websocket_throttle_unittest.cc9
7 files changed, 562 insertions, 144 deletions
diff --git a/net/websockets/websocket_frame_handler.cc b/net/websockets/websocket_frame_handler.cc
new file mode 100644
index 0000000..a822652
--- /dev/null
+++ b/net/websockets/websocket_frame_handler.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <limits>
+
+#include "net/websockets/websocket_frame_handler.h"
+
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+WebSocketFrameHandler::WebSocketFrameHandler()
+ : current_buffer_size_(0),
+ original_current_buffer_size_(0) {
+}
+
+WebSocketFrameHandler::~WebSocketFrameHandler() {
+}
+
+void WebSocketFrameHandler::AppendData(const char* data, int length) {
+ scoped_refptr<IOBufferWithSize> buffer = new IOBufferWithSize(length);
+ memcpy(buffer->data(), data, length);
+ pending_buffers_.push_back(buffer);
+}
+
+int WebSocketFrameHandler::UpdateCurrentBuffer() {
+ if (current_buffer_)
+ return 0;
+ DCHECK(!current_buffer_size_);
+ DCHECK(!original_current_buffer_size_);
+
+ if (pending_buffers_.empty())
+ return 0;
+ scoped_refptr<IOBufferWithSize> buffer = pending_buffers_.front();
+ std::vector<FrameInfo> frame_info;
+ int buffer_size =
+ ParseWebSocketFrame(buffer->data(), buffer->size(), &frame_info);
+ if (buffer_size <= 0)
+ return buffer_size;
+
+ original_current_buffer_size_ = buffer_size;
+
+ // TODO(ukai): filter(e.g. compress or decompress) frame messages.
+
+ current_buffer_ = buffer;
+ current_buffer_size_ = buffer_size;
+ return buffer_size;
+}
+
+void WebSocketFrameHandler::ReleaseCurrentBuffer() {
+ DCHECK(!pending_buffers_.empty());
+ scoped_refptr<IOBufferWithSize> front_buffer = pending_buffers_.front();
+ pending_buffers_.pop_front();
+ int remaining_size = front_buffer->size() - original_current_buffer_size_;
+ if (remaining_size > 0) {
+ scoped_refptr<IOBufferWithSize> next_buffer = NULL;
+ int buffer_size = remaining_size;
+ if (!pending_buffers_.empty()) {
+ next_buffer = pending_buffers_.front();
+ buffer_size += next_buffer->size();
+ pending_buffers_.pop_front();
+ }
+ scoped_refptr<IOBufferWithSize> buffer = new IOBufferWithSize(buffer_size);
+ memcpy(buffer->data(), front_buffer->data() + original_current_buffer_size_,
+ remaining_size);
+ if (next_buffer)
+ memcpy(buffer->data() + remaining_size,
+ next_buffer->data(), next_buffer->size());
+ pending_buffers_.push_front(buffer);
+ }
+ current_buffer_ = NULL;
+ current_buffer_size_ = 0;
+ original_current_buffer_size_ = 0;
+}
+
+/* static */
+int WebSocketFrameHandler::ParseWebSocketFrame(
+ const char* buffer, int size, std::vector<FrameInfo>* frame_info) {
+ const char* end = buffer + size;
+ const char* p = buffer;
+ int buffer_size = 0;
+ while (p < end) {
+ FrameInfo frame;
+ frame.frame_start = p;
+ frame.message_length = -1;
+ unsigned char frame_byte = static_cast<unsigned char>(*p++);
+ if ((frame_byte & 0x80) == 0x80) {
+ int length = 0;
+ while (p < end) {
+ // Note: might overflow later if numeric_limits<int>::max() is not
+ // n*128-1.
+ if (length > std::numeric_limits<int>::max() / 128) {
+ // frame length overflow.
+ return ERR_INSUFFICIENT_RESOURCES;
+ }
+ unsigned char c = static_cast<unsigned char>(*p);
+ length = length * 128 + (c & 0x7f);
+ ++p;
+ if ((c & 0x80) != 0x80)
+ break;
+ }
+ if (end - p >= length) {
+ frame.message_start = p;
+ frame.message_length = length;
+ p += length;
+ } else {
+ break;
+ }
+ } else {
+ frame.message_start = p;
+ while (p < end && *p != '\xff')
+ ++p;
+ if (p < end && *p == '\xff') {
+ frame.message_length = p - frame.message_start;
+ ++p;
+ } else {
+ break;
+ }
+ }
+ if (frame.message_length >= 0 && p <= end) {
+ frame.frame_length = p - frame.frame_start;
+ buffer_size += frame.frame_length;
+ frame_info->push_back(frame);
+ }
+ }
+ return buffer_size;
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_frame_handler.h b/net/websockets/websocket_frame_handler.h
new file mode 100644
index 0000000..df72e45
--- /dev/null
+++ b/net/websockets/websocket_frame_handler.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_FRAME_HANDLER_H_
+#define NET_WEBSOCKETS_WEBSOCKET_FRAME_HANDLER_H_
+
+#include <deque>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/ref_counted.h"
+
+namespace net {
+
+class IOBuffer;
+class IOBufferWithSize;
+
+// Handles WebSocket frame messages.
+class WebSocketFrameHandler {
+ public:
+ struct FrameInfo {
+ const char* frame_start;
+ int frame_length;
+ const char* message_start;
+ int message_length;
+ };
+
+ WebSocketFrameHandler();
+ ~WebSocketFrameHandler();
+
+ // Appends WebSocket raw data on connection.
+ // For sending, this is data from WebKit.
+ // For receiving, this is data from network.
+ void AppendData(const char* data, int len);
+
+ // Updates current IOBuffer.
+ // Returns available size of data, 0 if no more data or current buffer was
+ // not released, and negative if some error occurred.
+ int UpdateCurrentBuffer();
+
+ // Gets current IOBuffer.
+ // For sending, this is data to network.
+ // For receiving, this is data to WebKit.
+ // Returns NULL just after ReleaseCurrentBuffer() was called.
+ IOBuffer* GetCurrentBuffer() { return current_buffer_.get(); }
+ int GetCurrentBufferSize() const { return current_buffer_size_; }
+
+ // Returns original buffer size of current IOBuffer.
+ // This might differ from GetCurrentBufferSize() if frame message is
+ // compressed or decompressed.
+ int GetOriginalBufferSize() const { return original_current_buffer_size_; }
+
+ // Releases current IOBuffer.
+ void ReleaseCurrentBuffer();
+
+ // Parses WebSocket frame in [|buffer|, |buffer|+|size|), fills frame
+ // information in |frame_info|, and returns number of bytes for
+ // complete WebSocket frames.
+ static int ParseWebSocketFrame(const char* buffer, int size,
+ std::vector<FrameInfo>* frame_info);
+
+ private:
+ typedef std::deque< scoped_refptr<IOBufferWithSize> > PendingDataQueue;
+
+ scoped_refptr<IOBuffer> current_buffer_;
+ int current_buffer_size_;
+
+ int original_current_buffer_size_;
+
+ // Deque of IOBuffers in pending.
+ PendingDataQueue pending_buffers_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketFrameHandler);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_FRAME_HANDLER_H_
diff --git a/net/websockets/websocket_frame_handler_unittest.cc b/net/websockets/websocket_frame_handler_unittest.cc
new file mode 100644
index 0000000..80f0e59
--- /dev/null
+++ b/net/websockets/websocket_frame_handler_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/scoped_ptr.h"
+#include "net/base/io_buffer.h"
+#include "net/websockets/websocket_frame_handler.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+TEST(WebSocketFrameHandlerTest, Basic) {
+ const char kInputData[] = "\0hello, world\xff";
+ const int kInputDataLen = sizeof(kInputData) - 1; // no terminating NUL.
+
+ scoped_ptr<WebSocketFrameHandler> handler(new WebSocketFrameHandler);
+
+ // No data.
+ EXPECT_EQ(0, handler->UpdateCurrentBuffer());
+ EXPECT_TRUE(handler->GetCurrentBuffer() == NULL);
+ EXPECT_EQ(0, handler->GetCurrentBufferSize());
+
+ // WebKit sends data (WebSocketJob::SendData),
+ // or data is received from network (WebSocketJob::OnReceivedData)
+ handler->AppendData(kInputData, kInputDataLen);
+ EXPECT_TRUE(handler->GetCurrentBuffer() == NULL);
+ EXPECT_GT(handler->UpdateCurrentBuffer(), 0);
+ // Get data to send to the socket (send),
+ // or to send to WebKit (receive).
+ IOBuffer* buf = handler->GetCurrentBuffer();
+ ASSERT_TRUE(buf != NULL);
+ EXPECT_TRUE(memcmp(buf->data(), kInputData, kInputDataLen) == 0);
+ EXPECT_EQ(kInputDataLen, handler->GetCurrentBufferSize());
+ EXPECT_EQ(kInputDataLen, handler->GetOriginalBufferSize());
+ // Data was sent. (WebSocketJob::OnSentData)
+ buf = NULL;
+ handler->ReleaseCurrentBuffer();
+ EXPECT_TRUE(handler->GetCurrentBuffer() == NULL);
+ EXPECT_EQ(0, handler->GetCurrentBufferSize());
+ EXPECT_EQ(0, handler->UpdateCurrentBuffer());
+}
+
+TEST(WebSocketFrameHandlerTest, ParseFrame) {
+ std::vector<WebSocketFrameHandler::FrameInfo> frames;
+ const char kInputData[] = "\0hello, world\xff\xff\0";
+ const int kInputDataLen = sizeof(kInputData) - 1;
+ const int kHelloWorldFrameLen = 14;
+
+ EXPECT_EQ(kInputDataLen,
+ WebSocketFrameHandler::ParseWebSocketFrame(
+ kInputData, kInputDataLen, &frames));
+ EXPECT_EQ(2UL, frames.size());
+
+ EXPECT_EQ(kInputData, frames[0].frame_start);
+ EXPECT_EQ(kHelloWorldFrameLen, frames[0].frame_length);
+ EXPECT_EQ(kInputData + 1, frames[0].message_start);
+ EXPECT_EQ(kHelloWorldFrameLen - 2, frames[0].message_length);
+
+ EXPECT_EQ(kInputData + kHelloWorldFrameLen, frames[1].frame_start);
+ EXPECT_EQ(2, frames[1].frame_length);
+ EXPECT_EQ(0, frames[1].message_length);
+}
+
+TEST(WebSocketFrameHandlerTest, ParseFrameLength) {
+ std::vector<WebSocketFrameHandler::FrameInfo> frames;
+ const char kHelloWorldFrame[] = "\0hello, world\xff";
+ const int kHelloWorldFrameLen = sizeof(kHelloWorldFrame) - 1;
+ const char kLengthFrame[3 + 129] = "\x80\x81\x01\x01\0should be skipped\xff";
+ const int kLengthFrameLen = sizeof(kLengthFrame);
+ const int kInputDataLen = kHelloWorldFrameLen +
+ kLengthFrameLen +
+ kHelloWorldFrameLen;
+ char inputData[kInputDataLen];
+ memcpy(inputData, kHelloWorldFrame, kHelloWorldFrameLen);
+ memcpy(inputData + kHelloWorldFrameLen, kLengthFrame, kLengthFrameLen);
+ memcpy(inputData + kHelloWorldFrameLen + kLengthFrameLen,
+ kHelloWorldFrame, kHelloWorldFrameLen);
+
+ EXPECT_EQ(kInputDataLen,
+ WebSocketFrameHandler::ParseWebSocketFrame(
+ inputData, kInputDataLen, &frames));
+ ASSERT_EQ(3UL, frames.size());
+
+ EXPECT_EQ(inputData, frames[0].frame_start);
+ EXPECT_EQ(kHelloWorldFrameLen, frames[0].frame_length);
+ EXPECT_EQ(inputData + 1, frames[0].message_start);
+ EXPECT_EQ(kHelloWorldFrameLen - 2, frames[0].message_length);
+
+ EXPECT_EQ(inputData + kHelloWorldFrameLen, frames[1].frame_start);
+ EXPECT_EQ(kLengthFrameLen, frames[1].frame_length);
+ EXPECT_EQ(inputData + kHelloWorldFrameLen + 3, frames[1].message_start);
+ EXPECT_EQ(kLengthFrameLen - 3, frames[1].message_length);
+
+ EXPECT_EQ(inputData + kHelloWorldFrameLen + kLengthFrameLen,
+ frames[2].frame_start);
+ EXPECT_EQ(kHelloWorldFrameLen, frames[2].frame_length);
+ EXPECT_EQ(inputData + kHelloWorldFrameLen + kLengthFrameLen + 1,
+ frames[2].message_start);
+ EXPECT_EQ(kHelloWorldFrameLen - 2, frames[2].message_length);
+}
+
+TEST(WebSocketFrameHandlerTest, ParsePartialFrame) {
+ std::vector<WebSocketFrameHandler::FrameInfo> frames;
+ const char kInputData[] = "\0hello, world\xff"
+ "\x80\x81\x01" // skip 1*128+1 bytes.
+ "\x01\xff"
+ "\0should be skipped\xff";
+ const int kInputDataLen = sizeof(kInputData) - 1;
+ const int kHelloWorldFrameLen = 14;
+
+ EXPECT_EQ(kHelloWorldFrameLen,
+ WebSocketFrameHandler::ParseWebSocketFrame(
+ kInputData, kInputDataLen, &frames));
+ ASSERT_EQ(1UL, frames.size());
+
+ EXPECT_EQ(kInputData, frames[0].frame_start);
+ EXPECT_EQ(kHelloWorldFrameLen, frames[0].frame_length);
+ EXPECT_EQ(kInputData + 1, frames[0].message_start);
+ EXPECT_EQ(kHelloWorldFrameLen - 2, frames[0].message_length);
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_job.cc b/net/websockets/websocket_job.cc
index 2f6d4a2..c096d5c 100644
--- a/net/websockets/websocket_job.cc
+++ b/net/websockets/websocket_job.cc
@@ -11,24 +11,29 @@
#include "net/base/net_errors.h"
#include "net/base/cookie_policy.h"
#include "net/base/cookie_store.h"
+#include "net/base/io_buffer.h"
#include "net/http/http_util.h"
#include "net/url_request/url_request_context.h"
+#include "net/websockets/websocket_frame_handler.h"
#include "net/websockets/websocket_throttle.h"
-namespace net {
+namespace {
+
+const size_t kRequestKey3Size = 8U;
+const size_t kResponseKeySize = 16U;
// lower-case header names.
-static const char* const kCookieHeaders[] = {
+const char* const kCookieHeaders[] = {
"cookie", "cookie2"
};
-static const char* const kSetCookieHeaders[] = {
+const char* const kSetCookieHeaders[] = {
"set-cookie", "set-cookie2"
};
-static SocketStreamJob* WebSocketJobFactory(
- const GURL& url, SocketStream::Delegate* delegate) {
- WebSocketJob* job = new WebSocketJob(delegate);
- job->InitSocketStream(new SocketStream(url, job));
+net::SocketStreamJob* WebSocketJobFactory(
+ const GURL& url, net::SocketStream::Delegate* delegate) {
+ net::WebSocketJob* job = new net::WebSocketJob(delegate);
+ job->InitSocketStream(new net::SocketStream(url, job));
return job;
}
@@ -36,12 +41,12 @@ class WebSocketJobInitSingleton {
private:
friend struct DefaultSingletonTraits<WebSocketJobInitSingleton>;
WebSocketJobInitSingleton() {
- SocketStreamJob::RegisterProtocolFactory("ws", WebSocketJobFactory);
- SocketStreamJob::RegisterProtocolFactory("wss", WebSocketJobFactory);
+ net::SocketStreamJob::RegisterProtocolFactory("ws", WebSocketJobFactory);
+ net::SocketStreamJob::RegisterProtocolFactory("wss", WebSocketJobFactory);
}
};
-static void ParseHandshakeMessage(
+void ParseHandshakeMessage(
const char* handshake_message, int len,
std::string* status_line,
std::string* header) {
@@ -55,12 +60,12 @@ static void ParseHandshakeMessage(
*header = std::string(handshake_message + i + 2, len - i - 2);
}
-static void FetchResponseCookies(
+void FetchResponseCookies(
const char* handshake_message, int len,
std::vector<std::string>* response_cookies) {
std::string handshake_response(handshake_message, len);
- HttpUtil::HeadersIterator iter(handshake_response.begin(),
- handshake_response.end(), "\r\n");
+ net::HttpUtil::HeadersIterator iter(handshake_response.begin(),
+ handshake_response.end(), "\r\n");
while (iter.GetNext()) {
for (size_t i = 0; i < arraysize(kSetCookieHeaders); i++) {
if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(),
@@ -71,25 +76,25 @@ static void FetchResponseCookies(
}
}
-static bool GetHeaderName(std::string::const_iterator line_begin,
- std::string::const_iterator line_end,
- std::string::const_iterator* name_begin,
- std::string::const_iterator* name_end) {
+bool GetHeaderName(std::string::const_iterator line_begin,
+ std::string::const_iterator line_end,
+ std::string::const_iterator* name_begin,
+ std::string::const_iterator* name_end) {
std::string::const_iterator colon = std::find(line_begin, line_end, ':');
if (colon == line_end) {
return false;
}
*name_begin = line_begin;
*name_end = colon;
- if (*name_begin == *name_end || HttpUtil::IsLWS(**name_begin))
+ if (*name_begin == *name_end || net::HttpUtil::IsLWS(**name_begin))
return false;
- HttpUtil::TrimLWS(name_begin, name_end);
+ net::HttpUtil::TrimLWS(name_begin, name_end);
return true;
}
// Similar to HttpUtil::StripHeaders, but it preserves malformed headers, that
// is, lines that are not formatted as "<name>: <value>\r\n".
-static std::string FilterHeaders(
+std::string FilterHeaders(
const std::string& headers,
const char* const headers_to_remove[],
size_t headers_to_remove_len) {
@@ -118,6 +123,9 @@ static std::string FilterHeaders(
return filtered_headers;
}
+} // anonymous namespace
+
+namespace net {
// static
void WebSocketJob::EnsureInit() {
@@ -135,7 +143,9 @@ WebSocketJob::WebSocketJob(SocketStream::Delegate* delegate)
ALLOW_THIS_IN_INITIALIZER_LIST(can_get_cookies_callback_(
this, &WebSocketJob::OnCanGetCookiesCompleted)),
ALLOW_THIS_IN_INITIALIZER_LIST(can_set_cookie_callback_(
- this, &WebSocketJob::OnCanSetCookieCompleted)) {
+ this, &WebSocketJob::OnCanSetCookieCompleted)),
+ send_frame_handler_(new WebSocketFrameHandler),
+ receive_frame_handler_(new WebSocketFrameHandler) {
}
WebSocketJob::~WebSocketJob() {
@@ -160,8 +170,23 @@ bool WebSocketJob::SendData(const char* data, int len) {
return SendHandshakeRequest(data, len);
case OPEN:
- return socket_->SendData(data, len);
+ {
+ send_frame_handler_->AppendData(data, len);
+ // If current buffer is sending now, this data will be sent in
+ // SendPending() after current data was sent.
+ int err = 0;
+ if (!send_frame_handler_->GetCurrentBuffer() &&
+ (err = send_frame_handler_->UpdateCurrentBuffer()) > 0) {
+ current_buffer_ = new DrainableIOBuffer(
+ send_frame_handler_->GetCurrentBuffer(),
+ send_frame_handler_->GetCurrentBufferSize());
+ return socket_->SendData(
+ current_buffer_->data(), current_buffer_->BytesRemaining());
+ }
+ return err >= 0;
+ }
+ case CLOSING:
case CLOSED:
return false;
}
@@ -169,6 +194,11 @@ bool WebSocketJob::SendData(const char* data, int len) {
}
void WebSocketJob::Close() {
+ state_ = CLOSING;
+ if (current_buffer_) {
+ // Will close in SendPending.
+ return;
+ }
state_ = CLOSED;
socket_->Close();
}
@@ -222,8 +252,22 @@ void WebSocketJob::OnSentData(SocketStream* socket, int amount_sent) {
OnSentHandshakeRequest(socket, amount_sent);
return;
}
- if (delegate_)
+ if (delegate_) {
+ DCHECK_GT(amount_sent, 0);
+ current_buffer_->DidConsume(amount_sent);
+ if (current_buffer_->BytesRemaining() > 0)
+ return;
+
+ // We need to report amount_sent of original buffer size, instead of
+ // amount sent to |socket|.
+ amount_sent = send_frame_handler_->GetOriginalBufferSize();
+ DCHECK_GT(amount_sent, 0);
+ current_buffer_ = NULL;
+ send_frame_handler_->ReleaseCurrentBuffer();
delegate_->OnSentData(socket, amount_sent);
+ MessageLoopForIO::current()->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &WebSocketJob::SendPending));
+ }
}
void WebSocketJob::OnReceivedData(
@@ -232,8 +276,17 @@ void WebSocketJob::OnReceivedData(
OnReceivedHandshakeResponse(socket, data, len);
return;
}
- if (delegate_)
- delegate_->OnReceivedData(socket, data, len);
+ std::string received_data;
+ receive_frame_handler_->AppendData(data, len);
+ while (receive_frame_handler_->UpdateCurrentBuffer() > 0) {
+ received_data +=
+ std::string(receive_frame_handler_->GetCurrentBuffer()->data(),
+ receive_frame_handler_->GetCurrentBufferSize());
+ receive_frame_handler_->ReleaseCurrentBuffer();
+ }
+ if (delegate_ && received_data.size() > 0)
+ delegate_->OnReceivedData(
+ socket, received_data.data(), received_data.size());
}
void WebSocketJob::OnClose(SocketStream* socket) {
@@ -277,7 +330,9 @@ bool WebSocketJob::SendHandshakeRequest(const char* data, int len) {
original_handshake_request_header_length_ =
HttpUtil::LocateEndOfHeaders(original_handshake_request_.data(),
original_handshake_request_.size(), 0);
- if (original_handshake_request_header_length_ > 0) {
+ if (original_handshake_request_header_length_ > 0 &&
+ original_handshake_request_header_length_ + kRequestKey3Size <=
+ original_handshake_request_.size()) {
// handshake message is completed.
AddCookieHeaderAndSend();
}
@@ -333,6 +388,13 @@ void WebSocketJob::OnCanGetCookiesCompleted(int policy) {
// draft-hixie-thewebsocketprotocol-76 or later will send /key3/
// after handshake request header.
+ // Assumes WebKit doesn't send any data after handshake request message
+ // until handshake is finished.
+ // Thus, additional_data is part of handshake message, and not in part
+ // of websocket frame stream.
+ DCHECK_EQ(kRequestKey3Size,
+ original_handshake_request_.size() -
+ original_handshake_request_header_length_);
std::string additional_data =
std::string(original_handshake_request_.data() +
original_handshake_request_header_length_,
@@ -353,6 +415,7 @@ void WebSocketJob::OnSentHandshakeRequest(
SocketStream* socket, int amount_sent) {
DCHECK_EQ(state_, CONNECTING);
handshake_request_sent_ += amount_sent;
+ DCHECK_LE(handshake_request_sent_, handshake_request_.size());
if (handshake_request_sent_ >= handshake_request_.size()) {
// handshake request has been sent.
// notify original size of handshake request to delegate.
@@ -364,11 +427,22 @@ void WebSocketJob::OnSentHandshakeRequest(
void WebSocketJob::OnReceivedHandshakeResponse(
SocketStream* socket, const char* data, int len) {
DCHECK_EQ(state_, CONNECTING);
+ // Check if response is already full received before appending new data
+ // to |handshake_response_|
+ if (handshake_response_header_length_ > 0 &&
+ handshake_response_header_length_ + kResponseKeySize
+ <= handshake_response_.size()) {
+ // already started cookies processing.
+ handshake_response_.append(data, len);
+ return;
+ }
handshake_response_.append(data, len);
handshake_response_header_length_ = HttpUtil::LocateEndOfHeaders(
handshake_response_.data(),
handshake_response_.size(), 0);
- if (handshake_response_header_length_ > 0) {
+ if (handshake_response_header_length_ > 0 &&
+ handshake_response_header_length_ + kResponseKeySize
+ <= handshake_response_.size()) {
// handshake message is completed.
SaveCookiesAndNotifyHeaderComplete();
}
@@ -406,23 +480,37 @@ void WebSocketJob::SaveNextCookie() {
std::string filtered_handshake_response_header =
FilterHeaders(handshake_response_header,
kSetCookieHeaders, arraysize(kSetCookieHeaders));
- std::string remaining_data =
+ std::string response_key =
std::string(handshake_response_.data() +
handshake_response_header_length_,
- handshake_response_.size() -
- handshake_response_header_length_);
+ kResponseKeySize);
std::string received_data =
handshake_response_status_line +
filtered_handshake_response_header +
"\r\n" +
- remaining_data;
+ response_key;
+ if (handshake_response_header_length_ + kResponseKeySize
+ < handshake_response_.size()) {
+ receive_frame_handler_->AppendData(
+ handshake_response_.data() + handshake_response_header_length_ +
+ kResponseKeySize,
+ handshake_response_.size() - handshake_response_header_length_ -
+ kResponseKeySize);
+ while (receive_frame_handler_->UpdateCurrentBuffer() > 0) {
+ received_data +=
+ std::string(receive_frame_handler_->GetCurrentBuffer()->data(),
+ receive_frame_handler_->GetCurrentBufferSize());
+ receive_frame_handler_->ReleaseCurrentBuffer();
+ }
+ }
+
state_ = OPEN;
+ if (delegate_)
+ delegate_->OnReceivedData(
+ socket_, received_data.data(), received_data.size());
+
Singleton<WebSocketThrottle>::get()->RemoveFromQueue(this);
Singleton<WebSocketThrottle>::get()->WakeupSocketIfNecessary();
-
- if (delegate_)
- delegate_->OnReceivedData(socket_,
- received_data.data(), received_data.size());
return;
}
@@ -504,4 +592,20 @@ void WebSocketJob::DoCallback() {
}
}
+void WebSocketJob::SendPending() {
+ if (current_buffer_)
+ return;
+ // Current buffer is done. Try next buffer if any.
+ if (send_frame_handler_->UpdateCurrentBuffer() <= 0) {
+ // No more data to send.
+ if (state_ == CLOSING)
+ socket_->Close();
+ return;
+ }
+ current_buffer_ = new DrainableIOBuffer(
+ send_frame_handler_->GetCurrentBuffer(),
+ send_frame_handler_->GetCurrentBufferSize());
+ socket_->SendData(current_buffer_->data(), current_buffer_->BytesRemaining());
+}
+
} // namespace net
diff --git a/net/websockets/websocket_job.h b/net/websockets/websocket_job.h
index bb4ac1e..db436be 100644
--- a/net/websockets/websocket_job.h
+++ b/net/websockets/websocket_job.h
@@ -16,6 +16,9 @@ class GURL;
namespace net {
+class DrainableIOBuffer;
+class WebSocketFrameHandler;
+
// WebSocket protocol specific job on SocketStream.
// It captures WebSocket handshake message and handles cookie operations.
// Chrome security policy doesn't allow renderer process (except dev tools)
@@ -29,7 +32,8 @@ class WebSocketJob : public SocketStreamJob, public SocketStream::Delegate {
INITIALIZED = -1,
CONNECTING = 0,
OPEN = 1,
- CLOSED = 2,
+ CLOSING = 2,
+ CLOSED = 3,
};
static void EnsureInit();
@@ -83,6 +87,8 @@ class WebSocketJob : public SocketStreamJob, public SocketStream::Delegate {
void Wakeup();
void DoCallback();
+ void SendPending();
+
SocketStream::Delegate* delegate_;
State state_;
bool waiting_;
@@ -102,6 +108,10 @@ class WebSocketJob : public SocketStreamJob, public SocketStream::Delegate {
CompletionCallbackImpl<WebSocketJob> can_get_cookies_callback_;
CompletionCallbackImpl<WebSocketJob> can_set_cookie_callback_;
+ scoped_ptr<WebSocketFrameHandler> send_frame_handler_;
+ scoped_refptr<DrainableIOBuffer> current_buffer_;
+ scoped_ptr<WebSocketFrameHandler> receive_frame_handler_;
+
DISALLOW_COPY_AND_ASSIGN(WebSocketJob);
};
diff --git a/net/websockets/websocket_job_unittest.cc b/net/websockets/websocket_job_unittest.cc
index cadadc1..0ec760c 100644
--- a/net/websockets/websocket_job_unittest.cc
+++ b/net/websockets/websocket_job_unittest.cc
@@ -249,12 +249,15 @@ TEST_F(WebSocketJobTest, SimpleHandshake) {
static const char* kHandshakeRequestMessage =
"GET /demo HTTP/1.1\r\n"
- "Upgrade: WebSocket\r\n"
- "Connection: Upgrade\r\n"
"Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
"Origin: http://example.com\r\n"
- "WebSocket-Protocol: sample\r\n"
- "\r\n";
+ "\r\n"
+ "^n:ds[4U";
bool sent = websocket_->SendData(kHandshakeRequestMessage,
strlen(kHandshakeRequestMessage));
@@ -266,13 +269,14 @@ TEST_F(WebSocketJobTest, SimpleHandshake) {
EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent());
const char kHandshakeResponseMessage[] =
- "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
"Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
- "WebSocket-Origin: http://example.com\r\n"
- "WebSocket-Location: ws://example.com/demo\r\n"
- "WebSocket-Protocol: sample\r\n"
- "\r\n";
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
websocket_->OnReceivedData(socket_.get(),
kHandshakeResponseMessage,
@@ -283,7 +287,7 @@ TEST_F(WebSocketJobTest, SimpleHandshake) {
CloseWebSocketJob();
}
-TEST_F(WebSocketJobTest, SimpleHandshakeDraft76) {
+TEST_F(WebSocketJobTest, SlowHandshake) {
GURL url("ws://example.com/demo");
MockSocketStreamDelegate delegate;
InitWebSocketJob(url, &delegate);
@@ -303,6 +307,8 @@ TEST_F(WebSocketJobTest, SimpleHandshakeDraft76) {
bool sent = websocket_->SendData(kHandshakeRequestMessage,
strlen(kHandshakeRequestMessage));
EXPECT_EQ(true, sent);
+ // We assume request is sent in one data chunk (from WebKit)
+ // We don't support streaming request.
MessageLoop::current()->RunAllPending();
EXPECT_EQ(kHandshakeRequestMessage, socket_->sent_data());
EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
@@ -319,65 +325,7 @@ TEST_F(WebSocketJobTest, SimpleHandshakeDraft76) {
"\r\n"
"8jKS'y:G*Co,Wxa-";
- websocket_->OnReceivedData(socket_.get(),
- kHandshakeResponseMessage,
- strlen(kHandshakeResponseMessage));
- MessageLoop::current()->RunAllPending();
- EXPECT_EQ(kHandshakeResponseMessage, delegate.received_data());
- EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
- CloseWebSocketJob();
-}
-
-TEST_F(WebSocketJobTest, SlowHandshake) {
- GURL url("ws://example.com/demo");
- MockSocketStreamDelegate delegate;
- InitWebSocketJob(url, &delegate);
-
- static const char* kHandshakeRequestMessage =
- "GET /demo HTTP/1.1\r\n"
- "Upgrade: WebSocket\r\n"
- "Connection: Upgrade\r\n"
- "Host: example.com\r\n"
- "Origin: http://example.com\r\n"
- "WebSocket-Protocol: sample\r\n"
- "\r\n";
std::vector<std::string> lines;
- SplitString(kHandshakeRequestMessage, '\n', &lines);
- for (size_t i = 0; i < lines.size() - 2; i++) {
- std::string line = lines[i] + "\r\n";
- SCOPED_TRACE("Line: " + line);
- bool sent = websocket_->SendData(line.c_str(), line.size());
- EXPECT_EQ(true, sent);
- MessageLoop::current()->RunAllPending();
- EXPECT_TRUE(socket_->sent_data().empty());
- EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
- }
- bool sent = websocket_->SendData("\r\n", 2);
- EXPECT_EQ(true, sent);
- MessageLoop::current()->RunAllPending();
- EXPECT_EQ(kHandshakeRequestMessage, socket_->sent_data());
- EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
-
- for (size_t i = 0; i < lines.size() - 2; i++) {
- std::string line = lines[i] + "\r\n";
- SCOPED_TRACE("Line: " + line);
- websocket_->OnSentData(socket_.get(), line.size());
- EXPECT_EQ(0U, delegate.amount_sent());
- }
- websocket_->OnSentData(socket_.get(), 2); // \r\n
- EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent());
- EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
-
- const char kHandshakeResponseMessage[] =
- "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
- "Upgrade: WebSocket\r\n"
- "Connection: Upgrade\r\n"
- "WebSocket-Origin: http://example.com\r\n"
- "WebSocket-Location: ws://example.com/demo\r\n"
- "WebSocket-Protocol: sample\r\n"
- "\r\n";
-
- lines.clear();
SplitString(kHandshakeResponseMessage, '\n', &lines);
for (size_t i = 0; i < lines.size() - 2; i++) {
std::string line = lines[i] + "\r\n";
@@ -391,6 +339,9 @@ TEST_F(WebSocketJobTest, SlowHandshake) {
}
websocket_->OnReceivedData(socket_.get(), "\r\n", 2);
MessageLoop::current()->RunAllPending();
+ EXPECT_TRUE(delegate.received_data().empty());
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ websocket_->OnReceivedData(socket_.get(), "8jKS'y:G*Co,Wxa-", 16);
EXPECT_EQ(kHandshakeResponseMessage, delegate.received_data());
EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
CloseWebSocketJob();
@@ -411,23 +362,29 @@ TEST_F(WebSocketJobTest, HandshakeWithCookie) {
static const char* kHandshakeRequestMessage =
"GET /demo HTTP/1.1\r\n"
- "Upgrade: WebSocket\r\n"
- "Connection: Upgrade\r\n"
"Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
"Origin: http://example.com\r\n"
- "WebSocket-Protocol: sample\r\n"
"Cookie: WK-test=1\r\n"
- "\r\n";
+ "\r\n"
+ "^n:ds[4U";
static const char* kHandshakeRequestExpected =
"GET /demo HTTP/1.1\r\n"
- "Upgrade: WebSocket\r\n"
- "Connection: Upgrade\r\n"
"Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
"Origin: http://example.com\r\n"
- "WebSocket-Protocol: sample\r\n"
"Cookie: CR-test=1; CR-test-httponly=1\r\n"
- "\r\n";
+ "\r\n"
+ "^n:ds[4U";
bool sent = websocket_->SendData(kHandshakeRequestMessage,
strlen(kHandshakeRequestMessage));
@@ -439,23 +396,25 @@ TEST_F(WebSocketJobTest, HandshakeWithCookie) {
EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent());
const char kHandshakeResponseMessage[] =
- "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
"Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
- "WebSocket-Origin: http://example.com\r\n"
- "WebSocket-Location: ws://example.com/demo\r\n"
- "WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
"Set-Cookie: CR-set-test=1\r\n"
- "\r\n";
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
static const char* kHandshakeResponseExpected =
- "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
"Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
- "WebSocket-Origin: http://example.com\r\n"
- "WebSocket-Location: ws://example.com/demo\r\n"
- "WebSocket-Protocol: sample\r\n"
- "\r\n";
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
websocket_->OnReceivedData(socket_.get(),
kHandshakeResponseMessage,
@@ -491,22 +450,28 @@ TEST_F(WebSocketJobTest, HandshakeWithCookieButNotAllowed) {
static const char* kHandshakeRequestMessage =
"GET /demo HTTP/1.1\r\n"
- "Upgrade: WebSocket\r\n"
- "Connection: Upgrade\r\n"
"Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
"Origin: http://example.com\r\n"
- "WebSocket-Protocol: sample\r\n"
"Cookie: WK-test=1\r\n"
- "\r\n";
+ "\r\n"
+ "^n:ds[4U";
static const char* kHandshakeRequestExpected =
"GET /demo HTTP/1.1\r\n"
- "Upgrade: WebSocket\r\n"
- "Connection: Upgrade\r\n"
"Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
"Origin: http://example.com\r\n"
- "WebSocket-Protocol: sample\r\n"
- "\r\n";
+ "\r\n"
+ "^n:ds[4U";
bool sent = websocket_->SendData(kHandshakeRequestMessage,
strlen(kHandshakeRequestMessage));
@@ -518,23 +483,25 @@ TEST_F(WebSocketJobTest, HandshakeWithCookieButNotAllowed) {
EXPECT_EQ(strlen(kHandshakeRequestMessage), delegate.amount_sent());
const char kHandshakeResponseMessage[] =
- "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
"Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
- "WebSocket-Origin: http://example.com\r\n"
- "WebSocket-Location: ws://example.com/demo\r\n"
- "WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
"Set-Cookie: CR-set-test=1\r\n"
- "\r\n";
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
static const char* kHandshakeResponseExpected =
- "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
"Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
- "WebSocket-Origin: http://example.com\r\n"
- "WebSocket-Location: ws://example.com/demo\r\n"
- "WebSocket-Protocol: sample\r\n"
- "\r\n";
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
websocket_->OnReceivedData(socket_.get(),
kHandshakeResponseMessage,
diff --git a/net/websockets/websocket_throttle_unittest.cc b/net/websockets/websocket_throttle_unittest.cc
index 6c0dfbb..6d8c619 100644
--- a/net/websockets/websocket_throttle_unittest.cc
+++ b/net/websockets/websocket_throttle_unittest.cc
@@ -195,7 +195,7 @@ TEST_F(WebSocketThrottleTest, Throttle) {
// Receive partial response on w1, still connecting.
DLOG(INFO) << "socket1 1";
- static const char kHeader[] = "HTTP/1.1 101 Web Socket Protocol\r\n";
+ static const char kHeader[] = "HTTP/1.1 101 WebSocket Protocol\r\n";
w1->OnReceivedData(s1.get(), kHeader, sizeof(kHeader) - 1);
EXPECT_FALSE(callback_s2.have_result());
EXPECT_FALSE(callback_s3.have_result());
@@ -208,9 +208,10 @@ TEST_F(WebSocketThrottleTest, Throttle) {
static const char kHeader2[] =
"Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
- "WebSocket-Origin: http://www.google.com\r\n"
- "WebSocket-Location: ws://websocket.chromium.org\r\n"
- "\r\n";
+ "Sec-WebSocket-Origin: http://www.google.com\r\n"
+ "Sec-WebSocket-Location: ws://websocket.chromium.org\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
w1->OnReceivedData(s1.get(), kHeader2, sizeof(kHeader2) - 1);
MessageLoopForIO::current()->RunAllPending();
// Now, w1 is open.