@@ -67,6 +67,7 @@ init_dispatch() ->
6767 {" /ws_timeout_hibernate" , ws_timeout_hibernate , []},
6868 {" /ws_timeout_cancel" , ws_timeout_cancel , []},
6969 {" /ws_max_frame_size" , ws_max_frame_size , []},
70+ {" /ws_max_frame_size_deflate" , ws_max_frame_size_deflate , []},
7071 {" /ws_deflate_opts" , ws_deflate_opts_h , []},
7172 {" /ws_dont_validate_utf8" , ws_dont_validate_utf8_h , []},
7273 {" /ws_ping" , ws_ping_h , []}
@@ -222,6 +223,46 @@ ws_deflate_max_frame_size_close(Config) ->
222223 {error , closed } = gen_tcp :recv (Socket , 0 , 6000 ),
223224 ok .
224225
226+ ws_deflate_max_frame_size_chunked_close (Config ) ->
227+ doc (" Server closes connection when decompressed frame size exceeds "
228+ " max_frame_size option, even when the compressed payload is "
229+ " delivered over multiple separate TCP segments." ),
230+ % % max_frame_size is set to 100 bytes in ws_max_frame_size_deflate.
231+ {ok , Socket , Headers } = do_handshake (" /ws_max_frame_size_deflate" ,
232+ " Sec-WebSocket-Extensions: permessage-deflate\r\n " , Config ),
233+ {_ , " permessage-deflate" } = lists :keyfind (" sec-websocket-extensions" , 1 , Headers ),
234+ Mask = 16#11223344 ,
235+ Z = zlib :open (),
236+ zlib :deflateInit (Z , best_compression , deflated , - 15 , 8 , default ),
237+ % % Three independently sync-flushed chunks, each decompressing to
238+ % % exactly 50 bytes. After the first two (100 bytes, exactly at
239+ % % max_frame_size) the server must not close yet; after the third
240+ % % (150 bytes, over max_frame_size) it must close with code 1009,
241+ % % regardless of the fact that each individual chunk's own wire
242+ % % size (~8-10 bytes) never looks oversized by itself.
243+ [Chunk1 , Chunk2 , Chunk3 ] = [iolist_to_binary (zlib :deflate (Z , <<0 :400 >>, sync ))
244+ || _ <- lists :seq (1 , 3 )],
245+ Chunk1Size = byte_size (Chunk1 ),
246+ Chunk2Size = byte_size (Chunk2 ),
247+ CompressedData0 = iolist_to_binary ([Chunk1 , Chunk2 , Chunk3 ]),
248+ CompressedData = binary :part (CompressedData0 , 0 , byte_size (CompressedData0 ) - 4 ),
249+ MaskedData = do_mask (CompressedData , Mask , <<>>),
250+ Len = byte_size (MaskedData ),
251+ true = Len < 100 ,
252+ << MaskedChunk1 :Chunk1Size /binary , MaskedChunk2 :Chunk2Size /binary ,
253+ MaskedChunk3 /binary >> = MaskedData ,
254+ ok = gen_tcp :send (Socket , << 1 :1 , 1 :1 , 0 :2 , 1 :4 , 1 :1 , Len :7 , Mask :32 >>),
255+ ok = gen_tcp :send (Socket , MaskedChunk1 ),
256+ ok = gen_tcp :send (Socket , MaskedChunk2 ),
257+ % % 100 bytes decompressed so far: exactly at the limit, connection
258+ % % must still be open.
259+ {error , timeout } = gen_tcp :recv (Socket , 0 , 200 ),
260+ ok = gen_tcp :send (Socket , MaskedChunk3 ),
261+ % % 150 bytes decompressed: over the limit, connection must close now.
262+ {ok , << 1 :1 , 0 :3 , 8 :4 , 0 :1 , 2 :7 , 1009 :16 >>} = gen_tcp :recv (Socket , 0 , 6000 ),
263+ {error , closed } = gen_tcp :recv (Socket , 0 , 6000 ),
264+ ok .
265+
225266ws_deflate_opts_client_context_takeover (Config ) ->
226267 doc (" Handler is configured with client context takeover enabled." ),
227268 {ok , _ , Headers1 } = do_handshake (" /ws_deflate_opts?client_context_takeover" ,
0 commit comments