aboutsummaryrefslogtreecommitdiffstats
path: root/test/decompress_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'test/decompress_SUITE.erl')
-rw-r--r--test/decompress_SUITE.erl327
1 files changed, 327 insertions, 0 deletions
diff --git a/test/decompress_SUITE.erl b/test/decompress_SUITE.erl
new file mode 100644
index 0000000..6fd0a73
--- /dev/null
+++ b/test/decompress_SUITE.erl
@@ -0,0 +1,327 @@
+-module(decompress_SUITE).
+-compile(export_all).
+-compile(nowarn_export_all).
+
+-import(ct_helper, [config/2]).
+-import(ct_helper, [doc/1]).
+-import(cowboy_test, [gun_open/1]).
+
+%% ct.
+
+all() ->
+ cowboy_test:common_all().
+
+groups() ->
+ cowboy_test:common_groups(ct_helper:all(?MODULE)).
+
+init_per_group(Name = http, Config) ->
+ cowboy_test:init_http(Name, init_plain_opts(Config), Config);
+init_per_group(Name = https, Config) ->
+ cowboy_test:init_http(Name, init_plain_opts(Config), Config);
+init_per_group(Name = h2, Config) ->
+ cowboy_test:init_http2(Name, init_plain_opts(Config), Config);
+init_per_group(Name = h2c, Config) ->
+ Config1 = cowboy_test:init_http(Name, init_plain_opts(Config), Config),
+ lists:keyreplace(protocol, 1, Config1, {protocol, http2});
+init_per_group(Name = http_compress, Config) ->
+ cowboy_test:init_http(Name, init_compress_opts(Config), Config);
+init_per_group(Name = https_compress, Config) ->
+ cowboy_test:init_http(Name, init_compress_opts(Config), Config);
+init_per_group(Name = h2_compress, Config) ->
+ cowboy_test:init_http2(Name, init_compress_opts(Config), Config);
+init_per_group(Name = h2c_compress, Config) ->
+ Config1 = cowboy_test:init_http(Name, init_compress_opts(Config), Config),
+ lists:keyreplace(protocol, 1, Config1, {protocol, http2}).
+
+end_per_group(Name, _) ->
+ cowboy:stop_listener(Name).
+
+init_plain_opts(Config) ->
+ #{
+ env => #{dispatch => cowboy_router:compile(init_routes(Config))},
+ stream_handlers => [cowboy_decompress_h, cowboy_stream_h]
+ }.
+
+init_compress_opts(Config) ->
+ #{
+ env => #{dispatch => cowboy_router:compile(init_routes(Config))},
+ stream_handlers => [cowboy_decompress_h, cowboy_compress_h, cowboy_stream_h]
+ }.
+
+init_routes(_) ->
+ [{'_', [
+ {"/echo/:what", decompress_h, []},
+ {"/header", decompress_h, header_command},
+ {"/invalid-header", decompress_h, invalid_header},
+ {"/accept-identity", decompress_h, accept_identity},
+ {"/reject-explicit-header", decompress_h, reject_explicit_header},
+ {"/reject-implicit-header", decompress_h, reject_implicit_header},
+ {"/accept-explicit-header", decompress_h, accept_explicit_header},
+ {"/accept-implicit-header", decompress_h, accept_implicit_header}
+ ]}].
+
+%% Internal.
+
+do_post(Path, ReqHeaders, Body, Config) ->
+ ConnPid = gun_open(Config),
+ Ref = gun:post(ConnPid, Path, ReqHeaders, Body),
+ {response, IsFin, Status, RespHeaders} = gun:await(ConnPid, Ref),
+ {ok, ResponseBody} = case IsFin of
+ nofin -> gun:await_body(ConnPid, Ref);
+ fin -> {ok, <<>>}
+ end,
+ gun:close(ConnPid),
+ {Status, RespHeaders, ResponseBody}.
+
+create_gzip_bomb() ->
+ Z = zlib:open(),
+ zlib:deflateInit(Z, 9, deflated, 31, 8, default),
+ %% 1000 chunks of 100000 zeroes (100MB).
+ Bomb = do_create_gzip_bomb(Z, 1000),
+ zlib:deflateEnd(Z),
+ zlib:close(Z),
+ iolist_to_binary(Bomb).
+
+do_create_gzip_bomb(Z, 0) ->
+ zlib:deflate(Z, << >>, finish);
+do_create_gzip_bomb(Z, N) ->
+ Data = <<0:800000>>,
+ Deflate = zlib:deflate(Z, Data),
+ [Deflate | do_create_gzip_bomb(Z, N - 1)].
+
+%% Tests.
+
+content_encoding_none(Config) ->
+ doc("Send no content-encoding; get echo."),
+ Body = <<"test">>,
+ {200, _, Body} = do_post("/echo/normal",
+ [{<<"content-encoding">>, <<";">>}], Body, Config),
+ ok.
+
+content_encoding_malformed(Config) ->
+ doc("Send malformed content-encoding; get echo."),
+ Body = <<"test">>,
+ {200, _, Body} = do_post("/echo/normal",
+ [{<<"content-encoding">>, <<";">>}], Body, Config),
+ ok.
+
+content_encoding_not_supported(Config) ->
+ doc("Send content-encoding: compress (unsupported by Cowboy); get echo."),
+ Body = <<"test">>,
+ {200, _, Body} = do_post("/echo/normal",
+ [{<<"content-encoding">>, <<"compress">>}], Body, Config),
+ ok.
+
+content_encoding_wrong(Config) ->
+ doc("Send content-encoding and unencoded body; get 400."),
+ Body = <<"test">>,
+ {400, _, _} = do_post("/echo/normal",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ ok.
+
+decompress(Config) ->
+ doc("Send content-encoding and encoded body; get decompressed response."),
+ Data = <<"test">>,
+ Body = zlib:gzip(Data),
+ {200, _, Data} = do_post("/echo/normal",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ ok.
+
+decompress_stream(Config) ->
+ doc("Stream encoded body; get decompressed response."),
+ %% Handler read length 1KB. Compressing 3KB should be enough to trigger more.
+ Data = crypto:strong_rand_bytes(3000),
+ Body = zlib:gzip(Data),
+ Size = byte_size(Body),
+ ConnPid = gun_open(Config),
+ Ref = gun:post(ConnPid, "/echo/normal", [{<<"content-encoding">>, <<"gzip">>}]),
+ gun:data(ConnPid, Ref, nofin, binary:part(Body, 0, Size div 2)),
+ timer:sleep(1000),
+ gun:data(ConnPid, Ref, fin, binary:part(Body, Size div 2, Size div 2 + Size rem 2)),
+ {response, IsFin, 200, _} = gun:await(ConnPid, Ref),
+ {ok, Data} = case IsFin of
+ nofin -> gun:await_body(ConnPid, Ref);
+ fin -> {ok, <<>>}
+ end,
+ gun:close(ConnPid),
+ {200, _, Data} = do_post("/echo/normal",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ ok.
+
+opts_decompress_ignore(Config0) ->
+ doc("Confirm that the decompress_ignore option can be set."),
+ Fun = case config(ref, Config0) of
+ HTTPS when HTTPS =:= https_compress; HTTPS =:= https -> init_https;
+ H2 when H2 =:= h2_compress; H2 =:= h2 -> init_http2;
+ _ -> init_http
+ end,
+ Config = cowboy_test:Fun(?FUNCTION_NAME, #{
+ env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
+ stream_handlers => [cowboy_decompress_h, cowboy_stream_h],
+ decompress_ignore => true
+ }, Config0),
+ Data = <<"test">>,
+ Body = zlib:gzip(Data),
+ try
+ {200, _, Body} = do_post("/echo/normal",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config)
+ after
+ cowboy:stop_listener(?FUNCTION_NAME)
+ end.
+
+set_options_decompress_ignore(Config) ->
+ doc("Confirm that the decompress_ignore option can be dynamically
+ set to true and the data received is not decompressed."),
+ Data = <<"test">>,
+ Body = zlib:gzip(Data),
+ {200, _, Body} = do_post("/echo/decompress_ignore",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ ok.
+
+opts_decompress_ratio_limit(Config0) ->
+ doc("Confirm that the decompress_ignore option can be set"),
+ Fun = case config(ref, Config0) of
+ HTTPS when HTTPS =:= https_compress; HTTPS =:= https -> init_https;
+ H2 when H2 =:= h2_compress; H2 =:= h2 -> init_http2;
+ _ -> init_http
+ end,
+ Config = cowboy_test:Fun(?FUNCTION_NAME, #{
+ env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
+ stream_handlers => [cowboy_decompress_h, cowboy_stream_h],
+ decompress_ratio_limit => 1
+ }, Config0),
+ %% Data must be big enough for compression to be effective, so that ratio_limit=1 will fail.
+ Data = <<0:800>>,
+ Body = zlib:gzip(Data),
+ try
+ {413, _, _} = do_post("/echo/normal",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config)
+ after
+ cowboy:stop_listener(?FUNCTION_NAME)
+ end.
+
+set_options_decompress_ratio_limit(Config) ->
+ doc("Confirm that the decompress_ratio_limit option can be dynamically set."),
+ %% Data must be big enough for compression to be effective, so that ratio_limit=1 will fail.
+ Data = <<0:800>>,
+ Body = zlib:gzip(Data),
+ {413, _, _} = do_post("/echo/decompress_ratio_limit",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ ok.
+
+gzip_bomb(Config) ->
+ doc("Send body compressed with suspiciously large ratio; get 413."),
+ Body = create_gzip_bomb(),
+ {413, _, _} = do_post("/echo/normal",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ ok.
+
+%% RFC 9110. Section 12.5.3. 3. When sent by a server in a response,
+%% Accept-Encoding provides information about which content codings are
+%% preferred in the content of a subsequent request to the same resource.
+%%
+%% Set or add gzip
+set_accept_encoding_response(Config) ->
+ doc("Header accept-encoding must be set on valid response command."),
+ Data = <<"test">>,
+ Body = zlib:gzip(Data),
+ {200, Headers, Data} = do_post("/echo/normal",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ {_, <<"gzip">>} = lists:keyfind(<<"accept-encoding">>, 1, Headers),
+ ok.
+
+set_accept_encoding_header(Config) ->
+ doc("Header accept-encoding must be set on valid header command."),
+ Data = <<"test">>,
+ Body = zlib:gzip(Data),
+ {200, Headers, Data} = do_post("/header",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ {_, <<"gzip">>} = lists:keyfind(<<"accept-encoding">>, 1, Headers),
+ ok.
+
+add_accept_encoding_header_valid(Config) ->
+ doc("Header accept-encoding must be added on valid accept-encoding."),
+ Data = <<"test">>,
+ Body = zlib:gzip(Data),
+ {200, Headers, Data} = do_post("/accept-identity",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ {_, <<"identity, gzip">>} = lists:keyfind(<<"accept-encoding">>, 1, Headers),
+ ok.
+
+override_accept_encoding_header_invalid(Config) ->
+ doc("Header accept-encoding must override invalid accept-encoding."),
+ Data = <<"test">>,
+ Body = zlib:gzip(Data),
+ {200, Headers, Data} = do_post("/invalid-header",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ {_, <<"gzip">>} = lists:keyfind(<<"accept-encoding">>, 1, Headers),
+ ok.
+
+%% RFC 9110. Section 12.5.3. 10.3. If the representation's content coding is
+%% one of the content codings listed in the Accept-Encoding field value, then
+%% it is acceptable unless it is accompanied by a qvalue of 0.
+%%
+%% gzip must not have a qvalue of 0 when the handler is used. Set to 1.
+override_accept_encoding_excluded(Config) ->
+ doc("Header accept-encoding must override when explicitly excluded."),
+ Data = <<"test">>,
+ Body = zlib:gzip(Data),
+ {200, Headers, Data} = do_post("/reject-explicit-header",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ {_, <<"identity;q=1, gzip;q=1">>} = lists:keyfind(<<"accept-encoding">>, 1, Headers),
+ ok.
+
+%% RFC 9110. Section 12.5.3. 10.2. If the representation has no content coding,
+%% then it is acceptable by default unless specifically excluded by the
+%% Accept-Encoding field stating either "identity;q=0" or "*;q=0" wihout a more
+%% specific entry for "identity".
+%%
+%% *;q=0 will reject codings that are not listed. Specific entry gzip must
+%% always be listed when the handler is used. Add gzip.
+add_accept_encoding_excluded(Config) ->
+ doc("Header accept-encoding must added when implicitly excluded."),
+ Data = <<"test">>,
+ Body = zlib:gzip(Data),
+ {200, Headers, Data} = do_post("/reject-implicit-header",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ {_, <<"gzip;q=1, identity;q=1, *;q=0">>} = lists:keyfind(<<"accept-encoding">>, 1, Headers),
+ ok.
+
+no_override_accept_coding_set_explicit(Config) ->
+ doc("Confirm that accept-encoding is not overridden when explicitly set."),
+ Data = <<"test">>,
+ Body = zlib:gzip(Data),
+ {200, Headers, Data} = do_post("/accept-explicit-header",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ {_, <<"identity, gzip;q=0.5">>} = lists:keyfind(<<"accept-encoding">>, 1, Headers),
+ ok.
+
+no_override_accept_coding_set_implicit(Config) ->
+ doc("Confirm that accept-encoding is not overridden when implicitly set."),
+ Data = <<"test">>,
+ Body = zlib:gzip(Data),
+ {200, Headers, Data} = do_post("/accept-implicit-header",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ {_, <<"identity, *;q=0.5">>} = lists:keyfind(<<"accept-encoding">>, 1, Headers),
+ ok.
+
+%% RFC 9110. Section 12.5.3. 10.1. If no Accept-Encoding header field is in the
+%% request, any content coding is considered acceptable by the user agent.
+%%
+%% Don't add anything on error or when that handler is not used.
+no_set_accept_encoding(Config) ->
+ doc("No header accept-encoding on invalid responses."),
+ Body = <<"test">>,
+ {400, Headers, _} = do_post("/echo/normal",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ false = lists:keyfind(<<"accept-encoding">>, 1, Headers),
+ ok.
+
+no_set_accept_encoding_ignore(Config) ->
+ doc("Confirm that no accept-encoding is set when stream is ignored."),
+ Data = <<"test">>,
+ Body = zlib:gzip(Data),
+ {200, Headers, Body} = do_post("/echo/decompress_ignore",
+ [{<<"content-encoding">>, <<"gzip">>}], Body, Config),
+ false = lists:keyfind(<<"accept-encoding">>, 1, Headers),
+ ok.