Browse Source

Remove support for Websocket hixie76 draft

It was only used by Safari 5.0.1 and possibly 5.1. Their market share
is dropping as we speak. It was also insecure (disabled in Firefox
for that reason).

This will allow us to make much more efficient and cleaner code for
the rest of the Websocket versions we support (drafts 7 to 17 + RFC),
which are pretty much all versions seen in the wild excluding the
one we're removing here.
Loïc Hoguin 12 years ago
parent
commit
10e3692fa6
2 changed files with 49 additions and 194 deletions
  1. 30 132
      src/cowboy_websocket.erl
  2. 19 62
      test/ws_SUITE.erl

+ 30 - 132
src/cowboy_websocket.erl

@@ -12,7 +12,10 @@
 %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 
-%% @doc WebSocket protocol implementation.
+%% @doc Websocket protocol implementation.
+%%
+%% Cowboy supports versions 7 through 17 of the Websocket drafts.
+%% It also supports RFC6455, the proposed standard for Websocket.
 -module(cowboy_websocket).
 -module(cowboy_websocket).
 
 
 %% API.
 %% API.
@@ -41,22 +44,19 @@
 	env :: cowboy_middleware:env(),
 	env :: cowboy_middleware:env(),
 	socket = undefined :: inet:socket(),
 	socket = undefined :: inet:socket(),
 	transport = undefined :: module(),
 	transport = undefined :: module(),
-	version :: 0 | 7 | 8 | 13,
 	handler :: module(),
 	handler :: module(),
 	handler_opts :: any(),
 	handler_opts :: any(),
-	challenge = undefined :: undefined | binary() | {binary(), binary()},
+	key = undefined :: undefined | binary(),
 	timeout = infinity :: timeout(),
 	timeout = infinity :: timeout(),
 	timeout_ref = undefined :: undefined | reference(),
 	timeout_ref = undefined :: undefined | reference(),
 	messages = undefined :: undefined | {atom(), atom(), atom()},
 	messages = undefined :: undefined | {atom(), atom(), atom()},
 	hibernate = false :: boolean(),
 	hibernate = false :: boolean(),
-	eop :: undefined | tuple(), %% hixie-76 specific.
-	origin = undefined :: undefined | binary(), %% hixie-76 specific.
 	frag_state = undefined :: frag_state()
 	frag_state = undefined :: frag_state()
 }).
 }).
 
 
-%% @doc Upgrade a HTTP request to the WebSocket protocol.
+%% @doc Upgrade an HTTP request to the Websocket protocol.
 %%
 %%
-%% You do not need to call this function manually. To upgrade to the WebSocket
+%% You do not need to call this function manually. To upgrade to the Websocket
 %% protocol, you simply need to return <em>{upgrade, protocol, {@module}}</em>
 %% protocol, you simply need to return <em>{upgrade, protocol, {@module}}</em>
 %% in your <em>cowboy_http_handler:init/3</em> handler function.
 %% in your <em>cowboy_http_handler:init/3</em> handler function.
 -spec upgrade(Req, Env, module(), any())
 -spec upgrade(Req, Env, module(), any())
@@ -84,36 +84,13 @@ websocket_upgrade(State, Req) ->
 	{ok, [<<"websocket">>], Req3}
 	{ok, [<<"websocket">>], Req3}
 		= cowboy_req:parse_header(<<"upgrade">>, Req2),
 		= cowboy_req:parse_header(<<"upgrade">>, Req2),
 	{Version, Req4} = cowboy_req:header(<<"sec-websocket-version">>, Req3),
 	{Version, Req4} = cowboy_req:header(<<"sec-websocket-version">>, Req3),
-	websocket_upgrade(Version, State, Req4).
-
-%% @todo Handle the Sec-Websocket-Protocol header.
-%% @todo Reply a proper error, don't die, if a required header is undefined.
--spec websocket_upgrade(undefined | <<_:8>>, #state{}, Req)
-	-> {ok, #state{}, Req} when Req::cowboy_req:req().
-%% No version given. Assuming hixie-76 draft.
-%%
-%% We need to wait to send a reply back before trying to read the
-%% third part of the challenge key, because proxies will wait for
-%% a reply before sending it. Therefore we calculate the challenge
-%% key only in websocket_handshake/3.
-websocket_upgrade(undefined, State, Req) ->
-	{Origin, Req2} = cowboy_req:header(<<"origin">>, Req),
-	{Key1, Req3} = cowboy_req:header(<<"sec-websocket-key1">>, Req2),
-	{Key2, Req4} = cowboy_req:header(<<"sec-websocket-key2">>, Req3),
-	false = lists:member(undefined, [Origin, Key1, Key2]),
-	EOP = binary:compile_pattern(<< 255 >>),
-	{ok, State#state{version=0, origin=Origin, challenge={Key1, Key2},
-		eop=EOP}, cowboy_req:set_meta(websocket_version, 0, Req4)};
-%% Versions 7 and 8. Implementation follows the hybi 7 through 17 drafts.
-websocket_upgrade(Version, State, Req)
-		when Version =:= <<"7">>; Version =:= <<"8">>;
-			Version =:= <<"13">> ->
-	{Key, Req2} = cowboy_req:header(<<"sec-websocket-key">>, Req),
-	false = Key =:= undefined,
-	Challenge = hybi_challenge(Key),
 	IntVersion = list_to_integer(binary_to_list(Version)),
 	IntVersion = list_to_integer(binary_to_list(Version)),
-	{ok, State#state{version=IntVersion, challenge=Challenge},
-		cowboy_req:set_meta(websocket_version, IntVersion, Req2)}.
+	true = (IntVersion =:= 7) orelse (IntVersion =:= 8)
+		orelse (IntVersion =:= 13),
+	{Key, Req5} = cowboy_req:header(<<"sec-websocket-key">>, Req4),
+	false = Key =:= undefined,
+	{ok, State#state{key=Key},
+		cowboy_req:set_meta(websocket_version, IntVersion, Req5)}.
 
 
 -spec handler_init(#state{}, Req)
 -spec handler_init(#state{}, Req)
 	-> {ok, Req, cowboy_middleware:env()} | {error, 400, Req}
 	-> {ok, Req, cowboy_middleware:env()} | {error, 400, Req}
@@ -160,39 +137,10 @@ upgrade_error(Req, Env) ->
 	-> {ok, Req, cowboy_middleware:env()}
 	-> {ok, Req, cowboy_middleware:env()}
 	| {suspend, module(), atom(), [any()]}
 	| {suspend, module(), atom(), [any()]}
 	when Req::cowboy_req:req().
 	when Req::cowboy_req:req().
-websocket_handshake(State=#state{socket=Socket, transport=Transport,
-		version=0, origin=Origin, challenge={Key1, Key2}},
-		Req, HandlerState) ->
-	{<< "http", Location/binary >>, Req1} = cowboy_req:url(Req),
-	{ok, Req2} = cowboy_req:upgrade_reply(
-		<<"101 WebSocket Protocol Handshake">>,
-		[{<<"upgrade">>, <<"WebSocket">>},
-		 {<<"sec-websocket-location">>, << "ws", Location/binary >>},
-		 {<<"sec-websocket-origin">>, Origin}],
-		Req1),
-	%% Flush the resp_sent message before moving on.
-	receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
-	%% We replied with a proper response. Proxies should be happy enough,
-	%% we can now read the 8 last bytes of the challenge keys and send
-	%% the challenge response directly to the socket.
-	%%
-	%% We use a trick here to read exactly 8 bytes of the body regardless
-	%% of what's in the buffer.
-	{ok, Req3} = cowboy_req:init_stream(
-		fun cowboy_http:te_identity/2, {0, 8},
-		fun cowboy_http:ce_identity/1, Req2),
-	case cowboy_req:body(Req3) of
-		{ok, Key3, Req4} ->
-			Challenge = hixie76_challenge(Key1, Key2, Key3),
-			Transport:send(Socket, Challenge),
-			handler_before_loop(State#state{messages=Transport:messages()},
-				Req4, HandlerState, <<>>);
-		_Any ->
-			%% If an error happened reading the body, stop there.
-			handler_terminate(State, Req3, HandlerState, {error, closed})
-	end;
-websocket_handshake(State=#state{transport=Transport, challenge=Challenge},
+websocket_handshake(State=#state{transport=Transport, key=Key},
 		Req, HandlerState) ->
 		Req, HandlerState) ->
+	Challenge = base64:encode(crypto:sha(
+		<< Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>)),
 	{ok, Req2} = cowboy_req:upgrade_reply(
 	{ok, Req2} = cowboy_req:upgrade_reply(
 		101,
 		101,
 		[{<<"upgrade">>, <<"websocket">>},
 		[{<<"upgrade">>, <<"websocket">>},
@@ -201,8 +149,8 @@ websocket_handshake(State=#state{transport=Transport, challenge=Challenge},
 	%% Flush the resp_sent message before moving on.
 	%% Flush the resp_sent message before moving on.
 	receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
 	receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
 	State2 = handler_loop_timeout(State),
 	State2 = handler_loop_timeout(State),
-	handler_before_loop(State2#state{messages=Transport:messages()},
-		Req2, HandlerState, <<>>).
+	handler_before_loop(State2#state{key=undefined,
+		messages=Transport:messages()}, Req2, HandlerState, <<>>).
 
 
 -spec handler_before_loop(#state{}, Req, any(), binary())
 -spec handler_before_loop(#state{}, Req, any(), binary())
 	-> {ok, Req, cowboy_middleware:env()}
 	-> {ok, Req, cowboy_middleware:env()}
@@ -261,26 +209,8 @@ handler_loop(State=#state{
 %% No more data.
 %% No more data.
 websocket_data(State, Req, HandlerState, <<>>) ->
 websocket_data(State, Req, HandlerState, <<>>) ->
 	handler_before_loop(State, Req, HandlerState, <<>>);
 	handler_before_loop(State, Req, HandlerState, <<>>);
-%% hixie-76 close frame.
-websocket_data(State=#state{version=0}, Req, HandlerState,
-		<< 255, 0, _Rest/binary >>) ->
-	websocket_close(State, Req, HandlerState, {normal, closed});
-%% hixie-76 data frame. We only support the frame type 0, same as the specs.
-websocket_data(State=#state{version=0, eop=EOP}, Req, HandlerState,
-		Data = << 0, _/binary >>) ->
-	case binary:match(Data, EOP) of
-		{Pos, 1} ->
-			Pos2 = Pos - 1,
-			<< 0, Payload:Pos2/binary, 255, Rest/bits >> = Data,
-			handler_call(State, Req, HandlerState,
-				Rest, websocket_handle, {text, Payload}, fun websocket_data/4);
-		nomatch ->
-			%% @todo We probably should allow limiting frame length.
-			handler_before_loop(State, Req, HandlerState, Data)
-	end;
-%% incomplete hybi data frame.
-websocket_data(State=#state{version=Version}, Req, HandlerState, Data)
-		when Version =/= 0, byte_size(Data) =:= 1 ->
+%% Incomplete.
+websocket_data(State, Req, HandlerState, Data) when byte_size(Data) =:= 1 ->
 	handler_before_loop(State, Req, HandlerState, Data);
 	handler_before_loop(State, Req, HandlerState, Data);
 %% 7 bit payload length prefix exists
 %% 7 bit payload length prefix exists
 websocket_data(State, Req, HandlerState,
 websocket_data(State, Req, HandlerState,
@@ -361,8 +291,8 @@ websocket_data(State, Req, HandlerState, _Fin, _Rsv, Opcode, _Mask, PayloadLen,
 		_Rest, _Data) when Opcode >= 8, PayloadLen > 125 ->
 		_Rest, _Data) when Opcode >= 8, PayloadLen > 125 ->
 	 websocket_close(State, Req, HandlerState, {error, badframe});
 	 websocket_close(State, Req, HandlerState, {error, badframe});
 %% unfragmented message. unmask and dispatch the message.
 %% unfragmented message. unmask and dispatch the message.
-websocket_data(State=#state{version=Version}, Req, HandlerState, _Fin=1, _Rsv=0,
-		Opcode, Mask, PayloadLen, Rest, Data) when Version =/= 0 ->
+websocket_data(State, Req, HandlerState, _Fin=1, _Rsv=0,
+		Opcode, Mask, PayloadLen, Rest, Data) ->
 	websocket_before_unmask(
 	websocket_before_unmask(
 			State, Req, HandlerState, Data, Rest, Opcode, Mask, PayloadLen);
 			State, Req, HandlerState, Data, Rest, Opcode, Mask, PayloadLen);
 %% Something was wrong with the frame. Close the connection.
 %% Something was wrong with the frame. Close the connection.
@@ -370,7 +300,7 @@ websocket_data(State, Req, HandlerState, _Fin, _Rsv, _Opcode, _Mask,
 		_PayloadLen, _Rest, _Data) ->
 		_PayloadLen, _Rest, _Data) ->
 		websocket_close(State, Req, HandlerState, {error, badframe}).
 		websocket_close(State, Req, HandlerState, {error, badframe}).
 
 
-%% hybi routing depending on whether unmasking is needed.
+%% Routing depending on whether unmasking is needed.
 -spec websocket_before_unmask(#state{}, Req, any(), binary(),
 -spec websocket_before_unmask(#state{}, Req, any(), binary(),
 	binary(), opcode(), 0 | 1, non_neg_integer() | undefined)
 	binary(), opcode(), 0 | 1, non_neg_integer() | undefined)
 	-> {ok, Req, cowboy_middleware:env()}
 	-> {ok, Req, cowboy_middleware:env()}
@@ -390,7 +320,7 @@ websocket_before_unmask(State, Req, HandlerState, Data,
 				Opcode, Payload, MaskKey)
 				Opcode, Payload, MaskKey)
 	end.
 	end.
 
 
-%% hybi unmasking.
+%% Unmasking.
 -spec websocket_unmask(#state{}, Req, any(), binary(),
 -spec websocket_unmask(#state{}, Req, any(), binary(),
 	opcode(), binary(), mask_key())
 	opcode(), binary(), mask_key())
 	-> {ok, Req, cowboy_middleware:env()}
 	-> {ok, Req, cowboy_middleware:env()}
@@ -434,7 +364,7 @@ websocket_unmask(State, Req, HandlerState, RemainingData,
 	websocket_dispatch(State, Req, HandlerState, RemainingData,
 	websocket_dispatch(State, Req, HandlerState, RemainingData,
 		Opcode, Acc).
 		Opcode, Acc).
 
 
-%% hybi dispatching.
+%% Dispatching.
 -spec websocket_dispatch(#state{}, Req, any(), binary(), opcode(), binary())
 -spec websocket_dispatch(#state{}, Req, any(), binary(), opcode(), binary())
 	-> {ok, Req, cowboy_middleware:env()}
 	-> {ok, Req, cowboy_middleware:env()}
 	| {suspend, module(), atom(), [any()]}
 	| {suspend, module(), atom(), [any()]}
@@ -470,7 +400,7 @@ websocket_dispatch(State, Req, HandlerState, _RemainingData, 8, _Payload) ->
 %% Ping control frame. Send a pong back and forward the ping to the handler.
 %% Ping control frame. Send a pong back and forward the ping to the handler.
 websocket_dispatch(State=#state{socket=Socket, transport=Transport},
 websocket_dispatch(State=#state{socket=Socket, transport=Transport},
 		Req, HandlerState, RemainingData, 9, Payload) ->
 		Req, HandlerState, RemainingData, 9, Payload) ->
-	Len = hybi_payload_length(byte_size(Payload)),
+	Len = payload_length_to_binary(byte_size(Payload)),
 	Transport:send(Socket, << 1:1, 0:3, 10:4, 0:1, Len/bits, Payload/binary >>),
 	Transport:send(Socket, << 1:1, 0:3, 10:4, 0:1, Len/bits, Payload/binary >>),
 	handler_call(State, Req, HandlerState, RemainingData,
 	handler_call(State, Req, HandlerState, RemainingData,
 		websocket_handle, {ping, Payload}, fun websocket_data/4);
 		websocket_handle, {ping, Payload}, fun websocket_data/4);
@@ -563,13 +493,6 @@ websocket_opcode(pong) -> 10.
 
 
 -spec websocket_send(frame(), #state{})
 -spec websocket_send(frame(), #state{})
 	-> ok | shutdown | {error, atom()}.
 	-> ok | shutdown | {error, atom()}.
-%% hixie-76 text frame.
-websocket_send({text, Payload}, #state{
-		socket=Socket, transport=Transport, version=0}) ->
-	Transport:send(Socket, [0, Payload, 255]);
-%% Ignore all unknown frame types for compatibility with hixie 76.
-websocket_send(_Any, #state{version=0}) ->
-	ok;
 websocket_send(Type, #state{socket=Socket, transport=Transport})
 websocket_send(Type, #state{socket=Socket, transport=Transport})
 		when Type =:= close ->
 		when Type =:= close ->
 	Opcode = websocket_opcode(Type),
 	Opcode = websocket_opcode(Type),
@@ -589,7 +512,7 @@ websocket_send({Type = close, StatusCode, Payload}, #state{
 	Len = 2 + iolist_size(Payload),
 	Len = 2 + iolist_size(Payload),
 	%% Control packets must not be > 125 in length.
 	%% Control packets must not be > 125 in length.
 	true = Len =< 125,
 	true = Len =< 125,
-	BinLen = hybi_payload_length(Len),
+	BinLen = payload_length_to_binary(Len),
 	Transport:send(Socket,
 	Transport:send(Socket,
 		[<< 1:1, 0:3, Opcode:4, 0:1, BinLen/bits, StatusCode:16 >>, Payload]),
 		[<< 1:1, 0:3, Opcode:4, 0:1, BinLen/bits, StatusCode:16 >>, Payload]),
 	shutdown;
 	shutdown;
@@ -602,7 +525,7 @@ websocket_send({Type, Payload}, #state{socket=Socket, transport=Transport}) ->
 		true ->
 		true ->
 			true
 			true
 	end,
 	end,
-	BinLen = hybi_payload_length(Len),
+	BinLen = payload_length_to_binary(Len),
 	Transport:send(Socket,
 	Transport:send(Socket,
 		[<< 1:1, 0:3, Opcode:4, 0:1, BinLen/bits >>, Payload]).
 		[<< 1:1, 0:3, Opcode:4, 0:1, BinLen/bits >>, Payload]).
 
 
@@ -620,10 +543,6 @@ websocket_send_many([Frame|Tail], State) ->
 -spec websocket_close(#state{}, Req, any(), {atom(), atom()})
 -spec websocket_close(#state{}, Req, any(), {atom(), atom()})
 	-> {ok, Req, cowboy_middleware:env()}
 	-> {ok, Req, cowboy_middleware:env()}
 	when Req::cowboy_req:req().
 	when Req::cowboy_req:req().
-websocket_close(State=#state{socket=Socket, transport=Transport, version=0},
-		Req, HandlerState, Reason) ->
-	Transport:send(Socket, << 255, 0 >>),
-	handler_terminate(State, Req, HandlerState, Reason);
 websocket_close(State=#state{socket=Socket, transport=Transport},
 websocket_close(State=#state{socket=Socket, transport=Transport},
 		Req, HandlerState, Reason) ->
 		Req, HandlerState, Reason) ->
 	Transport:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>),
 	Transport:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>),
@@ -648,30 +567,9 @@ handler_terminate(#state{env=Env, handler=Handler, handler_opts=HandlerOpts},
 	end,
 	end,
 	{ok, Req, [{result, closed}|Env]}.
 	{ok, Req, [{result, closed}|Env]}.
 
 
-%% hixie-76 specific.
-
--spec hixie76_challenge(binary(), binary(), binary()) -> binary().
-hixie76_challenge(Key1, Key2, Key3) ->
-	IntKey1 = hixie76_key_to_integer(Key1),
-	IntKey2 = hixie76_key_to_integer(Key2),
-	erlang:md5(<< IntKey1:32, IntKey2:32, Key3/binary >>).
-
--spec hixie76_key_to_integer(binary()) -> integer().
-hixie76_key_to_integer(Key) ->
-	Number = list_to_integer([C || << C >> <= Key, C >= $0, C =< $9]),
-	Spaces = length([C || << C >> <= Key, C =:= 32]),
-	Number div Spaces.
-
-%% hybi specific.
-
--spec hybi_challenge(binary()) -> binary().
-hybi_challenge(Key) ->
-	Bin = << Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>,
-	base64:encode(crypto:sha(Bin)).
-
--spec hybi_payload_length(0..16#7fffffffffffffff)
+-spec payload_length_to_binary(0..16#7fffffffffffffff)
 	-> << _:7 >> | << _:23 >> | << _:71 >>.
 	-> << _:7 >> | << _:23 >> | << _:71 >>.
-hybi_payload_length(N) ->
+payload_length_to_binary(N) ->
 	case N of
 	case N of
 		N when N =< 125 -> << N:7 >>;
 		N when N =< 125 -> << N:7 >>;
 		N when N =< 16#ffff -> << 126:7, N:16 >>;
 		N when N =< 16#ffff -> << 126:7, N:16 >>;

+ 19 - 62
test/ws_SUITE.erl

@@ -122,10 +122,7 @@ init_dispatch() ->
 
 
 %% ws and wss.
 %% ws and wss.
 
 
-%% This test makes sure the code works even if we wait for a reply
-%% before sending the third challenge key in the GET body.
-%%
-%% This ensures that Cowboy will work fine with proxies on hixie.
+%% We do not support hixie76 anymore.
 ws0(Config) ->
 ws0(Config) ->
 	{port, Port} = lists:keyfind(port, 1, Config),
 	{port, Port} = lists:keyfind(port, 1, Config),
 	{ok, Socket} = gen_tcp:connect("localhost", Port,
 	{ok, Socket} = gen_tcp:connect("localhost", Port,
@@ -140,34 +137,8 @@ ws0(Config) ->
 		"Sec-Websocket-Key2: 1711 M;4\\74  80<6\r\n"
 		"Sec-Websocket-Key2: 1711 M;4\\74  80<6\r\n"
 		"\r\n"),
 		"\r\n"),
 	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
 	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
-	{ok, {http_response, {1, 1}, 101, "WebSocket Protocol Handshake"}, Rest}
-		= erlang:decode_packet(http, Handshake, []),
-	[Headers, <<>>] = websocket_headers(
-		erlang:decode_packet(httph, Rest, []), []),
-	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
-	{'Upgrade', "WebSocket"} = lists:keyfind('Upgrade', 1, Headers),
-	{"sec-websocket-location", "ws://localhost/websocket"}
-		= lists:keyfind("sec-websocket-location", 1, Headers),
-	{"sec-websocket-origin", "http://localhost"}
-		= lists:keyfind("sec-websocket-origin", 1, Headers),
-	ok = gen_tcp:send(Socket, <<15,245,8,18,2,204,133,33>>),
-	{ok, Body} = gen_tcp:recv(Socket, 0, 6000),
-	<<169,244,191,103,146,33,149,59,74,104,67,5,99,118,171,236>> = Body,
-	ok = gen_tcp:send(Socket, << 0, "client_msg", 255 >>),
-	{ok, << 0, "client_msg", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
-	{ok, << 0, "websocket_init", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
-	{ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
-	{ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
-	{ok, << 0, "websocket_handle", 255 >>} = gen_tcp:recv(Socket, 0, 6000),
-	%% We try to send another HTTP request to make sure
-	%% the server closed the request.
-	ok = gen_tcp:send(Socket, [
-		<< 255, 0 >>, %% Close websocket command.
-		"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n" %% Server should ignore it.
-	]),
-	{ok, << 255, 0 >>} = gen_tcp:recv(Socket, 0, 6000),
-	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
-	ok.
+	{ok, {http_response, {1, 1}, 400, _}, _}
+		= erlang:decode_packet(http, Handshake, []).
 
 
 ws8(Config) ->
 ws8(Config) ->
 	{port, Port} = lists:keyfind(port, 1, Config),
 	{port, Port} = lists:keyfind(port, 1, Config),
@@ -479,7 +450,6 @@ ws_text_fragments(Config) ->
 		<< 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
 		<< 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
 	{ok, << 1:1, 0:3, 1:4, 0:1, 15:7, "HelloHelloHello" >>}
 	{ok, << 1:1, 0:3, 1:4, 0:1, 15:7, "HelloHelloHello" >>}
 		= gen_tcp:recv(Socket, 0, 6000),
 		= gen_tcp:recv(Socket, 0, 6000),
-
 	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
 	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
 	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
 	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
 	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
 	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
@@ -547,41 +517,28 @@ ws_timeout_reset(Config) ->
 		"GET /ws_timeout_cancel HTTP/1.1\r\n"
 		"GET /ws_timeout_cancel HTTP/1.1\r\n"
 		"Host: localhost\r\n"
 		"Host: localhost\r\n"
 		"Connection: Upgrade\r\n"
 		"Connection: Upgrade\r\n"
-		"Upgrade: WebSocket\r\n"
-		"Origin: http://localhost\r\n"
-		"Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i\r\n"
-		"Sec-Websocket-Key2: 1711 M;4\\74  80<6\r\n"
+		"Upgrade: websocket\r\n"
+		"Sec-WebSocket-Origin: http://localhost\r\n"
+		"Sec-Websocket-Version: 13\r\n"
+		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
 		"\r\n"]),
 		"\r\n"]),
 	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
 	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
-	{ok, {http_response, {1, 1}, 101, "WebSocket Protocol Handshake"}, Rest}
+	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
 		= erlang:decode_packet(http, Handshake, []),
 		= erlang:decode_packet(http, Handshake, []),
 	[Headers, <<>>] = websocket_headers(
 	[Headers, <<>>] = websocket_headers(
 		erlang:decode_packet(httph, Rest, []), []),
 		erlang:decode_packet(httph, Rest, []), []),
 	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
 	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
-	{'Upgrade', "WebSocket"} = lists:keyfind('Upgrade', 1, Headers),
-	{"sec-websocket-location", "ws://localhost/ws_timeout_cancel"}
-		= lists:keyfind("sec-websocket-location", 1, Headers),
-	{"sec-websocket-origin", "http://localhost"}
-		= lists:keyfind("sec-websocket-origin", 1, Headers),
-	ok = gen_tcp:send(Socket, <<15,245,8,18,2,204,133,33>>),
-	{ok, Body} = gen_tcp:recv(Socket, 0, 6000),
-	<<169,244,191,103,146,33,149,59,74,104,67,5,99,118,171,236>> = Body,
-	ok = gen_tcp:send(Socket, << 0, "msg sent", 255 >>),
-	{ok, << 0, "msg sent", 255 >>}
-		= gen_tcp:recv(Socket, 0, 6000),
-	ok = timer:sleep(500),
-	ok = gen_tcp:send(Socket, << 0, "msg sent", 255 >>),
-	{ok, << 0, "msg sent", 255 >>}
-		= gen_tcp:recv(Socket, 0, 6000),
-	ok = timer:sleep(500),
-	ok = gen_tcp:send(Socket, << 0, "msg sent", 255 >>),
-	{ok, << 0, "msg sent", 255 >>}
-		= gen_tcp:recv(Socket, 0, 6000),
-	ok = timer:sleep(500),
-	ok = gen_tcp:send(Socket, << 0, "msg sent", 255 >>),
-	{ok, << 0, "msg sent", 255 >>}
-		= gen_tcp:recv(Socket, 0, 6000),
-	{ok, << 255, 0 >>} = gen_tcp:recv(Socket, 0, 6000),
+	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
+	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
+		= lists:keyfind("sec-websocket-accept", 1, Headers),
+	[begin
+		ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
+			16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
+		{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
+			= gen_tcp:recv(Socket, 0, 6000),
+		ok = timer:sleep(500)
+	end || _ <- [1, 2, 3, 4]],
+	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
 	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
 	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
 	ok.
 	ok.