class MockSocketStream : public SocketStream {
public:
- MockSocketStream(const GURL& url, SocketStream::Delegate* delegate)
- : SocketStream(url, delegate) {}
+ MockSocketStream(const GURL& url, SocketStream::Delegate* delegate,
+ URLRequestContext* context, CookieStore* cookie_store)
+ : SocketStream(url, delegate, context, cookie_store) {}
- virtual void Connect() OVERRIDE {}
- virtual bool SendData(const char* data, int len) OVERRIDE {
+ void Connect() override {}
+ bool SendData(const char* data, int len) override {
sent_data_ += std::string(data, len);
return true;
}
- virtual void Close() OVERRIDE {}
- virtual void RestartWithAuth(
- const AuthCredentials& credentials) OVERRIDE {
- }
+ void Close() override {}
+ void RestartWithAuth(const AuthCredentials& credentials) override {}
- virtual void DetachDelegate() OVERRIDE {
- delegate_ = NULL;
- }
+ void DetachDelegate() override { delegate_ = NULL; }
const std::string& sent_data() const {
return sent_data_;
}
protected:
- virtual ~MockSocketStream() {}
+ ~MockSocketStream() override {}
private:
std::string sent_data_;
void set_allow_all_cookies(bool allow_all_cookies) {
allow_all_cookies_ = allow_all_cookies;
}
- virtual ~MockSocketStreamDelegate() {}
+ ~MockSocketStreamDelegate() override {}
void SetOnStartOpenConnection(const base::Closure& callback) {
on_start_open_connection_ = callback;
on_close_ = callback;
}
- virtual int OnStartOpenConnection(
- SocketStream* socket,
- const CompletionCallback& callback) OVERRIDE {
+ int OnStartOpenConnection(SocketStream* socket,
+ const CompletionCallback& callback) override {
if (!on_start_open_connection_.is_null())
on_start_open_connection_.Run();
return OK;
}
- virtual void OnConnected(SocketStream* socket,
- int max_pending_send_allowed) OVERRIDE {
+ void OnConnected(SocketStream* socket,
+ int max_pending_send_allowed) override {
if (!on_connected_.is_null())
on_connected_.Run();
}
- virtual void OnSentData(SocketStream* socket,
- int amount_sent) OVERRIDE {
+ void OnSentData(SocketStream* socket, int amount_sent) override {
amount_sent_ += amount_sent;
if (!on_sent_data_.is_null())
on_sent_data_.Run();
}
- virtual void OnReceivedData(SocketStream* socket,
- const char* data, int len) OVERRIDE {
+ void OnReceivedData(SocketStream* socket,
+ const char* data,
+ int len) override {
received_data_ += std::string(data, len);
if (!on_received_data_.is_null())
on_received_data_.Run();
}
- virtual void OnClose(SocketStream* socket) OVERRIDE {
+ void OnClose(SocketStream* socket) override {
if (!on_close_.is_null())
on_close_.Run();
}
- virtual bool CanGetCookies(SocketStream* socket,
- const GURL& url) OVERRIDE {
+ bool CanGetCookies(SocketStream* socket, const GURL& url) override {
return allow_all_cookies_;
}
- virtual bool CanSetCookie(SocketStream* request,
- const GURL& url,
- const std::string& cookie_line,
- CookieOptions* options) OVERRIDE {
+ bool CanSetCookie(SocketStream* request,
+ const GURL& url,
+ const std::string& cookie_line,
+ CookieOptions* options) override {
return allow_all_cookies_;
}
}
// CookieStore:
- virtual void SetCookieWithOptionsAsync(
- const GURL& url,
- const std::string& cookie_line,
- const CookieOptions& options,
- const SetCookiesCallback& callback) OVERRIDE {
+ void SetCookieWithOptionsAsync(const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options,
+ const SetCookiesCallback& callback) override {
bool result = SetCookieWithOptions(url, cookie_line, options);
if (!callback.is_null())
callback.Run(result);
}
- virtual void GetCookiesWithOptionsAsync(
- const GURL& url,
- const CookieOptions& options,
- const GetCookiesCallback& callback) OVERRIDE {
+ void GetCookiesWithOptionsAsync(const GURL& url,
+ const CookieOptions& options,
+ const GetCookiesCallback& callback) override {
if (!callback.is_null())
callback.Run(GetCookiesWithOptions(url, options));
}
- virtual void DeleteCookieAsync(const GURL& url,
- const std::string& cookie_name,
- const base::Closure& callback) OVERRIDE {
+ void GetAllCookiesForURLAsync(
+ const GURL& url,
+ const GetCookieListCallback& callback) override {
+ ADD_FAILURE();
+ }
+
+ void DeleteCookieAsync(const GURL& url,
+ const std::string& cookie_name,
+ const base::Closure& callback) override {
ADD_FAILURE();
}
- virtual void DeleteAllCreatedBetweenAsync(
- const base::Time& delete_begin,
- const base::Time& delete_end,
- const DeleteCallback& callback) OVERRIDE {
+ void DeleteAllCreatedBetweenAsync(const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const DeleteCallback& callback) override {
ADD_FAILURE();
}
- virtual void DeleteAllCreatedBetweenForHostAsync(
+ void DeleteAllCreatedBetweenForHostAsync(
const base::Time delete_begin,
const base::Time delete_end,
const GURL& url,
- const DeleteCallback& callback) OVERRIDE {
+ const DeleteCallback& callback) override {
ADD_FAILURE();
}
- virtual void DeleteSessionCookiesAsync(const DeleteCallback&) OVERRIDE {
+ void DeleteSessionCookiesAsync(const DeleteCallback&) override {
ADD_FAILURE();
}
- virtual CookieMonster* GetCookieMonster() OVERRIDE { return NULL; }
+ CookieMonster* GetCookieMonster() override { return NULL; }
+
+ scoped_ptr<CookieStore::CookieChangedSubscription>
+ AddCallbackForCookie(const GURL& url, const std::string& name,
+ const CookieChangedCallback& callback) override {
+ ADD_FAILURE();
+ return scoped_ptr<CookieChangedSubscription>();
+ }
const std::vector<Entry>& entries() const { return entries_; }
private:
friend class base::RefCountedThreadSafe<MockCookieStore>;
- virtual ~MockCookieStore() {}
+ ~MockCookieStore() override {}
std::vector<Entry> entries_;
};
class MockSSLConfigService : public SSLConfigService {
public:
- virtual void GetSSLConfig(SSLConfig* config) OVERRIDE {}
+ void GetSSLConfig(SSLConfig* config) override {}
protected:
- virtual ~MockSSLConfigService() {}
+ ~MockSSLConfigService() override {}
};
class MockURLRequestContext : public URLRequestContext {
include_subdomains);
}
- virtual ~MockURLRequestContext() {}
+ ~MockURLRequestContext() override { AssertNoURLRequests(); }
private:
TransportSecurityState transport_security_state_;
class MockHttpTransactionFactory : public HttpTransactionFactory {
public:
- MockHttpTransactionFactory(NextProto next_proto, OrderedSocketData* data) {
+ MockHttpTransactionFactory(NextProto next_proto,
+ OrderedSocketData* data,
+ bool enable_websocket_over_spdy) {
data_ = data;
MockConnect connect_data(SYNCHRONOUS, OK);
data_->set_connect_data(connect_data);
session_deps_.reset(new SpdySessionDependencies(next_proto));
+ session_deps_->enable_websocket_over_spdy = enable_websocket_over_spdy;
session_deps_->socket_factory->AddSocketDataProvider(data_);
http_session_ =
SpdySessionDependencies::SpdyCreateSession(session_deps_.get());
host_port_pair_.set_port(80);
spdy_session_key_ = SpdySessionKey(host_port_pair_,
ProxyServer::Direct(),
- kPrivacyModeDisabled);
+ PRIVACY_MODE_DISABLED);
session_ = CreateInsecureSpdySession(
http_session_, spdy_session_key_, BoundNetLog());
}
- virtual int CreateTransaction(
- RequestPriority priority,
- scoped_ptr<HttpTransaction>* trans) OVERRIDE {
+ int CreateTransaction(RequestPriority priority,
+ scoped_ptr<HttpTransaction>* trans) override {
NOTREACHED();
return ERR_UNEXPECTED;
}
- virtual HttpCache* GetCache() OVERRIDE {
+ HttpCache* GetCache() override {
NOTREACHED();
return NULL;
}
- virtual HttpNetworkSession* GetSession() OVERRIDE {
- return http_session_.get();
- }
+ HttpNetworkSession* GetSession() override { return http_session_.get(); }
private:
OrderedSocketData* data_;
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
+
+ void OnConnected(SocketStream* socket,
+ int max_pending_send_allowed) override {
+ DeleteJobMaybe();
+ }
+
+ void OnSentData(SocketStream* socket, int amount_sent) override {
+ DeleteJobMaybe();
+ }
+
+ void OnReceivedData(SocketStream* socket,
+ const char* data,
+ int len) override {
+ DeleteJobMaybe();
+ }
+
+ void OnClose(SocketStream* socket) override { DeleteJobMaybe(); }
+
+ void OnAuthRequired(SocketStream* socket,
+ AuthChallengeInfo* auth_info) override {
+ DeleteJobMaybe();
+ }
+
+ void OnSSLCertificateError(SocketStream* socket,
+ const SSLInfo& ssl_info,
+ bool fatal) override {
+ DeleteJobMaybe();
+ }
+
+ 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,
public ::testing::WithParamInterface<NextProto> {
public:
- WebSocketJobTest() : spdy_util_(GetParam()) {}
+ WebSocketJobTest()
+ : spdy_util_(GetParam()),
+ enable_websocket_over_spdy_(false) {}
- virtual void SetUp() OVERRIDE {
+ void SetUp() override {
stream_type_ = STREAM_INVALID;
cookie_store_ = new MockCookieStore;
context_.reset(new MockURLRequestContext(cookie_store_.get()));
}
- virtual void TearDown() OVERRIDE {
+ void TearDown() override {
cookie_store_ = NULL;
context_.reset();
websocket_ = NULL;
int WaitForResult() {
return sync_test_callback_.WaitForResult();
}
+
protected:
enum StreamType {
STREAM_INVALID,
websocket_ = new WebSocketJob(delegate);
if (stream_type == STREAM_MOCK_SOCKET)
- socket_ = new MockSocketStream(url, websocket_.get());
+ socket_ = new MockSocketStream(url, websocket_.get(), context_.get(),
+ NULL);
if (stream_type == STREAM_SOCKET || stream_type == STREAM_SPDY_WEBSOCKET) {
if (stream_type == STREAM_SPDY_WEBSOCKET) {
- http_factory_.reset(
- new MockHttpTransactionFactory(GetParam(), data_.get()));
+ http_factory_.reset(new MockHttpTransactionFactory(
+ GetParam(), data_.get(), enable_websocket_over_spdy_));
context_->set_http_transaction_factory(http_factory_.get());
}
host_resolver_.reset(new MockHostResolver);
context_->set_host_resolver(host_resolver_.get());
- socket_ = new SocketStream(url, websocket_.get());
+ socket_ = new SocketStream(url, websocket_.get(), context_.get(), NULL);
socket_factory_.reset(new MockClientSocketFactory);
DCHECK(data_.get());
socket_factory_->AddSocketDataProvider(data_.get());
}
websocket_->InitSocketStream(socket_.get());
- websocket_->set_context(context_.get());
// MockHostResolver resolves all hosts to 127.0.0.1; however, when we create
// a WebSocketJob purely to block another one in a throttling test, we don't
// perform a real connect. In that case, the following address is used
scoped_ptr<MockHostResolver> host_resolver_;
scoped_ptr<MockHttpTransactionFactory> http_factory_;
+ // Must be set before call to enable_websocket_over_spdy, defaults to false.
+ bool enable_websocket_over_spdy_;
+
static const char kHandshakeRequestWithoutCookie[];
static const char kHandshakeRequestWithCookie[];
static const char kHandshakeRequestWithFilteredCookie[];
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"
NextProto,
WebSocketJobTest,
testing::Values(kProtoDeprecatedSPDY2,
- kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
- kProtoHTTP2Draft04));
+ kProtoSPDY3, kProtoSPDY31, kProtoSPDY4));
TEST_P(WebSocketJobTest, DelayedCookies) {
- WebSocketJob::set_websocket_over_spdy_enabled(true);
+ enable_websocket_over_spdy_ = true;
GURL url("ws://example.com/demo");
GURL cookieUrl("http://example.com/demo");
CookieOptions cookie_options;
scoped_refptr<SocketStreamJob> job =
SocketStreamJob::CreateSocketStreamJob(
url, &delegate, context_->transport_security_state(),
- context_->ssl_config_service());
+ context_->ssl_config_service(), NULL, NULL);
EXPECT_TRUE(GetSocket(job.get())->is_secure());
job->DetachDelegate();
url = GURL("ws://donotupgrademe.com/");
job = SocketStreamJob::CreateSocketStreamJob(
url, &delegate, context_->transport_security_state(),
- context_->ssl_config_service());
+ context_->ssl_config_service(), NULL, NULL);
EXPECT_FALSE(GetSocket(job.get())->is_secure());
job->DetachDelegate();
}
scoped_refptr<WebSocketJob> job = new WebSocketJob(NULL);
job->addresses_ = AddressList(AddressList::CreateFromIPAddress(ip, 80));
if (i >= kMaxWebSocketJobsThrottled)
- EXPECT_FALSE(WebSocketThrottle::GetInstance()->PutInQueue(job));
+ EXPECT_FALSE(WebSocketThrottle::GetInstance()->PutInQueue(job.get()));
else
- EXPECT_TRUE(WebSocketThrottle::GetInstance()->PutInQueue(job));
+ EXPECT_TRUE(WebSocketThrottle::GetInstance()->PutInQueue(job.get()));
jobs.push_back(job);
}
// Execute tests in both spdy-disabled mode and spdy-enabled mode.
TEST_P(WebSocketJobTest, SimpleHandshake) {
- WebSocketJob::set_websocket_over_spdy_enabled(false);
TestSimpleHandshake();
}
TEST_P(WebSocketJobTest, SlowHandshake) {
- WebSocketJob::set_websocket_over_spdy_enabled(false);
TestSlowHandshake();
}
TEST_P(WebSocketJobTest, HandshakeWithCookie) {
- WebSocketJob::set_websocket_over_spdy_enabled(false);
TestHandshakeWithCookie();
}
TEST_P(WebSocketJobTest, HandshakeWithCookieButNotAllowed) {
- WebSocketJob::set_websocket_over_spdy_enabled(false);
TestHandshakeWithCookieButNotAllowed();
}
TEST_P(WebSocketJobTest, HSTSUpgrade) {
- WebSocketJob::set_websocket_over_spdy_enabled(false);
TestHSTSUpgrade();
}
TEST_P(WebSocketJobTest, InvalidSendData) {
- WebSocketJob::set_websocket_over_spdy_enabled(false);
TestInvalidSendData();
}
TEST_P(WebSocketJobTest, SimpleHandshakeSpdyEnabled) {
- WebSocketJob::set_websocket_over_spdy_enabled(true);
+ enable_websocket_over_spdy_ = true;
TestSimpleHandshake();
}
TEST_P(WebSocketJobTest, SlowHandshakeSpdyEnabled) {
- WebSocketJob::set_websocket_over_spdy_enabled(true);
+ enable_websocket_over_spdy_ = true;
TestSlowHandshake();
}
TEST_P(WebSocketJobTest, HandshakeWithCookieSpdyEnabled) {
- WebSocketJob::set_websocket_over_spdy_enabled(true);
+ enable_websocket_over_spdy_ = true;
TestHandshakeWithCookie();
}
TEST_P(WebSocketJobTest, HandshakeWithCookieButNotAllowedSpdyEnabled) {
- WebSocketJob::set_websocket_over_spdy_enabled(true);
+ enable_websocket_over_spdy_ = true;
TestHandshakeWithCookieButNotAllowed();
}
TEST_P(WebSocketJobTest, HSTSUpgradeSpdyEnabled) {
- WebSocketJob::set_websocket_over_spdy_enabled(true);
+ enable_websocket_over_spdy_ = true;
TestHSTSUpgrade();
}
TEST_P(WebSocketJobTest, InvalidSendDataSpdyEnabled) {
- WebSocketJob::set_websocket_over_spdy_enabled(true);
+ enable_websocket_over_spdy_ = true;
TestInvalidSendData();
}
TEST_P(WebSocketJobTest, ConnectByWebSocket) {
- WebSocketJob::set_websocket_over_spdy_enabled(false);
+ enable_websocket_over_spdy_ = true;
TestConnectByWebSocket(THROTTLING_OFF);
}
TEST_P(WebSocketJobTest, ConnectByWebSocketSpdyEnabled) {
- WebSocketJob::set_websocket_over_spdy_enabled(true);
+ enable_websocket_over_spdy_ = true;
TestConnectByWebSocket(THROTTLING_OFF);
}
TEST_P(WebSocketJobTest, ConnectBySpdy) {
- WebSocketJob::set_websocket_over_spdy_enabled(false);
TestConnectBySpdy(SPDY_OFF, THROTTLING_OFF);
}
TEST_P(WebSocketJobTest, ConnectBySpdySpdyEnabled) {
- WebSocketJob::set_websocket_over_spdy_enabled(true);
+ enable_websocket_over_spdy_ = true;
TestConnectBySpdy(SPDY_ON, THROTTLING_OFF);
}
TEST_P(WebSocketJobTest, ThrottlingWebSocket) {
- WebSocketJob::set_websocket_over_spdy_enabled(false);
TestConnectByWebSocket(THROTTLING_ON);
}
}
TEST_P(WebSocketJobTest, ThrottlingWebSocketSpdyEnabled) {
- WebSocketJob::set_websocket_over_spdy_enabled(true);
+ enable_websocket_over_spdy_ = true;
TestConnectByWebSocket(THROTTLING_ON);
}
TEST_P(WebSocketJobTest, ThrottlingSpdy) {
- WebSocketJob::set_websocket_over_spdy_enabled(false);
TestConnectBySpdy(SPDY_OFF, THROTTLING_ON);
}
TEST_P(WebSocketJobTest, ThrottlingSpdySpdyEnabled) {
- WebSocketJob::set_websocket_over_spdy_enabled(true);
+ enable_websocket_over_spdy_ = true;
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