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

All frames sent from client to server MUST be masked

Good in theory, but implementations may vary. If something stops
working after this commit we might need some tweaks to support
existing clients.

Please try it and give feedback.
This commit is contained in:
Loïc Hoguin 2013-01-12 16:04:35 +01:00
parent 1d45698757
commit 71b68d53d9
2 changed files with 38 additions and 40 deletions

View file

@ -214,107 +214,105 @@ 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,
<< Fin:1, Rsv:3, Opcode:4, Mask:1, PayloadLen:7, Rest/bits >> << Fin:1, Rsv:3, Opcode:4, 1:1, PayloadLen:7, Rest/bits >>
= Data) when PayloadLen < 126 -> = Data) when PayloadLen < 126 ->
websocket_data(State, Req, HandlerState, websocket_data(State, Req, HandlerState,
Fin, Rsv, Opcode, Mask, PayloadLen, Rest, Data); Fin, Rsv, Opcode, PayloadLen, Rest, Data);
%% 7+16 bits payload length prefix exists %% 7+16 bits payload length prefix exists
websocket_data(State, Req, HandlerState, websocket_data(State, Req, HandlerState,
<< Fin:1, Rsv:3, Opcode:4, Mask:1, 126:7, PayloadLen:16, Rest/bits >> << Fin:1, Rsv:3, Opcode:4, 1:1, 126:7, PayloadLen:16, Rest/bits >>
= Data) when PayloadLen > 125 -> = Data) when PayloadLen > 125 ->
websocket_data(State, Req, HandlerState, websocket_data(State, Req, HandlerState,
Fin, Rsv, Opcode, Mask, PayloadLen, Rest, Data); Fin, Rsv, Opcode, PayloadLen, Rest, Data);
%% 7+16 bits payload length prefix missing %% 7+16 bits payload length prefix missing
websocket_data(State, Req, HandlerState, websocket_data(State, Req, HandlerState,
<< _Fin:1, _Rsv:3, _Opcode:4, _Mask:1, 126:7, Rest/bits >> << _Fin:1, _Rsv:3, _Opcode:4, 1:1, 126:7, Rest/bits >>
= Data) when byte_size(Rest) < 2 -> = Data) when byte_size(Rest) < 2 ->
handler_before_loop(State, Req, HandlerState, Data); handler_before_loop(State, Req, HandlerState, Data);
%% 7+64 bits payload length prefix exists %% 7+64 bits payload length prefix exists
websocket_data(State, Req, HandlerState, websocket_data(State, Req, HandlerState,
<< Fin:1, Rsv:3, Opcode:4, Mask:1, 127:7, 0:1, PayloadLen:63, << Fin:1, Rsv:3, Opcode:4, 1:1, 127:7, 0:1, PayloadLen:63,
Rest/bits >> = Data) when PayloadLen > 16#FFFF -> Rest/bits >> = Data) when PayloadLen > 16#FFFF ->
websocket_data(State, Req, HandlerState, websocket_data(State, Req, HandlerState,
Fin, Rsv, Opcode, Mask, PayloadLen, Rest, Data); Fin, Rsv, Opcode, PayloadLen, Rest, Data);
%% 7+64 bits payload length prefix missing %% 7+64 bits payload length prefix missing
websocket_data(State, Req, HandlerState, websocket_data(State, Req, HandlerState,
<< _Fin:1, _Rsv:3, _Opcode:4, _Mask:1, 127:7, Rest/bits >> << _Fin:1, _Rsv:3, _Opcode:4, 1:1, 127:7, Rest/bits >>
= Data) when byte_size(Rest) < 8 -> = Data) when byte_size(Rest) < 8 ->
handler_before_loop(State, Req, HandlerState, Data); handler_before_loop(State, Req, HandlerState, Data);
%% invalid payload length prefix. %% Invalid payload length or mask bit was not set.
websocket_data(State, Req, HandlerState, _Data) -> websocket_data(State, Req, HandlerState, _Data) ->
websocket_close(State, Req, HandlerState, {error, badframe}). websocket_close(State, Req, HandlerState, {error, badframe}).
-spec websocket_data(#state{}, Req, any(), non_neg_integer(), -spec websocket_data(#state{}, Req, any(), non_neg_integer(),
non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(),
non_neg_integer(), binary(), binary()) non_neg_integer(), binary(), binary())
-> {ok, Req, cowboy_middleware:env()} -> {ok, Req, cowboy_middleware:env()}
when Req::cowboy_req:req(). when Req::cowboy_req:req().
%% A fragmented message MUST start a non-zero opcode. %% A fragmented message MUST start a non-zero opcode.
websocket_data(State=#state{frag_state=undefined}, Req, HandlerState, websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
_Fin=0, _Rsv=0, _Opcode=0, _Mask, _PayloadLen, _Rest, _Buffer) -> _Fin=0, _Rsv=0, _Opcode=0, _PayloadLen, _Rest, _Buffer) ->
websocket_close(State, Req, HandlerState, {error, badframe}); websocket_close(State, Req, HandlerState, {error, badframe});
%% A control message MUST NOT be fragmented. %% A control message MUST NOT be fragmented.
websocket_data(State, Req, HandlerState, _Fin=0, _Rsv=0, Opcode, _Mask, websocket_data(State, Req, HandlerState, _Fin=0, _Rsv=0, Opcode,
_PayloadLen, _Rest, _Buffer) when Opcode >= 8 -> _PayloadLen, _Rest, _Buffer) when Opcode >= 8 ->
websocket_close(State, Req, HandlerState, {error, badframe}); websocket_close(State, Req, HandlerState, {error, badframe});
%% The opcode is only included in the first message fragment. %% The opcode is only included in the first message fragment.
websocket_data(State=#state{frag_state=undefined}, Req, HandlerState, websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
_Fin=0, _Rsv=0, Opcode, Mask, PayloadLen, Rest, Data) -> _Fin=0, _Rsv=0, Opcode, PayloadLen, Rest, Data) ->
websocket_before_unmask( websocket_before_unmask(
State#state{frag_state={nofin, Opcode}}, Req, HandlerState, State#state{frag_state={nofin, Opcode}}, Req, HandlerState,
Data, Rest, 0, Mask, PayloadLen); Data, Rest, 0, PayloadLen);
%% non-control opcode when expecting control message or next fragment. %% non-control opcode when expecting control message or next fragment.
websocket_data(State=#state{frag_state={nofin, _, _}}, Req, HandlerState, _Fin, websocket_data(State=#state{frag_state={nofin, _, _}}, Req, HandlerState, _Fin,
_Rsv=0, Opcode, _Mask, _Ln, _Rest, _Data) when Opcode > 0, Opcode < 8 -> _Rsv=0, Opcode, _Ln, _Rest, _Data) when Opcode > 0, Opcode < 8 ->
websocket_close(State, Req, HandlerState, {error, badframe}); websocket_close(State, Req, HandlerState, {error, badframe});
%% If the first message fragment was incomplete, retry unmasking. %% If the first message fragment was incomplete, retry unmasking.
websocket_data(State=#state{frag_state={nofin, Opcode}}, Req, HandlerState, websocket_data(State=#state{frag_state={nofin, Opcode}}, Req, HandlerState,
_Fin=0, _Rsv=0, Opcode, Mask, PayloadLen, Rest, Data) -> _Fin=0, _Rsv=0, Opcode, PayloadLen, Rest, Data) ->
websocket_before_unmask( websocket_before_unmask(
State#state{frag_state={nofin, Opcode}}, Req, HandlerState, State#state{frag_state={nofin, Opcode}}, Req, HandlerState,
Data, Rest, 0, Mask, PayloadLen); Data, Rest, 0, PayloadLen);
%% if the opcode is zero and the fin flag is zero, unmask and await next. %% if the opcode is zero and the fin flag is zero, unmask and await next.
websocket_data(State=#state{frag_state={nofin, _Opcode, _Payloads}}, Req, websocket_data(State=#state{frag_state={nofin, _Opcode, _Payloads}}, Req,
HandlerState, _Fin=0, _Rsv=0, _Opcode2=0, Mask, PayloadLen, Rest, HandlerState, _Fin=0, _Rsv=0, _Opcode2=0, PayloadLen, Rest,
Data) -> Data) ->
websocket_before_unmask( websocket_before_unmask(
State, Req, HandlerState, Data, Rest, 0, Mask, PayloadLen); State, Req, HandlerState, Data, Rest, 0, PayloadLen);
%% when the last fragment is seen. Update the fragmentation status. %% when the last fragment is seen. Update the fragmentation status.
websocket_data(State=#state{frag_state={nofin, Opcode, Payloads}}, Req, websocket_data(State=#state{frag_state={nofin, Opcode, Payloads}}, Req,
HandlerState, _Fin=1, _Rsv=0, _Opcode=0, Mask, PayloadLen, Rest, HandlerState, _Fin=1, _Rsv=0, _Opcode=0, PayloadLen, Rest,
Data) -> Data) ->
websocket_before_unmask( websocket_before_unmask(
State#state{frag_state={fin, Opcode, Payloads}}, State#state{frag_state={fin, Opcode, Payloads}},
Req, HandlerState, Data, Rest, 0, Mask, PayloadLen); Req, HandlerState, Data, Rest, 0, PayloadLen);
%% control messages MUST NOT use 7+16 bits or 7+64 bits payload length prefixes %% control messages MUST NOT use 7+16 bits or 7+64 bits payload length prefixes
websocket_data(State, Req, HandlerState, _Fin, _Rsv, Opcode, _Mask, PayloadLen, websocket_data(State, Req, HandlerState, _Fin, _Rsv, Opcode, 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, Req, HandlerState, _Fin=1, _Rsv=0, websocket_data(State, Req, HandlerState, _Fin=1, _Rsv=0,
Opcode, Mask, PayloadLen, Rest, Data) -> Opcode, PayloadLen, Rest, Data) ->
websocket_before_unmask( websocket_before_unmask(
State, Req, HandlerState, Data, Rest, Opcode, Mask, PayloadLen); State, Req, HandlerState, Data, Rest, Opcode, PayloadLen);
%% Something was wrong with the frame. Close the connection. %% Something was wrong with the frame. Close the connection.
websocket_data(State, Req, HandlerState, _Fin, _Rsv, _Opcode, _Mask, websocket_data(State, Req, HandlerState, _Fin, _Rsv, _Opcode,
_PayloadLen, _Rest, _Data) -> _PayloadLen, _Rest, _Data) ->
websocket_close(State, Req, HandlerState, {error, badframe}). websocket_close(State, Req, HandlerState, {error, badframe}).
%% 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(), non_neg_integer() | undefined)
-> {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_before_unmask(State, Req, HandlerState, Data, websocket_before_unmask(State, Req, HandlerState, Data,
Rest, Opcode, Mask, PayloadLen) -> Rest, Opcode, PayloadLen) ->
case {Mask, PayloadLen} of case PayloadLen of
{0, 0} -> N when N + 4 > byte_size(Rest); N =:= undefined ->
websocket_dispatch(State, Req, HandlerState, Rest, Opcode, <<>>);
{1, N} when N + 4 > byte_size(Rest); N =:= undefined ->
%% @todo We probably should allow limiting frame length. %% @todo We probably should allow limiting frame length.
handler_before_loop(State, Req, HandlerState, Data); handler_before_loop(State, Req, HandlerState, Data);
{1, _N} -> _N ->
<< MaskKey:32, Payload:PayloadLen/binary, Rest2/bits >> = Rest, << MaskKey:32, Payload:PayloadLen/binary, Rest2/bits >> = Rest,
websocket_unmask(State, Req, HandlerState, Rest2, websocket_unmask(State, Req, HandlerState, Rest2,
Opcode, Payload, MaskKey) Opcode, Payload, MaskKey)

View file

@ -174,9 +174,9 @@ ws8(Config) ->
= gen_tcp:recv(Socket, 0, 6000), = gen_tcp:recv(Socket, 0, 6000),
{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
= gen_tcp:recv(Socket, 0, 6000), = gen_tcp:recv(Socket, 0, 6000),
ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
{ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
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, 1:1, 0:7, 0:32 >>), %% 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),
ok. ok.
@ -253,9 +253,9 @@ ws8_single_bytes(Config) ->
= gen_tcp:recv(Socket, 0, 6000), = gen_tcp:recv(Socket, 0, 6000),
{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
= gen_tcp:recv(Socket, 0, 6000), = gen_tcp:recv(Socket, 0, 6000),
ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
{ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
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, 1:1, 0:7, 0:32 >>), %% 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),
ok. ok.
@ -288,7 +288,7 @@ ws13(Config) ->
{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} {ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
= gen_tcp:recv(Socket, 0, 6000), = gen_tcp:recv(Socket, 0, 6000),
%% binary (empty) %% binary (empty)
ok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 0:8 >>), ok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 1:1, 0:7, 0:32 >>),
{ok, << 1:1, 0:3, 2:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), {ok, << 1:1, 0:3, 2:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
%% binary %% binary
ok = gen_tcp:send(Socket, << 16#82, 16#85, 16#37, 16#fa, 16#21, 16#3d, ok = gen_tcp:send(Socket, << 16#82, 16#85, 16#37, 16#fa, 16#21, 16#3d,
@ -304,9 +304,9 @@ ws13(Config) ->
= gen_tcp:recv(Socket, 0, 6000), = gen_tcp:recv(Socket, 0, 6000),
{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} {ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
= gen_tcp:recv(Socket, 0, 6000), = gen_tcp:recv(Socket, 0, 6000),
ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 0:8 >>), %% ping ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
{ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong {ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
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, 1:1, 0:7, 0:32 >>), %% 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),
ok. ok.