blob: 0547e88a79c327a47dd8ccb2085a18088bc8ffda [file] [log] [blame]
// Copyright 2017 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/websocket/web_socket.h"
#include <memory>
#include <vector>
#include "base/memory/ref_counted.h"
#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/dom/testing/test_with_javascript.h"
#include "cobalt/dom/window.h"
#include "cobalt/script/script_exception.h"
#include "cobalt/script/testing/mock_exception_state.h"
#include "cobalt/web/context.h"
#include "cobalt/web/dom_exception.h"
#include "testing/gtest/include/gtest/gtest.h"
using cobalt::script::testing::MockExceptionState;
using ::testing::_;
using ::testing::SaveArg;
using ::testing::StrictMock;
namespace cobalt {
namespace websocket {
class WebSocketTest : public dom::testing::TestWithJavaScript {
public:
web::EnvironmentSettings* settings() {
return window()->environment_settings();
}
protected:
WebSocketTest() {
settings()->set_creation_url(GURL("https://example.com"));
window()->location()->set_url(settings()->creation_url());
}
};
TEST_F(WebSocketTest, BadOrigin) {
scoped_refptr<script::ScriptException> exception;
window()->location()->set_url(GURL());
EXPECT_CALL(*exception_state(), SetException(_))
.WillOnce(SaveArg<0>(&exception));
scoped_refptr<WebSocket> ws(
new WebSocket(settings(), "ws://example.com", exception_state(), false));
ASSERT_TRUE(exception.get());
web::DOMException& dom_exception(
*base::polymorphic_downcast<web::DOMException*>(exception.get()));
EXPECT_EQ(web::DOMException::kNone, dom_exception.code());
EXPECT_EQ(
dom_exception.message(),
"Internal error: base_url (the url of the entry script) must be valid.");
}
TEST_F(WebSocketTest, GoodOrigin) {
struct OriginTestCase {
const char* const input_base_url;
const char* const expected_output;
} origin_test_cases[] = {
{"https://example.com/", "https://example.com/"},
{"https://exAMPle.com/", "https://example.com/"},
};
EXPECT_CALL(*exception_state(), SetException(_)).Times(0);
for (std::size_t i(0); i != ARRAYSIZE_UNSAFE(origin_test_cases); ++i) {
const OriginTestCase& test_case(origin_test_cases[i]);
std::string new_base = test_case.input_base_url;
window()->location()->set_url(GURL(new_base));
scoped_refptr<WebSocket> ws(new WebSocket(settings(), "ws://example.com",
exception_state(), false));
EXPECT_EQ(ws->entry_script_origin_, test_case.expected_output);
}
}
TEST_F(WebSocketTest, TestInitialReadyState) {
scoped_refptr<WebSocket> ws(
new WebSocket(settings(), "ws://example.com", exception_state(), false));
EXPECT_EQ(ws->ready_state(), WebSocket::kConnecting);
}
TEST_F(WebSocketTest, SyntaxErrorWhenBadScheme) {
scoped_refptr<script::ScriptException> exception;
EXPECT_CALL(*exception_state(), SetException(_))
.WillOnce(SaveArg<0>(&exception));
scoped_refptr<WebSocket> ws(new WebSocket(
settings(), "badscheme://example.com", exception_state(), false));
web::DOMException& dom_exception(
*base::polymorphic_downcast<web::DOMException*>(exception.get()));
ASSERT_TRUE(exception.get());
EXPECT_EQ(web::DOMException::kSyntaxErr, dom_exception.code());
EXPECT_EQ(
dom_exception.message(),
"Invalid scheme [badscheme]. Only ws, and wss schemes are supported.");
}
TEST_F(WebSocketTest, ParseWsAndWssCorrectly) {
EXPECT_CALL(*exception_state(), SetException(_)).Times(0);
scoped_refptr<WebSocket> ws(
new WebSocket(settings(), "ws://example.com/", exception_state(), false));
EXPECT_CALL(*exception_state(), SetException(_)).Times(0);
scoped_refptr<WebSocket> wss(
new WebSocket(settings(), "wss://example.com", exception_state(), false));
}
TEST_F(WebSocketTest, CheckSecure) {
EXPECT_CALL(*exception_state(), SetException(_)).Times(0);
scoped_refptr<WebSocket> ws(
new WebSocket(settings(), "ws://example.com/", exception_state(), false));
EXPECT_FALSE(ws->IsSecure());
EXPECT_CALL(*exception_state(), SetException(_)).Times(0);
scoped_refptr<WebSocket> wss(
new WebSocket(settings(), "wss://example.com", exception_state(), false));
EXPECT_TRUE(wss->IsSecure());
}
// Ithe url string is not an absolute URL, then fail this algorithm.
TEST_F(WebSocketTest, SyntaxErrorWhenRelativeUrl) {
scoped_refptr<script::ScriptException> exception;
EXPECT_CALL(*exception_state(), SetException(_))
.WillOnce(SaveArg<0>(&exception));
scoped_refptr<WebSocket> ws(
new WebSocket(settings(), "relative_url", exception_state(), false));
web::DOMException& dom_exception(
*base::polymorphic_downcast<web::DOMException*>(exception.get()));
ASSERT_TRUE(exception.get());
EXPECT_EQ(web::DOMException::kSyntaxErr, dom_exception.code());
EXPECT_EQ(dom_exception.message(),
"Only relative URLs are supported. [relative_url] is not an "
"absolute URL.");
}
TEST_F(WebSocketTest, URLHasFragments) {
scoped_refptr<script::ScriptException> exception;
EXPECT_CALL(*exception_state(), SetException(_))
.WillOnce(SaveArg<0>(&exception));
scoped_refptr<WebSocket> ws(new WebSocket(
settings(), "wss://example.com/#fragment", exception_state(), false));
web::DOMException& dom_exception(
*base::polymorphic_downcast<web::DOMException*>(exception.get()));
ASSERT_TRUE(exception.get());
EXPECT_EQ(web::DOMException::kSyntaxErr, dom_exception.code());
EXPECT_EQ(dom_exception.message(),
"URL has a fragment 'fragment'. Fragments are not are supported "
"in websocket URLs.");
}
TEST_F(WebSocketTest, URLHasPort) {
scoped_refptr<WebSocket> ws(new WebSocket(settings(), "wss://example.com:789",
exception_state(), false));
EXPECT_EQ(ws->GetPort(), 789);
EXPECT_EQ(ws->GetPortAsString(), "789");
}
TEST_F(WebSocketTest, URLHasNoPort) {
// Per spec:
// If there is no explicit port, then: if secure is false, let port be 80,
// otherwise let port be 443.
scoped_refptr<WebSocket> ws(
new WebSocket(settings(), "ws://example.com", exception_state(), false));
EXPECT_EQ(ws->GetPort(), 80);
EXPECT_EQ(ws->GetPortAsString(), "80");
scoped_refptr<WebSocket> wss(
new WebSocket(settings(), "wss://example.com", exception_state(), false));
EXPECT_EQ(wss->GetPort(), 443);
EXPECT_EQ(wss->GetPortAsString(), "443");
}
TEST_F(WebSocketTest, ParseHost) {
// Per spec:
// Let host be the value of the <host> component of url, converted to ASCII
// lowercase.
scoped_refptr<WebSocket> ws(
new WebSocket(settings(), "wss://eXAmpLe.com", exception_state(), false));
std::string host(ws->GetHost());
EXPECT_EQ(host, "example.com");
}
TEST_F(WebSocketTest, ParseResourceName) {
// Per spec:
// Let resource name be the value of the <path> component (which might be
// empty) of
// url.
scoped_refptr<WebSocket> ws(new WebSocket(
settings(), "ws://eXAmpLe.com/resource_name", exception_state(), false));
std::string resource_name(ws->GetResourceName());
EXPECT_EQ(resource_name, "/resource_name");
}
TEST_F(WebSocketTest, ParseEmptyResourceName) {
// Per spec:
// If resource name is the empty string, set it to a single character U+002F
// SOLIDUS (/).
scoped_refptr<WebSocket> ws(
new WebSocket(settings(), "ws://example.com", exception_state(), false));
std::string resource_name(ws->GetResourceName());
EXPECT_EQ(resource_name, "/");
}
TEST_F(WebSocketTest, ParseResourceNameWithQuery) {
// Per spec:
// If url has a <query> component, then append a single U+003F QUESTION MARK
// character (?) to resource name, followed by the value of the <query>
// component.
scoped_refptr<WebSocket> ws(
new WebSocket(settings(), "ws://example.com/resource_name?abc=xyz&j=3",
exception_state(), false));
std::string resource_name(ws->GetResourceName());
EXPECT_EQ(resource_name, "/resource_name?abc=xyz&j=3");
}
TEST_F(WebSocketTest, FailInsecurePort) {
scoped_refptr<script::ScriptException> exception;
EXPECT_CALL(*exception_state(), SetException(_))
.WillOnce(SaveArg<0>(&exception));
scoped_refptr<WebSocket> ws(new WebSocket(settings(), "ws://example.com:22",
exception_state(), false));
web::DOMException& dom_exception(
*base::polymorphic_downcast<web::DOMException*>(exception.get()));
ASSERT_TRUE(exception.get());
EXPECT_EQ(web::DOMException::kSecurityErr, dom_exception.code());
EXPECT_EQ(dom_exception.message(),
"Connecting to port 22 using websockets is not allowed.");
}
TEST_F(WebSocketTest, FailInvalidSubProtocols) {
struct InvalidSubProtocolCase {
const char* sub_protocol;
bool exception_thrown;
web::DOMException::ExceptionCode exception_code;
const char* error_message;
} invalid_subprotocol_cases[] = {
{"a,b", true, web::DOMException::kSyntaxErr,
"Invalid subprotocol [a,b]. Subprotocols' characters must be in valid "
"range and not have separating characters. See RFC 2616 for details."},
{"a b", true, web::DOMException::kSyntaxErr,
"Invalid subprotocol [a b]. Subprotocols' characters must be in valid "
"range and not have separating characters. See RFC 2616 for details."},
{"? b", true, web::DOMException::kSyntaxErr,
"Invalid subprotocol [? b]. Subprotocols' characters must be in valid "
"range and not have separating characters. See RFC 2616 for details."},
{" b", true, web::DOMException::kSyntaxErr,
"Invalid subprotocol [ b]. Subprotocols' characters must be in valid "
"range and not have separating characters. See RFC 2616 for details."},
};
for (std::size_t i(0); i != ARRAYSIZE_UNSAFE(invalid_subprotocol_cases);
++i) {
const InvalidSubProtocolCase& test_case(invalid_subprotocol_cases[i]);
scoped_refptr<script::ScriptException> exception;
if (test_case.exception_thrown) {
EXPECT_CALL(*exception_state(), SetException(_))
.WillOnce(SaveArg<0>(&exception));
}
scoped_refptr<WebSocket> ws(new WebSocket(settings(), "ws://example.com",
test_case.sub_protocol,
exception_state(), false));
if (test_case.exception_thrown) {
web::DOMException& dom_exception(
*base::polymorphic_downcast<web::DOMException*>(exception.get()));
ASSERT_TRUE(exception.get());
EXPECT_EQ(dom_exception.code(), test_case.exception_code);
EXPECT_EQ(dom_exception.message(), test_case.error_message);
}
}
}
TEST_F(WebSocketTest, SubProtocols) {
{
scoped_refptr<script::ScriptException> exception;
std::vector<std::string> sub_protocols;
sub_protocols.push_back("chat.example.com");
sub_protocols.push_back("bicker.example.com");
scoped_refptr<WebSocket> ws(new WebSocket(settings(), "ws://example.com",
sub_protocols, exception_state(),
false));
ASSERT_FALSE(exception.get());
}
{
scoped_refptr<script::ScriptException> exception;
std::string sub_protocol("chat");
scoped_refptr<WebSocket> ws(new WebSocket(settings(), "ws://example.com",
sub_protocol, exception_state(),
false));
ASSERT_FALSE(exception.get());
}
}
TEST_F(WebSocketTest, DuplicatedSubProtocols) {
scoped_refptr<script::ScriptException> exception;
EXPECT_CALL(*exception_state(), SetException(_))
.WillOnce(SaveArg<0>(&exception));
std::vector<std::string> sub_protocols;
sub_protocols.push_back("chat");
sub_protocols.push_back("chat");
scoped_refptr<WebSocket> ws(new WebSocket(
settings(), "ws://example.com", sub_protocols, exception_state(), false));
ASSERT_TRUE(exception.get());
web::DOMException& dom_exception(
*base::polymorphic_downcast<web::DOMException*>(exception.get()));
ASSERT_TRUE(exception.get());
EXPECT_EQ(dom_exception.code(), web::DOMException::kSyntaxErr);
EXPECT_EQ(dom_exception.message(), "Subprotocol values must be unique.");
}
} // namespace websocket
} // namespace cobalt