0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 20:30:23 +00:00

Merge branch 'fix/websocket-inflate-unmask' of git://github.com/soundrop/cowboy

This commit is contained in:
Loïc Hoguin 2013-07-12 21:57:27 +02:00
commit ddefa33a38
2 changed files with 173 additions and 61 deletions

View file

@ -57,9 +57,8 @@
frag_state = undefined :: frag_state(), frag_state = undefined :: frag_state(),
utf8_state = <<>> :: binary(), utf8_state = <<>> :: binary(),
deflate_frame = false :: boolean(), deflate_frame = false :: boolean(),
inflate_state :: any(), inflate_state :: undefined | port(),
inflate_buffer = <<>> :: binary(), deflate_state :: undefined | port()
deflate_state :: any()
}). }).
%% @doc Upgrade an HTTP request to the Websocket protocol. %% @doc Upgrade an HTTP request to the Websocket protocol.
@ -121,7 +120,6 @@ websocket_extensions(State, Req) ->
{ok, State#state{ {ok, State#state{
deflate_frame = true, deflate_frame = true,
inflate_state = Inflate, inflate_state = Inflate,
inflate_buffer = <<>>,
deflate_state = Deflate deflate_state = Deflate
}, Req2}; }, Req2};
_ -> _ ->
@ -331,45 +329,49 @@ websocket_data(State, Req, HandlerState, Data) ->
websocket_data(State=#state{frag_state=undefined}, Req, HandlerState, websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
Opcode, Len, MaskKey, Data, Rsv, 0) -> Opcode, Len, MaskKey, Data, Rsv, 0) ->
websocket_payload(State#state{frag_state={nofin, Opcode, <<>>}}, websocket_payload(State#state{frag_state={nofin, Opcode, <<>>}},
Req, HandlerState, 0, Len, MaskKey, <<>>, Data, Rsv); Req, HandlerState, 0, Len, MaskKey, <<>>, 0, Data, Rsv);
%% Subsequent frame fragments. %% Subsequent frame fragments.
websocket_data(State=#state{frag_state={nofin, _, _}}, Req, HandlerState, websocket_data(State=#state{frag_state={nofin, _, _}}, Req, HandlerState,
0, Len, MaskKey, Data, Rsv, 0) -> 0, Len, MaskKey, Data, Rsv, 0) ->
websocket_payload(State, Req, HandlerState, websocket_payload(State, Req, HandlerState,
0, Len, MaskKey, <<>>, Data, Rsv); 0, Len, MaskKey, <<>>, 0, Data, Rsv);
%% Final frame fragment. %% Final frame fragment.
websocket_data(State=#state{frag_state={nofin, Opcode, SoFar}}, websocket_data(State=#state{frag_state={nofin, Opcode, SoFar}},
Req, HandlerState, 0, Len, MaskKey, Data, Rsv, 1) -> Req, HandlerState, 0, Len, MaskKey, Data, Rsv, 1) ->
websocket_payload(State#state{frag_state={fin, Opcode, SoFar}}, websocket_payload(State#state{frag_state={fin, Opcode, SoFar}},
Req, HandlerState, 0, Len, MaskKey, <<>>, Data, Rsv); Req, HandlerState, 0, Len, MaskKey, <<>>, 0, Data, Rsv);
%% Unfragmented frame. %% Unfragmented frame.
websocket_data(State, Req, HandlerState, Opcode, Len, MaskKey, Data, Rsv, 1) -> websocket_data(State, Req, HandlerState, Opcode, Len, MaskKey, Data, Rsv, 1) ->
websocket_payload(State, Req, HandlerState, websocket_payload(State, Req, HandlerState,
Opcode, Len, MaskKey, <<>>, Data, Rsv). Opcode, Len, MaskKey, <<>>, 0, Data, Rsv).
-spec websocket_payload(#state{}, Req, any(), -spec websocket_payload(#state{}, Req, any(),
opcode(), non_neg_integer(), mask_key(), binary(), binary(), rsv()) opcode(), non_neg_integer(), mask_key(), binary(), non_neg_integer(),
binary(), rsv())
-> {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().
%% Close control frames with a payload MUST contain a valid close code. %% Close control frames with a payload MUST contain a valid close code.
websocket_payload(State, Req, HandlerState, websocket_payload(State, Req, HandlerState,
Opcode=8, Len, MaskKey, <<>>, << MaskedCode:2/binary, Rest/bits >>, Rsv) -> Opcode=8, Len, MaskKey, <<>>, 0,
<< MaskedCode:2/binary, Rest/bits >>, Rsv) ->
Unmasked = << Code:16 >> = websocket_unmask(MaskedCode, MaskKey, <<>>), Unmasked = << Code:16 >> = websocket_unmask(MaskedCode, MaskKey, <<>>),
if Code < 1000; Code =:= 1004; Code =:= 1005; Code =:= 1006; if Code < 1000; Code =:= 1004; Code =:= 1005; Code =:= 1006;
(Code > 1011) and (Code < 3000); Code > 4999 -> (Code > 1011) and (Code < 3000); Code > 4999 ->
websocket_close(State, Req, HandlerState, {error, badframe}); websocket_close(State, Req, HandlerState, {error, badframe});
true -> true ->
websocket_payload(State, Req, HandlerState, websocket_payload(State, Req, HandlerState,
Opcode, Len - 2, MaskKey, Unmasked, Rest, Rsv) Opcode, Len - 2, MaskKey, Unmasked, byte_size(MaskedCode),
Rest, Rsv)
end; end;
%% Text frames and close control frames MUST have a payload that is valid UTF-8. %% Text frames and close control frames MUST have a payload that is valid UTF-8.
websocket_payload(State=#state{utf8_state=Incomplete}, websocket_payload(State=#state{utf8_state=Incomplete},
Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Data, Rsv) Req, HandlerState, Opcode, Len, MaskKey, Unmasked, UnmaskedLen,
Data, Rsv)
when (byte_size(Data) < Len) andalso ((Opcode =:= 1) orelse when (byte_size(Data) < Len) andalso ((Opcode =:= 1) orelse
((Opcode =:= 8) andalso (Unmasked =/= <<>>))) -> ((Opcode =:= 8) andalso (Unmasked =/= <<>>))) ->
Unmasked2 = websocket_unmask(Data, Unmasked2 = websocket_unmask(Data,
rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>), rotate_mask_key(MaskKey, UnmaskedLen), <<>>),
{Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State), {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State),
case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of
false -> false ->
@ -377,14 +379,16 @@ websocket_payload(State=#state{utf8_state=Incomplete},
Utf8State -> Utf8State ->
websocket_payload_loop(State2#state{utf8_state=Utf8State}, websocket_payload_loop(State2#state{utf8_state=Utf8State},
Req, HandlerState, Opcode, Len - byte_size(Data), MaskKey, Req, HandlerState, Opcode, Len - byte_size(Data), MaskKey,
<< Unmasked/binary, Unmasked3/binary >>, Rsv) << Unmasked/binary, Unmasked3/binary >>,
UnmaskedLen + byte_size(Data), Rsv)
end; end;
websocket_payload(State=#state{utf8_state=Incomplete}, websocket_payload(State=#state{utf8_state=Incomplete},
Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Data, Rsv) Req, HandlerState, Opcode, Len, MaskKey, Unmasked, UnmaskedLen,
Data, Rsv)
when Opcode =:= 1; (Opcode =:= 8) and (Unmasked =/= <<>>) -> when Opcode =:= 1; (Opcode =:= 8) and (Unmasked =/= <<>>) ->
<< End:Len/binary, Rest/bits >> = Data, << End:Len/binary, Rest/bits >> = Data,
Unmasked2 = websocket_unmask(End, Unmasked2 = websocket_unmask(End,
rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>), rotate_mask_key(MaskKey, UnmaskedLen), <<>>),
{Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, true, State), {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, true, State),
case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of
<<>> -> <<>> ->
@ -396,10 +400,11 @@ websocket_payload(State=#state{utf8_state=Incomplete},
end; end;
%% Fragmented text frames may cut payload in the middle of UTF-8 codepoints. %% Fragmented text frames may cut payload in the middle of UTF-8 codepoints.
websocket_payload(State=#state{frag_state={_, 1, _}, utf8_state=Incomplete}, websocket_payload(State=#state{frag_state={_, 1, _}, utf8_state=Incomplete},
Req, HandlerState, Opcode=0, Len, MaskKey, Unmasked, Data, Rsv) Req, HandlerState, Opcode=0, Len, MaskKey, Unmasked, UnmaskedLen,
Data, Rsv)
when byte_size(Data) < Len -> when byte_size(Data) < Len ->
Unmasked2 = websocket_unmask(Data, Unmasked2 = websocket_unmask(Data,
rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>), rotate_mask_key(MaskKey, UnmaskedLen), <<>>),
{Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State), {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State),
case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of
false -> false ->
@ -407,14 +412,16 @@ websocket_payload(State=#state{frag_state={_, 1, _}, utf8_state=Incomplete},
Utf8State -> Utf8State ->
websocket_payload_loop(State2#state{utf8_state=Utf8State}, websocket_payload_loop(State2#state{utf8_state=Utf8State},
Req, HandlerState, Opcode, Len - byte_size(Data), MaskKey, Req, HandlerState, Opcode, Len - byte_size(Data), MaskKey,
<< Unmasked/binary, Unmasked3/binary >>, Rsv) << Unmasked/binary, Unmasked3/binary >>,
UnmaskedLen + byte_size(Data), Rsv)
end; end;
websocket_payload(State=#state{frag_state={Fin, 1, _}, utf8_state=Incomplete}, websocket_payload(State=#state{frag_state={Fin, 1, _}, utf8_state=Incomplete},
Req, HandlerState, Opcode=0, Len, MaskKey, Unmasked, Data, Rsv) -> Req, HandlerState, Opcode=0, Len, MaskKey, Unmasked, UnmaskedLen,
Data, Rsv) ->
<< End:Len/binary, Rest/bits >> = Data, << End:Len/binary, Rest/bits >> = Data,
Unmasked2 = websocket_unmask(End, Unmasked2 = websocket_unmask(End,
rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>), rotate_mask_key(MaskKey, UnmaskedLen), <<>>),
{Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, true, State), {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, Fin =:= fin, State),
case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of
<<>> -> <<>> ->
websocket_dispatch(State2#state{utf8_state= <<>>}, websocket_dispatch(State2#state{utf8_state= <<>>},
@ -429,20 +436,23 @@ websocket_payload(State=#state{frag_state={Fin, 1, _}, utf8_state=Incomplete},
end; end;
%% Other frames have a binary payload. %% Other frames have a binary payload.
websocket_payload(State, Req, HandlerState, websocket_payload(State, Req, HandlerState,
Opcode, Len, MaskKey, Unmasked, Data, Rsv) Opcode, Len, MaskKey, Unmasked, UnmaskedLen, Data, Rsv)
when byte_size(Data) < Len -> when byte_size(Data) < Len ->
Unmasked2 = websocket_unmask(Data, Unmasked2 = websocket_unmask(Data,
rotate_mask_key(MaskKey, byte_size(Unmasked)), Unmasked), rotate_mask_key(MaskKey, UnmaskedLen), <<>>),
{Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State), {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State),
websocket_payload_loop(State2, Req, HandlerState, websocket_payload_loop(State2, Req, HandlerState,
Opcode, Len - byte_size(Data), MaskKey, Unmasked3, Rsv); Opcode, Len - byte_size(Data), MaskKey,
<< Unmasked/binary, Unmasked3/binary >>, UnmaskedLen + byte_size(Data),
Rsv);
websocket_payload(State, Req, HandlerState, websocket_payload(State, Req, HandlerState,
Opcode, Len, MaskKey, Unmasked, Data, Rsv) -> Opcode, Len, MaskKey, Unmasked, UnmaskedLen, Data, Rsv) ->
<< End:Len/binary, Rest/bits >> = Data, << End:Len/binary, Rest/bits >> = Data,
Unmasked2 = websocket_unmask(End, Unmasked2 = websocket_unmask(End,
rotate_mask_key(MaskKey, byte_size(Unmasked)), Unmasked), rotate_mask_key(MaskKey, UnmaskedLen), <<>>),
{Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, true, State), {Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, true, State),
websocket_dispatch(State2, Req, HandlerState, Rest, Opcode, Unmasked3). websocket_dispatch(State2, Req, HandlerState, Rest, Opcode,
<< Unmasked/binary, Unmasked3/binary >>).
-spec websocket_inflate_frame(binary(), rsv(), boolean(), #state{}) -> -spec websocket_inflate_frame(binary(), rsv(), boolean(), #state{}) ->
{binary(), #state{}}. {binary(), #state{}}.
@ -450,14 +460,13 @@ websocket_inflate_frame(Data, << Rsv1:1, _:2 >>, _,
#state{deflate_frame = DeflateFrame} = State) #state{deflate_frame = DeflateFrame} = State)
when DeflateFrame =:= false orelse Rsv1 =:= 0 -> when DeflateFrame =:= false orelse Rsv1 =:= 0 ->
{Data, State}; {Data, State};
websocket_inflate_frame(Data, << 1:1, _:2 >>, false, websocket_inflate_frame(Data, << 1:1, _:2 >>, false, State) ->
#state{inflate_buffer = Buffer} = State) -> Result = zlib:inflate(State#state.inflate_state, Data),
{<<>>, State#state{inflate_buffer = << Buffer/binary, Data/binary >>}}; {iolist_to_binary(Result), State};
websocket_inflate_frame(Data, << 1:1, _:2 >>, true, websocket_inflate_frame(Data, << 1:1, _:2 >>, true, State) ->
#state{inflate_state = Inflate, inflate_buffer = Buffer} = State) -> Result = zlib:inflate(State#state.inflate_state,
Deflated = << Buffer/binary, Data/binary, 0:8, 0:8, 255:8, 255:8 >>, << Data/binary, 0:8, 0:8, 255:8, 255:8 >>),
Result = zlib:inflate(Inflate, Deflated), {iolist_to_binary(Result), State}.
{iolist_to_binary(Result), State#state{inflate_buffer = <<>>}}.
-spec websocket_unmask(B, mask_key(), B) -> B when B::binary(). -spec websocket_unmask(B, mask_key(), B) -> B when B::binary().
websocket_unmask(<<>>, _, Unmasked) -> websocket_unmask(<<>>, _, Unmasked) ->
@ -516,19 +525,20 @@ is_utf8(_) ->
false. false.
-spec websocket_payload_loop(#state{}, Req, any(), -spec websocket_payload_loop(#state{}, Req, any(),
opcode(), non_neg_integer(), mask_key(), binary(), rsv()) opcode(), non_neg_integer(), mask_key(), binary(),
non_neg_integer(), rsv())
-> {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_payload_loop(State=#state{socket=Socket, transport=Transport, websocket_payload_loop(State=#state{socket=Socket, transport=Transport,
messages={OK, Closed, Error}, timeout_ref=TRef}, messages={OK, Closed, Error}, timeout_ref=TRef},
Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Rsv) -> Req, HandlerState, Opcode, Len, MaskKey, Unmasked, UnmaskedLen, Rsv) ->
Transport:setopts(Socket, [{active, once}]), Transport:setopts(Socket, [{active, once}]),
receive receive
{OK, Socket, Data} -> {OK, Socket, Data} ->
State2 = handler_loop_timeout(State), State2 = handler_loop_timeout(State),
websocket_payload(State2, Req, HandlerState, websocket_payload(State2, Req, HandlerState,
Opcode, Len, MaskKey, Unmasked, Data, Rsv); Opcode, Len, MaskKey, Unmasked, UnmaskedLen, Data, Rsv);
{Closed, Socket} -> {Closed, Socket} ->
handler_terminate(State, Req, HandlerState, {error, closed}); handler_terminate(State, Req, HandlerState, {error, closed});
{Error, Socket, Reason} -> {Error, Socket, Reason} ->
@ -537,13 +547,13 @@ websocket_payload_loop(State=#state{socket=Socket, transport=Transport,
websocket_close(State, Req, HandlerState, {normal, timeout}); websocket_close(State, Req, HandlerState, {normal, timeout});
{timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) -> {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
websocket_payload_loop(State, Req, HandlerState, websocket_payload_loop(State, Req, HandlerState,
Opcode, Len, MaskKey, Unmasked, Rsv); Opcode, Len, MaskKey, Unmasked, UnmaskedLen, Rsv);
Message -> Message ->
handler_call(State, Req, HandlerState, handler_call(State, Req, HandlerState,
<<>>, websocket_info, Message, <<>>, websocket_info, Message,
fun (State2, Req2, HandlerState2, _) -> fun (State2, Req2, HandlerState2, _) ->
websocket_payload_loop(State2, Req2, HandlerState2, websocket_payload_loop(State2, Req2, HandlerState2,
Opcode, Len, MaskKey, Unmasked, Rsv) Opcode, Len, MaskKey, Unmasked, UnmaskedLen, Rsv)
end) end)
end. end.
@ -665,19 +675,20 @@ websocket_opcode(close) -> 8;
websocket_opcode(ping) -> 9; websocket_opcode(ping) -> 9;
websocket_opcode(pong) -> 10. websocket_opcode(pong) -> 10.
-spec websocket_deflate_frame(opcode(), binary(), #state{}) -> {binary(), <<_:3>>, #state{}}. -spec websocket_deflate_frame(opcode(), binary(), #state{}) ->
{binary(), rsv(), #state{}}.
websocket_deflate_frame(Opcode, Payload, websocket_deflate_frame(Opcode, Payload,
State=#state{deflate_frame = DeflateFrame}) State=#state{deflate_frame = DeflateFrame})
when DeflateFrame =:= false orelse Opcode >= 8 -> when DeflateFrame =:= false orelse Opcode >= 8 ->
{Payload, <<0:3>>, State}; {Payload, << 0:3 >>, State};
websocket_deflate_frame(_, Payload, State=#state{deflate_state = Deflate}) -> websocket_deflate_frame(_, Payload, State=#state{deflate_state = Deflate}) ->
Deflated = iolist_to_binary(zlib:deflate(Deflate, Payload, sync)), Deflated = iolist_to_binary(zlib:deflate(Deflate, Payload, sync)),
DeflatedBodyLength = erlang:size(Deflated) - 4, DeflatedBodyLength = erlang:size(Deflated) - 4,
Deflated1 = case Deflated of Deflated1 = case Deflated of
<<Body:DeflatedBodyLength/binary, 0:8, 0:8, 255:8, 255:8>> -> Body; << Body:DeflatedBodyLength/binary, 0:8, 0:8, 255:8, 255:8 >> -> Body;
_ -> Deflated _ -> Deflated
end, end,
{Deflated1, <<1:1, 0:2>>, State}. {Deflated1, << 1:1, 0:2 >>, State}.
-spec websocket_send(frame(), #state{}) -spec websocket_send(frame(), #state{})
-> {ok, #state{}} | {shutdown, #state{}} | {{error, atom()}, #state{}}. -> {ok, #state{}} | {shutdown, #state{}} | {{error, atom()}, #state{}}.

View file

@ -31,6 +31,8 @@
-export([ws8_single_bytes/1]). -export([ws8_single_bytes/1]).
-export([ws13/1]). -export([ws13/1]).
-export([ws_deflate/1]). -export([ws_deflate/1]).
-export([ws_deflate_chunks/1]).
-export([ws_deflate_fragments/1]).
-export([ws_send_close/1]). -export([ws_send_close/1]).
-export([ws_send_close_payload/1]). -export([ws_send_close_payload/1]).
-export([ws_send_many/1]). -export([ws_send_many/1]).
@ -53,6 +55,8 @@ groups() ->
ws8_single_bytes, ws8_single_bytes,
ws13, ws13,
ws_deflate, ws_deflate,
ws_deflate_chunks,
ws_deflate_fragments,
ws_send_close, ws_send_close,
ws_send_close_payload, ws_send_close_payload,
ws_send_many, ws_send_many,
@ -315,7 +319,7 @@ ws13(Config) ->
ws_deflate(Config) -> ws_deflate(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,
[binary, {active, false}, {packet, raw}]), [binary, {active, false}, {packet, raw}, {nodelay, true}]),
ok = gen_tcp:send(Socket, [ ok = gen_tcp:send(Socket, [
"GET /ws_echo HTTP/1.1\r\n" "GET /ws_echo HTTP/1.1\r\n"
"Host: localhost\r\n" "Host: localhost\r\n"
@ -338,25 +342,104 @@ ws_deflate(Config) ->
{"sec-websocket-extensions", "x-webkit-deflate-frame"} {"sec-websocket-extensions", "x-webkit-deflate-frame"}
= lists:keyfind("sec-websocket-extensions", 1, Headers), = lists:keyfind("sec-websocket-extensions", 1, Headers),
% send uncompressed text frame containing the Hello string Mask = 16#11223344,
ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d, Hello = << 242, 72, 205, 201, 201, 7, 0 >>,
16#7f, 16#9f, 16#4d, 16#51, 16#58 >>), MaskedHello = websocket_mask(Hello, Mask, <<>>),
% send compressed text frame containing the Hello string
ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32,
MaskedHello/binary >>),
% receive compressed text frame containing the Hello string % receive compressed text frame containing the Hello string
{ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, 242, 72, 205, 201, 201, 7, 0 >>} {ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, Hello/binary >>}
= gen_tcp:recv(Socket, 0, 6000), = gen_tcp:recv(Socket, 0, 6000),
% send uncompressed text frame containing the HelloHello string ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
{error, closed} = gen_tcp:recv(Socket, 0, 6000),
ok.
ws_deflate_chunks(Config) ->
{port, Port} = lists:keyfind(port, 1, Config),
{ok, Socket} = gen_tcp:connect("localhost", Port,
[binary, {active, false}, {packet, raw}, {nodelay, true}]),
ok = gen_tcp:send(Socket, [
"GET /ws_echo HTTP/1.1\r\n"
"Host: localhost\r\n"
"Connection: Upgrade\r\n"
"Upgrade: websocket\r\n"
"Sec-WebSocket-Origin: http://localhost\r\n"
"Sec-WebSocket-Version: 8\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n"
"\r\n"]),
{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, 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-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
= lists:keyfind("sec-websocket-accept", 1, Headers),
{"sec-websocket-extensions", "x-webkit-deflate-frame"}
= lists:keyfind("sec-websocket-extensions", 1, Headers),
Mask = 16#11223344,
Hello = << 242, 72, 205, 201, 201, 7, 0 >>,
MaskedHello = websocket_mask(Hello, Mask, <<>>),
% send compressed text frame containing the Hello string
ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32,
(binary:part(MaskedHello, 0, 4))/binary >>),
ok = timer:sleep(100),
ok = gen_tcp:send(Socket, binary:part(MaskedHello, 4, 3)),
% receive compressed text frame containing the Hello string
{ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, Hello/binary >>}
= gen_tcp:recv(Socket, 0, 6000),
ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
{error, closed} = gen_tcp:recv(Socket, 0, 6000),
ok.
ws_deflate_fragments(Config) ->
{port, Port} = lists:keyfind(port, 1, Config),
{ok, Socket} = gen_tcp:connect("localhost", Port,
[binary, {active, false}, {packet, raw}, {nodelay, true}]),
ok = gen_tcp:send(Socket, [
"GET /ws_echo HTTP/1.1\r\n"
"Host: localhost\r\n"
"Connection: Upgrade\r\n"
"Upgrade: websocket\r\n"
"Sec-WebSocket-Origin: http://localhost\r\n"
"Sec-WebSocket-Version: 8\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n"
"\r\n"]),
{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, 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-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
= lists:keyfind("sec-websocket-accept", 1, Headers),
{"sec-websocket-extensions", "x-webkit-deflate-frame"}
= lists:keyfind("sec-websocket-extensions", 1, Headers),
Mask = 16#11223344,
Hello = << 242, 72, 205, 201, 201, 7, 0 >>,
% send compressed text frame containing the Hello string
% as 2 separate fragments % as 2 separate fragments
ok = gen_tcp:send(Socket, [ ok = gen_tcp:send(Socket, << 0:1, 1:1, 0:2, 1:4, 1:1, 4:7, Mask:32,
<< 0:1, 0:3, 1:4, 1:1, 5:7 >>, (websocket_mask(binary:part(Hello, 0, 4), Mask, <<>>))/binary >>),
<< 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>, ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 0:4, 1:1, 3:7, Mask:32,
<< 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]), (websocket_mask(binary:part(Hello, 4, 3), Mask, <<>>))/binary >>),
ok = gen_tcp:send(Socket, [ % receive compressed text frame containing the Hello string
<< 1:1, 0:3, 0:4, 1:1, 5:7 >>, {ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, Hello/binary >>}
<< 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
<< 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
% receive compressed text frame containing the HelloHello string
{ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 5:7, 242, 128, 19, 0, 0 >>}
= gen_tcp:recv(Socket, 0, 6000), = gen_tcp:recv(Socket, 0, 6000),
ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
@ -632,3 +715,21 @@ websocket_headers({ok, {http_header, _I, Key, _R, Value}, Rest}, Acc) ->
F = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end, F = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end,
websocket_headers(erlang:decode_packet(httph, Rest, []), websocket_headers(erlang:decode_packet(httph, Rest, []),
[{F(Key), Value}|Acc]). [{F(Key), Value}|Acc]).
websocket_mask(<<>>, _, Unmasked) ->
Unmasked;
websocket_mask(<< O:32, Rest/bits >>, MaskKey, Acc) ->
T = O bxor MaskKey,
websocket_mask(Rest, MaskKey, << Acc/binary, T:32 >>);
websocket_mask(<< O:24 >>, MaskKey, Acc) ->
<< MaskKey2:24, _:8 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:24 >>;
websocket_mask(<< O:16 >>, MaskKey, Acc) ->
<< MaskKey2:16, _:16 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:16 >>;
websocket_mask(<< O:8 >>, MaskKey, Acc) ->
<< MaskKey2:8, _:24 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:8 >>.