aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2018-11-12 18:08:28 +0100
committerLoïc Hoguin <[email protected]>2018-11-12 18:08:28 +0100
commitf760f979b48e6d8991be10499d9dd7217c98060b (patch)
tree62c97e2e7df832bb828181c03081037cd9b25335
parent664ada1c19c277bcab250c1d38d579672c02a628 (diff)
downloadcowlib-f760f979b48e6d8991be10499d9dd7217c98060b.tar.gz
cowlib-f760f979b48e6d8991be10499d9dd7217c98060b.tar.bz2
cowlib-f760f979b48e6d8991be10499d9dd7217c98060b.zip
Add deflate options for Websocket compression
They allow the server to configure what it is willing to accept for the negotiated configuration (takeover and window bits).
-rw-r--r--src/cow_ws.erl156
1 files changed, 123 insertions, 33 deletions
diff --git a/src/cow_ws.erl b/src/cow_ws.erl
index bac8450..1300a65 100644
--- a/src/cow_ws.erl
+++ b/src/cow_ws.erl
@@ -35,6 +35,22 @@
-type extensions() :: map().
-export_type([extensions/0]).
+-type deflate_opts() :: #{
+ %% Compression parameters.
+ level => zlib:zlevel(),
+ mem_level => zlib:zmemlevel(),
+ strategy => zlib:zstrategy(),
+
+ %% Whether the compression context will carry over between frames.
+ server_context_takeover => takeover | no_takeover,
+ client_context_takeover => takeover | no_takeover,
+
+ %% LZ77 sliding window size limits.
+ server_max_window_bits => 8..15,
+ client_max_window_bits => 8..15
+}.
+-export_type([deflate_opts/0]).
+
-type frag_state() :: undefined | {fin | nofin, text | binary, rsv()}.
-export_type([frag_state/0]).
@@ -70,6 +86,9 @@ encode_key(Key) ->
%% @doc Negotiate the permessage-deflate extension.
+-spec negotiate_permessage_deflate(
+ [binary() | {binary(), binary()}], Exts, deflate_opts())
+ -> ignore | {ok, iolist(), Exts} when Exts::extensions().
%% Ignore if deflate already negotiated.
negotiate_permessage_deflate(_, #{deflate := _}, _) ->
ignore;
@@ -79,48 +98,117 @@ negotiate_permessage_deflate(Params, Extensions, Opts) ->
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
+ negotiate_permessage_deflate1(Params2, Extensions, Opts)
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) ->
+negotiate_permessage_deflate1(Params, Extensions, Opts) ->
+ %% We are allowed to send back no_takeover even if the client
+ %% accepts takeover. Therefore we use no_takeover if any of
+ %% the inputs have it.
+ ServerTakeover = maps:get(server_context_takeover, Opts, takeover),
+ ClientTakeover = maps:get(client_context_takeover, Opts, takeover),
+ %% We can send back window bits smaller than or equal to what
+ %% the client sends us.
+ ServerMaxWindowBits = maps:get(server_max_window_bits, Opts, 15),
+ ClientMaxWindowBits = maps:get(client_max_window_bits, Opts, 15),
+ %% We may need to send back no_context_takeover depending on configuration.
+ RespParams0 = case ServerTakeover of
+ takeover -> [];
+ no_takeover -> [<<"; server_no_context_takeover">>]
+ end,
+ RespParams1 = case ClientTakeover of
+ takeover -> RespParams0;
+ no_takeover -> [<<"; client_no_context_takeover">>|RespParams0]
+ end,
+ Negotiated0 = #{
+ server_context_takeover => ServerTakeover,
+ client_context_takeover => ClientTakeover,
+ server_max_window_bits => ServerMaxWindowBits,
+ client_max_window_bits => ClientMaxWindowBits
+ },
+ case negotiate_params(Params, Negotiated0, RespParams1) of
+ ignore ->
+ ignore;
+ {#{server_max_window_bits := SB}, _} when SB > ServerMaxWindowBits ->
+ ignore;
+ {#{client_max_window_bits := CB}, _} when CB > ClientMaxWindowBits ->
+ ignore;
+ {Negotiated, RespParams2} ->
+ %% We add the configured max window bits if necessary.
+ RespParams = case Negotiated of
+ #{server_max_window_bits_set := true} -> RespParams2;
+ _ when ServerMaxWindowBits =:= 15 -> RespParams2;
+ _ -> [<<"; server_max_window_bits=">>,
+ integer_to_binary(ServerMaxWindowBits)|RespParams2]
+ end,
+ {Inflate, Deflate} = init_permessage_deflate(
+ maps:get(client_max_window_bits, Negotiated),
+ maps:get(server_max_window_bits, Negotiated), Opts),
+ {ok, [<<"permessage-deflate">>, RespParams], Extensions#{
+ deflate => Deflate,
+ deflate_takeover => maps:get(server_context_takeover, Negotiated),
+ inflate => Inflate,
+ inflate_takeover => maps:get(client_context_takeover, Negotiated)}}
+ end.
+
+negotiate_params([], Negotiated, RespParams) ->
+ {Negotiated, RespParams};
+%% We must only send the client_max_window_bits parameter if the
+%% request explicitly indicated the client supports it.
+negotiate_params([<<"client_max_window_bits">>|Tail], Negotiated, RespParams) ->
+ CB = maps:get(client_max_window_bits, Negotiated),
+ negotiate_params(Tail, Negotiated#{client_max_window_bits_set => true},
+ [<<"; client_max_window_bits=">>, integer_to_binary(CB)|RespParams]);
+negotiate_params([{<<"client_max_window_bits">>, Max}|Tail], Negotiated, RespParams) ->
+ CB0 = maps:get(client_max_window_bits, Negotiated, undefined),
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])
+ CB when CB =< CB0 ->
+ negotiate_params(Tail, Negotiated#{client_max_window_bits => CB},
+ [<<"; client_max_window_bits=">>, Max|RespParams]);
+ %% When the client sends window bits larger than the server wants
+ %% to use, we use what the server defined.
+ _ ->
+ negotiate_params(Tail, Negotiated,
+ [<<"; client_max_window_bits=">>, integer_to_binary(CB0)|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) ->
+negotiate_params([{<<"server_max_window_bits">>, Max}|Tail], Negotiated, RespParams) ->
+ SB0 = maps:get(server_max_window_bits, Negotiated, undefined),
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])
+ SB when SB =< SB0 ->
+ negotiate_params(Tail, Negotiated#{
+ server_max_window_bits => SB,
+ server_max_window_bits_set => true},
+ [<<"; server_max_window_bits=">>, Max|RespParams]);
+ %% When the client sends window bits larger than the server wants
+ %% to use, we use what the server defined. The parameter will be
+ %% set only when this function returns.
+ _ ->
+ negotiate_params(Tail, Negotiated, RespParams)
+ end;
+%% We only need to send the no_context_takeover parameter back
+%% here if we didn't already define it via configuration.
+negotiate_params([<<"client_no_context_takeover">>|Tail], Negotiated, RespParams) ->
+ case maps:get(client_context_takeover, Negotiated) of
+ no_takeover ->
+ negotiate_params(Tail, Negotiated, RespParams);
+ takeover ->
+ negotiate_params(Tail, Negotiated#{client_context_takeover => no_takeover},
+ [<<"; client_no_context_takeover">>|RespParams])
+ end;
+negotiate_params([<<"server_no_context_takeover">>|Tail], Negotiated, RespParams) ->
+ case maps:get(server_context_takeover, Negotiated) of
+ no_takeover ->
+ negotiate_params(Tail, Negotiated, RespParams);
+ takeover ->
+ negotiate_params(Tail, Negotiated#{server_context_takeover => no_takeover},
+ [<<"; server_no_context_takeover">>|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(_, _, _, _, _, _) ->
+negotiate_params(_, _, _) ->
ignore.
parse_max_window_bits(<<"8">>) -> 8;
@@ -133,7 +221,7 @@ 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.
+%% 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),
@@ -194,6 +282,9 @@ set_owner(Pid, Inflate, Deflate) ->
%% The implementation is very basic and none of the parameters
%% are currently supported.
+-spec negotiate_x_webkit_deflate_frame(
+ [binary() | {binary(), binary()}], Exts, deflate_opts())
+ -> ignore | {ok, binary(), Exts} when Exts::extensions().
negotiate_x_webkit_deflate_frame(_, #{deflate := _}, _) ->
ignore;
negotiate_x_webkit_deflate_frame(_Params, Extensions, Opts) ->
@@ -219,7 +310,6 @@ validate_permessage_deflate(Params, Extensions, Opts) ->
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;