| Index: net/websockets/websocket_job_test.cc
|
| diff --git a/net/websockets/websocket_job_test.cc b/net/websockets/websocket_job_test.cc
|
| index e12ddae81c587432b4a6df41a6ce4ce75e9aebad..2b303890a981fb01645eeb6f7ed56852b40b5539 100644
|
| --- a/net/websockets/websocket_job_test.cc
|
| +++ b/net/websockets/websocket_job_test.cc
|
| @@ -316,6 +316,74 @@ class MockHttpTransactionFactory : public HttpTransactionFactory {
|
| SpdySessionKey spdy_session_key_;
|
| };
|
|
|
| +class DeletingSocketStreamDelegate : public SocketStream::Delegate {
|
| + public:
|
| + DeletingSocketStreamDelegate()
|
| + : delete_next_(false) {}
|
| +
|
| + // Since this class needs to be able to delete |job_|, it must be the only
|
| + // reference holder (except for temporary references). Provide access to the
|
| + // pointer for tests to use.
|
| + WebSocketJob* job() { return job_.get(); }
|
| +
|
| + void set_job(WebSocketJob* job) { job_ = job; }
|
| +
|
| + // After calling this, the next call to a method on this delegate will delete
|
| + // the WebSocketJob object.
|
| + void set_delete_next(bool delete_next) { delete_next_ = delete_next; }
|
| +
|
| + void DeleteJobMaybe() {
|
| + if (delete_next_) {
|
| + job_->DetachContext();
|
| + job_->DetachDelegate();
|
| + job_ = NULL;
|
| + }
|
| + }
|
| +
|
| + // SocketStream::Delegate implementation
|
| +
|
| + // OnStartOpenConnection() is not implemented by SocketStreamDispatcherHost
|
| +
|
| + virtual void OnConnected(SocketStream* socket,
|
| + int max_pending_send_allowed) OVERRIDE {
|
| + DeleteJobMaybe();
|
| + }
|
| +
|
| + virtual void OnSentData(SocketStream* socket, int amount_sent) OVERRIDE {
|
| + DeleteJobMaybe();
|
| + }
|
| +
|
| + virtual void OnReceivedData(SocketStream* socket,
|
| + const char* data,
|
| + int len) OVERRIDE {
|
| + DeleteJobMaybe();
|
| + }
|
| +
|
| + virtual void OnClose(SocketStream* socket) OVERRIDE { DeleteJobMaybe(); }
|
| +
|
| + virtual void OnAuthRequired(SocketStream* socket,
|
| + AuthChallengeInfo* auth_info) OVERRIDE {
|
| + DeleteJobMaybe();
|
| + }
|
| +
|
| + virtual void OnSSLCertificateError(SocketStream* socket,
|
| + const SSLInfo& ssl_info,
|
| + bool fatal) OVERRIDE {
|
| + DeleteJobMaybe();
|
| + }
|
| +
|
| + virtual void OnError(const SocketStream* socket, int error) OVERRIDE {
|
| + DeleteJobMaybe();
|
| + }
|
| +
|
| + // CanGetCookies() and CanSetCookies() do not appear to be able to delete the
|
| + // WebSocketJob object.
|
| +
|
| + private:
|
| + scoped_refptr<WebSocketJob> job_;
|
| + bool delete_next_;
|
| +};
|
| +
|
| } // namespace
|
|
|
| class WebSocketJobTest : public PlatformTest,
|
| @@ -480,6 +548,34 @@ class WebSocketJobTest : public PlatformTest,
|
| static const size_t kDataWorldLength;
|
| };
|
|
|
| +// Tests using this fixture verify that the WebSocketJob can handle being
|
| +// deleted while calling back to the delegate correctly. These tests need to be
|
| +// run under AddressSanitizer or other systems for detecting use-after-free
|
| +// errors in order to find problems.
|
| +class WebSocketJobDeleteTest : public ::testing::Test {
|
| + protected:
|
| + WebSocketJobDeleteTest()
|
| + : delegate_(new DeletingSocketStreamDelegate),
|
| + cookie_store_(new MockCookieStore),
|
| + context_(new MockURLRequestContext(cookie_store_.get())) {
|
| + WebSocketJob* websocket = new WebSocketJob(delegate_.get());
|
| + delegate_->set_job(websocket);
|
| +
|
| + socket_ = new MockSocketStream(
|
| + GURL("ws://127.0.0.1/"), websocket, context_.get(), NULL);
|
| +
|
| + websocket->InitSocketStream(socket_.get());
|
| + }
|
| +
|
| + void SetDeleteNext() { return delegate_->set_delete_next(true); }
|
| + WebSocketJob* job() { return delegate_->job(); }
|
| +
|
| + scoped_ptr<DeletingSocketStreamDelegate> delegate_;
|
| + scoped_refptr<MockCookieStore> cookie_store_;
|
| + scoped_ptr<MockURLRequestContext> context_;
|
| + scoped_refptr<SocketStream> socket_;
|
| +};
|
| +
|
| const char WebSocketJobTest::kHandshakeRequestWithoutCookie[] =
|
| "GET /demo HTTP/1.1\r\n"
|
| "Host: example.com\r\n"
|
| @@ -1122,6 +1218,76 @@ TEST_P(WebSocketJobTest, ThrottlingSpdySpdyEnabled) {
|
| TestConnectBySpdy(SPDY_ON, THROTTLING_ON);
|
| }
|
|
|
| +TEST_F(WebSocketJobDeleteTest, OnClose) {
|
| + SetDeleteNext();
|
| + job()->OnClose(socket_.get());
|
| + // OnClose() sets WebSocketJob::_socket to NULL before we can detach it, so
|
| + // socket_->delegate is still set at this point. Clear it to avoid hitting
|
| + // DCHECK(!delegate_) in the SocketStream destructor. SocketStream::Finish()
|
| + // is the only caller of this method in real code, and it also sets delegate_
|
| + // to NULL.
|
| + socket_->DetachDelegate();
|
| + EXPECT_FALSE(job());
|
| +}
|
| +
|
| +TEST_F(WebSocketJobDeleteTest, OnAuthRequired) {
|
| + SetDeleteNext();
|
| + job()->OnAuthRequired(socket_.get(), NULL);
|
| + EXPECT_FALSE(job());
|
| +}
|
| +
|
| +TEST_F(WebSocketJobDeleteTest, OnSSLCertificateError) {
|
| + SSLInfo ssl_info;
|
| + SetDeleteNext();
|
| + job()->OnSSLCertificateError(socket_.get(), ssl_info, true);
|
| + EXPECT_FALSE(job());
|
| +}
|
| +
|
| +TEST_F(WebSocketJobDeleteTest, OnError) {
|
| + SetDeleteNext();
|
| + job()->OnError(socket_.get(), ERR_CONNECTION_RESET);
|
| + EXPECT_FALSE(job());
|
| +}
|
| +
|
| +TEST_F(WebSocketJobDeleteTest, OnSentSpdyHeaders) {
|
| + job()->Connect();
|
| + SetDeleteNext();
|
| + job()->OnSentSpdyHeaders();
|
| + EXPECT_FALSE(job());
|
| +}
|
| +
|
| +TEST_F(WebSocketJobDeleteTest, OnSentHandshakeRequest) {
|
| + static const char kMinimalRequest[] =
|
| + "GET /demo HTTP/1.1\r\n"
|
| + "Host: example.com\r\n"
|
| + "Upgrade: WebSocket\r\n"
|
| + "Connection: Upgrade\r\n"
|
| + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
|
| + "Origin: http://example.com\r\n"
|
| + "Sec-WebSocket-Version: 13\r\n"
|
| + "\r\n";
|
| + const size_t kMinimalRequestSize = arraysize(kMinimalRequest) - 1;
|
| + job()->Connect();
|
| + job()->SendData(kMinimalRequest, kMinimalRequestSize);
|
| + SetDeleteNext();
|
| + job()->OnSentData(socket_.get(), kMinimalRequestSize);
|
| + EXPECT_FALSE(job());
|
| +}
|
| +
|
| +TEST_F(WebSocketJobDeleteTest, NotifyHeadersComplete) {
|
| + static const char kMinimalResponse[] =
|
| + "HTTP/1.1 101 Switching Protocols\r\n"
|
| + "Upgrade: websocket\r\n"
|
| + "Connection: Upgrade\r\n"
|
| + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
|
| + "\r\n";
|
| + job()->Connect();
|
| + SetDeleteNext();
|
| + job()->OnReceivedData(
|
| + socket_.get(), kMinimalResponse, arraysize(kMinimalResponse) - 1);
|
| + EXPECT_FALSE(job());
|
| +}
|
| +
|
| // TODO(toyoshim): Add tests to verify throttling, SPDY stream limitation.
|
| // TODO(toyoshim,yutak): Add tests to verify closing handshake.
|
| } // namespace net
|
|
|