diff options
Diffstat (limited to 'src/cow_ws.erl')
-rw-r--r-- | src/cow_ws.erl | 473 |
1 files changed, 382 insertions, 91 deletions
diff --git a/src/cow_ws.erl b/src/cow_ws.erl index de7d0b5..c89c17a 100644 --- a/src/cow_ws.erl +++ b/src/cow_ws.erl @@ -14,27 +14,206 @@ -module(cow_ws). +-export([key/0]). +-export([encode_key/1]). + +-export([negotiate_permessage_deflate/3]). +-export([negotiate_x_webkit_deflate_frame/3]). + +-export([validate_permessage_deflate/3]). + -export([parse_header/3]). --export([parse_close_code/2]). -export([parse_payload/9]). +-export([make_frame/4]). + -export([frame/2]). +-export([masked_frame/2]). -type close_code() :: 1000..1003 | 1006..1011 | 3000..4999. -export_type([close_code/0]). --type frag_state() :: undefined | {fin | nofin, text | binary}. +-type extensions() :: map(). +-export_type([extensions/0]). + +-type frag_state() :: undefined | {fin | nofin, text | binary, rsv()}. -export_type([frag_state/0]). -type frame() :: close | ping | pong | {text | binary | close | ping | pong, iodata()} - | {close, close_code(), iodata()}. + | {close, close_code(), iodata()} + | {fragment, fin | nofin, text | binary | continuation, iodata()}. -export_type([frame/0]). --type extensions() :: map(). -type frame_type() :: fragment | text | binary | close | ping | pong. +-export_type([frame_type/0]). + -type mask_key() :: undefined | 0..16#ffffffff. +-export_type([mask_key/0]). + -type rsv() :: <<_:3>>. --type utf8_state() :: <<>> | <<_:8>> | <<_:16>> | <<_:24>>. +-export_type([rsv/0]). + +-type utf8_state() :: 0..8. +-export_type([utf8_state/0]). + +%% @doc Generate a key for the Websocket handshake request. + +-spec key() -> binary(). +key() -> + base64:encode(crypto:rand_bytes(16)). + +%% @doc Encode the key into the accept value for the Websocket handshake response. + +-spec encode_key(binary()) -> binary(). +encode_key(Key) -> + base64:encode(crypto:hash(sha, [Key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"])). + +%% @doc Negotiate the permessage-deflate extension. + +%% Ignore if deflate already negotiated. +negotiate_permessage_deflate(_, #{deflate := _}, _) -> + ignore; +negotiate_permessage_deflate(Params, Extensions, Opts) -> + case lists:usort(Params) of + %% Ignore if multiple parameters with the same name. + Params2 when length(Params) =/= length(Params2) -> + ignore; + Params2 -> + %% @todo Might want to make these configurable defaults. + case parse_request_permessage_deflate_params(Params2, 15, takeover, 15, takeover, []) of + ignore -> + ignore; + {ClientWindowBits, ClientTakeOver, ServerWindowBits, ServerTakeOver, RespParams} -> + {Inflate, Deflate} = init_permessage_deflate(ClientWindowBits, ServerWindowBits, Opts), + {ok, [<<"permessage-deflate">>, RespParams], + Extensions#{ + deflate => Deflate, + deflate_takeover => ServerTakeOver, + inflate => Inflate, + inflate_takeover => ClientTakeOver}} + end + end. + +parse_request_permessage_deflate_params([], CB, CTO, SB, STO, RespParams) -> + {CB, CTO, SB, STO, RespParams}; +parse_request_permessage_deflate_params([<<"client_max_window_bits">>|Tail], CB, CTO, SB, STO, RespParams) -> + parse_request_permessage_deflate_params(Tail, CB, CTO, SB, STO, + [<<"; ">>, <<"client_max_window_bits=">>, integer_to_binary(CB)|RespParams]); +parse_request_permessage_deflate_params([{<<"client_max_window_bits">>, Max}|Tail], _, CTO, SB, STO, RespParams) -> + case parse_max_window_bits(Max) of + error -> + ignore; + CB -> + parse_request_permessage_deflate_params(Tail, CB, CTO, SB, STO, + [<<"; ">>, <<"client_max_window_bits=">>, Max|RespParams]) + end; +parse_request_permessage_deflate_params([<<"client_no_context_takeover">>|Tail], CB, _, SB, STO, RespParams) -> + parse_request_permessage_deflate_params(Tail, CB, no_takeover, SB, STO, [<<"; ">>, <<"client_no_context_takeover">>|RespParams]); +parse_request_permessage_deflate_params([{<<"server_max_window_bits">>, Max}|Tail], CB, CTO, _, STO, RespParams) -> + case parse_max_window_bits(Max) of + error -> + ignore; + SB -> + parse_request_permessage_deflate_params(Tail, CB, CTO, SB, STO, + [<<"; ">>, <<"server_max_window_bits=">>, Max|RespParams]) + end; +parse_request_permessage_deflate_params([<<"server_no_context_takeover">>|Tail], CB, CTO, SB, _, RespParams) -> + parse_request_permessage_deflate_params(Tail, CB, CTO, SB, no_takeover, [<<"; ">>, <<"server_no_context_takeover">>|RespParams]); +%% Ignore if unknown parameter; ignore if parameter with invalid or missing value. +parse_request_permessage_deflate_params(_, _, _, _, _, _) -> + ignore. + +parse_max_window_bits(<<"8">>) -> 8; +parse_max_window_bits(<<"9">>) -> 9; +parse_max_window_bits(<<"10">>) -> 10; +parse_max_window_bits(<<"11">>) -> 11; +parse_max_window_bits(<<"12">>) -> 12; +parse_max_window_bits(<<"13">>) -> 13; +parse_max_window_bits(<<"14">>) -> 14; +parse_max_window_bits(<<"15">>) -> 15; +parse_max_window_bits(_) -> error. + +% A negative WindowBits value indicates that zlib headers are not used. +init_permessage_deflate(InflateWindowBits, DeflateWindowBits, Opts) -> + Inflate = zlib:open(), + ok = zlib:inflateInit(Inflate, -InflateWindowBits), + Deflate = zlib:open(), + %% @todo Remove this case .. of for OTP 18+ if PR https://github.com/erlang/otp/pull/633 gets merged. + DeflateWindowBits2 = case DeflateWindowBits of + 8 -> 9; + _ -> DeflateWindowBits + end, + ok = zlib:deflateInit(Deflate, + maps:get(level, Opts, best_compression), + deflated, + -DeflateWindowBits2, + maps:get(mem_level, Opts, 8), + maps:get(strategy, Opts, default)), + {Inflate, Deflate}. + +%% @doc Negotiate the x-webkit-deflate-frame extension. +%% +%% The implementation is very basic and none of the parameters +%% are currently supported. + +negotiate_x_webkit_deflate_frame(_, #{deflate := _}, _) -> + ignore; +negotiate_x_webkit_deflate_frame(_Params, Extensions, Opts) -> + % Since we are negotiating an unconstrained deflate-frame + % then we must be willing to accept frames using the + % maximum window size which is 2^15. + {Inflate, Deflate} = init_permessage_deflate(15, 15, Opts), + {ok, <<"x-webkit-deflate-frame">>, + Extensions#{ + deflate => Deflate, + deflate_takeover => takeover, + inflate => Inflate, + inflate_takeover => takeover}}. + +%% @doc Validate the negotiated permessage-deflate extension. + +%% Error when more than one deflate extension was negotiated. +validate_permessage_deflate(_, #{deflate := _}, _) -> + error; +validate_permessage_deflate(Params, Extensions, Opts) -> + case lists:usort(Params) of + %% Error if multiple parameters with the same name. + Params2 when length(Params) =/= length(Params2) -> + error; + Params2 -> + %% @todo Might want to make some of these configurable defaults if at all possible. + case parse_response_permessage_deflate_params(Params2, 15, takeover, 15, takeover) of + error -> + error; + {ClientWindowBits, ClientTakeOver, ServerWindowBits, ServerTakeOver} -> + {Inflate, Deflate} = init_permessage_deflate(ServerWindowBits, ClientWindowBits, Opts), + {ok, Extensions#{ + deflate => Deflate, + deflate_takeover => ClientTakeOver, + inflate => Inflate, + inflate_takeover => ServerTakeOver}} + end + end. + +parse_response_permessage_deflate_params([], CB, CTO, SB, STO) -> + {CB, CTO, SB, STO}; +parse_response_permessage_deflate_params([{<<"client_max_window_bits">>, Max}|Tail], _, CTO, SB, STO) -> + case parse_max_window_bits(Max) of + error -> error; + CB -> parse_response_permessage_deflate_params(Tail, CB, CTO, SB, STO) + end; +parse_response_permessage_deflate_params([<<"client_no_context_takeover">>|Tail], CB, _, SB, STO) -> + parse_response_permessage_deflate_params(Tail, CB, no_takeover, SB, STO); +parse_response_permessage_deflate_params([{<<"server_max_window_bits">>, Max}|Tail], CB, CTO, _, STO) -> + case parse_max_window_bits(Max) of + error -> error; + SB -> parse_response_permessage_deflate_params(Tail, CB, CTO, SB, STO) + end; +parse_response_permessage_deflate_params([<<"server_no_context_takeover">>|Tail], CB, CTO, SB, _) -> + parse_response_permessage_deflate_params(Tail, CB, CTO, SB, no_takeover); +%% Error if unknown parameter; error if parameter with invalid or missing value. +parse_response_permessage_deflate_params(_, _, _, _, _) -> + error. %% @doc Parse and validate the Websocket frame header. %% @@ -47,8 +226,8 @@ %% that defines meanings for non-zero values. parse_header(<< _:1, Rsv:3, _/bits >>, Extensions, _) when Extensions =:= #{}, Rsv =/= 0 -> error; %% Last 2 RSV bits MUST be 0 if deflate-frame extension is used. -parse_header(<< _:2, 1:1, _/bits >>, #{deflate_frame := _}, _) -> error; -parse_header(<< _:3, 1:1, _/bits >>, #{deflate_frame := _}, _) -> error; +parse_header(<< _:2, 1:1, _/bits >>, #{deflate := _}, _) -> error; +parse_header(<< _:3, 1:1, _/bits >>, #{deflate := _}, _) -> error; %% Invalid opcode. Note that these opcodes may be used by extensions. parse_header(<< _:4, 3:4, _/bits >>, _, _) -> error; parse_header(<< _:4, 4:4, _/bits >>, _, _) -> error; @@ -65,13 +244,13 @@ parse_header(<< 0:1, _:3, Opcode:4, _/bits >>, _, _) when Opcode >= 8 -> error; %% A frame MUST NOT use the zero opcode unless fragmentation was initiated. parse_header(<< _:4, 0:4, _/bits >>, _, undefined) -> error; %% Non-control opcode when expecting control message or next fragment. -parse_header(<< _:4, 1:4, _/bits >>, _, {_, _}) -> error; -parse_header(<< _:4, 2:4, _/bits >>, _, {_, _}) -> error; -parse_header(<< _:4, 3:4, _/bits >>, _, {_, _}) -> error; -parse_header(<< _:4, 4:4, _/bits >>, _, {_, _}) -> error; -parse_header(<< _:4, 5:4, _/bits >>, _, {_, _}) -> error; -parse_header(<< _:4, 6:4, _/bits >>, _, {_, _}) -> error; -parse_header(<< _:4, 7:4, _/bits >>, _, {_, _}) -> error; +parse_header(<< _:4, 1:4, _/bits >>, _, {_, _, _}) -> error; +parse_header(<< _:4, 2:4, _/bits >>, _, {_, _, _}) -> error; +parse_header(<< _:4, 3:4, _/bits >>, _, {_, _, _}) -> error; +parse_header(<< _:4, 4:4, _/bits >>, _, {_, _, _}) -> error; +parse_header(<< _:4, 5:4, _/bits >>, _, {_, _, _}) -> error; +parse_header(<< _:4, 6:4, _/bits >>, _, {_, _, _}) -> error; +parse_header(<< _:4, 7:4, _/bits >>, _, {_, _, _}) -> error; %% Close control frame length MUST be 0 or >= 2. parse_header(<< _:4, 8:4, _:1, 1:7, _/bits >>, _, _) -> error; %% Close control frame with incomplete close code. Need more data. @@ -111,7 +290,7 @@ parse_header(Opcode, Fin, FragState, Rsv, Len, MaskKey, Rest) -> 0 -> fragment; 1 -> Type end, - {Type2, frag_state(Type, Fin, FragState), Rsv, Len, MaskKey, Rest}. + {Type2, frag_state(Type, Fin, Rsv, FragState), Rsv, Len, MaskKey, Rest}. opcode_to_frame_type(0) -> fragment; opcode_to_frame_type(1) -> text; @@ -120,25 +299,10 @@ opcode_to_frame_type(8) -> close; opcode_to_frame_type(9) -> ping; opcode_to_frame_type(10) -> pong. -frag_state(Type, 0, undefined) -> {nofin, Type}; -frag_state(fragment, 0, FragState = {nofin, _}) -> FragState; -frag_state(fragment, 1, {nofin, Type}) -> {fin, Type}; -frag_state(_, 1, FragState) -> FragState. - -%% @doc Parse and validate the close frame's close code. -%% -%% The close code is part of the payload and must therefore be unmasked. - --spec parse_close_code(binary(), mask_key()) -> {ok, close_code(), binary()} | error. -parse_close_code(<< MaskedCode:2/binary, Rest/bits >>, MaskKey) -> - << Code:16 >> = unmask(MaskedCode, MaskKey, 0), - if - Code < 1000; Code =:= 1004; Code =:= 1005; Code =:= 1006; - (Code > 1011) and (Code < 3000); Code > 4999 -> - error; - true -> - {ok, Code, Rest} - end. +frag_state(Type, 0, Rsv, undefined) -> {nofin, Type, Rsv}; +frag_state(fragment, 0, _, FragState = {nofin, _, _}) -> FragState; +frag_state(fragment, 1, _, {nofin, Type, Rsv}) -> {fin, Type, Rsv}; +frag_state(_, 1, _, FragState) -> FragState. %% @doc Parse and validate the frame's payload. %% @@ -148,10 +312,46 @@ parse_close_code(<< MaskedCode:2/binary, Rest/bits >>, MaskKey) -> -spec parse_payload(binary(), mask_key(), utf8_state(), non_neg_integer(), frame_type(), non_neg_integer(), frag_state(), extensions(), rsv()) -> {ok, binary(), utf8_state(), binary()} | {more, binary(), utf8_state()} | error. -parse_payload(Data, MaskKey, Utf8State, ParsedLen, Type, Len, FragState, #{deflate_frame := Inflate}, << 1:1, 0:2 >>) -> +%% Empty last frame of compressed message. +parse_payload(Data, _, Utf8State, _, _, 0, {fin, _, << 1:1, 0:2 >>}, + #{inflate := Inflate, inflate_takeover := TakeOver}, _) -> + zlib:inflate(Inflate, << 0, 0, 255, 255 >>), + case TakeOver of + no_takeover -> zlib:inflateReset(Inflate); + takeover -> ok + end, + {ok, <<>>, Utf8State, Data}; +%% Compressed fragmented frame. +parse_payload(Data, MaskKey, Utf8State, ParsedLen, Type, Len, FragState = {_, _, << 1:1, 0:2 >>}, + #{inflate := Inflate, inflate_takeover := TakeOver}, _) -> {Data2, Rest, Eof} = split_payload(Data, Len), - Payload = inflate_frame(unmask(Data2, MaskKey, ParsedLen), Inflate, FragState, Eof), + Payload = inflate_frame(unmask(Data2, MaskKey, ParsedLen), Inflate, TakeOver, FragState, Eof), validate_payload(Payload, Rest, Utf8State, ParsedLen, Type, FragState, Eof); +%% Compressed frame. +parse_payload(Data, MaskKey, Utf8State, ParsedLen, Type, Len, FragState, + #{inflate := Inflate, inflate_takeover := TakeOver}, << 1:1, 0:2 >>) when Type =:= text; Type =:= binary -> + {Data2, Rest, Eof} = split_payload(Data, Len), + Payload = inflate_frame(unmask(Data2, MaskKey, ParsedLen), Inflate, TakeOver, FragState, Eof), + validate_payload(Payload, Rest, Utf8State, ParsedLen, Type, FragState, Eof); +%% Empty frame. +parse_payload(Data, _, Utf8State = 0, 0, _, 0, _, _, _) -> + {ok, <<>>, Utf8State, Data}; +%% Start of close frame. +parse_payload(Data, MaskKey, Utf8State, 0, Type = close, Len, FragState, _, << 0:3 >>) -> + {<< MaskedCode:2/binary, Data2/bits >>, Rest, Eof} = split_payload(Data, Len), + << CloseCode:16 >> = unmask(MaskedCode, MaskKey, 0), + case validate_close_code(CloseCode) of + ok -> + Payload = unmask(Data2, MaskKey, 2), + case validate_payload(Payload, Rest, Utf8State, 2, Type, FragState, Eof) of + {ok, _, Utf8State2, _} -> {ok, CloseCode, Payload, Utf8State2, Rest}; + {more, _, Utf8State2} -> {more, CloseCode, Payload, Utf8State2}; + Error -> Error + end; + error -> + {error, badframe} + end; +%% Normal frame. parse_payload(Data, MaskKey, Utf8State, ParsedLen, Type, Len, FragState, _, << 0:3 >>) -> {Data2, Rest, Eof} = split_payload(Data, Len), Payload = unmask(Data2, MaskKey, ParsedLen), @@ -168,88 +368,125 @@ split_payload(Data, Len) -> {Data2, Rest, true} end. +validate_close_code(Code) -> + if + Code < 1000 -> error; + Code =:= 1004 -> error; + Code =:= 1005 -> error; + Code =:= 1006 -> error; + Code > 1011, Code < 3000 -> error; + Code > 4999 -> error; + true -> ok + end. + +unmask(Data, undefined, _) -> + Data; unmask(Data, MaskKey, 0) -> - do_unmask(Data, MaskKey, <<>>); + mask(Data, MaskKey, <<>>); %% We unmask on the fly so we need to continue from the right mask byte. unmask(Data, MaskKey, UnmaskedLen) -> Left = UnmaskedLen rem 4, Right = 4 - Left, MaskKey2 = (MaskKey bsl (Left * 8)) + (MaskKey bsr (Right * 8)), - do_unmask(Data, MaskKey2, <<>>). + mask(Data, MaskKey2, <<>>). -do_unmask(<<>>, _, Unmasked) -> +mask(<<>>, _, Unmasked) -> Unmasked; -do_unmask(<< O:32, Rest/bits >>, MaskKey, Acc) -> +mask(<< O:32, Rest/bits >>, MaskKey, Acc) -> T = O bxor MaskKey, - do_unmask(Rest, MaskKey, << Acc/binary, T:32 >>); -do_unmask(<< O:24 >>, MaskKey, Acc) -> + mask(Rest, MaskKey, << Acc/binary, T:32 >>); +mask(<< O:24 >>, MaskKey, Acc) -> << MaskKey2:24, _:8 >> = << MaskKey:32 >>, T = O bxor MaskKey2, << Acc/binary, T:24 >>; -do_unmask(<< O:16 >>, MaskKey, Acc) -> +mask(<< O:16 >>, MaskKey, Acc) -> << MaskKey2:16, _:16 >> = << MaskKey:32 >>, T = O bxor MaskKey2, << Acc/binary, T:16 >>; -do_unmask(<< O:8 >>, MaskKey, Acc) -> +mask(<< O:8 >>, MaskKey, Acc) -> << MaskKey2:8, _:24 >> = << MaskKey:32 >>, T = O bxor MaskKey2, << Acc/binary, T:8 >>. -%% @todo Try using iodata() and see if it improves anything. -inflate_frame(Data, Inflate, fin, true) -> - iolist_to_binary(zlib:inflate(Inflate, << Data/binary, 0, 0, 255, 255 >>)); -inflate_frame(Data, Inflate, _, _) -> +inflate_frame(Data, Inflate, TakeOver, FragState, true) + when FragState =:= undefined; element(1, FragState) =:= fin -> + Data2 = zlib:inflate(Inflate, << Data/binary, 0, 0, 255, 255 >>), + case TakeOver of + no_takeover -> zlib:inflateReset(Inflate); + takeover -> ok + end, + iolist_to_binary(Data2); +inflate_frame(Data, Inflate, _T, _F, _E) -> iolist_to_binary(zlib:inflate(Inflate, Data)). %% Text frames and close control frames MUST have a payload that is valid UTF-8. validate_payload(Payload, Rest, Utf8State, _, Type, _, Eof) when Type =:= text; Type =:= close -> - case validate_utf8(<< Utf8State/binary, Payload/binary >>) of - false -> error; - Utf8State when not Eof -> {more, Payload, Utf8State}; - <<>> when Eof -> {ok, Payload, <<>>, Rest}; - _ -> error + case validate_utf8(Payload, Utf8State) of + 1 -> {error, badencoding}; + Utf8State2 when not Eof -> {more, Payload, Utf8State2}; + 0 when Eof -> {ok, Payload, 0, Rest}; + _ -> {error, badencoding} end; -validate_payload(Payload, Rest, Utf8State, _, fragment, {Fin, text}, Eof) -> - case validate_utf8(<< Utf8State/binary, Payload/binary >>) of - false -> error; - <<>> when Eof -> {ok, Payload, <<>>, Rest}; +validate_payload(Payload, Rest, Utf8State, _, fragment, {Fin, text, _}, Eof) -> + case validate_utf8(Payload, Utf8State) of + 1 -> {error, badencoding}; + 0 when Eof -> {ok, Payload, 0, Rest}; Utf8State2 when Eof, Fin =:= nofin -> {ok, Payload, Utf8State2, Rest}; Utf8State2 when not Eof -> {more, Payload, Utf8State2}; - _ -> error + _ -> {error, badencoding} end; validate_payload(Payload, _, Utf8State, _, _, _, false) -> {more, Payload, Utf8State}; validate_payload(Payload, Rest, Utf8State, _, _, _, true) -> {ok, Payload, Utf8State, Rest}. -%% Returns <<>> if the argument is valid UTF-8, false if not, -%% or the incomplete part of the argument if we need more data. -validate_utf8(Valid = <<>>) -> - Valid; -validate_utf8(<< _/utf8, Rest/bits >>) -> - validate_utf8(Rest); -%% 2 bytes. Codepages C0 and C1 are invalid; fail early. -validate_utf8(<< 2#1100000:7, _/bits >>) -> - false; -validate_utf8(Incomplete = << 2#110:3, _:5 >>) -> - Incomplete; -%% 3 bytes. -validate_utf8(Incomplete = << 2#1110:4, _:4 >>) -> - Incomplete; -validate_utf8(Incomplete = << 2#1110:4, _:4, 2#10:2, _:6 >>) -> - Incomplete; -%% 4 bytes. Codepage F4 may have invalid values greater than 0x10FFFF. -validate_utf8(<< 2#11110100:8, 2#10:2, High:6, _/bits >>) when High >= 2#10000 -> - false; -validate_utf8(Incomplete = << 2#11110:5, _:3 >>) -> - Incomplete; -validate_utf8(Incomplete = << 2#11110:5, _:3, 2#10:2, _:6 >>) -> - Incomplete; -validate_utf8(Incomplete = << 2#11110:5, _:3, 2#10:2, _:6, 2#10:2, _:6 >>) -> - Incomplete; -%% Invalid. -validate_utf8(_) -> - false. +%% Based on the Flexible and Economical UTF-8 Decoder algorithm by +%% Bjoern Hoehrmann <[email protected]> (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/). +%% +%% The original algorithm has been unrolled into all combinations of values for C and State +%% each with a clause. The common clauses were then grouped together. +%% +%% This function returns 0 on success, 1 on error, and 2..8 on incomplete data. +validate_utf8(<<>>, State) -> State; +validate_utf8(<< C, Rest/bits >>, 0) when C < 128 -> validate_utf8(Rest, 0); +validate_utf8(<< C, Rest/bits >>, 2) when C >= 128, C < 144 -> validate_utf8(Rest, 0); +validate_utf8(<< C, Rest/bits >>, 3) when C >= 128, C < 144 -> validate_utf8(Rest, 2); +validate_utf8(<< C, Rest/bits >>, 5) when C >= 128, C < 144 -> validate_utf8(Rest, 2); +validate_utf8(<< C, Rest/bits >>, 7) when C >= 128, C < 144 -> validate_utf8(Rest, 3); +validate_utf8(<< C, Rest/bits >>, 8) when C >= 128, C < 144 -> validate_utf8(Rest, 3); +validate_utf8(<< C, Rest/bits >>, 2) when C >= 144, C < 160 -> validate_utf8(Rest, 0); +validate_utf8(<< C, Rest/bits >>, 3) when C >= 144, C < 160 -> validate_utf8(Rest, 2); +validate_utf8(<< C, Rest/bits >>, 5) when C >= 144, C < 160 -> validate_utf8(Rest, 2); +validate_utf8(<< C, Rest/bits >>, 6) when C >= 144, C < 160 -> validate_utf8(Rest, 3); +validate_utf8(<< C, Rest/bits >>, 7) when C >= 144, C < 160 -> validate_utf8(Rest, 3); +validate_utf8(<< C, Rest/bits >>, 2) when C >= 160, C < 192 -> validate_utf8(Rest, 0); +validate_utf8(<< C, Rest/bits >>, 3) when C >= 160, C < 192 -> validate_utf8(Rest, 2); +validate_utf8(<< C, Rest/bits >>, 4) when C >= 160, C < 192 -> validate_utf8(Rest, 2); +validate_utf8(<< C, Rest/bits >>, 6) when C >= 160, C < 192 -> validate_utf8(Rest, 3); +validate_utf8(<< C, Rest/bits >>, 7) when C >= 160, C < 192 -> validate_utf8(Rest, 3); +validate_utf8(<< C, Rest/bits >>, 0) when C >= 194, C < 224 -> validate_utf8(Rest, 2); +validate_utf8(<< 224, Rest/bits >>, 0) -> validate_utf8(Rest, 4); +validate_utf8(<< C, Rest/bits >>, 0) when C >= 225, C < 237 -> validate_utf8(Rest, 3); +validate_utf8(<< 237, Rest/bits >>, 0) -> validate_utf8(Rest, 5); +validate_utf8(<< C, Rest/bits >>, 0) when C =:= 238; C =:= 239 -> validate_utf8(Rest, 3); +validate_utf8(<< 240, Rest/bits >>, 0) -> validate_utf8(Rest, 6); +validate_utf8(<< C, Rest/bits >>, 0) when C =:= 241; C =:= 242; C =:= 243 -> validate_utf8(Rest, 7); +validate_utf8(<< 244, Rest/bits >>, 0) -> validate_utf8(Rest, 8); +validate_utf8(_, _) -> 1. + +%% @doc Return a frame tuple from parsed state and data. + +-spec make_frame(frame_type(), binary(), close_code(), frag_state()) -> frame(). +%% Fragmented frame. +make_frame(fragment, Payload, _, {Fin, Type, _}) -> {fragment, Fin, Type, Payload}; +make_frame(text, Payload, _, _) -> {text, Payload}; +make_frame(binary, Payload, _, _) -> {binary, Payload}; +make_frame(close, <<>>, undefined, _) -> close; +make_frame(close, Payload, CloseCode, _) -> {close, CloseCode, Payload}; +make_frame(ping, <<>>, _, _) -> ping; +make_frame(ping, Payload, _, _) -> {ping, Payload}; +make_frame(pong, <<>>, _, _) -> pong; +make_frame(pong, Payload, _, _) -> {pong, Payload}. %% @doc Construct an unmasked Websocket frame. @@ -276,12 +513,12 @@ frame({pong, Payload}, _) -> true = Len =< 125, [<< 1:1, 0:3, 10:4, 0:1, Len:7 >>, Payload]; %% Data frames, deflate-frame extension. -frame({text, Payload}, #{deflate_frame := Deflate}) -> - Payload2 = deflate_frame(Payload, Deflate), +frame({text, Payload}, #{deflate := Deflate, deflate_takeover := TakeOver}) -> + Payload2 = deflate_frame(Payload, Deflate, TakeOver), Len = payload_length(Payload2), [<< 1:1, 1:1, 0:2, 1:4, 0:1, Len/bits >>, Payload2]; -frame({binary, Payload}, #{deflate_frame := Deflate}) -> - Payload2 = deflate_frame(Payload, Deflate), +frame({binary, Payload}, #{deflate := Deflate, deflate_takeover := TakeOver}) -> + Payload2 = deflate_frame(Payload, Deflate, TakeOver), Len = payload_length(Payload2), [<< 1:1, 1:1, 0:2, 2:4, 0:1, Len/bits >>, Payload2]; %% Data frames. @@ -292,6 +529,56 @@ frame({binary, Payload}, _) -> Len = payload_length(Payload), [<< 1:1, 0:3, 2:4, 0:1, Len/bits >>, Payload]. +%% @doc Construct a masked Websocket frame. +%% +%% We use a mask key of 0 if there is no payload for close, ping and pong frames. + +-spec masked_frame(frame(), extensions()) -> iodata(). +%% Control frames. Control packets must not be > 125 in length. +masked_frame(close, _) -> + << 1:1, 0:3, 8:4, 1:1, 0:39 >>; +masked_frame(ping, _) -> + << 1:1, 0:3, 9:4, 1:1, 0:39 >>; +masked_frame(pong, _) -> + << 1:1, 0:3, 10:4, 1:1, 0:39 >>; +masked_frame({close, Payload}, Extensions) -> + frame({close, 1000, Payload}, Extensions); +masked_frame({close, StatusCode, Payload}, _) -> + Len = 2 + iolist_size(Payload), + true = Len =< 125, + MaskKeyBin = << MaskKey:32 >> = crypto:rand_bytes(4), + [<< 1:1, 0:3, 8:4, 1:1, Len:7 >>, MaskKeyBin, mask(iolist_to_binary([<< StatusCode:16 >>, Payload]), MaskKey, <<>>)]; +masked_frame({ping, Payload}, _) -> + Len = iolist_size(Payload), + true = Len =< 125, + MaskKeyBin = << MaskKey:32 >> = crypto:rand_bytes(4), + [<< 1:1, 0:3, 9:4, 1:1, Len:7 >>, MaskKeyBin, mask(iolist_to_binary(Payload), MaskKey, <<>>)]; +masked_frame({pong, Payload}, _) -> + Len = iolist_size(Payload), + true = Len =< 125, + MaskKeyBin = << MaskKey:32 >> = crypto:rand_bytes(4), + [<< 1:1, 0:3, 10:4, 1:1, Len:7 >>, MaskKeyBin, mask(iolist_to_binary(Payload), MaskKey, <<>>)]; +%% Data frames, deflate-frame extension. +masked_frame({text, Payload}, #{deflate := Deflate, deflate_takeover := TakeOver}) -> + MaskKeyBin = << MaskKey:32 >> = crypto:rand_bytes(4), + Payload2 = mask(deflate_frame(Payload, Deflate, TakeOver), MaskKey, <<>>), + Len = payload_length(Payload2), + [<< 1:1, 1:1, 0:2, 1:4, 1:1, Len/bits >>, MaskKeyBin, Payload2]; +masked_frame({binary, Payload}, #{deflate := Deflate, deflate_takeover := TakeOver}) -> + MaskKeyBin = << MaskKey:32 >> = crypto:rand_bytes(4), + Payload2 = mask(deflate_frame(Payload, Deflate, TakeOver), MaskKey, <<>>), + Len = payload_length(Payload2), + [<< 1:1, 1:1, 0:2, 2:4, 1:1, Len/bits >>, MaskKeyBin, Payload2]; +%% Data frames. +masked_frame({text, Payload}, _) -> + MaskKeyBin = << MaskKey:32 >> = crypto:rand_bytes(4), + Len = payload_length(Payload), + [<< 1:1, 0:3, 1:4, 1:1, Len/bits >>, MaskKeyBin, mask(iolist_to_binary(Payload), MaskKey, <<>>)]; +masked_frame({binary, Payload}, _) -> + MaskKeyBin = << MaskKey:32 >> = crypto:rand_bytes(4), + Len = payload_length(Payload), + [<< 1:1, 0:3, 2:4, 1:1, Len/bits >>, MaskKeyBin, mask(iolist_to_binary(Payload), MaskKey, <<>>)]. + payload_length(Payload) -> case byte_size(Payload) of N when N =< 125 -> << N:7 >>; @@ -299,8 +586,12 @@ payload_length(Payload) -> N when N =< 16#7fffffffffffffff -> << 127:7, N:64 >> end. -deflate_frame(Payload, Deflate) -> +deflate_frame(Payload, Deflate, TakeOver) -> Deflated = iolist_to_binary(zlib:deflate(Deflate, Payload, sync)), + case TakeOver of + no_takeover -> zlib:deflateReset(Deflate); + takeover -> ok + end, Len = byte_size(Deflated) - 4, case Deflated of << Body:Len/binary, 0:8, 0:8, 255:8, 255:8 >> -> Body; |