aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2025-01-22 12:30:27 +0100
committerLoïc Hoguin <[email protected]>2025-01-22 15:20:20 +0100
commit7f739cad6de9403391391b5811ac4a4af31e4688 (patch)
treea5c80c2be7f9afe422a556ce8e6341cd48d6f706
parent6e221d38b1ece97ec1f6ef675b82216dc71dac9f (diff)
downloadcowboy-7f739cad6de9403391391b5811ac4a4af31e4688.tar.gz
cowboy-7f739cad6de9403391391b5811ac4a4af31e4688.tar.bz2
cowboy-7f739cad6de9403391391b5811ac4a4af31e4688.zip
Websocket: Also apply max_frame_size limit to decompressed data
Before this commit frames could "cheat" by compressing data below the limit which would get expanded above the limit. Now Cowboy will stop decompressing data when the limit is reached.
-rw-r--r--src/cowboy_websocket.erl8
-rw-r--r--test/ws_SUITE.erl19
-rw-r--r--test/ws_SUITE_data/ws_max_frame_size.erl2
3 files changed, 26 insertions, 3 deletions
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl
index 12c99ba..3d85a75 100644
--- a/src/cowboy_websocket.erl
+++ b/src/cowboy_websocket.erl
@@ -480,12 +480,16 @@ parse_header(State=#state{opts=Opts, frag_state=FragState, extensions=Extensions
websocket_close(State, HandlerState, {error, badframe})
end.
-parse_payload(State=#state{frag_state=FragState, utf8_state=Incomplete, extensions=Extensions},
+parse_payload(State=#state{opts=Opts, frag_state=FragState, utf8_state=Incomplete, extensions=Extensions},
HandlerState, ParseState=#ps_payload{
type=Type, len=Len, mask_key=MaskKey, rsv=Rsv,
unmasked=Unmasked, unmasked_len=UnmaskedLen}, Data) ->
+ MaxFrameSize = case maps:get(max_frame_size, Opts, infinity) of
+ infinity -> infinity;
+ MaxFrameSize0 -> MaxFrameSize0 - UnmaskedLen
+ end,
case cow_ws:parse_payload(Data, MaskKey, Incomplete, UnmaskedLen,
- Type, Len, FragState, Extensions, Rsv) of
+ Type, Len, FragState, Extensions#{max_inflate_size => MaxFrameSize}, Rsv) of
{ok, CloseCode, Payload, Utf8State, Rest} ->
dispatch_frame(State#state{utf8_state=Utf8State}, HandlerState,
ParseState#ps_payload{unmasked= <<Unmasked/binary, Payload/binary>>,
diff --git a/test/ws_SUITE.erl b/test/ws_SUITE.erl
index 3b74339..9c1f880 100644
--- a/test/ws_SUITE.erl
+++ b/test/ws_SUITE.erl
@@ -203,6 +203,25 @@ do_ws_version(Socket) ->
{error, closed} = gen_tcp:recv(Socket, 0, 6000),
ok.
+ws_deflate_max_frame_size_close(Config) ->
+ doc("Server closes connection when decompressed frame size exceeds max_frame_size option"),
+ %% max_frame_size is set to 8 bytes in ws_max_frame_size.
+ {ok, Socket, Headers} = do_handshake("/ws_max_frame_size",
+ "Sec-WebSocket-Extensions: permessage-deflate\r\n", Config),
+ {_, "permessage-deflate"} = lists:keyfind("sec-websocket-extensions", 1, Headers),
+ Mask = 16#11223344,
+ Z = zlib:open(),
+ zlib:deflateInit(Z, best_compression, deflated, -15, 8, default),
+ CompressedData0 = iolist_to_binary(zlib:deflate(Z, <<0:800>>, sync)),
+ CompressedData = binary:part(CompressedData0, 0, byte_size(CompressedData0) - 4),
+ MaskedData = do_mask(CompressedData, Mask, <<>>),
+ Len = byte_size(MaskedData),
+ true = Len < 8,
+ ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 1:4, 1:1, Len:7, Mask:32, MaskedData/binary >>),
+ {ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1009:16 >>} = gen_tcp:recv(Socket, 0, 6000),
+ {error, closed} = gen_tcp:recv(Socket, 0, 6000),
+ ok.
+
ws_deflate_opts_client_context_takeover(Config) ->
doc("Handler is configured with client context takeover enabled."),
{ok, _, Headers1} = do_handshake("/ws_deflate_opts?client_context_takeover",
diff --git a/test/ws_SUITE_data/ws_max_frame_size.erl b/test/ws_SUITE_data/ws_max_frame_size.erl
index 3d81497..76df0b0 100644
--- a/test/ws_SUITE_data/ws_max_frame_size.erl
+++ b/test/ws_SUITE_data/ws_max_frame_size.erl
@@ -5,7 +5,7 @@
-export([websocket_info/2]).
init(Req, State) ->
- {cowboy_websocket, Req, State, #{max_frame_size => 8}}.
+ {cowboy_websocket, Req, State, #{max_frame_size => 8, compress => true}}.
websocket_handle({text, Data}, State) ->
{[{text, Data}], State};