aboutsummaryrefslogtreecommitdiff
path: root/server/_build/default/lib/cowlib/src
diff options
context:
space:
mode:
authorCalvin Morrison <calvin@pobox.com>2025-09-03 21:15:36 -0400
committerCalvin Morrison <calvin@pobox.com>2025-09-03 21:15:36 -0400
commit49fa5aa2a127bdf8924d02bf77e5086b39c7a447 (patch)
tree61d86a7705dacc9fddccc29fa79d075d83ab8059 /server/_build/default/lib/cowlib/src
i vibe coded itHEADmaster
Diffstat (limited to 'server/_build/default/lib/cowlib/src')
-rw-r--r--server/_build/default/lib/cowlib/src/cow_base64url.erl81
-rw-r--r--server/_build/default/lib/cowlib/src/cow_cookie.erl456
-rw-r--r--server/_build/default/lib/cowlib/src/cow_date.erl434
-rw-r--r--server/_build/default/lib/cowlib/src/cow_hpack.erl1449
-rw-r--r--server/_build/default/lib/cowlib/src/cow_hpack_dec_huffman_lookup.hrl4132
-rw-r--r--server/_build/default/lib/cowlib/src/cow_http.erl426
-rw-r--r--server/_build/default/lib/cowlib/src/cow_http2.erl482
-rw-r--r--server/_build/default/lib/cowlib/src/cow_http2_machine.erl1647
-rw-r--r--server/_build/default/lib/cowlib/src/cow_http_hd.erl3642
-rw-r--r--server/_build/default/lib/cowlib/src/cow_http_struct_hd.erl522
-rw-r--r--server/_build/default/lib/cowlib/src/cow_http_te.erl373
-rw-r--r--server/_build/default/lib/cowlib/src/cow_iolists.erl95
-rw-r--r--server/_build/default/lib/cowlib/src/cow_link.erl445
-rw-r--r--server/_build/default/lib/cowlib/src/cow_mimetypes.erl1045
-rw-r--r--server/_build/default/lib/cowlib/src/cow_mimetypes.erl.src61
-rw-r--r--server/_build/default/lib/cowlib/src/cow_multipart.erl775
-rw-r--r--server/_build/default/lib/cowlib/src/cow_qs.erl563
-rw-r--r--server/_build/default/lib/cowlib/src/cow_spdy.erl313
-rw-r--r--server/_build/default/lib/cowlib/src/cow_spdy.hrl181
-rw-r--r--server/_build/default/lib/cowlib/src/cow_sse.erl349
-rw-r--r--server/_build/default/lib/cowlib/src/cow_uri.erl339
-rw-r--r--server/_build/default/lib/cowlib/src/cow_uri_template.erl360
-rw-r--r--server/_build/default/lib/cowlib/src/cow_ws.erl741
23 files changed, 18911 insertions, 0 deletions
diff --git a/server/_build/default/lib/cowlib/src/cow_base64url.erl b/server/_build/default/lib/cowlib/src/cow_base64url.erl
new file mode 100644
index 0000000..e591fcf
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_base64url.erl
@@ -0,0 +1,81 @@
+%% Copyright (c) 2017-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% This module implements "base64url" following the algorithm
+%% found in Appendix C of RFC7515. The option #{padding => false}
+%% must be given to reproduce this variant exactly. The default
+%% will leave the padding characters.
+-module(cow_base64url).
+
+-export([decode/1]).
+-export([decode/2]).
+-export([encode/1]).
+-export([encode/2]).
+
+-ifdef(TEST).
+-include_lib("proper/include/proper.hrl").
+-endif.
+
+decode(Enc) ->
+ decode(Enc, #{}).
+
+decode(Enc0, Opts) ->
+ Enc1 = << << case C of
+ $- -> $+;
+ $_ -> $/;
+ _ -> C
+ end >> || << C >> <= Enc0 >>,
+ Enc = case Opts of
+ #{padding := false} ->
+ case byte_size(Enc1) rem 4 of
+ 0 -> Enc1;
+ 2 -> << Enc1/binary, "==" >>;
+ 3 -> << Enc1/binary, "=" >>
+ end;
+ _ ->
+ Enc1
+ end,
+ base64:decode(Enc).
+
+encode(Dec) ->
+ encode(Dec, #{}).
+
+encode(Dec, Opts) ->
+ encode(base64:encode(Dec), Opts, <<>>).
+
+encode(<<$+, R/bits>>, Opts, Acc) -> encode(R, Opts, <<Acc/binary, $->>);
+encode(<<$/, R/bits>>, Opts, Acc) -> encode(R, Opts, <<Acc/binary, $_>>);
+encode(<<$=, _/bits>>, #{padding := false}, Acc) -> Acc;
+encode(<<C, R/bits>>, Opts, Acc) -> encode(R, Opts, <<Acc/binary, C>>);
+encode(<<>>, _, Acc) -> Acc.
+
+-ifdef(TEST).
+
+rfc7515_test() ->
+ Dec = <<3,236,255,224,193>>,
+ Enc = <<"A-z_4ME">>,
+ Pad = <<"A-z_4ME=">>,
+ Dec = decode(<<Enc/binary,$=>>),
+ Dec = decode(Enc, #{padding => false}),
+ Pad = encode(Dec),
+ Enc = encode(Dec, #{padding => false}),
+ ok.
+
+prop_identity() ->
+ ?FORALL(B, binary(), B =:= decode(encode(B))).
+
+prop_identity_no_padding() ->
+ ?FORALL(B, binary(), B =:= decode(encode(B, #{padding => false}), #{padding => false})).
+
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_cookie.erl b/server/_build/default/lib/cowlib/src/cow_cookie.erl
new file mode 100644
index 0000000..11cf339
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_cookie.erl
@@ -0,0 +1,456 @@
+%% Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_cookie).
+
+-export([parse_cookie/1]).
+-export([parse_set_cookie/1]).
+-export([cookie/1]).
+-export([setcookie/3]).
+
+-type cookie_attrs() :: #{
+ expires => calendar:datetime(),
+ max_age => calendar:datetime(),
+ domain => binary(),
+ path => binary(),
+ secure => true,
+ http_only => true,
+ same_site => default | none | strict | lax
+}.
+-export_type([cookie_attrs/0]).
+
+-type cookie_opts() :: #{
+ domain => binary(),
+ http_only => boolean(),
+ max_age => non_neg_integer(),
+ path => binary(),
+ same_site => default | none | strict | lax,
+ secure => boolean()
+}.
+-export_type([cookie_opts/0]).
+
+-include("cow_inline.hrl").
+
+%% Cookie header.
+
+-spec parse_cookie(binary()) -> [{binary(), binary()}].
+parse_cookie(Cookie) ->
+ parse_cookie(Cookie, []).
+
+parse_cookie(<<>>, Acc) ->
+ lists:reverse(Acc);
+parse_cookie(<< $\s, Rest/binary >>, Acc) ->
+ parse_cookie(Rest, Acc);
+parse_cookie(<< $\t, Rest/binary >>, Acc) ->
+ parse_cookie(Rest, Acc);
+parse_cookie(<< $,, Rest/binary >>, Acc) ->
+ parse_cookie(Rest, Acc);
+parse_cookie(<< $;, Rest/binary >>, Acc) ->
+ parse_cookie(Rest, Acc);
+parse_cookie(Cookie, Acc) ->
+ parse_cookie_name(Cookie, Acc, <<>>).
+
+parse_cookie_name(<<>>, Acc, Name) ->
+ lists:reverse([{<<>>, parse_cookie_trim(Name)}|Acc]);
+parse_cookie_name(<< $=, _/binary >>, _, <<>>) ->
+ error(badarg);
+parse_cookie_name(<< $=, Rest/binary >>, Acc, Name) ->
+ parse_cookie_value(Rest, Acc, Name, <<>>);
+parse_cookie_name(<< $,, _/binary >>, _, _) ->
+ error(badarg);
+parse_cookie_name(<< $;, Rest/binary >>, Acc, Name) ->
+ parse_cookie(Rest, [{<<>>, parse_cookie_trim(Name)}|Acc]);
+parse_cookie_name(<< $\t, _/binary >>, _, _) ->
+ error(badarg);
+parse_cookie_name(<< $\r, _/binary >>, _, _) ->
+ error(badarg);
+parse_cookie_name(<< $\n, _/binary >>, _, _) ->
+ error(badarg);
+parse_cookie_name(<< $\013, _/binary >>, _, _) ->
+ error(badarg);
+parse_cookie_name(<< $\014, _/binary >>, _, _) ->
+ error(badarg);
+parse_cookie_name(<< C, Rest/binary >>, Acc, Name) ->
+ parse_cookie_name(Rest, Acc, << Name/binary, C >>).
+
+parse_cookie_value(<<>>, Acc, Name, Value) ->
+ lists:reverse([{Name, parse_cookie_trim(Value)}|Acc]);
+parse_cookie_value(<< $;, Rest/binary >>, Acc, Name, Value) ->
+ parse_cookie(Rest, [{Name, parse_cookie_trim(Value)}|Acc]);
+parse_cookie_value(<< $\t, _/binary >>, _, _, _) ->
+ error(badarg);
+parse_cookie_value(<< $\r, _/binary >>, _, _, _) ->
+ error(badarg);
+parse_cookie_value(<< $\n, _/binary >>, _, _, _) ->
+ error(badarg);
+parse_cookie_value(<< $\013, _/binary >>, _, _, _) ->
+ error(badarg);
+parse_cookie_value(<< $\014, _/binary >>, _, _, _) ->
+ error(badarg);
+parse_cookie_value(<< C, Rest/binary >>, Acc, Name, Value) ->
+ parse_cookie_value(Rest, Acc, Name, << Value/binary, C >>).
+
+parse_cookie_trim(Value = <<>>) ->
+ Value;
+parse_cookie_trim(Value) ->
+ case binary:last(Value) of
+ $\s ->
+ Size = byte_size(Value) - 1,
+ << Value2:Size/binary, _ >> = Value,
+ parse_cookie_trim(Value2);
+ _ ->
+ Value
+ end.
+
+-ifdef(TEST).
+parse_cookie_test_() ->
+ %% {Value, Result}.
+ Tests = [
+ {<<"name=value; name2=value2">>, [
+ {<<"name">>, <<"value">>},
+ {<<"name2">>, <<"value2">>}
+ ]},
+ %% Space in value.
+ {<<"foo=Thu Jul 11 2013 15:38:43 GMT+0400 (MSK)">>,
+ [{<<"foo">>, <<"Thu Jul 11 2013 15:38:43 GMT+0400 (MSK)">>}]},
+ %% Comma in value. Google Analytics sets that kind of cookies.
+ {<<"refk=sOUZDzq2w2; sk=B602064E0139D842D620C7569640DBB4C81C45080651"
+ "9CC124EF794863E10E80; __utma=64249653.825741573.1380181332.1400"
+ "015657.1400019557.703; __utmb=64249653.1.10.1400019557; __utmc="
+ "64249653; __utmz=64249653.1400019557.703.13.utmcsr=bluesky.chic"
+ "agotribune.com|utmccn=(referral)|utmcmd=referral|utmcct=/origin"
+ "als/chi-12-indispensable-digital-tools-bsi,0,0.storygallery">>, [
+ {<<"refk">>, <<"sOUZDzq2w2">>},
+ {<<"sk">>, <<"B602064E0139D842D620C7569640DBB4C81C45080651"
+ "9CC124EF794863E10E80">>},
+ {<<"__utma">>, <<"64249653.825741573.1380181332.1400"
+ "015657.1400019557.703">>},
+ {<<"__utmb">>, <<"64249653.1.10.1400019557">>},
+ {<<"__utmc">>, <<"64249653">>},
+ {<<"__utmz">>, <<"64249653.1400019557.703.13.utmcsr=bluesky.chic"
+ "agotribune.com|utmccn=(referral)|utmcmd=referral|utmcct=/origin"
+ "als/chi-12-indispensable-digital-tools-bsi,0,0.storygallery">>}
+ ]},
+ %% Potential edge cases (initially from Mochiweb).
+ {<<"foo=\\x">>, [{<<"foo">>, <<"\\x">>}]},
+ {<<"foo=;bar=">>, [{<<"foo">>, <<>>}, {<<"bar">>, <<>>}]},
+ {<<"foo=\\\";;bar=good ">>,
+ [{<<"foo">>, <<"\\\"">>}, {<<"bar">>, <<"good">>}]},
+ {<<"foo=\"\\\";bar=good">>,
+ [{<<"foo">>, <<"\"\\\"">>}, {<<"bar">>, <<"good">>}]},
+ {<<>>, []}, %% Flash player.
+ {<<"foo=bar , baz=wibble ">>, [{<<"foo">>, <<"bar , baz=wibble">>}]},
+ %% Technically invalid, but seen in the wild
+ {<<"foo">>, [{<<>>, <<"foo">>}]},
+ {<<"foo ">>, [{<<>>, <<"foo">>}]},
+ {<<"foo;">>, [{<<>>, <<"foo">>}]},
+ {<<"bar;foo=1">>, [{<<>>, <<"bar">>}, {<<"foo">>, <<"1">>}]}
+ ],
+ [{V, fun() -> R = parse_cookie(V) end} || {V, R} <- Tests].
+
+parse_cookie_error_test_() ->
+ %% Value.
+ Tests = [
+ <<"=">>
+ ],
+ [{V, fun() -> {'EXIT', {badarg, _}} = (catch parse_cookie(V)) end} || V <- Tests].
+-endif.
+
+%% Set-Cookie header.
+
+-spec parse_set_cookie(binary())
+ -> {ok, binary(), binary(), cookie_attrs()}
+ | ignore.
+parse_set_cookie(SetCookie) ->
+ case has_non_ws_ctl(SetCookie) of
+ true ->
+ ignore;
+ false ->
+ {NameValuePair, UnparsedAttrs} = take_until_semicolon(SetCookie, <<>>),
+ {Name, Value} = case binary:split(NameValuePair, <<$=>>) of
+ [Value0] -> {<<>>, trim(Value0)};
+ [Name0, Value0] -> {trim(Name0), trim(Value0)}
+ end,
+ case {Name, Value} of
+ {<<>>, <<>>} ->
+ ignore;
+ _ ->
+ Attrs = parse_set_cookie_attrs(UnparsedAttrs, #{}),
+ {ok, Name, Value, Attrs}
+ end
+ end.
+
+has_non_ws_ctl(<<>>) ->
+ false;
+has_non_ws_ctl(<<C,R/bits>>) ->
+ if
+ C =< 16#08 -> true;
+ C >= 16#0A, C =< 16#1F -> true;
+ C =:= 16#7F -> true;
+ true -> has_non_ws_ctl(R)
+ end.
+
+parse_set_cookie_attrs(<<>>, Attrs) ->
+ Attrs;
+parse_set_cookie_attrs(<<$;,Rest0/bits>>, Attrs) ->
+ {Av, Rest} = take_until_semicolon(Rest0, <<>>),
+ {Name, Value} = case binary:split(Av, <<$=>>) of
+ [Name0] -> {trim(Name0), <<>>};
+ [Name0, Value0] -> {trim(Name0), trim(Value0)}
+ end,
+ if
+ byte_size(Value) > 1024 ->
+ parse_set_cookie_attrs(Rest, Attrs);
+ true ->
+ case parse_set_cookie_attr(?LOWER(Name), Value) of
+ {ok, AttrName, AttrValue} ->
+ parse_set_cookie_attrs(Rest, Attrs#{AttrName => AttrValue});
+ {ignore, AttrName} ->
+ parse_set_cookie_attrs(Rest, maps:remove(AttrName, Attrs));
+ ignore ->
+ parse_set_cookie_attrs(Rest, Attrs)
+ end
+ end.
+
+take_until_semicolon(Rest = <<$;,_/bits>>, Acc) -> {Acc, Rest};
+take_until_semicolon(<<C,R/bits>>, Acc) -> take_until_semicolon(R, <<Acc/binary,C>>);
+take_until_semicolon(<<>>, Acc) -> {Acc, <<>>}.
+
+trim(String) ->
+ string:trim(String, both, [$\s, $\t]).
+
+parse_set_cookie_attr(<<"expires">>, Value) ->
+ try cow_date:parse_date(Value) of
+ DateTime ->
+ {ok, expires, DateTime}
+ catch _:_ ->
+ ignore
+ end;
+parse_set_cookie_attr(<<"max-age">>, Value) ->
+ try binary_to_integer(Value) of
+ MaxAge when MaxAge =< 0 ->
+ %% Year 0 corresponds to 1 BC.
+ {ok, max_age, {{0, 1, 1}, {0, 0, 0}}};
+ MaxAge ->
+ CurrentTime = erlang:universaltime(),
+ {ok, max_age, calendar:gregorian_seconds_to_datetime(
+ calendar:datetime_to_gregorian_seconds(CurrentTime) + MaxAge)}
+ catch _:_ ->
+ ignore
+ end;
+parse_set_cookie_attr(<<"domain">>, Value) ->
+ case Value of
+ <<>> ->
+ ignore;
+ <<".",Rest/bits>> ->
+ {ok, domain, ?LOWER(Rest)};
+ _ ->
+ {ok, domain, ?LOWER(Value)}
+ end;
+parse_set_cookie_attr(<<"path">>, Value) ->
+ case Value of
+ <<"/",_/bits>> ->
+ {ok, path, Value};
+ %% When the path is not absolute, or the path is empty, the default-path will be used.
+ %% Note that the default-path is also used when there are no path attributes,
+ %% so we are simply ignoring the attribute here.
+ _ ->
+ {ignore, path}
+ end;
+parse_set_cookie_attr(<<"secure">>, _) ->
+ {ok, secure, true};
+parse_set_cookie_attr(<<"httponly">>, _) ->
+ {ok, http_only, true};
+parse_set_cookie_attr(<<"samesite">>, Value) ->
+ case ?LOWER(Value) of
+ <<"none">> ->
+ {ok, same_site, none};
+ <<"strict">> ->
+ {ok, same_site, strict};
+ <<"lax">> ->
+ {ok, same_site, lax};
+ %% Unknown values and lack of value are equivalent.
+ _ ->
+ {ok, same_site, default}
+ end;
+parse_set_cookie_attr(_, _) ->
+ ignore.
+
+-ifdef(TEST).
+parse_set_cookie_test_() ->
+ Tests = [
+ {<<"a=b">>, {ok, <<"a">>, <<"b">>, #{}}},
+ {<<"a=b; Secure">>, {ok, <<"a">>, <<"b">>, #{secure => true}}},
+ {<<"a=b; HttpOnly">>, {ok, <<"a">>, <<"b">>, #{http_only => true}}},
+ {<<"a=b; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Expires=Wed, 21 Oct 2015 07:29:00 GMT">>,
+ {ok, <<"a">>, <<"b">>, #{expires => {{2015,10,21},{7,29,0}}}}},
+ {<<"a=b; Max-Age=999; Max-Age=0">>,
+ {ok, <<"a">>, <<"b">>, #{max_age => {{0,1,1},{0,0,0}}}}},
+ {<<"a=b; Domain=example.org; Domain=foo.example.org">>,
+ {ok, <<"a">>, <<"b">>, #{domain => <<"foo.example.org">>}}},
+ {<<"a=b; Path=/path/to/resource; Path=/">>,
+ {ok, <<"a">>, <<"b">>, #{path => <<"/">>}}},
+ {<<"a=b; SameSite=UnknownValue">>, {ok, <<"a">>, <<"b">>, #{same_site => default}}},
+ {<<"a=b; SameSite=None">>, {ok, <<"a">>, <<"b">>, #{same_site => none}}},
+ {<<"a=b; SameSite=Lax">>, {ok, <<"a">>, <<"b">>, #{same_site => lax}}},
+ {<<"a=b; SameSite=Strict">>, {ok, <<"a">>, <<"b">>, #{same_site => strict}}},
+ {<<"a=b; SameSite=Lax; SameSite=Strict">>,
+ {ok, <<"a">>, <<"b">>, #{same_site => strict}}}
+ ],
+ [{SetCookie, fun() -> Res = parse_set_cookie(SetCookie) end}
+ || {SetCookie, Res} <- Tests].
+-endif.
+
+%% Build a cookie header.
+
+-spec cookie([{iodata(), iodata()}]) -> iolist().
+cookie([]) ->
+ [];
+cookie([{<<>>, Value}]) ->
+ [Value];
+cookie([{Name, Value}]) ->
+ [Name, $=, Value];
+cookie([{<<>>, Value}|Tail]) ->
+ [Value, $;, $\s|cookie(Tail)];
+cookie([{Name, Value}|Tail]) ->
+ [Name, $=, Value, $;, $\s|cookie(Tail)].
+
+-ifdef(TEST).
+cookie_test_() ->
+ Tests = [
+ {[], <<>>},
+ {[{<<"a">>, <<"b">>}], <<"a=b">>},
+ {[{<<"a">>, <<"b">>}, {<<"c">>, <<"d">>}], <<"a=b; c=d">>},
+ {[{<<>>, <<"b">>}, {<<"c">>, <<"d">>}], <<"b; c=d">>},
+ {[{<<"a">>, <<"b">>}, {<<>>, <<"d">>}], <<"a=b; d">>}
+ ],
+ [{Res, fun() -> Res = iolist_to_binary(cookie(Cookies)) end}
+ || {Cookies, Res} <- Tests].
+-endif.
+
+%% Convert a cookie name, value and options to its iodata form.
+%%
+%% Initially from Mochiweb:
+%% * Copyright 2007 Mochi Media, Inc.
+%% Initial binary implementation:
+%% * Copyright 2011 Thomas Burdick <thomas.burdick@gmail.com>
+%%
+%% @todo Rename the function to set_cookie eventually.
+
+-spec setcookie(iodata(), iodata(), cookie_opts()) -> iolist().
+setcookie(Name, Value, Opts) ->
+ nomatch = binary:match(iolist_to_binary(Name), [<<$=>>, <<$,>>, <<$;>>,
+ <<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]),
+ nomatch = binary:match(iolist_to_binary(Value), [<<$,>>, <<$;>>,
+ <<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]),
+ [Name, <<"=">>, Value, attributes(maps:to_list(Opts))].
+
+attributes([]) -> [];
+attributes([{domain, Domain}|Tail]) -> [<<"; Domain=">>, Domain|attributes(Tail)];
+attributes([{http_only, false}|Tail]) -> attributes(Tail);
+attributes([{http_only, true}|Tail]) -> [<<"; HttpOnly">>|attributes(Tail)];
+%% MSIE requires an Expires date in the past to delete a cookie.
+attributes([{max_age, 0}|Tail]) ->
+ [<<"; Expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0">>|attributes(Tail)];
+attributes([{max_age, MaxAge}|Tail]) when is_integer(MaxAge), MaxAge > 0 ->
+ Secs = calendar:datetime_to_gregorian_seconds(calendar:universal_time()),
+ Expires = cow_date:rfc2109(calendar:gregorian_seconds_to_datetime(Secs + MaxAge)),
+ [<<"; Expires=">>, Expires, <<"; Max-Age=">>, integer_to_list(MaxAge)|attributes(Tail)];
+attributes([Opt={max_age, _}|_]) ->
+ error({badarg, Opt});
+attributes([{path, Path}|Tail]) -> [<<"; Path=">>, Path|attributes(Tail)];
+attributes([{secure, false}|Tail]) -> attributes(Tail);
+attributes([{secure, true}|Tail]) -> [<<"; Secure">>|attributes(Tail)];
+attributes([{same_site, default}|Tail]) -> attributes(Tail);
+attributes([{same_site, none}|Tail]) -> [<<"; SameSite=None">>|attributes(Tail)];
+attributes([{same_site, lax}|Tail]) -> [<<"; SameSite=Lax">>|attributes(Tail)];
+attributes([{same_site, strict}|Tail]) -> [<<"; SameSite=Strict">>|attributes(Tail)];
+%% Skip unknown options.
+attributes([_|Tail]) -> attributes(Tail).
+
+-ifdef(TEST).
+setcookie_test_() ->
+ %% {Name, Value, Opts, Result}
+ Tests = [
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{http_only => true, domain => <<"acme.com">>},
+ <<"Customer=WILE_E_COYOTE; "
+ "Domain=acme.com; HttpOnly">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{path => <<"/acme">>},
+ <<"Customer=WILE_E_COYOTE; Path=/acme">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{secure => true},
+ <<"Customer=WILE_E_COYOTE; Secure">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{secure => false, http_only => false},
+ <<"Customer=WILE_E_COYOTE">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{same_site => default},
+ <<"Customer=WILE_E_COYOTE">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{same_site => none},
+ <<"Customer=WILE_E_COYOTE; SameSite=None">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{same_site => lax},
+ <<"Customer=WILE_E_COYOTE; SameSite=Lax">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{same_site => strict},
+ <<"Customer=WILE_E_COYOTE; SameSite=Strict">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{path => <<"/acme">>, badoption => <<"negatory">>},
+ <<"Customer=WILE_E_COYOTE; Path=/acme">>}
+ ],
+ [{R, fun() -> R = iolist_to_binary(setcookie(N, V, O)) end}
+ || {N, V, O, R} <- Tests].
+
+setcookie_max_age_test() ->
+ F = fun(N, V, O) ->
+ binary:split(iolist_to_binary(
+ setcookie(N, V, O)), <<";">>, [global])
+ end,
+ [<<"Customer=WILE_E_COYOTE">>,
+ <<" Expires=", _/binary>>,
+ <<" Max-Age=111">>,
+ <<" Secure">>] = F(<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{max_age => 111, secure => true}),
+ case catch F(<<"Customer">>, <<"WILE_E_COYOTE">>, #{max_age => -111}) of
+ {'EXIT', {{badarg, {max_age, -111}}, _}} -> ok
+ end,
+ [<<"Customer=WILE_E_COYOTE">>,
+ <<" Expires=", _/binary>>,
+ <<" Max-Age=86417">>] = F(<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{max_age => 86417}),
+ ok.
+
+setcookie_failures_test_() ->
+ F = fun(N, V) ->
+ try setcookie(N, V, #{}) of
+ _ ->
+ false
+ catch _:_ ->
+ true
+ end
+ end,
+ Tests = [
+ {<<"Na=me">>, <<"Value">>},
+ {<<"Name;">>, <<"Value">>},
+ {<<"\r\name">>, <<"Value">>},
+ {<<"Name">>, <<"Value;">>},
+ {<<"Name">>, <<"\value">>}
+ ],
+ [{iolist_to_binary(io_lib:format("{~p, ~p} failure", [N, V])),
+ fun() -> true = F(N, V) end}
+ || {N, V} <- Tests].
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_date.erl b/server/_build/default/lib/cowlib/src/cow_date.erl
new file mode 100644
index 0000000..00bc8af
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_date.erl
@@ -0,0 +1,434 @@
+%% Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_date).
+
+-export([parse_date/1]).
+-export([rfc1123/1]).
+-export([rfc2109/1]).
+-export([rfc7231/1]).
+
+-ifdef(TEST).
+-include_lib("proper/include/proper.hrl").
+-endif.
+
+%% @doc Parse the HTTP date (IMF-fixdate, rfc850, asctime).
+
+-define(DIGITS(A, B), ((A - $0) * 10 + (B - $0))).
+-define(DIGITS(A, B, C, D), ((A - $0) * 1000 + (B - $0) * 100 + (C - $0) * 10 + (D - $0))).
+
+-spec parse_date(binary()) -> calendar:datetime().
+parse_date(DateBin) ->
+ Date = {{_, _, D}, {H, M, S}} = http_date(DateBin),
+ true = D >= 0 andalso D =< 31,
+ true = H >= 0 andalso H =< 23,
+ true = M >= 0 andalso M =< 59,
+ true = S >= 0 andalso S =< 60, %% Leap second.
+ Date.
+
+http_date(<<"Mon, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2));
+http_date(<<"Tue, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2));
+http_date(<<"Wed, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2));
+http_date(<<"Thu, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2));
+http_date(<<"Fri, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2));
+http_date(<<"Sat, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2));
+http_date(<<"Sun, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2));
+http_date(<<"Monday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2));
+http_date(<<"Tuesday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2));
+http_date(<<"Wednesday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2));
+http_date(<<"Thursday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2));
+http_date(<<"Friday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2));
+http_date(<<"Saturday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2));
+http_date(<<"Sunday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2));
+http_date(<<"Mon ", R/bits >>) -> asctime_date(R);
+http_date(<<"Tue ", R/bits >>) -> asctime_date(R);
+http_date(<<"Wed ", R/bits >>) -> asctime_date(R);
+http_date(<<"Thu ", R/bits >>) -> asctime_date(R);
+http_date(<<"Fri ", R/bits >>) -> asctime_date(R);
+http_date(<<"Sat ", R/bits >>) -> asctime_date(R);
+http_date(<<"Sun ", R/bits >>) -> asctime_date(R).
+
+fixdate(<<"Jan ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 1, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Feb ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 2, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Mar ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 3, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Apr ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 4, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"May ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 5, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Jun ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 6, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Jul ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 7, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Aug ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 8, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Sep ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 9, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Oct ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 10, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Nov ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 11, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Dec ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 12, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}.
+
+rfc850_date(<<"Jan-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 1, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Feb-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 2, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Mar-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 3, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Apr-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 4, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"May-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 5, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Jun-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 6, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Jul-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 7, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Aug-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 8, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Sep-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 9, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Oct-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 10, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Nov-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 11, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Dec-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 12, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}.
+
+rfc850_year(Y) when Y > 50 -> Y + 1900;
+rfc850_year(Y) -> Y + 2000.
+
+asctime_date(<<"Jan ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 1, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Feb ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 2, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Mar ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 3, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Apr ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 4, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"May ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 5, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Jun ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 6, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Jul ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 7, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Aug ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 8, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Sep ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 9, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Oct ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 10, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Nov ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 11, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Dec ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 12, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}.
+
+asctime_day($\s, D2) -> (D2 - $0);
+asctime_day(D1, D2) -> (D1 - $0) * 10 + (D2 - $0).
+
+-ifdef(TEST).
+day_name() -> oneof(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]).
+day_name_l() -> oneof(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]).
+year() -> integer(1951, 2050).
+month() -> integer(1, 12).
+day() -> integer(1, 31).
+hour() -> integer(0, 23).
+minute() -> integer(0, 59).
+second() -> integer(0, 60).
+
+fixdate_gen() ->
+ ?LET({DayName, Y, Mo, D, H, Mi, S},
+ {day_name(), year(), month(), day(), hour(), minute(), second()},
+ {{{Y, Mo, D}, {H, Mi, S}},
+ list_to_binary([DayName, ", ", pad_int(D), " ", month(Mo), " ", integer_to_binary(Y),
+ " ", pad_int(H), ":", pad_int(Mi), ":", pad_int(S), " GMT"])}).
+
+rfc850_gen() ->
+ ?LET({DayName, Y, Mo, D, H, Mi, S},
+ {day_name_l(), year(), month(), day(), hour(), minute(), second()},
+ {{{Y, Mo, D}, {H, Mi, S}},
+ list_to_binary([DayName, ", ", pad_int(D), "-", month(Mo), "-", pad_int(Y rem 100),
+ " ", pad_int(H), ":", pad_int(Mi), ":", pad_int(S), " GMT"])}).
+
+asctime_gen() ->
+ ?LET({DayName, Y, Mo, D, H, Mi, S},
+ {day_name(), year(), month(), day(), hour(), minute(), second()},
+ {{{Y, Mo, D}, {H, Mi, S}},
+ list_to_binary([DayName, " ", month(Mo), " ",
+ if D < 10 -> << $\s, (D + $0) >>; true -> integer_to_binary(D) end,
+ " ", pad_int(H), ":", pad_int(Mi), ":", pad_int(S), " ", integer_to_binary(Y)])}).
+
+prop_http_date() ->
+ ?FORALL({Date, DateBin},
+ oneof([fixdate_gen(), rfc850_gen(), asctime_gen()]),
+ Date =:= parse_date(DateBin)).
+
+http_date_test_() ->
+ Tests = [
+ {<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}},
+ {<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}},
+ {<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}}
+ ],
+ [{V, fun() -> R = http_date(V) end} || {V, R} <- Tests].
+
+horse_http_date_fixdate() ->
+ horse:repeat(200000,
+ http_date(<<"Sun, 06 Nov 1994 08:49:37 GMT">>)
+ ).
+
+horse_http_date_rfc850() ->
+ horse:repeat(200000,
+ http_date(<<"Sunday, 06-Nov-94 08:49:37 GMT">>)
+ ).
+
+horse_http_date_asctime() ->
+ horse:repeat(200000,
+ http_date(<<"Sun Nov 6 08:49:37 1994">>)
+ ).
+-endif.
+
+%% @doc Return the date formatted according to RFC1123.
+
+-spec rfc1123(calendar:datetime()) -> binary().
+rfc1123(DateTime) ->
+ rfc7231(DateTime).
+
+%% @doc Return the date formatted according to RFC2109.
+
+-spec rfc2109(calendar:datetime()) -> binary().
+rfc2109({Date = {Y, Mo, D}, {H, Mi, S}}) ->
+ Wday = calendar:day_of_the_week(Date),
+ << (weekday(Wday))/binary, ", ",
+ (pad_int(D))/binary, "-",
+ (month(Mo))/binary, "-",
+ (year(Y))/binary, " ",
+ (pad_int(H))/binary, ":",
+ (pad_int(Mi))/binary, ":",
+ (pad_int(S))/binary, " GMT" >>.
+
+-ifdef(TEST).
+rfc2109_test_() ->
+ Tests = [
+ {<<"Sat, 14-May-2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}}},
+ {<<"Sun, 01-Jan-2012 00:00:00 GMT">>, {{2012, 1, 1}, { 0, 0, 0}}}
+ ],
+ [{R, fun() -> R = rfc2109(D) end} || {R, D} <- Tests].
+
+horse_rfc2109_20130101_000000() ->
+ horse:repeat(100000,
+ rfc2109({{2013, 1, 1}, {0, 0, 0}})
+ ).
+
+horse_rfc2109_20131231_235959() ->
+ horse:repeat(100000,
+ rfc2109({{2013, 12, 31}, {23, 59, 59}})
+ ).
+
+horse_rfc2109_12340506_070809() ->
+ horse:repeat(100000,
+ rfc2109({{1234, 5, 6}, {7, 8, 9}})
+ ).
+-endif.
+
+%% @doc Return the date formatted according to RFC7231.
+
+-spec rfc7231(calendar:datetime()) -> binary().
+rfc7231({Date = {Y, Mo, D}, {H, Mi, S}}) ->
+ Wday = calendar:day_of_the_week(Date),
+ << (weekday(Wday))/binary, ", ",
+ (pad_int(D))/binary, " ",
+ (month(Mo))/binary, " ",
+ (year(Y))/binary, " ",
+ (pad_int(H))/binary, ":",
+ (pad_int(Mi))/binary, ":",
+ (pad_int(S))/binary, " GMT" >>.
+
+-ifdef(TEST).
+rfc7231_test_() ->
+ Tests = [
+ {<<"Sat, 14 May 2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}}},
+ {<<"Sun, 01 Jan 2012 00:00:00 GMT">>, {{2012, 1, 1}, { 0, 0, 0}}}
+ ],
+ [{R, fun() -> R = rfc7231(D) end} || {R, D} <- Tests].
+
+horse_rfc7231_20130101_000000() ->
+ horse:repeat(100000,
+ rfc7231({{2013, 1, 1}, {0, 0, 0}})
+ ).
+
+horse_rfc7231_20131231_235959() ->
+ horse:repeat(100000,
+ rfc7231({{2013, 12, 31}, {23, 59, 59}})
+ ).
+
+horse_rfc7231_12340506_070809() ->
+ horse:repeat(100000,
+ rfc7231({{1234, 5, 6}, {7, 8, 9}})
+ ).
+-endif.
+
+%% Internal.
+
+-spec pad_int(0..59) -> <<_:16>>.
+pad_int( 0) -> <<"00">>;
+pad_int( 1) -> <<"01">>;
+pad_int( 2) -> <<"02">>;
+pad_int( 3) -> <<"03">>;
+pad_int( 4) -> <<"04">>;
+pad_int( 5) -> <<"05">>;
+pad_int( 6) -> <<"06">>;
+pad_int( 7) -> <<"07">>;
+pad_int( 8) -> <<"08">>;
+pad_int( 9) -> <<"09">>;
+pad_int(10) -> <<"10">>;
+pad_int(11) -> <<"11">>;
+pad_int(12) -> <<"12">>;
+pad_int(13) -> <<"13">>;
+pad_int(14) -> <<"14">>;
+pad_int(15) -> <<"15">>;
+pad_int(16) -> <<"16">>;
+pad_int(17) -> <<"17">>;
+pad_int(18) -> <<"18">>;
+pad_int(19) -> <<"19">>;
+pad_int(20) -> <<"20">>;
+pad_int(21) -> <<"21">>;
+pad_int(22) -> <<"22">>;
+pad_int(23) -> <<"23">>;
+pad_int(24) -> <<"24">>;
+pad_int(25) -> <<"25">>;
+pad_int(26) -> <<"26">>;
+pad_int(27) -> <<"27">>;
+pad_int(28) -> <<"28">>;
+pad_int(29) -> <<"29">>;
+pad_int(30) -> <<"30">>;
+pad_int(31) -> <<"31">>;
+pad_int(32) -> <<"32">>;
+pad_int(33) -> <<"33">>;
+pad_int(34) -> <<"34">>;
+pad_int(35) -> <<"35">>;
+pad_int(36) -> <<"36">>;
+pad_int(37) -> <<"37">>;
+pad_int(38) -> <<"38">>;
+pad_int(39) -> <<"39">>;
+pad_int(40) -> <<"40">>;
+pad_int(41) -> <<"41">>;
+pad_int(42) -> <<"42">>;
+pad_int(43) -> <<"43">>;
+pad_int(44) -> <<"44">>;
+pad_int(45) -> <<"45">>;
+pad_int(46) -> <<"46">>;
+pad_int(47) -> <<"47">>;
+pad_int(48) -> <<"48">>;
+pad_int(49) -> <<"49">>;
+pad_int(50) -> <<"50">>;
+pad_int(51) -> <<"51">>;
+pad_int(52) -> <<"52">>;
+pad_int(53) -> <<"53">>;
+pad_int(54) -> <<"54">>;
+pad_int(55) -> <<"55">>;
+pad_int(56) -> <<"56">>;
+pad_int(57) -> <<"57">>;
+pad_int(58) -> <<"58">>;
+pad_int(59) -> <<"59">>;
+pad_int(60) -> <<"60">>;
+pad_int(Int) -> integer_to_binary(Int).
+
+-spec weekday(1..7) -> <<_:24>>.
+weekday(1) -> <<"Mon">>;
+weekday(2) -> <<"Tue">>;
+weekday(3) -> <<"Wed">>;
+weekday(4) -> <<"Thu">>;
+weekday(5) -> <<"Fri">>;
+weekday(6) -> <<"Sat">>;
+weekday(7) -> <<"Sun">>.
+
+-spec month(1..12) -> <<_:24>>.
+month( 1) -> <<"Jan">>;
+month( 2) -> <<"Feb">>;
+month( 3) -> <<"Mar">>;
+month( 4) -> <<"Apr">>;
+month( 5) -> <<"May">>;
+month( 6) -> <<"Jun">>;
+month( 7) -> <<"Jul">>;
+month( 8) -> <<"Aug">>;
+month( 9) -> <<"Sep">>;
+month(10) -> <<"Oct">>;
+month(11) -> <<"Nov">>;
+month(12) -> <<"Dec">>.
+
+-spec year(pos_integer()) -> <<_:32>>.
+year(1970) -> <<"1970">>;
+year(1971) -> <<"1971">>;
+year(1972) -> <<"1972">>;
+year(1973) -> <<"1973">>;
+year(1974) -> <<"1974">>;
+year(1975) -> <<"1975">>;
+year(1976) -> <<"1976">>;
+year(1977) -> <<"1977">>;
+year(1978) -> <<"1978">>;
+year(1979) -> <<"1979">>;
+year(1980) -> <<"1980">>;
+year(1981) -> <<"1981">>;
+year(1982) -> <<"1982">>;
+year(1983) -> <<"1983">>;
+year(1984) -> <<"1984">>;
+year(1985) -> <<"1985">>;
+year(1986) -> <<"1986">>;
+year(1987) -> <<"1987">>;
+year(1988) -> <<"1988">>;
+year(1989) -> <<"1989">>;
+year(1990) -> <<"1990">>;
+year(1991) -> <<"1991">>;
+year(1992) -> <<"1992">>;
+year(1993) -> <<"1993">>;
+year(1994) -> <<"1994">>;
+year(1995) -> <<"1995">>;
+year(1996) -> <<"1996">>;
+year(1997) -> <<"1997">>;
+year(1998) -> <<"1998">>;
+year(1999) -> <<"1999">>;
+year(2000) -> <<"2000">>;
+year(2001) -> <<"2001">>;
+year(2002) -> <<"2002">>;
+year(2003) -> <<"2003">>;
+year(2004) -> <<"2004">>;
+year(2005) -> <<"2005">>;
+year(2006) -> <<"2006">>;
+year(2007) -> <<"2007">>;
+year(2008) -> <<"2008">>;
+year(2009) -> <<"2009">>;
+year(2010) -> <<"2010">>;
+year(2011) -> <<"2011">>;
+year(2012) -> <<"2012">>;
+year(2013) -> <<"2013">>;
+year(2014) -> <<"2014">>;
+year(2015) -> <<"2015">>;
+year(2016) -> <<"2016">>;
+year(2017) -> <<"2017">>;
+year(2018) -> <<"2018">>;
+year(2019) -> <<"2019">>;
+year(2020) -> <<"2020">>;
+year(2021) -> <<"2021">>;
+year(2022) -> <<"2022">>;
+year(2023) -> <<"2023">>;
+year(2024) -> <<"2024">>;
+year(2025) -> <<"2025">>;
+year(2026) -> <<"2026">>;
+year(2027) -> <<"2027">>;
+year(2028) -> <<"2028">>;
+year(2029) -> <<"2029">>;
+year(Year) -> integer_to_binary(Year).
diff --git a/server/_build/default/lib/cowlib/src/cow_hpack.erl b/server/_build/default/lib/cowlib/src/cow_hpack.erl
new file mode 100644
index 0000000..d7ae475
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_hpack.erl
@@ -0,0 +1,1449 @@
+%% Copyright (c) 2015-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% The current implementation is not suitable for use in
+%% intermediaries as the information about headers that
+%% should never be indexed is currently lost.
+
+-module(cow_hpack).
+-dialyzer(no_improper_lists).
+
+-export([init/0]).
+-export([init/1]).
+-export([set_max_size/2]).
+
+-export([decode/1]).
+-export([decode/2]).
+
+-export([encode/1]).
+-export([encode/2]).
+-export([encode/3]).
+
+-record(state, {
+ size = 0 :: non_neg_integer(),
+ max_size = 4096 :: non_neg_integer(),
+ configured_max_size = 4096 :: non_neg_integer(),
+ dyn_table = [] :: [{pos_integer(), {binary(), binary()}}]
+}).
+
+-opaque state() :: #state{}.
+-export_type([state/0]).
+
+-type opts() :: map().
+-export_type([opts/0]).
+
+-ifdef(TEST).
+-include_lib("proper/include/proper.hrl").
+-endif.
+
+%% State initialization.
+
+-spec init() -> state().
+init() ->
+ #state{}.
+
+-spec init(non_neg_integer()) -> state().
+init(MaxSize) ->
+ #state{max_size=MaxSize, configured_max_size=MaxSize}.
+
+%% Update the configured max size.
+%%
+%% When decoding, the local endpoint also needs to send a SETTINGS
+%% frame with this value and it is then up to the remote endpoint
+%% to decide what actual limit it will use. The actual limit is
+%% signaled via dynamic table size updates in the encoded data.
+%%
+%% When encoding, the local endpoint will call this function after
+%% receiving a SETTINGS frame with this value. The encoder will
+%% then use this value as the new max after signaling via a dynamic
+%% table size update. The value given as argument may be lower
+%% than the one received in the SETTINGS.
+
+-spec set_max_size(non_neg_integer(), State) -> State when State::state().
+set_max_size(MaxSize, State) ->
+ State#state{configured_max_size=MaxSize}.
+
+%% Decoding.
+
+-spec decode(binary()) -> {cow_http:headers(), state()}.
+decode(Data) ->
+ decode(Data, init()).
+
+-spec decode(binary(), State) -> {cow_http:headers(), State} when State::state().
+%% Dynamic table size update is only allowed at the beginning of a HEADERS block.
+decode(<< 0:2, 1:1, Rest/bits >>, State=#state{configured_max_size=ConfigMaxSize}) ->
+ {MaxSize, Rest2} = dec_int5(Rest),
+ if
+ MaxSize =< ConfigMaxSize ->
+ State2 = table_update_size(MaxSize, State),
+ decode(Rest2, State2)
+ end;
+decode(Data, State) ->
+ decode(Data, State, []).
+
+decode(<<>>, State, Acc) ->
+ {lists:reverse(Acc), State};
+%% Indexed header field representation.
+decode(<< 1:1, Rest/bits >>, State, Acc) ->
+ dec_indexed(Rest, State, Acc);
+%% Literal header field with incremental indexing: new name.
+decode(<< 0:1, 1:1, 0:6, Rest/bits >>, State, Acc) ->
+ dec_lit_index_new_name(Rest, State, Acc);
+%% Literal header field with incremental indexing: indexed name.
+decode(<< 0:1, 1:1, Rest/bits >>, State, Acc) ->
+ dec_lit_index_indexed_name(Rest, State, Acc);
+%% Literal header field without indexing: new name.
+decode(<< 0:8, Rest/bits >>, State, Acc) ->
+ dec_lit_no_index_new_name(Rest, State, Acc);
+%% Literal header field without indexing: indexed name.
+decode(<< 0:4, Rest/bits >>, State, Acc) ->
+ dec_lit_no_index_indexed_name(Rest, State, Acc);
+%% Literal header field never indexed: new name.
+%% @todo Keep track of "never indexed" headers.
+decode(<< 0:3, 1:1, 0:4, Rest/bits >>, State, Acc) ->
+ dec_lit_no_index_new_name(Rest, State, Acc);
+%% Literal header field never indexed: indexed name.
+%% @todo Keep track of "never indexed" headers.
+decode(<< 0:3, 1:1, Rest/bits >>, State, Acc) ->
+ dec_lit_no_index_indexed_name(Rest, State, Acc).
+
+%% Indexed header field representation.
+
+%% We do the integer decoding inline where appropriate, falling
+%% back to dec_big_int for larger values.
+dec_indexed(<<2#1111111:7, 0:1, Int:7, Rest/bits>>, State, Acc) ->
+ {Name, Value} = table_get(127 + Int, State),
+ decode(Rest, State, [{Name, Value}|Acc]);
+dec_indexed(<<2#1111111:7, Rest0/bits>>, State, Acc) ->
+ {Index, Rest} = dec_big_int(Rest0, 127, 0),
+ {Name, Value} = table_get(Index, State),
+ decode(Rest, State, [{Name, Value}|Acc]);
+dec_indexed(<<Index:7, Rest/bits>>, State, Acc) ->
+ {Name, Value} = table_get(Index, State),
+ decode(Rest, State, [{Name, Value}|Acc]).
+
+%% Literal header field with incremental indexing.
+
+dec_lit_index_new_name(Rest, State, Acc) ->
+ {Name, Rest2} = dec_str(Rest),
+ dec_lit_index(Rest2, State, Acc, Name).
+
+%% We do the integer decoding inline where appropriate, falling
+%% back to dec_big_int for larger values.
+dec_lit_index_indexed_name(<<2#111111:6, 0:1, Int:7, Rest/bits>>, State, Acc) ->
+ Name = table_get_name(63 + Int, State),
+ dec_lit_index(Rest, State, Acc, Name);
+dec_lit_index_indexed_name(<<2#111111:6, Rest0/bits>>, State, Acc) ->
+ {Index, Rest} = dec_big_int(Rest0, 63, 0),
+ Name = table_get_name(Index, State),
+ dec_lit_index(Rest, State, Acc, Name);
+dec_lit_index_indexed_name(<<Index:6, Rest/bits>>, State, Acc) ->
+ Name = table_get_name(Index, State),
+ dec_lit_index(Rest, State, Acc, Name).
+
+dec_lit_index(Rest, State, Acc, Name) ->
+ {Value, Rest2} = dec_str(Rest),
+ State2 = table_insert({Name, Value}, State),
+ decode(Rest2, State2, [{Name, Value}|Acc]).
+
+%% Literal header field without indexing.
+
+dec_lit_no_index_new_name(Rest, State, Acc) ->
+ {Name, Rest2} = dec_str(Rest),
+ dec_lit_no_index(Rest2, State, Acc, Name).
+
+%% We do the integer decoding inline where appropriate, falling
+%% back to dec_big_int for larger values.
+dec_lit_no_index_indexed_name(<<2#1111:4, 0:1, Int:7, Rest/bits>>, State, Acc) ->
+ Name = table_get_name(15 + Int, State),
+ dec_lit_no_index(Rest, State, Acc, Name);
+dec_lit_no_index_indexed_name(<<2#1111:4, Rest0/bits>>, State, Acc) ->
+ {Index, Rest} = dec_big_int(Rest0, 15, 0),
+ Name = table_get_name(Index, State),
+ dec_lit_no_index(Rest, State, Acc, Name);
+dec_lit_no_index_indexed_name(<<Index:4, Rest/bits>>, State, Acc) ->
+ Name = table_get_name(Index, State),
+ dec_lit_no_index(Rest, State, Acc, Name).
+
+dec_lit_no_index(Rest, State, Acc, Name) ->
+ {Value, Rest2} = dec_str(Rest),
+ decode(Rest2, State, [{Name, Value}|Acc]).
+
+%% @todo Literal header field never indexed.
+
+%% Decode an integer.
+
+%% The HPACK format has 4 different integer prefixes length (from 4 to 7)
+%% and each can be used to create an indefinite length integer if all bits
+%% of the prefix are set to 1.
+
+dec_int5(<< 2#11111:5, Rest/bits >>) ->
+ dec_big_int(Rest, 31, 0);
+dec_int5(<< Int:5, Rest/bits >>) ->
+ {Int, Rest}.
+
+dec_big_int(<< 0:1, Value:7, Rest/bits >>, Int, M) ->
+ {Int + (Value bsl M), Rest};
+dec_big_int(<< 1:1, Value:7, Rest/bits >>, Int, M) ->
+ dec_big_int(Rest, Int + (Value bsl M), M + 7).
+
+%% Decode a string.
+
+dec_str(<<0:1, 2#1111111:7, Rest0/bits>>) ->
+ {Length, Rest1} = dec_big_int(Rest0, 127, 0),
+ <<Str:Length/binary, Rest/bits>> = Rest1,
+ {Str, Rest};
+dec_str(<<0:1, Length:7, Rest0/bits>>) ->
+ <<Str:Length/binary, Rest/bits>> = Rest0,
+ {Str, Rest};
+dec_str(<<1:1, 2#1111111:7, Rest0/bits>>) ->
+ {Length, Rest} = dec_big_int(Rest0, 127, 0),
+ dec_huffman(Rest, Length, 0, <<>>);
+dec_str(<<1:1, Length:7, Rest/bits>>) ->
+ dec_huffman(Rest, Length, 0, <<>>).
+
+%% We use a lookup table that allows us to benefit from
+%% the binary match context optimization. A more naive
+%% implementation using bit pattern matching cannot reuse
+%% a match context because it wouldn't always match on
+%% byte boundaries.
+%%
+%% See cow_hpack_dec_huffman_lookup.hrl for more details.
+
+dec_huffman(<<A:4, B:4, R/bits>>, Len, Huff0, Acc) when Len > 1 ->
+ {_, CharA, Huff1} = dec_huffman_lookup(Huff0, A),
+ {_, CharB, Huff} = dec_huffman_lookup(Huff1, B),
+ case {CharA, CharB} of
+ {undefined, undefined} -> dec_huffman(R, Len - 1, Huff, Acc);
+ {CharA, undefined} -> dec_huffman(R, Len - 1, Huff, <<Acc/binary, CharA>>);
+ {undefined, CharB} -> dec_huffman(R, Len - 1, Huff, <<Acc/binary, CharB>>);
+ {CharA, CharB} -> dec_huffman(R, Len - 1, Huff, <<Acc/binary, CharA, CharB>>)
+ end;
+dec_huffman(<<A:4, B:4, Rest/bits>>, 1, Huff0, Acc) ->
+ {_, CharA, Huff} = dec_huffman_lookup(Huff0, A),
+ {ok, CharB, _} = dec_huffman_lookup(Huff, B),
+ case {CharA, CharB} of
+ %% {undefined, undefined} (> 7-bit final padding) is rejected with a crash.
+ {CharA, undefined} ->
+ {<<Acc/binary, CharA>>, Rest};
+ {undefined, CharB} ->
+ {<<Acc/binary, CharB>>, Rest};
+ _ ->
+ {<<Acc/binary, CharA, CharB>>, Rest}
+ end;
+%% Can only be reached when the string length to decode is 0.
+dec_huffman(Rest, 0, _, <<>>) ->
+ {<<>>, Rest}.
+
+-include("cow_hpack_dec_huffman_lookup.hrl").
+
+-ifdef(TEST).
+%% Test case extracted from h2spec.
+decode_reject_eos_test() ->
+ {'EXIT', _} = (catch decode(<<16#0085f2b24a84ff874951fffffffa7f:120>>)),
+ ok.
+
+req_decode_test() ->
+ %% First request (raw then huffman).
+ {Headers1, State1} = decode(<< 16#828684410f7777772e6578616d706c652e636f6d:160 >>),
+ {Headers1, State1} = decode(<< 16#828684418cf1e3c2e5f23a6ba0ab90f4ff:136 >>),
+ Headers1 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>}
+ ],
+ #state{size=57, dyn_table=[{57,{<<":authority">>, <<"www.example.com">>}}]} = State1,
+ %% Second request (raw then huffman).
+ {Headers2, State2} = decode(<< 16#828684be58086e6f2d6361636865:112 >>, State1),
+ {Headers2, State2} = decode(<< 16#828684be5886a8eb10649cbf:96 >>, State1),
+ Headers2 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"cache-control">>, <<"no-cache">>}
+ ],
+ #state{size=110, dyn_table=[
+ {53,{<<"cache-control">>, <<"no-cache">>}},
+ {57,{<<":authority">>, <<"www.example.com">>}}]} = State2,
+ %% Third request (raw then huffman).
+ {Headers3, State3} = decode(<< 16#828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565:232 >>, State2),
+ {Headers3, State3} = decode(<< 16#828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf:192 >>, State2),
+ Headers3 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"https">>},
+ {<<":path">>, <<"/index.html">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"custom-key">>, <<"custom-value">>}
+ ],
+ #state{size=164, dyn_table=[
+ {54,{<<"custom-key">>, <<"custom-value">>}},
+ {53,{<<"cache-control">>, <<"no-cache">>}},
+ {57,{<<":authority">>, <<"www.example.com">>}}]} = State3,
+ ok.
+
+resp_decode_test() ->
+ %% Use a max_size of 256 to trigger header evictions.
+ State0 = init(256),
+ %% First response (raw then huffman).
+ {Headers1, State1} = decode(<< 16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560 >>, State0),
+ {Headers1, State1} = decode(<< 16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432 >>, State0),
+ Headers1 = [
+ {<<":status">>, <<"302">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=222, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State1,
+ %% Second response (raw then huffman).
+ {Headers2, State2} = decode(<< 16#4803333037c1c0bf:64 >>, State1),
+ {Headers2, State2} = decode(<< 16#4883640effc1c0bf:64 >>, State1),
+ Headers2 = [
+ {<<":status">>, <<"307">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=222, dyn_table=[
+ {42,{<<":status">>, <<"307">>}},
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}}]} = State2,
+ %% Third response (raw then huffman).
+ {Headers3, State3} = decode(<< 16#88c1611d4d6f6e2c203231204f637420323031332032303a31333a323220474d54c05a04677a69707738666f6f3d4153444a4b48514b425a584f5157454f50495541585157454f49553b206d61782d6167653d333630303b2076657273696f6e3d31:784 >>, State2),
+ {Headers3, State3} = decode(<< 16#88c16196d07abe941054d444a8200595040b8166e084a62d1bffc05a839bd9ab77ad94e7821dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab270fb5291f9587316065c003ed4ee5b1063d5007:632 >>, State2),
+ Headers3 = [
+ {<<":status">>, <<"200">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:22 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>},
+ {<<"content-encoding">>, <<"gzip">>},
+ {<<"set-cookie">>, <<"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1">>}
+ ],
+ #state{size=215, dyn_table=[
+ {98,{<<"set-cookie">>, <<"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1">>}},
+ {52,{<<"content-encoding">>, <<"gzip">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:22 GMT">>}}]} = State3,
+ ok.
+
+table_update_decode_test() ->
+ %% Use a max_size of 256 to trigger header evictions
+ %% when the code is not updating the max size.
+ State0 = init(256),
+ %% First response (raw then huffman).
+ {Headers1, State1} = decode(<< 16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560 >>, State0),
+ {Headers1, State1} = decode(<< 16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432 >>, State0),
+ Headers1 = [
+ {<<":status">>, <<"302">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=222, configured_max_size=256, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State1,
+ %% Set a new configured max_size to avoid header evictions.
+ State2 = set_max_size(512, State1),
+ %% Second response with the table size update (raw then huffman).
+ MaxSize = enc_big_int(512 - 31, <<>>),
+ {Headers2, State3} = decode(
+ iolist_to_binary([<< 2#00111111>>, MaxSize, <<16#4803333037c1c0bf:64>>]),
+ State2),
+ {Headers2, State3} = decode(
+ iolist_to_binary([<< 2#00111111>>, MaxSize, <<16#4883640effc1c0bf:64>>]),
+ State2),
+ Headers2 = [
+ {<<":status">>, <<"307">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=264, configured_max_size=512, dyn_table=[
+ {42,{<<":status">>, <<"307">>}},
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State3,
+ ok.
+
+table_update_decode_smaller_test() ->
+ %% Use a max_size of 256 to trigger header evictions
+ %% when the code is not updating the max size.
+ State0 = init(256),
+ %% First response (raw then huffman).
+ {Headers1, State1} = decode(<< 16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560 >>, State0),
+ {Headers1, State1} = decode(<< 16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432 >>, State0),
+ Headers1 = [
+ {<<":status">>, <<"302">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=222, configured_max_size=256, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State1,
+ %% Set a new configured max_size to avoid header evictions.
+ State2 = set_max_size(512, State1),
+ %% Second response with the table size update smaller than the limit (raw then huffman).
+ MaxSize = enc_big_int(400 - 31, <<>>),
+ {Headers2, State3} = decode(
+ iolist_to_binary([<< 2#00111111>>, MaxSize, <<16#4803333037c1c0bf:64>>]),
+ State2),
+ {Headers2, State3} = decode(
+ iolist_to_binary([<< 2#00111111>>, MaxSize, <<16#4883640effc1c0bf:64>>]),
+ State2),
+ Headers2 = [
+ {<<":status">>, <<"307">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=264, configured_max_size=512, dyn_table=[
+ {42,{<<":status">>, <<"307">>}},
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State3,
+ ok.
+
+table_update_decode_too_large_test() ->
+ %% Use a max_size of 256 to trigger header evictions
+ %% when the code is not updating the max size.
+ State0 = init(256),
+ %% First response (raw then huffman).
+ {Headers1, State1} = decode(<< 16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560 >>, State0),
+ {Headers1, State1} = decode(<< 16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432 >>, State0),
+ Headers1 = [
+ {<<":status">>, <<"302">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=222, configured_max_size=256, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State1,
+ %% Set a new configured max_size to avoid header evictions.
+ State2 = set_max_size(512, State1),
+ %% Second response with the table size update (raw then huffman).
+ MaxSize = enc_big_int(1024 - 31, <<>>),
+ {'EXIT', _} = (catch decode(
+ iolist_to_binary([<< 2#00111111>>, MaxSize, <<16#4803333037c1c0bf:64>>]),
+ State2)),
+ {'EXIT', _} = (catch decode(
+ iolist_to_binary([<< 2#00111111>>, MaxSize, <<16#4883640effc1c0bf:64>>]),
+ State2)),
+ ok.
+
+table_update_decode_zero_test() ->
+ State0 = init(256),
+ %% First response (raw then huffman).
+ {Headers1, State1} = decode(<< 16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560 >>, State0),
+ {Headers1, State1} = decode(<< 16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432 >>, State0),
+ Headers1 = [
+ {<<":status">>, <<"302">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=222, configured_max_size=256, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State1,
+ %% Set a new configured max_size to avoid header evictions.
+ State2 = set_max_size(512, State1),
+ %% Second response with the table size update (raw then huffman).
+ %% We set the table size to 0 to evict all values before setting
+ %% it to 512 so we only get the second request indexed.
+ MaxSize = enc_big_int(512 - 31, <<>>),
+ {Headers1, State3} = decode(iolist_to_binary([
+ <<2#00100000, 2#00111111>>, MaxSize,
+ <<16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560>>]),
+ State2),
+ {Headers1, State3} = decode(iolist_to_binary([
+ <<2#00100000, 2#00111111>>, MaxSize,
+ <<16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432>>]),
+ State2),
+ #state{size=222, configured_max_size=512, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State3,
+ ok.
+
+horse_decode_raw() ->
+ horse:repeat(20000,
+ do_horse_decode_raw()
+ ).
+
+do_horse_decode_raw() ->
+ {_, State1} = decode(<<16#828684410f7777772e6578616d706c652e636f6d:160>>),
+ {_, State2} = decode(<<16#828684be58086e6f2d6361636865:112>>, State1),
+ {_, _} = decode(<<16#828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565:232>>, State2),
+ ok.
+
+horse_decode_huffman() ->
+ horse:repeat(20000,
+ do_horse_decode_huffman()
+ ).
+
+do_horse_decode_huffman() ->
+ {_, State1} = decode(<<16#828684418cf1e3c2e5f23a6ba0ab90f4ff:136>>),
+ {_, State2} = decode(<<16#828684be5886a8eb10649cbf:96>>, State1),
+ {_, _} = decode(<<16#828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf:192>>, State2),
+ ok.
+-endif.
+
+%% Encoding.
+
+-spec encode(cow_http:headers()) -> {iodata(), state()}.
+encode(Headers) ->
+ encode(Headers, init(), huffman, []).
+
+-spec encode(cow_http:headers(), State) -> {iodata(), State} when State::state().
+encode(Headers, State=#state{max_size=MaxSize, configured_max_size=MaxSize}) ->
+ encode(Headers, State, huffman, []);
+encode(Headers, State0=#state{configured_max_size=MaxSize}) ->
+ State1 = table_update_size(MaxSize, State0),
+ {Data, State} = encode(Headers, State1, huffman, []),
+ {[enc_int5(MaxSize, 2#001)|Data], State}.
+
+-spec encode(cow_http:headers(), State, opts()) -> {iodata(), State} when State::state().
+encode(Headers, State=#state{max_size=MaxSize, configured_max_size=MaxSize}, Opts) ->
+ encode(Headers, State, huffman_opt(Opts), []);
+encode(Headers, State0=#state{configured_max_size=MaxSize}, Opts) ->
+ State1 = table_update_size(MaxSize, State0),
+ {Data, State} = encode(Headers, State1, huffman_opt(Opts), []),
+ {[enc_int5(MaxSize, 2#001)|Data], State}.
+
+huffman_opt(#{huffman := false}) -> no_huffman;
+huffman_opt(_) -> huffman.
+
+%% @todo Handle cases where no/never indexing is expected.
+encode([], State, _, Acc) ->
+ {lists:reverse(Acc), State};
+encode([{Name, Value0}|Tail], State, HuffmanOpt, Acc) ->
+ %% We conditionally call iolist_to_binary/1 because a small
+ %% but noticeable speed improvement happens when we do this.
+ Value = if
+ is_binary(Value0) -> Value0;
+ true -> iolist_to_binary(Value0)
+ end,
+ Header = {Name, Value},
+ case table_find(Header, State) of
+ %% Indexed header field representation.
+ {field, Index} ->
+ encode(Tail, State, HuffmanOpt,
+ [enc_int7(Index, 2#1)|Acc]);
+ %% Literal header field representation: indexed name.
+ {name, Index} ->
+ State2 = table_insert(Header, State),
+ encode(Tail, State2, HuffmanOpt,
+ [[enc_int6(Index, 2#01)|enc_str(Value, HuffmanOpt)]|Acc]);
+ %% Literal header field representation: new name.
+ not_found ->
+ State2 = table_insert(Header, State),
+ encode(Tail, State2, HuffmanOpt,
+ [[<< 0:1, 1:1, 0:6 >>|[enc_str(Name, HuffmanOpt)|enc_str(Value, HuffmanOpt)]]|Acc])
+ end.
+
+%% Encode an integer.
+
+enc_int5(Int, Prefix) when Int < 31 ->
+ << Prefix:3, Int:5 >>;
+enc_int5(Int, Prefix) ->
+ enc_big_int(Int - 31, << Prefix:3, 2#11111:5 >>).
+
+enc_int6(Int, Prefix) when Int < 63 ->
+ << Prefix:2, Int:6 >>;
+enc_int6(Int, Prefix) ->
+ enc_big_int(Int - 63, << Prefix:2, 2#111111:6 >>).
+
+enc_int7(Int, Prefix) when Int < 127 ->
+ << Prefix:1, Int:7 >>;
+enc_int7(Int, Prefix) ->
+ enc_big_int(Int - 127, << Prefix:1, 2#1111111:7 >>).
+
+enc_big_int(Int, Acc) when Int < 128 ->
+ <<Acc/binary, Int:8>>;
+enc_big_int(Int, Acc) ->
+ enc_big_int(Int bsr 7, <<Acc/binary, 1:1, Int:7>>).
+
+%% Encode a string.
+
+enc_str(Str, huffman) ->
+ Str2 = enc_huffman(Str, <<>>),
+ [enc_int7(byte_size(Str2), 2#1)|Str2];
+enc_str(Str, no_huffman) ->
+ [enc_int7(byte_size(Str), 2#0)|Str].
+
+enc_huffman(<<>>, Acc) ->
+ case bit_size(Acc) rem 8 of
+ 1 -> << Acc/bits, 2#1111111:7 >>;
+ 2 -> << Acc/bits, 2#111111:6 >>;
+ 3 -> << Acc/bits, 2#11111:5 >>;
+ 4 -> << Acc/bits, 2#1111:4 >>;
+ 5 -> << Acc/bits, 2#111:3 >>;
+ 6 -> << Acc/bits, 2#11:2 >>;
+ 7 -> << Acc/bits, 2#1:1 >>;
+ 0 -> Acc
+ end;
+enc_huffman(<< 0, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111000:13 >>);
+enc_huffman(<< 1, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011000:23 >>);
+enc_huffman(<< 2, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111100010:28 >>);
+enc_huffman(<< 3, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111100011:28 >>);
+enc_huffman(<< 4, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111100100:28 >>);
+enc_huffman(<< 5, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111100101:28 >>);
+enc_huffman(<< 6, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111100110:28 >>);
+enc_huffman(<< 7, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111100111:28 >>);
+enc_huffman(<< 8, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101000:28 >>);
+enc_huffman(<< 9, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111101010:24 >>);
+enc_huffman(<< 10, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111111111100:30 >>);
+enc_huffman(<< 11, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101001:28 >>);
+enc_huffman(<< 12, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101010:28 >>);
+enc_huffman(<< 13, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111111111101:30 >>);
+enc_huffman(<< 14, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101011:28 >>);
+enc_huffman(<< 15, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101100:28 >>);
+enc_huffman(<< 16, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101101:28 >>);
+enc_huffman(<< 17, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101110:28 >>);
+enc_huffman(<< 18, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101111:28 >>);
+enc_huffman(<< 19, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110000:28 >>);
+enc_huffman(<< 20, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110001:28 >>);
+enc_huffman(<< 21, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110010:28 >>);
+enc_huffman(<< 22, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111111111110:30 >>);
+enc_huffman(<< 23, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110011:28 >>);
+enc_huffman(<< 24, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110100:28 >>);
+enc_huffman(<< 25, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110101:28 >>);
+enc_huffman(<< 26, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110110:28 >>);
+enc_huffman(<< 27, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110111:28 >>);
+enc_huffman(<< 28, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111111000:28 >>);
+enc_huffman(<< 29, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111111001:28 >>);
+enc_huffman(<< 30, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111111010:28 >>);
+enc_huffman(<< 31, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111111011:28 >>);
+enc_huffman(<< 32, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#010100:6 >>);
+enc_huffman(<< 33, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111000:10 >>);
+enc_huffman(<< 34, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111001:10 >>);
+enc_huffman(<< 35, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111010:12 >>);
+enc_huffman(<< 36, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111001:13 >>);
+enc_huffman(<< 37, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#010101:6 >>);
+enc_huffman(<< 38, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111000:8 >>);
+enc_huffman(<< 39, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111010:11 >>);
+enc_huffman(<< 40, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111010:10 >>);
+enc_huffman(<< 41, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111011:10 >>);
+enc_huffman(<< 42, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111001:8 >>);
+enc_huffman(<< 43, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111011:11 >>);
+enc_huffman(<< 44, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111010:8 >>);
+enc_huffman(<< 45, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#010110:6 >>);
+enc_huffman(<< 46, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#010111:6 >>);
+enc_huffman(<< 47, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011000:6 >>);
+enc_huffman(<< 48, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00000:5 >>);
+enc_huffman(<< 49, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00001:5 >>);
+enc_huffman(<< 50, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00010:5 >>);
+enc_huffman(<< 51, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011001:6 >>);
+enc_huffman(<< 52, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011010:6 >>);
+enc_huffman(<< 53, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011011:6 >>);
+enc_huffman(<< 54, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011100:6 >>);
+enc_huffman(<< 55, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011101:6 >>);
+enc_huffman(<< 56, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011110:6 >>);
+enc_huffman(<< 57, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011111:6 >>);
+enc_huffman(<< 58, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1011100:7 >>);
+enc_huffman(<< 59, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111011:8 >>);
+enc_huffman(<< 60, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111100:15 >>);
+enc_huffman(<< 61, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100000:6 >>);
+enc_huffman(<< 62, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111011:12 >>);
+enc_huffman(<< 63, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111100:10 >>);
+enc_huffman(<< 64, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111010:13 >>);
+enc_huffman(<< 65, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100001:6 >>);
+enc_huffman(<< 66, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1011101:7 >>);
+enc_huffman(<< 67, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1011110:7 >>);
+enc_huffman(<< 68, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1011111:7 >>);
+enc_huffman(<< 69, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100000:7 >>);
+enc_huffman(<< 70, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100001:7 >>);
+enc_huffman(<< 71, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100010:7 >>);
+enc_huffman(<< 72, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100011:7 >>);
+enc_huffman(<< 73, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100100:7 >>);
+enc_huffman(<< 74, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100101:7 >>);
+enc_huffman(<< 75, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100110:7 >>);
+enc_huffman(<< 76, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100111:7 >>);
+enc_huffman(<< 77, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101000:7 >>);
+enc_huffman(<< 78, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101001:7 >>);
+enc_huffman(<< 79, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101010:7 >>);
+enc_huffman(<< 80, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101011:7 >>);
+enc_huffman(<< 81, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101100:7 >>);
+enc_huffman(<< 82, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101101:7 >>);
+enc_huffman(<< 83, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101110:7 >>);
+enc_huffman(<< 84, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101111:7 >>);
+enc_huffman(<< 85, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110000:7 >>);
+enc_huffman(<< 86, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110001:7 >>);
+enc_huffman(<< 87, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110010:7 >>);
+enc_huffman(<< 88, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111100:8 >>);
+enc_huffman(<< 89, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110011:7 >>);
+enc_huffman(<< 90, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111101:8 >>);
+enc_huffman(<< 91, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111011:13 >>);
+enc_huffman(<< 92, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111110000:19 >>);
+enc_huffman(<< 93, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111100:13 >>);
+enc_huffman(<< 94, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111100:14 >>);
+enc_huffman(<< 95, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100010:6 >>);
+enc_huffman(<< 96, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111101:15 >>);
+enc_huffman(<< 97, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00011:5 >>);
+enc_huffman(<< 98, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100011:6 >>);
+enc_huffman(<< 99, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00100:5 >>);
+enc_huffman(<< 100, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100100:6 >>);
+enc_huffman(<< 101, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00101:5 >>);
+enc_huffman(<< 102, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100101:6 >>);
+enc_huffman(<< 103, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100110:6 >>);
+enc_huffman(<< 104, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100111:6 >>);
+enc_huffman(<< 105, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00110:5 >>);
+enc_huffman(<< 106, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110100:7 >>);
+enc_huffman(<< 107, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110101:7 >>);
+enc_huffman(<< 108, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#101000:6 >>);
+enc_huffman(<< 109, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#101001:6 >>);
+enc_huffman(<< 110, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#101010:6 >>);
+enc_huffman(<< 111, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00111:5 >>);
+enc_huffman(<< 112, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#101011:6 >>);
+enc_huffman(<< 113, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110110:7 >>);
+enc_huffman(<< 114, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#101100:6 >>);
+enc_huffman(<< 115, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#01000:5 >>);
+enc_huffman(<< 116, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#01001:5 >>);
+enc_huffman(<< 117, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#101101:6 >>);
+enc_huffman(<< 118, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110111:7 >>);
+enc_huffman(<< 119, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111000:7 >>);
+enc_huffman(<< 120, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111001:7 >>);
+enc_huffman(<< 121, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111010:7 >>);
+enc_huffman(<< 122, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111011:7 >>);
+enc_huffman(<< 123, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111110:15 >>);
+enc_huffman(<< 124, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111100:11 >>);
+enc_huffman(<< 125, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111101:14 >>);
+enc_huffman(<< 126, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111101:13 >>);
+enc_huffman(<< 127, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111111100:28 >>);
+enc_huffman(<< 128, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111100110:20 >>);
+enc_huffman(<< 129, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111010010:22 >>);
+enc_huffman(<< 130, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111100111:20 >>);
+enc_huffman(<< 131, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111101000:20 >>);
+enc_huffman(<< 132, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111010011:22 >>);
+enc_huffman(<< 133, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111010100:22 >>);
+enc_huffman(<< 134, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111010101:22 >>);
+enc_huffman(<< 135, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011001:23 >>);
+enc_huffman(<< 136, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111010110:22 >>);
+enc_huffman(<< 137, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011010:23 >>);
+enc_huffman(<< 138, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011011:23 >>);
+enc_huffman(<< 139, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011100:23 >>);
+enc_huffman(<< 140, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011101:23 >>);
+enc_huffman(<< 141, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011110:23 >>);
+enc_huffman(<< 142, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111101011:24 >>);
+enc_huffman(<< 143, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011111:23 >>);
+enc_huffman(<< 144, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111101100:24 >>);
+enc_huffman(<< 145, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111101101:24 >>);
+enc_huffman(<< 146, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111010111:22 >>);
+enc_huffman(<< 147, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100000:23 >>);
+enc_huffman(<< 148, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111101110:24 >>);
+enc_huffman(<< 149, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100001:23 >>);
+enc_huffman(<< 150, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100010:23 >>);
+enc_huffman(<< 151, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100011:23 >>);
+enc_huffman(<< 152, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100100:23 >>);
+enc_huffman(<< 153, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111011100:21 >>);
+enc_huffman(<< 154, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011000:22 >>);
+enc_huffman(<< 155, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100101:23 >>);
+enc_huffman(<< 156, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011001:22 >>);
+enc_huffman(<< 157, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100110:23 >>);
+enc_huffman(<< 158, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100111:23 >>);
+enc_huffman(<< 159, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111101111:24 >>);
+enc_huffman(<< 160, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011010:22 >>);
+enc_huffman(<< 161, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111011101:21 >>);
+enc_huffman(<< 162, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111101001:20 >>);
+enc_huffman(<< 163, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011011:22 >>);
+enc_huffman(<< 164, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011100:22 >>);
+enc_huffman(<< 165, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101000:23 >>);
+enc_huffman(<< 166, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101001:23 >>);
+enc_huffman(<< 167, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111011110:21 >>);
+enc_huffman(<< 168, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101010:23 >>);
+enc_huffman(<< 169, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011101:22 >>);
+enc_huffman(<< 170, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011110:22 >>);
+enc_huffman(<< 171, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111110000:24 >>);
+enc_huffman(<< 172, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111011111:21 >>);
+enc_huffman(<< 173, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011111:22 >>);
+enc_huffman(<< 174, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101011:23 >>);
+enc_huffman(<< 175, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101100:23 >>);
+enc_huffman(<< 176, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100000:21 >>);
+enc_huffman(<< 177, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100001:21 >>);
+enc_huffman(<< 178, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100000:22 >>);
+enc_huffman(<< 179, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100010:21 >>);
+enc_huffman(<< 180, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101101:23 >>);
+enc_huffman(<< 181, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100001:22 >>);
+enc_huffman(<< 182, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101110:23 >>);
+enc_huffman(<< 183, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101111:23 >>);
+enc_huffman(<< 184, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111101010:20 >>);
+enc_huffman(<< 185, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100010:22 >>);
+enc_huffman(<< 186, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100011:22 >>);
+enc_huffman(<< 187, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100100:22 >>);
+enc_huffman(<< 188, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111110000:23 >>);
+enc_huffman(<< 189, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100101:22 >>);
+enc_huffman(<< 190, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100110:22 >>);
+enc_huffman(<< 191, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111110001:23 >>);
+enc_huffman(<< 192, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100000:26 >>);
+enc_huffman(<< 193, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100001:26 >>);
+enc_huffman(<< 194, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111101011:20 >>);
+enc_huffman(<< 195, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111110001:19 >>);
+enc_huffman(<< 196, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100111:22 >>);
+enc_huffman(<< 197, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111110010:23 >>);
+enc_huffman(<< 198, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111101000:22 >>);
+enc_huffman(<< 199, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111101100:25 >>);
+enc_huffman(<< 200, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100010:26 >>);
+enc_huffman(<< 201, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100011:26 >>);
+enc_huffman(<< 202, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100100:26 >>);
+enc_huffman(<< 203, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111011110:27 >>);
+enc_huffman(<< 204, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111011111:27 >>);
+enc_huffman(<< 205, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100101:26 >>);
+enc_huffman(<< 206, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111110001:24 >>);
+enc_huffman(<< 207, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111101101:25 >>);
+enc_huffman(<< 208, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111110010:19 >>);
+enc_huffman(<< 209, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100011:21 >>);
+enc_huffman(<< 210, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100110:26 >>);
+enc_huffman(<< 211, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100000:27 >>);
+enc_huffman(<< 212, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100001:27 >>);
+enc_huffman(<< 213, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100111:26 >>);
+enc_huffman(<< 214, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100010:27 >>);
+enc_huffman(<< 215, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111110010:24 >>);
+enc_huffman(<< 216, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100100:21 >>);
+enc_huffman(<< 217, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100101:21 >>);
+enc_huffman(<< 218, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111101000:26 >>);
+enc_huffman(<< 219, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111101001:26 >>);
+enc_huffman(<< 220, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111111101:28 >>);
+enc_huffman(<< 221, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100011:27 >>);
+enc_huffman(<< 222, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100100:27 >>);
+enc_huffman(<< 223, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100101:27 >>);
+enc_huffman(<< 224, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111101100:20 >>);
+enc_huffman(<< 225, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111110011:24 >>);
+enc_huffman(<< 226, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111101101:20 >>);
+enc_huffman(<< 227, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100110:21 >>);
+enc_huffman(<< 228, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111101001:22 >>);
+enc_huffman(<< 229, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100111:21 >>);
+enc_huffman(<< 230, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111101000:21 >>);
+enc_huffman(<< 231, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111110011:23 >>);
+enc_huffman(<< 232, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111101010:22 >>);
+enc_huffman(<< 233, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111101011:22 >>);
+enc_huffman(<< 234, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111101110:25 >>);
+enc_huffman(<< 235, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111101111:25 >>);
+enc_huffman(<< 236, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111110100:24 >>);
+enc_huffman(<< 237, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111110101:24 >>);
+enc_huffman(<< 238, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111101010:26 >>);
+enc_huffman(<< 239, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111110100:23 >>);
+enc_huffman(<< 240, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111101011:26 >>);
+enc_huffman(<< 241, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100110:27 >>);
+enc_huffman(<< 242, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111101100:26 >>);
+enc_huffman(<< 243, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111101101:26 >>);
+enc_huffman(<< 244, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100111:27 >>);
+enc_huffman(<< 245, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101000:27 >>);
+enc_huffman(<< 246, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101001:27 >>);
+enc_huffman(<< 247, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101010:27 >>);
+enc_huffman(<< 248, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101011:27 >>);
+enc_huffman(<< 249, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111111110:28 >>);
+enc_huffman(<< 250, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101100:27 >>);
+enc_huffman(<< 251, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101101:27 >>);
+enc_huffman(<< 252, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101110:27 >>);
+enc_huffman(<< 253, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101111:27 >>);
+enc_huffman(<< 254, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111110000:27 >>);
+enc_huffman(<< 255, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111101110:26 >>).
+
+-ifdef(TEST).
+req_encode_test() ->
+ %% First request (raw then huffman).
+ Headers1 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>}
+ ],
+ {Raw1, State1} = encode(Headers1, init(), #{huffman => false}),
+ << 16#828684410f7777772e6578616d706c652e636f6d:160 >> = iolist_to_binary(Raw1),
+ {Huff1, State1} = encode(Headers1),
+ << 16#828684418cf1e3c2e5f23a6ba0ab90f4ff:136 >> = iolist_to_binary(Huff1),
+ #state{size=57, dyn_table=[{57,{<<":authority">>, <<"www.example.com">>}}]} = State1,
+ %% Second request (raw then huffman).
+ Headers2 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"cache-control">>, <<"no-cache">>}
+ ],
+ {Raw2, State2} = encode(Headers2, State1, #{huffman => false}),
+ << 16#828684be58086e6f2d6361636865:112 >> = iolist_to_binary(Raw2),
+ {Huff2, State2} = encode(Headers2, State1),
+ << 16#828684be5886a8eb10649cbf:96 >> = iolist_to_binary(Huff2),
+ #state{size=110, dyn_table=[
+ {53,{<<"cache-control">>, <<"no-cache">>}},
+ {57,{<<":authority">>, <<"www.example.com">>}}]} = State2,
+ %% Third request (raw then huffman).
+ Headers3 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"https">>},
+ {<<":path">>, <<"/index.html">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"custom-key">>, <<"custom-value">>}
+ ],
+ {Raw3, State3} = encode(Headers3, State2, #{huffman => false}),
+ << 16#828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565:232 >> = iolist_to_binary(Raw3),
+ {Huff3, State3} = encode(Headers3, State2),
+ << 16#828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf:192 >> = iolist_to_binary(Huff3),
+ #state{size=164, dyn_table=[
+ {54,{<<"custom-key">>, <<"custom-value">>}},
+ {53,{<<"cache-control">>, <<"no-cache">>}},
+ {57,{<<":authority">>, <<"www.example.com">>}}]} = State3,
+ ok.
+
+resp_encode_test() ->
+ %% Use a max_size of 256 to trigger header evictions.
+ State0 = init(256),
+ %% First response (raw then huffman).
+ Headers1 = [
+ {<<":status">>, <<"302">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ {Raw1, State1} = encode(Headers1, State0, #{huffman => false}),
+ << 16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560 >> = iolist_to_binary(Raw1),
+ {Huff1, State1} = encode(Headers1, State0),
+ << 16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432 >> = iolist_to_binary(Huff1),
+ #state{size=222, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State1,
+ %% Second response (raw then huffman).
+ Headers2 = [
+ {<<":status">>, <<"307">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ {Raw2, State2} = encode(Headers2, State1, #{huffman => false}),
+ << 16#4803333037c1c0bf:64 >> = iolist_to_binary(Raw2),
+ {Huff2, State2} = encode(Headers2, State1),
+ << 16#4883640effc1c0bf:64 >> = iolist_to_binary(Huff2),
+ #state{size=222, dyn_table=[
+ {42,{<<":status">>, <<"307">>}},
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}}]} = State2,
+ %% Third response (raw then huffman).
+ Headers3 = [
+ {<<":status">>, <<"200">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:22 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>},
+ {<<"content-encoding">>, <<"gzip">>},
+ {<<"set-cookie">>, <<"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1">>}
+ ],
+ {Raw3, State3} = encode(Headers3, State2, #{huffman => false}),
+ << 16#88c1611d4d6f6e2c203231204f637420323031332032303a31333a323220474d54c05a04677a69707738666f6f3d4153444a4b48514b425a584f5157454f50495541585157454f49553b206d61782d6167653d333630303b2076657273696f6e3d31:784 >> = iolist_to_binary(Raw3),
+ {Huff3, State3} = encode(Headers3, State2),
+ << 16#88c16196d07abe941054d444a8200595040b8166e084a62d1bffc05a839bd9ab77ad94e7821dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab270fb5291f9587316065c003ed4ee5b1063d5007:632 >> = iolist_to_binary(Huff3),
+ #state{size=215, dyn_table=[
+ {98,{<<"set-cookie">>, <<"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1">>}},
+ {52,{<<"content-encoding">>, <<"gzip">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:22 GMT">>}}]} = State3,
+ ok.
+
+%% This test assumes that table updates work correctly when decoding.
+table_update_encode_test() ->
+ %% Use a max_size of 256 to trigger header evictions
+ %% when the code is not updating the max size.
+ DecState0 = EncState0 = init(256),
+ %% First response.
+ Headers1 = [
+ {<<":status">>, <<"302">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ {Encoded1, EncState1} = encode(Headers1, EncState0),
+ {Headers1, DecState1} = decode(iolist_to_binary(Encoded1), DecState0),
+ #state{size=222, configured_max_size=256, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = DecState1,
+ #state{size=222, configured_max_size=256, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = EncState1,
+ %% Set a new configured max_size to avoid header evictions.
+ DecState2 = set_max_size(512, DecState1),
+ EncState2 = set_max_size(512, EncState1),
+ %% Second response.
+ Headers2 = [
+ {<<":status">>, <<"307">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ {Encoded2, EncState3} = encode(Headers2, EncState2),
+ {Headers2, DecState3} = decode(iolist_to_binary(Encoded2), DecState2),
+ #state{size=264, max_size=512, dyn_table=[
+ {42,{<<":status">>, <<"307">>}},
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = DecState3,
+ #state{size=264, max_size=512, dyn_table=[
+ {42,{<<":status">>, <<"307">>}},
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = EncState3,
+ ok.
+
+%% Check that encode/2 is using the new table size after calling
+%% set_max_size/1 and that adding entries larger than the max size
+%% results in an empty table.
+table_update_encode_max_size_0_test() ->
+ %% Encoding starts with default max size
+ EncState0 = init(),
+ %% Decoding starts with max size of 0
+ DecState0 = init(0),
+ %% First request.
+ Headers1 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>}
+ ],
+ {Encoded1, EncState1} = encode(Headers1, EncState0),
+ {Headers1, DecState1} = decode(iolist_to_binary(Encoded1), DecState0),
+ #state{size=57, dyn_table=[{57,{<<":authority">>, <<"www.example.com">>}}]} = EncState1,
+ #state{size=0, dyn_table=[]} = DecState1,
+ %% Settings received after the first request.
+ EncState2 = set_max_size(0, EncState1),
+ #state{configured_max_size=0, max_size=4096,
+ size=57, dyn_table=[{57,{<<":authority">>, <<"www.example.com">>}}]} = EncState2,
+ %% Second request.
+ Headers2 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"cache-control">>, <<"no-cache">>}
+ ],
+ {Encoded2, EncState3} = encode(Headers2, EncState2),
+ {Headers2, DecState2} = decode(iolist_to_binary(Encoded2), DecState1),
+ #state{configured_max_size=0, max_size=0, size=0, dyn_table=[]} = EncState3,
+ #state{size=0, dyn_table=[]} = DecState2,
+ ok.
+
+encode_iolist_test() ->
+ Headers = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"content-type">>, [<<"image">>,<<"/">>,<<"png">>,<<>>]}
+ ],
+ {_, _} = encode(Headers),
+ ok.
+
+horse_encode_raw() ->
+ horse:repeat(20000,
+ do_horse_encode_raw()
+ ).
+
+do_horse_encode_raw() ->
+ Headers1 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>}
+ ],
+ {_, State1} = encode(Headers1, init(), #{huffman => false}),
+ Headers2 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"cache-control">>, <<"no-cache">>}
+ ],
+ {_, State2} = encode(Headers2, State1, #{huffman => false}),
+ Headers3 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"https">>},
+ {<<":path">>, <<"/index.html">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"custom-key">>, <<"custom-value">>}
+ ],
+ {_, _} = encode(Headers3, State2, #{huffman => false}),
+ ok.
+
+horse_encode_huffman() ->
+ horse:repeat(20000,
+ do_horse_encode_huffman()
+ ).
+
+do_horse_encode_huffman() ->
+ Headers1 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>}
+ ],
+ {_, State1} = encode(Headers1),
+ Headers2 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"cache-control">>, <<"no-cache">>}
+ ],
+ {_, State2} = encode(Headers2, State1),
+ Headers3 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"https">>},
+ {<<":path">>, <<"/index.html">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"custom-key">>, <<"custom-value">>}
+ ],
+ {_, _} = encode(Headers3, State2),
+ ok.
+-endif.
+
+%% Static and dynamic tables.
+
+%% @todo There must be a more efficient way.
+table_find(Header = {Name, _}, State) ->
+ case table_find_field(Header, State) of
+ not_found ->
+ case table_find_name(Name, State) of
+ NotFound = not_found ->
+ NotFound;
+ Found ->
+ {name, Found}
+ end;
+ Found ->
+ {field, Found}
+ end.
+
+table_find_field({<<":authority">>, <<>>}, _) -> 1;
+table_find_field({<<":method">>, <<"GET">>}, _) -> 2;
+table_find_field({<<":method">>, <<"POST">>}, _) -> 3;
+table_find_field({<<":path">>, <<"/">>}, _) -> 4;
+table_find_field({<<":path">>, <<"/index.html">>}, _) -> 5;
+table_find_field({<<":scheme">>, <<"http">>}, _) -> 6;
+table_find_field({<<":scheme">>, <<"https">>}, _) -> 7;
+table_find_field({<<":status">>, <<"200">>}, _) -> 8;
+table_find_field({<<":status">>, <<"204">>}, _) -> 9;
+table_find_field({<<":status">>, <<"206">>}, _) -> 10;
+table_find_field({<<":status">>, <<"304">>}, _) -> 11;
+table_find_field({<<":status">>, <<"400">>}, _) -> 12;
+table_find_field({<<":status">>, <<"404">>}, _) -> 13;
+table_find_field({<<":status">>, <<"500">>}, _) -> 14;
+table_find_field({<<"accept-charset">>, <<>>}, _) -> 15;
+table_find_field({<<"accept-encoding">>, <<"gzip, deflate">>}, _) -> 16;
+table_find_field({<<"accept-language">>, <<>>}, _) -> 17;
+table_find_field({<<"accept-ranges">>, <<>>}, _) -> 18;
+table_find_field({<<"accept">>, <<>>}, _) -> 19;
+table_find_field({<<"access-control-allow-origin">>, <<>>}, _) -> 20;
+table_find_field({<<"age">>, <<>>}, _) -> 21;
+table_find_field({<<"allow">>, <<>>}, _) -> 22;
+table_find_field({<<"authorization">>, <<>>}, _) -> 23;
+table_find_field({<<"cache-control">>, <<>>}, _) -> 24;
+table_find_field({<<"content-disposition">>, <<>>}, _) -> 25;
+table_find_field({<<"content-encoding">>, <<>>}, _) -> 26;
+table_find_field({<<"content-language">>, <<>>}, _) -> 27;
+table_find_field({<<"content-length">>, <<>>}, _) -> 28;
+table_find_field({<<"content-location">>, <<>>}, _) -> 29;
+table_find_field({<<"content-range">>, <<>>}, _) -> 30;
+table_find_field({<<"content-type">>, <<>>}, _) -> 31;
+table_find_field({<<"cookie">>, <<>>}, _) -> 32;
+table_find_field({<<"date">>, <<>>}, _) -> 33;
+table_find_field({<<"etag">>, <<>>}, _) -> 34;
+table_find_field({<<"expect">>, <<>>}, _) -> 35;
+table_find_field({<<"expires">>, <<>>}, _) -> 36;
+table_find_field({<<"from">>, <<>>}, _) -> 37;
+table_find_field({<<"host">>, <<>>}, _) -> 38;
+table_find_field({<<"if-match">>, <<>>}, _) -> 39;
+table_find_field({<<"if-modified-since">>, <<>>}, _) -> 40;
+table_find_field({<<"if-none-match">>, <<>>}, _) -> 41;
+table_find_field({<<"if-range">>, <<>>}, _) -> 42;
+table_find_field({<<"if-unmodified-since">>, <<>>}, _) -> 43;
+table_find_field({<<"last-modified">>, <<>>}, _) -> 44;
+table_find_field({<<"link">>, <<>>}, _) -> 45;
+table_find_field({<<"location">>, <<>>}, _) -> 46;
+table_find_field({<<"max-forwards">>, <<>>}, _) -> 47;
+table_find_field({<<"proxy-authenticate">>, <<>>}, _) -> 48;
+table_find_field({<<"proxy-authorization">>, <<>>}, _) -> 49;
+table_find_field({<<"range">>, <<>>}, _) -> 50;
+table_find_field({<<"referer">>, <<>>}, _) -> 51;
+table_find_field({<<"refresh">>, <<>>}, _) -> 52;
+table_find_field({<<"retry-after">>, <<>>}, _) -> 53;
+table_find_field({<<"server">>, <<>>}, _) -> 54;
+table_find_field({<<"set-cookie">>, <<>>}, _) -> 55;
+table_find_field({<<"strict-transport-security">>, <<>>}, _) -> 56;
+table_find_field({<<"transfer-encoding">>, <<>>}, _) -> 57;
+table_find_field({<<"user-agent">>, <<>>}, _) -> 58;
+table_find_field({<<"vary">>, <<>>}, _) -> 59;
+table_find_field({<<"via">>, <<>>}, _) -> 60;
+table_find_field({<<"www-authenticate">>, <<>>}, _) -> 61;
+table_find_field(Header, #state{dyn_table=DynamicTable}) ->
+ table_find_field_dyn(Header, DynamicTable, 62).
+
+table_find_field_dyn(_, [], _) -> not_found;
+table_find_field_dyn(Header, [{_, Header}|_], Index) -> Index;
+table_find_field_dyn(Header, [_|Tail], Index) -> table_find_field_dyn(Header, Tail, Index + 1).
+
+table_find_name(<<":authority">>, _) -> 1;
+table_find_name(<<":method">>, _) -> 2;
+table_find_name(<<":path">>, _) -> 4;
+table_find_name(<<":scheme">>, _) -> 6;
+table_find_name(<<":status">>, _) -> 8;
+table_find_name(<<"accept-charset">>, _) -> 15;
+table_find_name(<<"accept-encoding">>, _) -> 16;
+table_find_name(<<"accept-language">>, _) -> 17;
+table_find_name(<<"accept-ranges">>, _) -> 18;
+table_find_name(<<"accept">>, _) -> 19;
+table_find_name(<<"access-control-allow-origin">>, _) -> 20;
+table_find_name(<<"age">>, _) -> 21;
+table_find_name(<<"allow">>, _) -> 22;
+table_find_name(<<"authorization">>, _) -> 23;
+table_find_name(<<"cache-control">>, _) -> 24;
+table_find_name(<<"content-disposition">>, _) -> 25;
+table_find_name(<<"content-encoding">>, _) -> 26;
+table_find_name(<<"content-language">>, _) -> 27;
+table_find_name(<<"content-length">>, _) -> 28;
+table_find_name(<<"content-location">>, _) -> 29;
+table_find_name(<<"content-range">>, _) -> 30;
+table_find_name(<<"content-type">>, _) -> 31;
+table_find_name(<<"cookie">>, _) -> 32;
+table_find_name(<<"date">>, _) -> 33;
+table_find_name(<<"etag">>, _) -> 34;
+table_find_name(<<"expect">>, _) -> 35;
+table_find_name(<<"expires">>, _) -> 36;
+table_find_name(<<"from">>, _) -> 37;
+table_find_name(<<"host">>, _) -> 38;
+table_find_name(<<"if-match">>, _) -> 39;
+table_find_name(<<"if-modified-since">>, _) -> 40;
+table_find_name(<<"if-none-match">>, _) -> 41;
+table_find_name(<<"if-range">>, _) -> 42;
+table_find_name(<<"if-unmodified-since">>, _) -> 43;
+table_find_name(<<"last-modified">>, _) -> 44;
+table_find_name(<<"link">>, _) -> 45;
+table_find_name(<<"location">>, _) -> 46;
+table_find_name(<<"max-forwards">>, _) -> 47;
+table_find_name(<<"proxy-authenticate">>, _) -> 48;
+table_find_name(<<"proxy-authorization">>, _) -> 49;
+table_find_name(<<"range">>, _) -> 50;
+table_find_name(<<"referer">>, _) -> 51;
+table_find_name(<<"refresh">>, _) -> 52;
+table_find_name(<<"retry-after">>, _) -> 53;
+table_find_name(<<"server">>, _) -> 54;
+table_find_name(<<"set-cookie">>, _) -> 55;
+table_find_name(<<"strict-transport-security">>, _) -> 56;
+table_find_name(<<"transfer-encoding">>, _) -> 57;
+table_find_name(<<"user-agent">>, _) -> 58;
+table_find_name(<<"vary">>, _) -> 59;
+table_find_name(<<"via">>, _) -> 60;
+table_find_name(<<"www-authenticate">>, _) -> 61;
+table_find_name(Name, #state{dyn_table=DynamicTable}) ->
+ table_find_name_dyn(Name, DynamicTable, 62).
+
+table_find_name_dyn(_, [], _) -> not_found;
+table_find_name_dyn(Name, [{Name, _}|_], Index) -> Index;
+table_find_name_dyn(Name, [_|Tail], Index) -> table_find_name_dyn(Name, Tail, Index + 1).
+
+table_get(1, _) -> {<<":authority">>, <<>>};
+table_get(2, _) -> {<<":method">>, <<"GET">>};
+table_get(3, _) -> {<<":method">>, <<"POST">>};
+table_get(4, _) -> {<<":path">>, <<"/">>};
+table_get(5, _) -> {<<":path">>, <<"/index.html">>};
+table_get(6, _) -> {<<":scheme">>, <<"http">>};
+table_get(7, _) -> {<<":scheme">>, <<"https">>};
+table_get(8, _) -> {<<":status">>, <<"200">>};
+table_get(9, _) -> {<<":status">>, <<"204">>};
+table_get(10, _) -> {<<":status">>, <<"206">>};
+table_get(11, _) -> {<<":status">>, <<"304">>};
+table_get(12, _) -> {<<":status">>, <<"400">>};
+table_get(13, _) -> {<<":status">>, <<"404">>};
+table_get(14, _) -> {<<":status">>, <<"500">>};
+table_get(15, _) -> {<<"accept-charset">>, <<>>};
+table_get(16, _) -> {<<"accept-encoding">>, <<"gzip, deflate">>};
+table_get(17, _) -> {<<"accept-language">>, <<>>};
+table_get(18, _) -> {<<"accept-ranges">>, <<>>};
+table_get(19, _) -> {<<"accept">>, <<>>};
+table_get(20, _) -> {<<"access-control-allow-origin">>, <<>>};
+table_get(21, _) -> {<<"age">>, <<>>};
+table_get(22, _) -> {<<"allow">>, <<>>};
+table_get(23, _) -> {<<"authorization">>, <<>>};
+table_get(24, _) -> {<<"cache-control">>, <<>>};
+table_get(25, _) -> {<<"content-disposition">>, <<>>};
+table_get(26, _) -> {<<"content-encoding">>, <<>>};
+table_get(27, _) -> {<<"content-language">>, <<>>};
+table_get(28, _) -> {<<"content-length">>, <<>>};
+table_get(29, _) -> {<<"content-location">>, <<>>};
+table_get(30, _) -> {<<"content-range">>, <<>>};
+table_get(31, _) -> {<<"content-type">>, <<>>};
+table_get(32, _) -> {<<"cookie">>, <<>>};
+table_get(33, _) -> {<<"date">>, <<>>};
+table_get(34, _) -> {<<"etag">>, <<>>};
+table_get(35, _) -> {<<"expect">>, <<>>};
+table_get(36, _) -> {<<"expires">>, <<>>};
+table_get(37, _) -> {<<"from">>, <<>>};
+table_get(38, _) -> {<<"host">>, <<>>};
+table_get(39, _) -> {<<"if-match">>, <<>>};
+table_get(40, _) -> {<<"if-modified-since">>, <<>>};
+table_get(41, _) -> {<<"if-none-match">>, <<>>};
+table_get(42, _) -> {<<"if-range">>, <<>>};
+table_get(43, _) -> {<<"if-unmodified-since">>, <<>>};
+table_get(44, _) -> {<<"last-modified">>, <<>>};
+table_get(45, _) -> {<<"link">>, <<>>};
+table_get(46, _) -> {<<"location">>, <<>>};
+table_get(47, _) -> {<<"max-forwards">>, <<>>};
+table_get(48, _) -> {<<"proxy-authenticate">>, <<>>};
+table_get(49, _) -> {<<"proxy-authorization">>, <<>>};
+table_get(50, _) -> {<<"range">>, <<>>};
+table_get(51, _) -> {<<"referer">>, <<>>};
+table_get(52, _) -> {<<"refresh">>, <<>>};
+table_get(53, _) -> {<<"retry-after">>, <<>>};
+table_get(54, _) -> {<<"server">>, <<>>};
+table_get(55, _) -> {<<"set-cookie">>, <<>>};
+table_get(56, _) -> {<<"strict-transport-security">>, <<>>};
+table_get(57, _) -> {<<"transfer-encoding">>, <<>>};
+table_get(58, _) -> {<<"user-agent">>, <<>>};
+table_get(59, _) -> {<<"vary">>, <<>>};
+table_get(60, _) -> {<<"via">>, <<>>};
+table_get(61, _) -> {<<"www-authenticate">>, <<>>};
+table_get(Index, #state{dyn_table=DynamicTable}) ->
+ {_, Header} = lists:nth(Index - 61, DynamicTable),
+ Header.
+
+table_get_name(1, _) -> <<":authority">>;
+table_get_name(2, _) -> <<":method">>;
+table_get_name(3, _) -> <<":method">>;
+table_get_name(4, _) -> <<":path">>;
+table_get_name(5, _) -> <<":path">>;
+table_get_name(6, _) -> <<":scheme">>;
+table_get_name(7, _) -> <<":scheme">>;
+table_get_name(8, _) -> <<":status">>;
+table_get_name(9, _) -> <<":status">>;
+table_get_name(10, _) -> <<":status">>;
+table_get_name(11, _) -> <<":status">>;
+table_get_name(12, _) -> <<":status">>;
+table_get_name(13, _) -> <<":status">>;
+table_get_name(14, _) -> <<":status">>;
+table_get_name(15, _) -> <<"accept-charset">>;
+table_get_name(16, _) -> <<"accept-encoding">>;
+table_get_name(17, _) -> <<"accept-language">>;
+table_get_name(18, _) -> <<"accept-ranges">>;
+table_get_name(19, _) -> <<"accept">>;
+table_get_name(20, _) -> <<"access-control-allow-origin">>;
+table_get_name(21, _) -> <<"age">>;
+table_get_name(22, _) -> <<"allow">>;
+table_get_name(23, _) -> <<"authorization">>;
+table_get_name(24, _) -> <<"cache-control">>;
+table_get_name(25, _) -> <<"content-disposition">>;
+table_get_name(26, _) -> <<"content-encoding">>;
+table_get_name(27, _) -> <<"content-language">>;
+table_get_name(28, _) -> <<"content-length">>;
+table_get_name(29, _) -> <<"content-location">>;
+table_get_name(30, _) -> <<"content-range">>;
+table_get_name(31, _) -> <<"content-type">>;
+table_get_name(32, _) -> <<"cookie">>;
+table_get_name(33, _) -> <<"date">>;
+table_get_name(34, _) -> <<"etag">>;
+table_get_name(35, _) -> <<"expect">>;
+table_get_name(36, _) -> <<"expires">>;
+table_get_name(37, _) -> <<"from">>;
+table_get_name(38, _) -> <<"host">>;
+table_get_name(39, _) -> <<"if-match">>;
+table_get_name(40, _) -> <<"if-modified-since">>;
+table_get_name(41, _) -> <<"if-none-match">>;
+table_get_name(42, _) -> <<"if-range">>;
+table_get_name(43, _) -> <<"if-unmodified-since">>;
+table_get_name(44, _) -> <<"last-modified">>;
+table_get_name(45, _) -> <<"link">>;
+table_get_name(46, _) -> <<"location">>;
+table_get_name(47, _) -> <<"max-forwards">>;
+table_get_name(48, _) -> <<"proxy-authenticate">>;
+table_get_name(49, _) -> <<"proxy-authorization">>;
+table_get_name(50, _) -> <<"range">>;
+table_get_name(51, _) -> <<"referer">>;
+table_get_name(52, _) -> <<"refresh">>;
+table_get_name(53, _) -> <<"retry-after">>;
+table_get_name(54, _) -> <<"server">>;
+table_get_name(55, _) -> <<"set-cookie">>;
+table_get_name(56, _) -> <<"strict-transport-security">>;
+table_get_name(57, _) -> <<"transfer-encoding">>;
+table_get_name(58, _) -> <<"user-agent">>;
+table_get_name(59, _) -> <<"vary">>;
+table_get_name(60, _) -> <<"via">>;
+table_get_name(61, _) -> <<"www-authenticate">>;
+table_get_name(Index, #state{dyn_table=DynamicTable}) ->
+ {_, {Name, _}} = lists:nth(Index - 61, DynamicTable),
+ Name.
+
+table_insert(Entry = {Name, Value}, State=#state{size=Size, max_size=MaxSize, dyn_table=DynamicTable}) ->
+ EntrySize = byte_size(Name) + byte_size(Value) + 32,
+ if
+ EntrySize + Size =< MaxSize ->
+ %% Add entry without eviction
+ State#state{size=Size + EntrySize, dyn_table=[{EntrySize, Entry}|DynamicTable]};
+ EntrySize =< MaxSize ->
+ %% Evict, then add entry
+ {DynamicTable2, Size2} = table_resize(DynamicTable, MaxSize - EntrySize, 0, []),
+ State#state{size=Size2 + EntrySize, dyn_table=[{EntrySize, Entry}|DynamicTable2]};
+ EntrySize > MaxSize ->
+ %% "an attempt to add an entry larger than the
+ %% maximum size causes the table to be emptied
+ %% of all existing entries and results in an
+ %% empty table" (RFC 7541, 4.4)
+ State#state{size=0, dyn_table=[]}
+ end.
+
+table_resize([], _, Size, Acc) ->
+ {lists:reverse(Acc), Size};
+table_resize([{EntrySize, _}|_], MaxSize, Size, Acc) when Size + EntrySize > MaxSize ->
+ {lists:reverse(Acc), Size};
+table_resize([Entry = {EntrySize, _}|Tail], MaxSize, Size, Acc) ->
+ table_resize(Tail, MaxSize, Size + EntrySize, [Entry|Acc]).
+
+table_update_size(0, State) ->
+ State#state{size=0, max_size=0, dyn_table=[]};
+table_update_size(MaxSize, State=#state{size=CurrentSize})
+ when CurrentSize =< MaxSize ->
+ State#state{max_size=MaxSize};
+table_update_size(MaxSize, State=#state{dyn_table=DynTable}) ->
+ {DynTable2, Size} = table_resize(DynTable, MaxSize, 0, []),
+ State#state{size=Size, max_size=MaxSize, dyn_table=DynTable2}.
+
+-ifdef(TEST).
+prop_str_raw() ->
+ ?FORALL(Str, binary(), begin
+ {Str, <<>>} =:= dec_str(iolist_to_binary(enc_str(Str, no_huffman)))
+ end).
+
+prop_str_huffman() ->
+ ?FORALL(Str, binary(), begin
+ {Str, <<>>} =:= dec_str(iolist_to_binary(enc_str(Str, huffman)))
+ end).
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_hpack_dec_huffman_lookup.hrl b/server/_build/default/lib/cowlib/src/cow_hpack_dec_huffman_lookup.hrl
new file mode 100644
index 0000000..6e5da31
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_hpack_dec_huffman_lookup.hrl
@@ -0,0 +1,4132 @@
+%% Copyright (c) 2019-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% This lookup function was created by converting the
+%% table from Nginx[1] into a form better suitable for
+%% Erlang/OTP. This particular table takes a byte-sized
+%% state and 4 bits to determine whether to emit a
+%% character and what the next state is. It is most
+%% appropriate for Erlang/OTP because we can benefit
+%% from binary pattern matching optimizations by
+%% matching the binary one byte at a time, calling
+%% this lookup function twice. This and similar
+%% algorithms are discussed here[2] and there[3].
+%%
+%% It is possible to write a lookup table taking
+%% a full byte instead of just 4 bits, but this
+%% would make this function take 65536 clauses instead
+%% of the current 4096. This could be done later
+%% as a further optimization but might not yield
+%% significant improvements.
+%%
+%% [1] https://hg.nginx.org/nginx/file/tip/src/http/v2/ngx_http_v2_huff_decode.c
+%% [2] http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.9.4248&rep=rep1&type=pdf
+%% [3] https://commandlinefanatic.com/cgi-bin/showarticle.cgi?article=art007
+
+dec_huffman_lookup(16#00, 16#0) -> {more, undefined, 16#04};
+dec_huffman_lookup(16#00, 16#1) -> {more, undefined, 16#05};
+dec_huffman_lookup(16#00, 16#2) -> {more, undefined, 16#07};
+dec_huffman_lookup(16#00, 16#3) -> {more, undefined, 16#08};
+dec_huffman_lookup(16#00, 16#4) -> {more, undefined, 16#0b};
+dec_huffman_lookup(16#00, 16#5) -> {more, undefined, 16#0c};
+dec_huffman_lookup(16#00, 16#6) -> {more, undefined, 16#10};
+dec_huffman_lookup(16#00, 16#7) -> {more, undefined, 16#13};
+dec_huffman_lookup(16#00, 16#8) -> {more, undefined, 16#19};
+dec_huffman_lookup(16#00, 16#9) -> {more, undefined, 16#1c};
+dec_huffman_lookup(16#00, 16#a) -> {more, undefined, 16#20};
+dec_huffman_lookup(16#00, 16#b) -> {more, undefined, 16#23};
+dec_huffman_lookup(16#00, 16#c) -> {more, undefined, 16#2a};
+dec_huffman_lookup(16#00, 16#d) -> {more, undefined, 16#31};
+dec_huffman_lookup(16#00, 16#e) -> {more, undefined, 16#39};
+dec_huffman_lookup(16#00, 16#f) -> {ok, undefined, 16#40};
+dec_huffman_lookup(16#01, 16#0) -> {ok, 16#30, 16#00};
+dec_huffman_lookup(16#01, 16#1) -> {ok, 16#31, 16#00};
+dec_huffman_lookup(16#01, 16#2) -> {ok, 16#32, 16#00};
+dec_huffman_lookup(16#01, 16#3) -> {ok, 16#61, 16#00};
+dec_huffman_lookup(16#01, 16#4) -> {ok, 16#63, 16#00};
+dec_huffman_lookup(16#01, 16#5) -> {ok, 16#65, 16#00};
+dec_huffman_lookup(16#01, 16#6) -> {ok, 16#69, 16#00};
+dec_huffman_lookup(16#01, 16#7) -> {ok, 16#6f, 16#00};
+dec_huffman_lookup(16#01, 16#8) -> {ok, 16#73, 16#00};
+dec_huffman_lookup(16#01, 16#9) -> {ok, 16#74, 16#00};
+dec_huffman_lookup(16#01, 16#a) -> {more, undefined, 16#0d};
+dec_huffman_lookup(16#01, 16#b) -> {more, undefined, 16#0e};
+dec_huffman_lookup(16#01, 16#c) -> {more, undefined, 16#11};
+dec_huffman_lookup(16#01, 16#d) -> {more, undefined, 16#12};
+dec_huffman_lookup(16#01, 16#e) -> {more, undefined, 16#14};
+dec_huffman_lookup(16#01, 16#f) -> {more, undefined, 16#15};
+dec_huffman_lookup(16#02, 16#0) -> {more, 16#30, 16#01};
+dec_huffman_lookup(16#02, 16#1) -> {ok, 16#30, 16#16};
+dec_huffman_lookup(16#02, 16#2) -> {more, 16#31, 16#01};
+dec_huffman_lookup(16#02, 16#3) -> {ok, 16#31, 16#16};
+dec_huffman_lookup(16#02, 16#4) -> {more, 16#32, 16#01};
+dec_huffman_lookup(16#02, 16#5) -> {ok, 16#32, 16#16};
+dec_huffman_lookup(16#02, 16#6) -> {more, 16#61, 16#01};
+dec_huffman_lookup(16#02, 16#7) -> {ok, 16#61, 16#16};
+dec_huffman_lookup(16#02, 16#8) -> {more, 16#63, 16#01};
+dec_huffman_lookup(16#02, 16#9) -> {ok, 16#63, 16#16};
+dec_huffman_lookup(16#02, 16#a) -> {more, 16#65, 16#01};
+dec_huffman_lookup(16#02, 16#b) -> {ok, 16#65, 16#16};
+dec_huffman_lookup(16#02, 16#c) -> {more, 16#69, 16#01};
+dec_huffman_lookup(16#02, 16#d) -> {ok, 16#69, 16#16};
+dec_huffman_lookup(16#02, 16#e) -> {more, 16#6f, 16#01};
+dec_huffman_lookup(16#02, 16#f) -> {ok, 16#6f, 16#16};
+dec_huffman_lookup(16#03, 16#0) -> {more, 16#30, 16#02};
+dec_huffman_lookup(16#03, 16#1) -> {more, 16#30, 16#09};
+dec_huffman_lookup(16#03, 16#2) -> {more, 16#30, 16#17};
+dec_huffman_lookup(16#03, 16#3) -> {ok, 16#30, 16#28};
+dec_huffman_lookup(16#03, 16#4) -> {more, 16#31, 16#02};
+dec_huffman_lookup(16#03, 16#5) -> {more, 16#31, 16#09};
+dec_huffman_lookup(16#03, 16#6) -> {more, 16#31, 16#17};
+dec_huffman_lookup(16#03, 16#7) -> {ok, 16#31, 16#28};
+dec_huffman_lookup(16#03, 16#8) -> {more, 16#32, 16#02};
+dec_huffman_lookup(16#03, 16#9) -> {more, 16#32, 16#09};
+dec_huffman_lookup(16#03, 16#a) -> {more, 16#32, 16#17};
+dec_huffman_lookup(16#03, 16#b) -> {ok, 16#32, 16#28};
+dec_huffman_lookup(16#03, 16#c) -> {more, 16#61, 16#02};
+dec_huffman_lookup(16#03, 16#d) -> {more, 16#61, 16#09};
+dec_huffman_lookup(16#03, 16#e) -> {more, 16#61, 16#17};
+dec_huffman_lookup(16#03, 16#f) -> {ok, 16#61, 16#28};
+dec_huffman_lookup(16#04, 16#0) -> {more, 16#30, 16#03};
+dec_huffman_lookup(16#04, 16#1) -> {more, 16#30, 16#06};
+dec_huffman_lookup(16#04, 16#2) -> {more, 16#30, 16#0a};
+dec_huffman_lookup(16#04, 16#3) -> {more, 16#30, 16#0f};
+dec_huffman_lookup(16#04, 16#4) -> {more, 16#30, 16#18};
+dec_huffman_lookup(16#04, 16#5) -> {more, 16#30, 16#1f};
+dec_huffman_lookup(16#04, 16#6) -> {more, 16#30, 16#29};
+dec_huffman_lookup(16#04, 16#7) -> {ok, 16#30, 16#38};
+dec_huffman_lookup(16#04, 16#8) -> {more, 16#31, 16#03};
+dec_huffman_lookup(16#04, 16#9) -> {more, 16#31, 16#06};
+dec_huffman_lookup(16#04, 16#a) -> {more, 16#31, 16#0a};
+dec_huffman_lookup(16#04, 16#b) -> {more, 16#31, 16#0f};
+dec_huffman_lookup(16#04, 16#c) -> {more, 16#31, 16#18};
+dec_huffman_lookup(16#04, 16#d) -> {more, 16#31, 16#1f};
+dec_huffman_lookup(16#04, 16#e) -> {more, 16#31, 16#29};
+dec_huffman_lookup(16#04, 16#f) -> {ok, 16#31, 16#38};
+dec_huffman_lookup(16#05, 16#0) -> {more, 16#32, 16#03};
+dec_huffman_lookup(16#05, 16#1) -> {more, 16#32, 16#06};
+dec_huffman_lookup(16#05, 16#2) -> {more, 16#32, 16#0a};
+dec_huffman_lookup(16#05, 16#3) -> {more, 16#32, 16#0f};
+dec_huffman_lookup(16#05, 16#4) -> {more, 16#32, 16#18};
+dec_huffman_lookup(16#05, 16#5) -> {more, 16#32, 16#1f};
+dec_huffman_lookup(16#05, 16#6) -> {more, 16#32, 16#29};
+dec_huffman_lookup(16#05, 16#7) -> {ok, 16#32, 16#38};
+dec_huffman_lookup(16#05, 16#8) -> {more, 16#61, 16#03};
+dec_huffman_lookup(16#05, 16#9) -> {more, 16#61, 16#06};
+dec_huffman_lookup(16#05, 16#a) -> {more, 16#61, 16#0a};
+dec_huffman_lookup(16#05, 16#b) -> {more, 16#61, 16#0f};
+dec_huffman_lookup(16#05, 16#c) -> {more, 16#61, 16#18};
+dec_huffman_lookup(16#05, 16#d) -> {more, 16#61, 16#1f};
+dec_huffman_lookup(16#05, 16#e) -> {more, 16#61, 16#29};
+dec_huffman_lookup(16#05, 16#f) -> {ok, 16#61, 16#38};
+dec_huffman_lookup(16#06, 16#0) -> {more, 16#63, 16#02};
+dec_huffman_lookup(16#06, 16#1) -> {more, 16#63, 16#09};
+dec_huffman_lookup(16#06, 16#2) -> {more, 16#63, 16#17};
+dec_huffman_lookup(16#06, 16#3) -> {ok, 16#63, 16#28};
+dec_huffman_lookup(16#06, 16#4) -> {more, 16#65, 16#02};
+dec_huffman_lookup(16#06, 16#5) -> {more, 16#65, 16#09};
+dec_huffman_lookup(16#06, 16#6) -> {more, 16#65, 16#17};
+dec_huffman_lookup(16#06, 16#7) -> {ok, 16#65, 16#28};
+dec_huffman_lookup(16#06, 16#8) -> {more, 16#69, 16#02};
+dec_huffman_lookup(16#06, 16#9) -> {more, 16#69, 16#09};
+dec_huffman_lookup(16#06, 16#a) -> {more, 16#69, 16#17};
+dec_huffman_lookup(16#06, 16#b) -> {ok, 16#69, 16#28};
+dec_huffman_lookup(16#06, 16#c) -> {more, 16#6f, 16#02};
+dec_huffman_lookup(16#06, 16#d) -> {more, 16#6f, 16#09};
+dec_huffman_lookup(16#06, 16#e) -> {more, 16#6f, 16#17};
+dec_huffman_lookup(16#06, 16#f) -> {ok, 16#6f, 16#28};
+dec_huffman_lookup(16#07, 16#0) -> {more, 16#63, 16#03};
+dec_huffman_lookup(16#07, 16#1) -> {more, 16#63, 16#06};
+dec_huffman_lookup(16#07, 16#2) -> {more, 16#63, 16#0a};
+dec_huffman_lookup(16#07, 16#3) -> {more, 16#63, 16#0f};
+dec_huffman_lookup(16#07, 16#4) -> {more, 16#63, 16#18};
+dec_huffman_lookup(16#07, 16#5) -> {more, 16#63, 16#1f};
+dec_huffman_lookup(16#07, 16#6) -> {more, 16#63, 16#29};
+dec_huffman_lookup(16#07, 16#7) -> {ok, 16#63, 16#38};
+dec_huffman_lookup(16#07, 16#8) -> {more, 16#65, 16#03};
+dec_huffman_lookup(16#07, 16#9) -> {more, 16#65, 16#06};
+dec_huffman_lookup(16#07, 16#a) -> {more, 16#65, 16#0a};
+dec_huffman_lookup(16#07, 16#b) -> {more, 16#65, 16#0f};
+dec_huffman_lookup(16#07, 16#c) -> {more, 16#65, 16#18};
+dec_huffman_lookup(16#07, 16#d) -> {more, 16#65, 16#1f};
+dec_huffman_lookup(16#07, 16#e) -> {more, 16#65, 16#29};
+dec_huffman_lookup(16#07, 16#f) -> {ok, 16#65, 16#38};
+dec_huffman_lookup(16#08, 16#0) -> {more, 16#69, 16#03};
+dec_huffman_lookup(16#08, 16#1) -> {more, 16#69, 16#06};
+dec_huffman_lookup(16#08, 16#2) -> {more, 16#69, 16#0a};
+dec_huffman_lookup(16#08, 16#3) -> {more, 16#69, 16#0f};
+dec_huffman_lookup(16#08, 16#4) -> {more, 16#69, 16#18};
+dec_huffman_lookup(16#08, 16#5) -> {more, 16#69, 16#1f};
+dec_huffman_lookup(16#08, 16#6) -> {more, 16#69, 16#29};
+dec_huffman_lookup(16#08, 16#7) -> {ok, 16#69, 16#38};
+dec_huffman_lookup(16#08, 16#8) -> {more, 16#6f, 16#03};
+dec_huffman_lookup(16#08, 16#9) -> {more, 16#6f, 16#06};
+dec_huffman_lookup(16#08, 16#a) -> {more, 16#6f, 16#0a};
+dec_huffman_lookup(16#08, 16#b) -> {more, 16#6f, 16#0f};
+dec_huffman_lookup(16#08, 16#c) -> {more, 16#6f, 16#18};
+dec_huffman_lookup(16#08, 16#d) -> {more, 16#6f, 16#1f};
+dec_huffman_lookup(16#08, 16#e) -> {more, 16#6f, 16#29};
+dec_huffman_lookup(16#08, 16#f) -> {ok, 16#6f, 16#38};
+dec_huffman_lookup(16#09, 16#0) -> {more, 16#73, 16#01};
+dec_huffman_lookup(16#09, 16#1) -> {ok, 16#73, 16#16};
+dec_huffman_lookup(16#09, 16#2) -> {more, 16#74, 16#01};
+dec_huffman_lookup(16#09, 16#3) -> {ok, 16#74, 16#16};
+dec_huffman_lookup(16#09, 16#4) -> {ok, 16#20, 16#00};
+dec_huffman_lookup(16#09, 16#5) -> {ok, 16#25, 16#00};
+dec_huffman_lookup(16#09, 16#6) -> {ok, 16#2d, 16#00};
+dec_huffman_lookup(16#09, 16#7) -> {ok, 16#2e, 16#00};
+dec_huffman_lookup(16#09, 16#8) -> {ok, 16#2f, 16#00};
+dec_huffman_lookup(16#09, 16#9) -> {ok, 16#33, 16#00};
+dec_huffman_lookup(16#09, 16#a) -> {ok, 16#34, 16#00};
+dec_huffman_lookup(16#09, 16#b) -> {ok, 16#35, 16#00};
+dec_huffman_lookup(16#09, 16#c) -> {ok, 16#36, 16#00};
+dec_huffman_lookup(16#09, 16#d) -> {ok, 16#37, 16#00};
+dec_huffman_lookup(16#09, 16#e) -> {ok, 16#38, 16#00};
+dec_huffman_lookup(16#09, 16#f) -> {ok, 16#39, 16#00};
+dec_huffman_lookup(16#0a, 16#0) -> {more, 16#73, 16#02};
+dec_huffman_lookup(16#0a, 16#1) -> {more, 16#73, 16#09};
+dec_huffman_lookup(16#0a, 16#2) -> {more, 16#73, 16#17};
+dec_huffman_lookup(16#0a, 16#3) -> {ok, 16#73, 16#28};
+dec_huffman_lookup(16#0a, 16#4) -> {more, 16#74, 16#02};
+dec_huffman_lookup(16#0a, 16#5) -> {more, 16#74, 16#09};
+dec_huffman_lookup(16#0a, 16#6) -> {more, 16#74, 16#17};
+dec_huffman_lookup(16#0a, 16#7) -> {ok, 16#74, 16#28};
+dec_huffman_lookup(16#0a, 16#8) -> {more, 16#20, 16#01};
+dec_huffman_lookup(16#0a, 16#9) -> {ok, 16#20, 16#16};
+dec_huffman_lookup(16#0a, 16#a) -> {more, 16#25, 16#01};
+dec_huffman_lookup(16#0a, 16#b) -> {ok, 16#25, 16#16};
+dec_huffman_lookup(16#0a, 16#c) -> {more, 16#2d, 16#01};
+dec_huffman_lookup(16#0a, 16#d) -> {ok, 16#2d, 16#16};
+dec_huffman_lookup(16#0a, 16#e) -> {more, 16#2e, 16#01};
+dec_huffman_lookup(16#0a, 16#f) -> {ok, 16#2e, 16#16};
+dec_huffman_lookup(16#0b, 16#0) -> {more, 16#73, 16#03};
+dec_huffman_lookup(16#0b, 16#1) -> {more, 16#73, 16#06};
+dec_huffman_lookup(16#0b, 16#2) -> {more, 16#73, 16#0a};
+dec_huffman_lookup(16#0b, 16#3) -> {more, 16#73, 16#0f};
+dec_huffman_lookup(16#0b, 16#4) -> {more, 16#73, 16#18};
+dec_huffman_lookup(16#0b, 16#5) -> {more, 16#73, 16#1f};
+dec_huffman_lookup(16#0b, 16#6) -> {more, 16#73, 16#29};
+dec_huffman_lookup(16#0b, 16#7) -> {ok, 16#73, 16#38};
+dec_huffman_lookup(16#0b, 16#8) -> {more, 16#74, 16#03};
+dec_huffman_lookup(16#0b, 16#9) -> {more, 16#74, 16#06};
+dec_huffman_lookup(16#0b, 16#a) -> {more, 16#74, 16#0a};
+dec_huffman_lookup(16#0b, 16#b) -> {more, 16#74, 16#0f};
+dec_huffman_lookup(16#0b, 16#c) -> {more, 16#74, 16#18};
+dec_huffman_lookup(16#0b, 16#d) -> {more, 16#74, 16#1f};
+dec_huffman_lookup(16#0b, 16#e) -> {more, 16#74, 16#29};
+dec_huffman_lookup(16#0b, 16#f) -> {ok, 16#74, 16#38};
+dec_huffman_lookup(16#0c, 16#0) -> {more, 16#20, 16#02};
+dec_huffman_lookup(16#0c, 16#1) -> {more, 16#20, 16#09};
+dec_huffman_lookup(16#0c, 16#2) -> {more, 16#20, 16#17};
+dec_huffman_lookup(16#0c, 16#3) -> {ok, 16#20, 16#28};
+dec_huffman_lookup(16#0c, 16#4) -> {more, 16#25, 16#02};
+dec_huffman_lookup(16#0c, 16#5) -> {more, 16#25, 16#09};
+dec_huffman_lookup(16#0c, 16#6) -> {more, 16#25, 16#17};
+dec_huffman_lookup(16#0c, 16#7) -> {ok, 16#25, 16#28};
+dec_huffman_lookup(16#0c, 16#8) -> {more, 16#2d, 16#02};
+dec_huffman_lookup(16#0c, 16#9) -> {more, 16#2d, 16#09};
+dec_huffman_lookup(16#0c, 16#a) -> {more, 16#2d, 16#17};
+dec_huffman_lookup(16#0c, 16#b) -> {ok, 16#2d, 16#28};
+dec_huffman_lookup(16#0c, 16#c) -> {more, 16#2e, 16#02};
+dec_huffman_lookup(16#0c, 16#d) -> {more, 16#2e, 16#09};
+dec_huffman_lookup(16#0c, 16#e) -> {more, 16#2e, 16#17};
+dec_huffman_lookup(16#0c, 16#f) -> {ok, 16#2e, 16#28};
+dec_huffman_lookup(16#0d, 16#0) -> {more, 16#20, 16#03};
+dec_huffman_lookup(16#0d, 16#1) -> {more, 16#20, 16#06};
+dec_huffman_lookup(16#0d, 16#2) -> {more, 16#20, 16#0a};
+dec_huffman_lookup(16#0d, 16#3) -> {more, 16#20, 16#0f};
+dec_huffman_lookup(16#0d, 16#4) -> {more, 16#20, 16#18};
+dec_huffman_lookup(16#0d, 16#5) -> {more, 16#20, 16#1f};
+dec_huffman_lookup(16#0d, 16#6) -> {more, 16#20, 16#29};
+dec_huffman_lookup(16#0d, 16#7) -> {ok, 16#20, 16#38};
+dec_huffman_lookup(16#0d, 16#8) -> {more, 16#25, 16#03};
+dec_huffman_lookup(16#0d, 16#9) -> {more, 16#25, 16#06};
+dec_huffman_lookup(16#0d, 16#a) -> {more, 16#25, 16#0a};
+dec_huffman_lookup(16#0d, 16#b) -> {more, 16#25, 16#0f};
+dec_huffman_lookup(16#0d, 16#c) -> {more, 16#25, 16#18};
+dec_huffman_lookup(16#0d, 16#d) -> {more, 16#25, 16#1f};
+dec_huffman_lookup(16#0d, 16#e) -> {more, 16#25, 16#29};
+dec_huffman_lookup(16#0d, 16#f) -> {ok, 16#25, 16#38};
+dec_huffman_lookup(16#0e, 16#0) -> {more, 16#2d, 16#03};
+dec_huffman_lookup(16#0e, 16#1) -> {more, 16#2d, 16#06};
+dec_huffman_lookup(16#0e, 16#2) -> {more, 16#2d, 16#0a};
+dec_huffman_lookup(16#0e, 16#3) -> {more, 16#2d, 16#0f};
+dec_huffman_lookup(16#0e, 16#4) -> {more, 16#2d, 16#18};
+dec_huffman_lookup(16#0e, 16#5) -> {more, 16#2d, 16#1f};
+dec_huffman_lookup(16#0e, 16#6) -> {more, 16#2d, 16#29};
+dec_huffman_lookup(16#0e, 16#7) -> {ok, 16#2d, 16#38};
+dec_huffman_lookup(16#0e, 16#8) -> {more, 16#2e, 16#03};
+dec_huffman_lookup(16#0e, 16#9) -> {more, 16#2e, 16#06};
+dec_huffman_lookup(16#0e, 16#a) -> {more, 16#2e, 16#0a};
+dec_huffman_lookup(16#0e, 16#b) -> {more, 16#2e, 16#0f};
+dec_huffman_lookup(16#0e, 16#c) -> {more, 16#2e, 16#18};
+dec_huffman_lookup(16#0e, 16#d) -> {more, 16#2e, 16#1f};
+dec_huffman_lookup(16#0e, 16#e) -> {more, 16#2e, 16#29};
+dec_huffman_lookup(16#0e, 16#f) -> {ok, 16#2e, 16#38};
+dec_huffman_lookup(16#0f, 16#0) -> {more, 16#2f, 16#01};
+dec_huffman_lookup(16#0f, 16#1) -> {ok, 16#2f, 16#16};
+dec_huffman_lookup(16#0f, 16#2) -> {more, 16#33, 16#01};
+dec_huffman_lookup(16#0f, 16#3) -> {ok, 16#33, 16#16};
+dec_huffman_lookup(16#0f, 16#4) -> {more, 16#34, 16#01};
+dec_huffman_lookup(16#0f, 16#5) -> {ok, 16#34, 16#16};
+dec_huffman_lookup(16#0f, 16#6) -> {more, 16#35, 16#01};
+dec_huffman_lookup(16#0f, 16#7) -> {ok, 16#35, 16#16};
+dec_huffman_lookup(16#0f, 16#8) -> {more, 16#36, 16#01};
+dec_huffman_lookup(16#0f, 16#9) -> {ok, 16#36, 16#16};
+dec_huffman_lookup(16#0f, 16#a) -> {more, 16#37, 16#01};
+dec_huffman_lookup(16#0f, 16#b) -> {ok, 16#37, 16#16};
+dec_huffman_lookup(16#0f, 16#c) -> {more, 16#38, 16#01};
+dec_huffman_lookup(16#0f, 16#d) -> {ok, 16#38, 16#16};
+dec_huffman_lookup(16#0f, 16#e) -> {more, 16#39, 16#01};
+dec_huffman_lookup(16#0f, 16#f) -> {ok, 16#39, 16#16};
+dec_huffman_lookup(16#10, 16#0) -> {more, 16#2f, 16#02};
+dec_huffman_lookup(16#10, 16#1) -> {more, 16#2f, 16#09};
+dec_huffman_lookup(16#10, 16#2) -> {more, 16#2f, 16#17};
+dec_huffman_lookup(16#10, 16#3) -> {ok, 16#2f, 16#28};
+dec_huffman_lookup(16#10, 16#4) -> {more, 16#33, 16#02};
+dec_huffman_lookup(16#10, 16#5) -> {more, 16#33, 16#09};
+dec_huffman_lookup(16#10, 16#6) -> {more, 16#33, 16#17};
+dec_huffman_lookup(16#10, 16#7) -> {ok, 16#33, 16#28};
+dec_huffman_lookup(16#10, 16#8) -> {more, 16#34, 16#02};
+dec_huffman_lookup(16#10, 16#9) -> {more, 16#34, 16#09};
+dec_huffman_lookup(16#10, 16#a) -> {more, 16#34, 16#17};
+dec_huffman_lookup(16#10, 16#b) -> {ok, 16#34, 16#28};
+dec_huffman_lookup(16#10, 16#c) -> {more, 16#35, 16#02};
+dec_huffman_lookup(16#10, 16#d) -> {more, 16#35, 16#09};
+dec_huffman_lookup(16#10, 16#e) -> {more, 16#35, 16#17};
+dec_huffman_lookup(16#10, 16#f) -> {ok, 16#35, 16#28};
+dec_huffman_lookup(16#11, 16#0) -> {more, 16#2f, 16#03};
+dec_huffman_lookup(16#11, 16#1) -> {more, 16#2f, 16#06};
+dec_huffman_lookup(16#11, 16#2) -> {more, 16#2f, 16#0a};
+dec_huffman_lookup(16#11, 16#3) -> {more, 16#2f, 16#0f};
+dec_huffman_lookup(16#11, 16#4) -> {more, 16#2f, 16#18};
+dec_huffman_lookup(16#11, 16#5) -> {more, 16#2f, 16#1f};
+dec_huffman_lookup(16#11, 16#6) -> {more, 16#2f, 16#29};
+dec_huffman_lookup(16#11, 16#7) -> {ok, 16#2f, 16#38};
+dec_huffman_lookup(16#11, 16#8) -> {more, 16#33, 16#03};
+dec_huffman_lookup(16#11, 16#9) -> {more, 16#33, 16#06};
+dec_huffman_lookup(16#11, 16#a) -> {more, 16#33, 16#0a};
+dec_huffman_lookup(16#11, 16#b) -> {more, 16#33, 16#0f};
+dec_huffman_lookup(16#11, 16#c) -> {more, 16#33, 16#18};
+dec_huffman_lookup(16#11, 16#d) -> {more, 16#33, 16#1f};
+dec_huffman_lookup(16#11, 16#e) -> {more, 16#33, 16#29};
+dec_huffman_lookup(16#11, 16#f) -> {ok, 16#33, 16#38};
+dec_huffman_lookup(16#12, 16#0) -> {more, 16#34, 16#03};
+dec_huffman_lookup(16#12, 16#1) -> {more, 16#34, 16#06};
+dec_huffman_lookup(16#12, 16#2) -> {more, 16#34, 16#0a};
+dec_huffman_lookup(16#12, 16#3) -> {more, 16#34, 16#0f};
+dec_huffman_lookup(16#12, 16#4) -> {more, 16#34, 16#18};
+dec_huffman_lookup(16#12, 16#5) -> {more, 16#34, 16#1f};
+dec_huffman_lookup(16#12, 16#6) -> {more, 16#34, 16#29};
+dec_huffman_lookup(16#12, 16#7) -> {ok, 16#34, 16#38};
+dec_huffman_lookup(16#12, 16#8) -> {more, 16#35, 16#03};
+dec_huffman_lookup(16#12, 16#9) -> {more, 16#35, 16#06};
+dec_huffman_lookup(16#12, 16#a) -> {more, 16#35, 16#0a};
+dec_huffman_lookup(16#12, 16#b) -> {more, 16#35, 16#0f};
+dec_huffman_lookup(16#12, 16#c) -> {more, 16#35, 16#18};
+dec_huffman_lookup(16#12, 16#d) -> {more, 16#35, 16#1f};
+dec_huffman_lookup(16#12, 16#e) -> {more, 16#35, 16#29};
+dec_huffman_lookup(16#12, 16#f) -> {ok, 16#35, 16#38};
+dec_huffman_lookup(16#13, 16#0) -> {more, 16#36, 16#02};
+dec_huffman_lookup(16#13, 16#1) -> {more, 16#36, 16#09};
+dec_huffman_lookup(16#13, 16#2) -> {more, 16#36, 16#17};
+dec_huffman_lookup(16#13, 16#3) -> {ok, 16#36, 16#28};
+dec_huffman_lookup(16#13, 16#4) -> {more, 16#37, 16#02};
+dec_huffman_lookup(16#13, 16#5) -> {more, 16#37, 16#09};
+dec_huffman_lookup(16#13, 16#6) -> {more, 16#37, 16#17};
+dec_huffman_lookup(16#13, 16#7) -> {ok, 16#37, 16#28};
+dec_huffman_lookup(16#13, 16#8) -> {more, 16#38, 16#02};
+dec_huffman_lookup(16#13, 16#9) -> {more, 16#38, 16#09};
+dec_huffman_lookup(16#13, 16#a) -> {more, 16#38, 16#17};
+dec_huffman_lookup(16#13, 16#b) -> {ok, 16#38, 16#28};
+dec_huffman_lookup(16#13, 16#c) -> {more, 16#39, 16#02};
+dec_huffman_lookup(16#13, 16#d) -> {more, 16#39, 16#09};
+dec_huffman_lookup(16#13, 16#e) -> {more, 16#39, 16#17};
+dec_huffman_lookup(16#13, 16#f) -> {ok, 16#39, 16#28};
+dec_huffman_lookup(16#14, 16#0) -> {more, 16#36, 16#03};
+dec_huffman_lookup(16#14, 16#1) -> {more, 16#36, 16#06};
+dec_huffman_lookup(16#14, 16#2) -> {more, 16#36, 16#0a};
+dec_huffman_lookup(16#14, 16#3) -> {more, 16#36, 16#0f};
+dec_huffman_lookup(16#14, 16#4) -> {more, 16#36, 16#18};
+dec_huffman_lookup(16#14, 16#5) -> {more, 16#36, 16#1f};
+dec_huffman_lookup(16#14, 16#6) -> {more, 16#36, 16#29};
+dec_huffman_lookup(16#14, 16#7) -> {ok, 16#36, 16#38};
+dec_huffman_lookup(16#14, 16#8) -> {more, 16#37, 16#03};
+dec_huffman_lookup(16#14, 16#9) -> {more, 16#37, 16#06};
+dec_huffman_lookup(16#14, 16#a) -> {more, 16#37, 16#0a};
+dec_huffman_lookup(16#14, 16#b) -> {more, 16#37, 16#0f};
+dec_huffman_lookup(16#14, 16#c) -> {more, 16#37, 16#18};
+dec_huffman_lookup(16#14, 16#d) -> {more, 16#37, 16#1f};
+dec_huffman_lookup(16#14, 16#e) -> {more, 16#37, 16#29};
+dec_huffman_lookup(16#14, 16#f) -> {ok, 16#37, 16#38};
+dec_huffman_lookup(16#15, 16#0) -> {more, 16#38, 16#03};
+dec_huffman_lookup(16#15, 16#1) -> {more, 16#38, 16#06};
+dec_huffman_lookup(16#15, 16#2) -> {more, 16#38, 16#0a};
+dec_huffman_lookup(16#15, 16#3) -> {more, 16#38, 16#0f};
+dec_huffman_lookup(16#15, 16#4) -> {more, 16#38, 16#18};
+dec_huffman_lookup(16#15, 16#5) -> {more, 16#38, 16#1f};
+dec_huffman_lookup(16#15, 16#6) -> {more, 16#38, 16#29};
+dec_huffman_lookup(16#15, 16#7) -> {ok, 16#38, 16#38};
+dec_huffman_lookup(16#15, 16#8) -> {more, 16#39, 16#03};
+dec_huffman_lookup(16#15, 16#9) -> {more, 16#39, 16#06};
+dec_huffman_lookup(16#15, 16#a) -> {more, 16#39, 16#0a};
+dec_huffman_lookup(16#15, 16#b) -> {more, 16#39, 16#0f};
+dec_huffman_lookup(16#15, 16#c) -> {more, 16#39, 16#18};
+dec_huffman_lookup(16#15, 16#d) -> {more, 16#39, 16#1f};
+dec_huffman_lookup(16#15, 16#e) -> {more, 16#39, 16#29};
+dec_huffman_lookup(16#15, 16#f) -> {ok, 16#39, 16#38};
+dec_huffman_lookup(16#16, 16#0) -> {more, undefined, 16#1a};
+dec_huffman_lookup(16#16, 16#1) -> {more, undefined, 16#1b};
+dec_huffman_lookup(16#16, 16#2) -> {more, undefined, 16#1d};
+dec_huffman_lookup(16#16, 16#3) -> {more, undefined, 16#1e};
+dec_huffman_lookup(16#16, 16#4) -> {more, undefined, 16#21};
+dec_huffman_lookup(16#16, 16#5) -> {more, undefined, 16#22};
+dec_huffman_lookup(16#16, 16#6) -> {more, undefined, 16#24};
+dec_huffman_lookup(16#16, 16#7) -> {more, undefined, 16#25};
+dec_huffman_lookup(16#16, 16#8) -> {more, undefined, 16#2b};
+dec_huffman_lookup(16#16, 16#9) -> {more, undefined, 16#2e};
+dec_huffman_lookup(16#16, 16#a) -> {more, undefined, 16#32};
+dec_huffman_lookup(16#16, 16#b) -> {more, undefined, 16#35};
+dec_huffman_lookup(16#16, 16#c) -> {more, undefined, 16#3a};
+dec_huffman_lookup(16#16, 16#d) -> {more, undefined, 16#3d};
+dec_huffman_lookup(16#16, 16#e) -> {more, undefined, 16#41};
+dec_huffman_lookup(16#16, 16#f) -> {ok, undefined, 16#44};
+dec_huffman_lookup(16#17, 16#0) -> {ok, 16#3d, 16#00};
+dec_huffman_lookup(16#17, 16#1) -> {ok, 16#41, 16#00};
+dec_huffman_lookup(16#17, 16#2) -> {ok, 16#5f, 16#00};
+dec_huffman_lookup(16#17, 16#3) -> {ok, 16#62, 16#00};
+dec_huffman_lookup(16#17, 16#4) -> {ok, 16#64, 16#00};
+dec_huffman_lookup(16#17, 16#5) -> {ok, 16#66, 16#00};
+dec_huffman_lookup(16#17, 16#6) -> {ok, 16#67, 16#00};
+dec_huffman_lookup(16#17, 16#7) -> {ok, 16#68, 16#00};
+dec_huffman_lookup(16#17, 16#8) -> {ok, 16#6c, 16#00};
+dec_huffman_lookup(16#17, 16#9) -> {ok, 16#6d, 16#00};
+dec_huffman_lookup(16#17, 16#a) -> {ok, 16#6e, 16#00};
+dec_huffman_lookup(16#17, 16#b) -> {ok, 16#70, 16#00};
+dec_huffman_lookup(16#17, 16#c) -> {ok, 16#72, 16#00};
+dec_huffman_lookup(16#17, 16#d) -> {ok, 16#75, 16#00};
+dec_huffman_lookup(16#17, 16#e) -> {more, undefined, 16#26};
+dec_huffman_lookup(16#17, 16#f) -> {more, undefined, 16#27};
+dec_huffman_lookup(16#18, 16#0) -> {more, 16#3d, 16#01};
+dec_huffman_lookup(16#18, 16#1) -> {ok, 16#3d, 16#16};
+dec_huffman_lookup(16#18, 16#2) -> {more, 16#41, 16#01};
+dec_huffman_lookup(16#18, 16#3) -> {ok, 16#41, 16#16};
+dec_huffman_lookup(16#18, 16#4) -> {more, 16#5f, 16#01};
+dec_huffman_lookup(16#18, 16#5) -> {ok, 16#5f, 16#16};
+dec_huffman_lookup(16#18, 16#6) -> {more, 16#62, 16#01};
+dec_huffman_lookup(16#18, 16#7) -> {ok, 16#62, 16#16};
+dec_huffman_lookup(16#18, 16#8) -> {more, 16#64, 16#01};
+dec_huffman_lookup(16#18, 16#9) -> {ok, 16#64, 16#16};
+dec_huffman_lookup(16#18, 16#a) -> {more, 16#66, 16#01};
+dec_huffman_lookup(16#18, 16#b) -> {ok, 16#66, 16#16};
+dec_huffman_lookup(16#18, 16#c) -> {more, 16#67, 16#01};
+dec_huffman_lookup(16#18, 16#d) -> {ok, 16#67, 16#16};
+dec_huffman_lookup(16#18, 16#e) -> {more, 16#68, 16#01};
+dec_huffman_lookup(16#18, 16#f) -> {ok, 16#68, 16#16};
+dec_huffman_lookup(16#19, 16#0) -> {more, 16#3d, 16#02};
+dec_huffman_lookup(16#19, 16#1) -> {more, 16#3d, 16#09};
+dec_huffman_lookup(16#19, 16#2) -> {more, 16#3d, 16#17};
+dec_huffman_lookup(16#19, 16#3) -> {ok, 16#3d, 16#28};
+dec_huffman_lookup(16#19, 16#4) -> {more, 16#41, 16#02};
+dec_huffman_lookup(16#19, 16#5) -> {more, 16#41, 16#09};
+dec_huffman_lookup(16#19, 16#6) -> {more, 16#41, 16#17};
+dec_huffman_lookup(16#19, 16#7) -> {ok, 16#41, 16#28};
+dec_huffman_lookup(16#19, 16#8) -> {more, 16#5f, 16#02};
+dec_huffman_lookup(16#19, 16#9) -> {more, 16#5f, 16#09};
+dec_huffman_lookup(16#19, 16#a) -> {more, 16#5f, 16#17};
+dec_huffman_lookup(16#19, 16#b) -> {ok, 16#5f, 16#28};
+dec_huffman_lookup(16#19, 16#c) -> {more, 16#62, 16#02};
+dec_huffman_lookup(16#19, 16#d) -> {more, 16#62, 16#09};
+dec_huffman_lookup(16#19, 16#e) -> {more, 16#62, 16#17};
+dec_huffman_lookup(16#19, 16#f) -> {ok, 16#62, 16#28};
+dec_huffman_lookup(16#1a, 16#0) -> {more, 16#3d, 16#03};
+dec_huffman_lookup(16#1a, 16#1) -> {more, 16#3d, 16#06};
+dec_huffman_lookup(16#1a, 16#2) -> {more, 16#3d, 16#0a};
+dec_huffman_lookup(16#1a, 16#3) -> {more, 16#3d, 16#0f};
+dec_huffman_lookup(16#1a, 16#4) -> {more, 16#3d, 16#18};
+dec_huffman_lookup(16#1a, 16#5) -> {more, 16#3d, 16#1f};
+dec_huffman_lookup(16#1a, 16#6) -> {more, 16#3d, 16#29};
+dec_huffman_lookup(16#1a, 16#7) -> {ok, 16#3d, 16#38};
+dec_huffman_lookup(16#1a, 16#8) -> {more, 16#41, 16#03};
+dec_huffman_lookup(16#1a, 16#9) -> {more, 16#41, 16#06};
+dec_huffman_lookup(16#1a, 16#a) -> {more, 16#41, 16#0a};
+dec_huffman_lookup(16#1a, 16#b) -> {more, 16#41, 16#0f};
+dec_huffman_lookup(16#1a, 16#c) -> {more, 16#41, 16#18};
+dec_huffman_lookup(16#1a, 16#d) -> {more, 16#41, 16#1f};
+dec_huffman_lookup(16#1a, 16#e) -> {more, 16#41, 16#29};
+dec_huffman_lookup(16#1a, 16#f) -> {ok, 16#41, 16#38};
+dec_huffman_lookup(16#1b, 16#0) -> {more, 16#5f, 16#03};
+dec_huffman_lookup(16#1b, 16#1) -> {more, 16#5f, 16#06};
+dec_huffman_lookup(16#1b, 16#2) -> {more, 16#5f, 16#0a};
+dec_huffman_lookup(16#1b, 16#3) -> {more, 16#5f, 16#0f};
+dec_huffman_lookup(16#1b, 16#4) -> {more, 16#5f, 16#18};
+dec_huffman_lookup(16#1b, 16#5) -> {more, 16#5f, 16#1f};
+dec_huffman_lookup(16#1b, 16#6) -> {more, 16#5f, 16#29};
+dec_huffman_lookup(16#1b, 16#7) -> {ok, 16#5f, 16#38};
+dec_huffman_lookup(16#1b, 16#8) -> {more, 16#62, 16#03};
+dec_huffman_lookup(16#1b, 16#9) -> {more, 16#62, 16#06};
+dec_huffman_lookup(16#1b, 16#a) -> {more, 16#62, 16#0a};
+dec_huffman_lookup(16#1b, 16#b) -> {more, 16#62, 16#0f};
+dec_huffman_lookup(16#1b, 16#c) -> {more, 16#62, 16#18};
+dec_huffman_lookup(16#1b, 16#d) -> {more, 16#62, 16#1f};
+dec_huffman_lookup(16#1b, 16#e) -> {more, 16#62, 16#29};
+dec_huffman_lookup(16#1b, 16#f) -> {ok, 16#62, 16#38};
+dec_huffman_lookup(16#1c, 16#0) -> {more, 16#64, 16#02};
+dec_huffman_lookup(16#1c, 16#1) -> {more, 16#64, 16#09};
+dec_huffman_lookup(16#1c, 16#2) -> {more, 16#64, 16#17};
+dec_huffman_lookup(16#1c, 16#3) -> {ok, 16#64, 16#28};
+dec_huffman_lookup(16#1c, 16#4) -> {more, 16#66, 16#02};
+dec_huffman_lookup(16#1c, 16#5) -> {more, 16#66, 16#09};
+dec_huffman_lookup(16#1c, 16#6) -> {more, 16#66, 16#17};
+dec_huffman_lookup(16#1c, 16#7) -> {ok, 16#66, 16#28};
+dec_huffman_lookup(16#1c, 16#8) -> {more, 16#67, 16#02};
+dec_huffman_lookup(16#1c, 16#9) -> {more, 16#67, 16#09};
+dec_huffman_lookup(16#1c, 16#a) -> {more, 16#67, 16#17};
+dec_huffman_lookup(16#1c, 16#b) -> {ok, 16#67, 16#28};
+dec_huffman_lookup(16#1c, 16#c) -> {more, 16#68, 16#02};
+dec_huffman_lookup(16#1c, 16#d) -> {more, 16#68, 16#09};
+dec_huffman_lookup(16#1c, 16#e) -> {more, 16#68, 16#17};
+dec_huffman_lookup(16#1c, 16#f) -> {ok, 16#68, 16#28};
+dec_huffman_lookup(16#1d, 16#0) -> {more, 16#64, 16#03};
+dec_huffman_lookup(16#1d, 16#1) -> {more, 16#64, 16#06};
+dec_huffman_lookup(16#1d, 16#2) -> {more, 16#64, 16#0a};
+dec_huffman_lookup(16#1d, 16#3) -> {more, 16#64, 16#0f};
+dec_huffman_lookup(16#1d, 16#4) -> {more, 16#64, 16#18};
+dec_huffman_lookup(16#1d, 16#5) -> {more, 16#64, 16#1f};
+dec_huffman_lookup(16#1d, 16#6) -> {more, 16#64, 16#29};
+dec_huffman_lookup(16#1d, 16#7) -> {ok, 16#64, 16#38};
+dec_huffman_lookup(16#1d, 16#8) -> {more, 16#66, 16#03};
+dec_huffman_lookup(16#1d, 16#9) -> {more, 16#66, 16#06};
+dec_huffman_lookup(16#1d, 16#a) -> {more, 16#66, 16#0a};
+dec_huffman_lookup(16#1d, 16#b) -> {more, 16#66, 16#0f};
+dec_huffman_lookup(16#1d, 16#c) -> {more, 16#66, 16#18};
+dec_huffman_lookup(16#1d, 16#d) -> {more, 16#66, 16#1f};
+dec_huffman_lookup(16#1d, 16#e) -> {more, 16#66, 16#29};
+dec_huffman_lookup(16#1d, 16#f) -> {ok, 16#66, 16#38};
+dec_huffman_lookup(16#1e, 16#0) -> {more, 16#67, 16#03};
+dec_huffman_lookup(16#1e, 16#1) -> {more, 16#67, 16#06};
+dec_huffman_lookup(16#1e, 16#2) -> {more, 16#67, 16#0a};
+dec_huffman_lookup(16#1e, 16#3) -> {more, 16#67, 16#0f};
+dec_huffman_lookup(16#1e, 16#4) -> {more, 16#67, 16#18};
+dec_huffman_lookup(16#1e, 16#5) -> {more, 16#67, 16#1f};
+dec_huffman_lookup(16#1e, 16#6) -> {more, 16#67, 16#29};
+dec_huffman_lookup(16#1e, 16#7) -> {ok, 16#67, 16#38};
+dec_huffman_lookup(16#1e, 16#8) -> {more, 16#68, 16#03};
+dec_huffman_lookup(16#1e, 16#9) -> {more, 16#68, 16#06};
+dec_huffman_lookup(16#1e, 16#a) -> {more, 16#68, 16#0a};
+dec_huffman_lookup(16#1e, 16#b) -> {more, 16#68, 16#0f};
+dec_huffman_lookup(16#1e, 16#c) -> {more, 16#68, 16#18};
+dec_huffman_lookup(16#1e, 16#d) -> {more, 16#68, 16#1f};
+dec_huffman_lookup(16#1e, 16#e) -> {more, 16#68, 16#29};
+dec_huffman_lookup(16#1e, 16#f) -> {ok, 16#68, 16#38};
+dec_huffman_lookup(16#1f, 16#0) -> {more, 16#6c, 16#01};
+dec_huffman_lookup(16#1f, 16#1) -> {ok, 16#6c, 16#16};
+dec_huffman_lookup(16#1f, 16#2) -> {more, 16#6d, 16#01};
+dec_huffman_lookup(16#1f, 16#3) -> {ok, 16#6d, 16#16};
+dec_huffman_lookup(16#1f, 16#4) -> {more, 16#6e, 16#01};
+dec_huffman_lookup(16#1f, 16#5) -> {ok, 16#6e, 16#16};
+dec_huffman_lookup(16#1f, 16#6) -> {more, 16#70, 16#01};
+dec_huffman_lookup(16#1f, 16#7) -> {ok, 16#70, 16#16};
+dec_huffman_lookup(16#1f, 16#8) -> {more, 16#72, 16#01};
+dec_huffman_lookup(16#1f, 16#9) -> {ok, 16#72, 16#16};
+dec_huffman_lookup(16#1f, 16#a) -> {more, 16#75, 16#01};
+dec_huffman_lookup(16#1f, 16#b) -> {ok, 16#75, 16#16};
+dec_huffman_lookup(16#1f, 16#c) -> {ok, 16#3a, 16#00};
+dec_huffman_lookup(16#1f, 16#d) -> {ok, 16#42, 16#00};
+dec_huffman_lookup(16#1f, 16#e) -> {ok, 16#43, 16#00};
+dec_huffman_lookup(16#1f, 16#f) -> {ok, 16#44, 16#00};
+dec_huffman_lookup(16#20, 16#0) -> {more, 16#6c, 16#02};
+dec_huffman_lookup(16#20, 16#1) -> {more, 16#6c, 16#09};
+dec_huffman_lookup(16#20, 16#2) -> {more, 16#6c, 16#17};
+dec_huffman_lookup(16#20, 16#3) -> {ok, 16#6c, 16#28};
+dec_huffman_lookup(16#20, 16#4) -> {more, 16#6d, 16#02};
+dec_huffman_lookup(16#20, 16#5) -> {more, 16#6d, 16#09};
+dec_huffman_lookup(16#20, 16#6) -> {more, 16#6d, 16#17};
+dec_huffman_lookup(16#20, 16#7) -> {ok, 16#6d, 16#28};
+dec_huffman_lookup(16#20, 16#8) -> {more, 16#6e, 16#02};
+dec_huffman_lookup(16#20, 16#9) -> {more, 16#6e, 16#09};
+dec_huffman_lookup(16#20, 16#a) -> {more, 16#6e, 16#17};
+dec_huffman_lookup(16#20, 16#b) -> {ok, 16#6e, 16#28};
+dec_huffman_lookup(16#20, 16#c) -> {more, 16#70, 16#02};
+dec_huffman_lookup(16#20, 16#d) -> {more, 16#70, 16#09};
+dec_huffman_lookup(16#20, 16#e) -> {more, 16#70, 16#17};
+dec_huffman_lookup(16#20, 16#f) -> {ok, 16#70, 16#28};
+dec_huffman_lookup(16#21, 16#0) -> {more, 16#6c, 16#03};
+dec_huffman_lookup(16#21, 16#1) -> {more, 16#6c, 16#06};
+dec_huffman_lookup(16#21, 16#2) -> {more, 16#6c, 16#0a};
+dec_huffman_lookup(16#21, 16#3) -> {more, 16#6c, 16#0f};
+dec_huffman_lookup(16#21, 16#4) -> {more, 16#6c, 16#18};
+dec_huffman_lookup(16#21, 16#5) -> {more, 16#6c, 16#1f};
+dec_huffman_lookup(16#21, 16#6) -> {more, 16#6c, 16#29};
+dec_huffman_lookup(16#21, 16#7) -> {ok, 16#6c, 16#38};
+dec_huffman_lookup(16#21, 16#8) -> {more, 16#6d, 16#03};
+dec_huffman_lookup(16#21, 16#9) -> {more, 16#6d, 16#06};
+dec_huffman_lookup(16#21, 16#a) -> {more, 16#6d, 16#0a};
+dec_huffman_lookup(16#21, 16#b) -> {more, 16#6d, 16#0f};
+dec_huffman_lookup(16#21, 16#c) -> {more, 16#6d, 16#18};
+dec_huffman_lookup(16#21, 16#d) -> {more, 16#6d, 16#1f};
+dec_huffman_lookup(16#21, 16#e) -> {more, 16#6d, 16#29};
+dec_huffman_lookup(16#21, 16#f) -> {ok, 16#6d, 16#38};
+dec_huffman_lookup(16#22, 16#0) -> {more, 16#6e, 16#03};
+dec_huffman_lookup(16#22, 16#1) -> {more, 16#6e, 16#06};
+dec_huffman_lookup(16#22, 16#2) -> {more, 16#6e, 16#0a};
+dec_huffman_lookup(16#22, 16#3) -> {more, 16#6e, 16#0f};
+dec_huffman_lookup(16#22, 16#4) -> {more, 16#6e, 16#18};
+dec_huffman_lookup(16#22, 16#5) -> {more, 16#6e, 16#1f};
+dec_huffman_lookup(16#22, 16#6) -> {more, 16#6e, 16#29};
+dec_huffman_lookup(16#22, 16#7) -> {ok, 16#6e, 16#38};
+dec_huffman_lookup(16#22, 16#8) -> {more, 16#70, 16#03};
+dec_huffman_lookup(16#22, 16#9) -> {more, 16#70, 16#06};
+dec_huffman_lookup(16#22, 16#a) -> {more, 16#70, 16#0a};
+dec_huffman_lookup(16#22, 16#b) -> {more, 16#70, 16#0f};
+dec_huffman_lookup(16#22, 16#c) -> {more, 16#70, 16#18};
+dec_huffman_lookup(16#22, 16#d) -> {more, 16#70, 16#1f};
+dec_huffman_lookup(16#22, 16#e) -> {more, 16#70, 16#29};
+dec_huffman_lookup(16#22, 16#f) -> {ok, 16#70, 16#38};
+dec_huffman_lookup(16#23, 16#0) -> {more, 16#72, 16#02};
+dec_huffman_lookup(16#23, 16#1) -> {more, 16#72, 16#09};
+dec_huffman_lookup(16#23, 16#2) -> {more, 16#72, 16#17};
+dec_huffman_lookup(16#23, 16#3) -> {ok, 16#72, 16#28};
+dec_huffman_lookup(16#23, 16#4) -> {more, 16#75, 16#02};
+dec_huffman_lookup(16#23, 16#5) -> {more, 16#75, 16#09};
+dec_huffman_lookup(16#23, 16#6) -> {more, 16#75, 16#17};
+dec_huffman_lookup(16#23, 16#7) -> {ok, 16#75, 16#28};
+dec_huffman_lookup(16#23, 16#8) -> {more, 16#3a, 16#01};
+dec_huffman_lookup(16#23, 16#9) -> {ok, 16#3a, 16#16};
+dec_huffman_lookup(16#23, 16#a) -> {more, 16#42, 16#01};
+dec_huffman_lookup(16#23, 16#b) -> {ok, 16#42, 16#16};
+dec_huffman_lookup(16#23, 16#c) -> {more, 16#43, 16#01};
+dec_huffman_lookup(16#23, 16#d) -> {ok, 16#43, 16#16};
+dec_huffman_lookup(16#23, 16#e) -> {more, 16#44, 16#01};
+dec_huffman_lookup(16#23, 16#f) -> {ok, 16#44, 16#16};
+dec_huffman_lookup(16#24, 16#0) -> {more, 16#72, 16#03};
+dec_huffman_lookup(16#24, 16#1) -> {more, 16#72, 16#06};
+dec_huffman_lookup(16#24, 16#2) -> {more, 16#72, 16#0a};
+dec_huffman_lookup(16#24, 16#3) -> {more, 16#72, 16#0f};
+dec_huffman_lookup(16#24, 16#4) -> {more, 16#72, 16#18};
+dec_huffman_lookup(16#24, 16#5) -> {more, 16#72, 16#1f};
+dec_huffman_lookup(16#24, 16#6) -> {more, 16#72, 16#29};
+dec_huffman_lookup(16#24, 16#7) -> {ok, 16#72, 16#38};
+dec_huffman_lookup(16#24, 16#8) -> {more, 16#75, 16#03};
+dec_huffman_lookup(16#24, 16#9) -> {more, 16#75, 16#06};
+dec_huffman_lookup(16#24, 16#a) -> {more, 16#75, 16#0a};
+dec_huffman_lookup(16#24, 16#b) -> {more, 16#75, 16#0f};
+dec_huffman_lookup(16#24, 16#c) -> {more, 16#75, 16#18};
+dec_huffman_lookup(16#24, 16#d) -> {more, 16#75, 16#1f};
+dec_huffman_lookup(16#24, 16#e) -> {more, 16#75, 16#29};
+dec_huffman_lookup(16#24, 16#f) -> {ok, 16#75, 16#38};
+dec_huffman_lookup(16#25, 16#0) -> {more, 16#3a, 16#02};
+dec_huffman_lookup(16#25, 16#1) -> {more, 16#3a, 16#09};
+dec_huffman_lookup(16#25, 16#2) -> {more, 16#3a, 16#17};
+dec_huffman_lookup(16#25, 16#3) -> {ok, 16#3a, 16#28};
+dec_huffman_lookup(16#25, 16#4) -> {more, 16#42, 16#02};
+dec_huffman_lookup(16#25, 16#5) -> {more, 16#42, 16#09};
+dec_huffman_lookup(16#25, 16#6) -> {more, 16#42, 16#17};
+dec_huffman_lookup(16#25, 16#7) -> {ok, 16#42, 16#28};
+dec_huffman_lookup(16#25, 16#8) -> {more, 16#43, 16#02};
+dec_huffman_lookup(16#25, 16#9) -> {more, 16#43, 16#09};
+dec_huffman_lookup(16#25, 16#a) -> {more, 16#43, 16#17};
+dec_huffman_lookup(16#25, 16#b) -> {ok, 16#43, 16#28};
+dec_huffman_lookup(16#25, 16#c) -> {more, 16#44, 16#02};
+dec_huffman_lookup(16#25, 16#d) -> {more, 16#44, 16#09};
+dec_huffman_lookup(16#25, 16#e) -> {more, 16#44, 16#17};
+dec_huffman_lookup(16#25, 16#f) -> {ok, 16#44, 16#28};
+dec_huffman_lookup(16#26, 16#0) -> {more, 16#3a, 16#03};
+dec_huffman_lookup(16#26, 16#1) -> {more, 16#3a, 16#06};
+dec_huffman_lookup(16#26, 16#2) -> {more, 16#3a, 16#0a};
+dec_huffman_lookup(16#26, 16#3) -> {more, 16#3a, 16#0f};
+dec_huffman_lookup(16#26, 16#4) -> {more, 16#3a, 16#18};
+dec_huffman_lookup(16#26, 16#5) -> {more, 16#3a, 16#1f};
+dec_huffman_lookup(16#26, 16#6) -> {more, 16#3a, 16#29};
+dec_huffman_lookup(16#26, 16#7) -> {ok, 16#3a, 16#38};
+dec_huffman_lookup(16#26, 16#8) -> {more, 16#42, 16#03};
+dec_huffman_lookup(16#26, 16#9) -> {more, 16#42, 16#06};
+dec_huffman_lookup(16#26, 16#a) -> {more, 16#42, 16#0a};
+dec_huffman_lookup(16#26, 16#b) -> {more, 16#42, 16#0f};
+dec_huffman_lookup(16#26, 16#c) -> {more, 16#42, 16#18};
+dec_huffman_lookup(16#26, 16#d) -> {more, 16#42, 16#1f};
+dec_huffman_lookup(16#26, 16#e) -> {more, 16#42, 16#29};
+dec_huffman_lookup(16#26, 16#f) -> {ok, 16#42, 16#38};
+dec_huffman_lookup(16#27, 16#0) -> {more, 16#43, 16#03};
+dec_huffman_lookup(16#27, 16#1) -> {more, 16#43, 16#06};
+dec_huffman_lookup(16#27, 16#2) -> {more, 16#43, 16#0a};
+dec_huffman_lookup(16#27, 16#3) -> {more, 16#43, 16#0f};
+dec_huffman_lookup(16#27, 16#4) -> {more, 16#43, 16#18};
+dec_huffman_lookup(16#27, 16#5) -> {more, 16#43, 16#1f};
+dec_huffman_lookup(16#27, 16#6) -> {more, 16#43, 16#29};
+dec_huffman_lookup(16#27, 16#7) -> {ok, 16#43, 16#38};
+dec_huffman_lookup(16#27, 16#8) -> {more, 16#44, 16#03};
+dec_huffman_lookup(16#27, 16#9) -> {more, 16#44, 16#06};
+dec_huffman_lookup(16#27, 16#a) -> {more, 16#44, 16#0a};
+dec_huffman_lookup(16#27, 16#b) -> {more, 16#44, 16#0f};
+dec_huffman_lookup(16#27, 16#c) -> {more, 16#44, 16#18};
+dec_huffman_lookup(16#27, 16#d) -> {more, 16#44, 16#1f};
+dec_huffman_lookup(16#27, 16#e) -> {more, 16#44, 16#29};
+dec_huffman_lookup(16#27, 16#f) -> {ok, 16#44, 16#38};
+dec_huffman_lookup(16#28, 16#0) -> {more, undefined, 16#2c};
+dec_huffman_lookup(16#28, 16#1) -> {more, undefined, 16#2d};
+dec_huffman_lookup(16#28, 16#2) -> {more, undefined, 16#2f};
+dec_huffman_lookup(16#28, 16#3) -> {more, undefined, 16#30};
+dec_huffman_lookup(16#28, 16#4) -> {more, undefined, 16#33};
+dec_huffman_lookup(16#28, 16#5) -> {more, undefined, 16#34};
+dec_huffman_lookup(16#28, 16#6) -> {more, undefined, 16#36};
+dec_huffman_lookup(16#28, 16#7) -> {more, undefined, 16#37};
+dec_huffman_lookup(16#28, 16#8) -> {more, undefined, 16#3b};
+dec_huffman_lookup(16#28, 16#9) -> {more, undefined, 16#3c};
+dec_huffman_lookup(16#28, 16#a) -> {more, undefined, 16#3e};
+dec_huffman_lookup(16#28, 16#b) -> {more, undefined, 16#3f};
+dec_huffman_lookup(16#28, 16#c) -> {more, undefined, 16#42};
+dec_huffman_lookup(16#28, 16#d) -> {more, undefined, 16#43};
+dec_huffman_lookup(16#28, 16#e) -> {more, undefined, 16#45};
+dec_huffman_lookup(16#28, 16#f) -> {ok, undefined, 16#48};
+dec_huffman_lookup(16#29, 16#0) -> {ok, 16#45, 16#00};
+dec_huffman_lookup(16#29, 16#1) -> {ok, 16#46, 16#00};
+dec_huffman_lookup(16#29, 16#2) -> {ok, 16#47, 16#00};
+dec_huffman_lookup(16#29, 16#3) -> {ok, 16#48, 16#00};
+dec_huffman_lookup(16#29, 16#4) -> {ok, 16#49, 16#00};
+dec_huffman_lookup(16#29, 16#5) -> {ok, 16#4a, 16#00};
+dec_huffman_lookup(16#29, 16#6) -> {ok, 16#4b, 16#00};
+dec_huffman_lookup(16#29, 16#7) -> {ok, 16#4c, 16#00};
+dec_huffman_lookup(16#29, 16#8) -> {ok, 16#4d, 16#00};
+dec_huffman_lookup(16#29, 16#9) -> {ok, 16#4e, 16#00};
+dec_huffman_lookup(16#29, 16#a) -> {ok, 16#4f, 16#00};
+dec_huffman_lookup(16#29, 16#b) -> {ok, 16#50, 16#00};
+dec_huffman_lookup(16#29, 16#c) -> {ok, 16#51, 16#00};
+dec_huffman_lookup(16#29, 16#d) -> {ok, 16#52, 16#00};
+dec_huffman_lookup(16#29, 16#e) -> {ok, 16#53, 16#00};
+dec_huffman_lookup(16#29, 16#f) -> {ok, 16#54, 16#00};
+dec_huffman_lookup(16#2a, 16#0) -> {more, 16#45, 16#01};
+dec_huffman_lookup(16#2a, 16#1) -> {ok, 16#45, 16#16};
+dec_huffman_lookup(16#2a, 16#2) -> {more, 16#46, 16#01};
+dec_huffman_lookup(16#2a, 16#3) -> {ok, 16#46, 16#16};
+dec_huffman_lookup(16#2a, 16#4) -> {more, 16#47, 16#01};
+dec_huffman_lookup(16#2a, 16#5) -> {ok, 16#47, 16#16};
+dec_huffman_lookup(16#2a, 16#6) -> {more, 16#48, 16#01};
+dec_huffman_lookup(16#2a, 16#7) -> {ok, 16#48, 16#16};
+dec_huffman_lookup(16#2a, 16#8) -> {more, 16#49, 16#01};
+dec_huffman_lookup(16#2a, 16#9) -> {ok, 16#49, 16#16};
+dec_huffman_lookup(16#2a, 16#a) -> {more, 16#4a, 16#01};
+dec_huffman_lookup(16#2a, 16#b) -> {ok, 16#4a, 16#16};
+dec_huffman_lookup(16#2a, 16#c) -> {more, 16#4b, 16#01};
+dec_huffman_lookup(16#2a, 16#d) -> {ok, 16#4b, 16#16};
+dec_huffman_lookup(16#2a, 16#e) -> {more, 16#4c, 16#01};
+dec_huffman_lookup(16#2a, 16#f) -> {ok, 16#4c, 16#16};
+dec_huffman_lookup(16#2b, 16#0) -> {more, 16#45, 16#02};
+dec_huffman_lookup(16#2b, 16#1) -> {more, 16#45, 16#09};
+dec_huffman_lookup(16#2b, 16#2) -> {more, 16#45, 16#17};
+dec_huffman_lookup(16#2b, 16#3) -> {ok, 16#45, 16#28};
+dec_huffman_lookup(16#2b, 16#4) -> {more, 16#46, 16#02};
+dec_huffman_lookup(16#2b, 16#5) -> {more, 16#46, 16#09};
+dec_huffman_lookup(16#2b, 16#6) -> {more, 16#46, 16#17};
+dec_huffman_lookup(16#2b, 16#7) -> {ok, 16#46, 16#28};
+dec_huffman_lookup(16#2b, 16#8) -> {more, 16#47, 16#02};
+dec_huffman_lookup(16#2b, 16#9) -> {more, 16#47, 16#09};
+dec_huffman_lookup(16#2b, 16#a) -> {more, 16#47, 16#17};
+dec_huffman_lookup(16#2b, 16#b) -> {ok, 16#47, 16#28};
+dec_huffman_lookup(16#2b, 16#c) -> {more, 16#48, 16#02};
+dec_huffman_lookup(16#2b, 16#d) -> {more, 16#48, 16#09};
+dec_huffman_lookup(16#2b, 16#e) -> {more, 16#48, 16#17};
+dec_huffman_lookup(16#2b, 16#f) -> {ok, 16#48, 16#28};
+dec_huffman_lookup(16#2c, 16#0) -> {more, 16#45, 16#03};
+dec_huffman_lookup(16#2c, 16#1) -> {more, 16#45, 16#06};
+dec_huffman_lookup(16#2c, 16#2) -> {more, 16#45, 16#0a};
+dec_huffman_lookup(16#2c, 16#3) -> {more, 16#45, 16#0f};
+dec_huffman_lookup(16#2c, 16#4) -> {more, 16#45, 16#18};
+dec_huffman_lookup(16#2c, 16#5) -> {more, 16#45, 16#1f};
+dec_huffman_lookup(16#2c, 16#6) -> {more, 16#45, 16#29};
+dec_huffman_lookup(16#2c, 16#7) -> {ok, 16#45, 16#38};
+dec_huffman_lookup(16#2c, 16#8) -> {more, 16#46, 16#03};
+dec_huffman_lookup(16#2c, 16#9) -> {more, 16#46, 16#06};
+dec_huffman_lookup(16#2c, 16#a) -> {more, 16#46, 16#0a};
+dec_huffman_lookup(16#2c, 16#b) -> {more, 16#46, 16#0f};
+dec_huffman_lookup(16#2c, 16#c) -> {more, 16#46, 16#18};
+dec_huffman_lookup(16#2c, 16#d) -> {more, 16#46, 16#1f};
+dec_huffman_lookup(16#2c, 16#e) -> {more, 16#46, 16#29};
+dec_huffman_lookup(16#2c, 16#f) -> {ok, 16#46, 16#38};
+dec_huffman_lookup(16#2d, 16#0) -> {more, 16#47, 16#03};
+dec_huffman_lookup(16#2d, 16#1) -> {more, 16#47, 16#06};
+dec_huffman_lookup(16#2d, 16#2) -> {more, 16#47, 16#0a};
+dec_huffman_lookup(16#2d, 16#3) -> {more, 16#47, 16#0f};
+dec_huffman_lookup(16#2d, 16#4) -> {more, 16#47, 16#18};
+dec_huffman_lookup(16#2d, 16#5) -> {more, 16#47, 16#1f};
+dec_huffman_lookup(16#2d, 16#6) -> {more, 16#47, 16#29};
+dec_huffman_lookup(16#2d, 16#7) -> {ok, 16#47, 16#38};
+dec_huffman_lookup(16#2d, 16#8) -> {more, 16#48, 16#03};
+dec_huffman_lookup(16#2d, 16#9) -> {more, 16#48, 16#06};
+dec_huffman_lookup(16#2d, 16#a) -> {more, 16#48, 16#0a};
+dec_huffman_lookup(16#2d, 16#b) -> {more, 16#48, 16#0f};
+dec_huffman_lookup(16#2d, 16#c) -> {more, 16#48, 16#18};
+dec_huffman_lookup(16#2d, 16#d) -> {more, 16#48, 16#1f};
+dec_huffman_lookup(16#2d, 16#e) -> {more, 16#48, 16#29};
+dec_huffman_lookup(16#2d, 16#f) -> {ok, 16#48, 16#38};
+dec_huffman_lookup(16#2e, 16#0) -> {more, 16#49, 16#02};
+dec_huffman_lookup(16#2e, 16#1) -> {more, 16#49, 16#09};
+dec_huffman_lookup(16#2e, 16#2) -> {more, 16#49, 16#17};
+dec_huffman_lookup(16#2e, 16#3) -> {ok, 16#49, 16#28};
+dec_huffman_lookup(16#2e, 16#4) -> {more, 16#4a, 16#02};
+dec_huffman_lookup(16#2e, 16#5) -> {more, 16#4a, 16#09};
+dec_huffman_lookup(16#2e, 16#6) -> {more, 16#4a, 16#17};
+dec_huffman_lookup(16#2e, 16#7) -> {ok, 16#4a, 16#28};
+dec_huffman_lookup(16#2e, 16#8) -> {more, 16#4b, 16#02};
+dec_huffman_lookup(16#2e, 16#9) -> {more, 16#4b, 16#09};
+dec_huffman_lookup(16#2e, 16#a) -> {more, 16#4b, 16#17};
+dec_huffman_lookup(16#2e, 16#b) -> {ok, 16#4b, 16#28};
+dec_huffman_lookup(16#2e, 16#c) -> {more, 16#4c, 16#02};
+dec_huffman_lookup(16#2e, 16#d) -> {more, 16#4c, 16#09};
+dec_huffman_lookup(16#2e, 16#e) -> {more, 16#4c, 16#17};
+dec_huffman_lookup(16#2e, 16#f) -> {ok, 16#4c, 16#28};
+dec_huffman_lookup(16#2f, 16#0) -> {more, 16#49, 16#03};
+dec_huffman_lookup(16#2f, 16#1) -> {more, 16#49, 16#06};
+dec_huffman_lookup(16#2f, 16#2) -> {more, 16#49, 16#0a};
+dec_huffman_lookup(16#2f, 16#3) -> {more, 16#49, 16#0f};
+dec_huffman_lookup(16#2f, 16#4) -> {more, 16#49, 16#18};
+dec_huffman_lookup(16#2f, 16#5) -> {more, 16#49, 16#1f};
+dec_huffman_lookup(16#2f, 16#6) -> {more, 16#49, 16#29};
+dec_huffman_lookup(16#2f, 16#7) -> {ok, 16#49, 16#38};
+dec_huffman_lookup(16#2f, 16#8) -> {more, 16#4a, 16#03};
+dec_huffman_lookup(16#2f, 16#9) -> {more, 16#4a, 16#06};
+dec_huffman_lookup(16#2f, 16#a) -> {more, 16#4a, 16#0a};
+dec_huffman_lookup(16#2f, 16#b) -> {more, 16#4a, 16#0f};
+dec_huffman_lookup(16#2f, 16#c) -> {more, 16#4a, 16#18};
+dec_huffman_lookup(16#2f, 16#d) -> {more, 16#4a, 16#1f};
+dec_huffman_lookup(16#2f, 16#e) -> {more, 16#4a, 16#29};
+dec_huffman_lookup(16#2f, 16#f) -> {ok, 16#4a, 16#38};
+dec_huffman_lookup(16#30, 16#0) -> {more, 16#4b, 16#03};
+dec_huffman_lookup(16#30, 16#1) -> {more, 16#4b, 16#06};
+dec_huffman_lookup(16#30, 16#2) -> {more, 16#4b, 16#0a};
+dec_huffman_lookup(16#30, 16#3) -> {more, 16#4b, 16#0f};
+dec_huffman_lookup(16#30, 16#4) -> {more, 16#4b, 16#18};
+dec_huffman_lookup(16#30, 16#5) -> {more, 16#4b, 16#1f};
+dec_huffman_lookup(16#30, 16#6) -> {more, 16#4b, 16#29};
+dec_huffman_lookup(16#30, 16#7) -> {ok, 16#4b, 16#38};
+dec_huffman_lookup(16#30, 16#8) -> {more, 16#4c, 16#03};
+dec_huffman_lookup(16#30, 16#9) -> {more, 16#4c, 16#06};
+dec_huffman_lookup(16#30, 16#a) -> {more, 16#4c, 16#0a};
+dec_huffman_lookup(16#30, 16#b) -> {more, 16#4c, 16#0f};
+dec_huffman_lookup(16#30, 16#c) -> {more, 16#4c, 16#18};
+dec_huffman_lookup(16#30, 16#d) -> {more, 16#4c, 16#1f};
+dec_huffman_lookup(16#30, 16#e) -> {more, 16#4c, 16#29};
+dec_huffman_lookup(16#30, 16#f) -> {ok, 16#4c, 16#38};
+dec_huffman_lookup(16#31, 16#0) -> {more, 16#4d, 16#01};
+dec_huffman_lookup(16#31, 16#1) -> {ok, 16#4d, 16#16};
+dec_huffman_lookup(16#31, 16#2) -> {more, 16#4e, 16#01};
+dec_huffman_lookup(16#31, 16#3) -> {ok, 16#4e, 16#16};
+dec_huffman_lookup(16#31, 16#4) -> {more, 16#4f, 16#01};
+dec_huffman_lookup(16#31, 16#5) -> {ok, 16#4f, 16#16};
+dec_huffman_lookup(16#31, 16#6) -> {more, 16#50, 16#01};
+dec_huffman_lookup(16#31, 16#7) -> {ok, 16#50, 16#16};
+dec_huffman_lookup(16#31, 16#8) -> {more, 16#51, 16#01};
+dec_huffman_lookup(16#31, 16#9) -> {ok, 16#51, 16#16};
+dec_huffman_lookup(16#31, 16#a) -> {more, 16#52, 16#01};
+dec_huffman_lookup(16#31, 16#b) -> {ok, 16#52, 16#16};
+dec_huffman_lookup(16#31, 16#c) -> {more, 16#53, 16#01};
+dec_huffman_lookup(16#31, 16#d) -> {ok, 16#53, 16#16};
+dec_huffman_lookup(16#31, 16#e) -> {more, 16#54, 16#01};
+dec_huffman_lookup(16#31, 16#f) -> {ok, 16#54, 16#16};
+dec_huffman_lookup(16#32, 16#0) -> {more, 16#4d, 16#02};
+dec_huffman_lookup(16#32, 16#1) -> {more, 16#4d, 16#09};
+dec_huffman_lookup(16#32, 16#2) -> {more, 16#4d, 16#17};
+dec_huffman_lookup(16#32, 16#3) -> {ok, 16#4d, 16#28};
+dec_huffman_lookup(16#32, 16#4) -> {more, 16#4e, 16#02};
+dec_huffman_lookup(16#32, 16#5) -> {more, 16#4e, 16#09};
+dec_huffman_lookup(16#32, 16#6) -> {more, 16#4e, 16#17};
+dec_huffman_lookup(16#32, 16#7) -> {ok, 16#4e, 16#28};
+dec_huffman_lookup(16#32, 16#8) -> {more, 16#4f, 16#02};
+dec_huffman_lookup(16#32, 16#9) -> {more, 16#4f, 16#09};
+dec_huffman_lookup(16#32, 16#a) -> {more, 16#4f, 16#17};
+dec_huffman_lookup(16#32, 16#b) -> {ok, 16#4f, 16#28};
+dec_huffman_lookup(16#32, 16#c) -> {more, 16#50, 16#02};
+dec_huffman_lookup(16#32, 16#d) -> {more, 16#50, 16#09};
+dec_huffman_lookup(16#32, 16#e) -> {more, 16#50, 16#17};
+dec_huffman_lookup(16#32, 16#f) -> {ok, 16#50, 16#28};
+dec_huffman_lookup(16#33, 16#0) -> {more, 16#4d, 16#03};
+dec_huffman_lookup(16#33, 16#1) -> {more, 16#4d, 16#06};
+dec_huffman_lookup(16#33, 16#2) -> {more, 16#4d, 16#0a};
+dec_huffman_lookup(16#33, 16#3) -> {more, 16#4d, 16#0f};
+dec_huffman_lookup(16#33, 16#4) -> {more, 16#4d, 16#18};
+dec_huffman_lookup(16#33, 16#5) -> {more, 16#4d, 16#1f};
+dec_huffman_lookup(16#33, 16#6) -> {more, 16#4d, 16#29};
+dec_huffman_lookup(16#33, 16#7) -> {ok, 16#4d, 16#38};
+dec_huffman_lookup(16#33, 16#8) -> {more, 16#4e, 16#03};
+dec_huffman_lookup(16#33, 16#9) -> {more, 16#4e, 16#06};
+dec_huffman_lookup(16#33, 16#a) -> {more, 16#4e, 16#0a};
+dec_huffman_lookup(16#33, 16#b) -> {more, 16#4e, 16#0f};
+dec_huffman_lookup(16#33, 16#c) -> {more, 16#4e, 16#18};
+dec_huffman_lookup(16#33, 16#d) -> {more, 16#4e, 16#1f};
+dec_huffman_lookup(16#33, 16#e) -> {more, 16#4e, 16#29};
+dec_huffman_lookup(16#33, 16#f) -> {ok, 16#4e, 16#38};
+dec_huffman_lookup(16#34, 16#0) -> {more, 16#4f, 16#03};
+dec_huffman_lookup(16#34, 16#1) -> {more, 16#4f, 16#06};
+dec_huffman_lookup(16#34, 16#2) -> {more, 16#4f, 16#0a};
+dec_huffman_lookup(16#34, 16#3) -> {more, 16#4f, 16#0f};
+dec_huffman_lookup(16#34, 16#4) -> {more, 16#4f, 16#18};
+dec_huffman_lookup(16#34, 16#5) -> {more, 16#4f, 16#1f};
+dec_huffman_lookup(16#34, 16#6) -> {more, 16#4f, 16#29};
+dec_huffman_lookup(16#34, 16#7) -> {ok, 16#4f, 16#38};
+dec_huffman_lookup(16#34, 16#8) -> {more, 16#50, 16#03};
+dec_huffman_lookup(16#34, 16#9) -> {more, 16#50, 16#06};
+dec_huffman_lookup(16#34, 16#a) -> {more, 16#50, 16#0a};
+dec_huffman_lookup(16#34, 16#b) -> {more, 16#50, 16#0f};
+dec_huffman_lookup(16#34, 16#c) -> {more, 16#50, 16#18};
+dec_huffman_lookup(16#34, 16#d) -> {more, 16#50, 16#1f};
+dec_huffman_lookup(16#34, 16#e) -> {more, 16#50, 16#29};
+dec_huffman_lookup(16#34, 16#f) -> {ok, 16#50, 16#38};
+dec_huffman_lookup(16#35, 16#0) -> {more, 16#51, 16#02};
+dec_huffman_lookup(16#35, 16#1) -> {more, 16#51, 16#09};
+dec_huffman_lookup(16#35, 16#2) -> {more, 16#51, 16#17};
+dec_huffman_lookup(16#35, 16#3) -> {ok, 16#51, 16#28};
+dec_huffman_lookup(16#35, 16#4) -> {more, 16#52, 16#02};
+dec_huffman_lookup(16#35, 16#5) -> {more, 16#52, 16#09};
+dec_huffman_lookup(16#35, 16#6) -> {more, 16#52, 16#17};
+dec_huffman_lookup(16#35, 16#7) -> {ok, 16#52, 16#28};
+dec_huffman_lookup(16#35, 16#8) -> {more, 16#53, 16#02};
+dec_huffman_lookup(16#35, 16#9) -> {more, 16#53, 16#09};
+dec_huffman_lookup(16#35, 16#a) -> {more, 16#53, 16#17};
+dec_huffman_lookup(16#35, 16#b) -> {ok, 16#53, 16#28};
+dec_huffman_lookup(16#35, 16#c) -> {more, 16#54, 16#02};
+dec_huffman_lookup(16#35, 16#d) -> {more, 16#54, 16#09};
+dec_huffman_lookup(16#35, 16#e) -> {more, 16#54, 16#17};
+dec_huffman_lookup(16#35, 16#f) -> {ok, 16#54, 16#28};
+dec_huffman_lookup(16#36, 16#0) -> {more, 16#51, 16#03};
+dec_huffman_lookup(16#36, 16#1) -> {more, 16#51, 16#06};
+dec_huffman_lookup(16#36, 16#2) -> {more, 16#51, 16#0a};
+dec_huffman_lookup(16#36, 16#3) -> {more, 16#51, 16#0f};
+dec_huffman_lookup(16#36, 16#4) -> {more, 16#51, 16#18};
+dec_huffman_lookup(16#36, 16#5) -> {more, 16#51, 16#1f};
+dec_huffman_lookup(16#36, 16#6) -> {more, 16#51, 16#29};
+dec_huffman_lookup(16#36, 16#7) -> {ok, 16#51, 16#38};
+dec_huffman_lookup(16#36, 16#8) -> {more, 16#52, 16#03};
+dec_huffman_lookup(16#36, 16#9) -> {more, 16#52, 16#06};
+dec_huffman_lookup(16#36, 16#a) -> {more, 16#52, 16#0a};
+dec_huffman_lookup(16#36, 16#b) -> {more, 16#52, 16#0f};
+dec_huffman_lookup(16#36, 16#c) -> {more, 16#52, 16#18};
+dec_huffman_lookup(16#36, 16#d) -> {more, 16#52, 16#1f};
+dec_huffman_lookup(16#36, 16#e) -> {more, 16#52, 16#29};
+dec_huffman_lookup(16#36, 16#f) -> {ok, 16#52, 16#38};
+dec_huffman_lookup(16#37, 16#0) -> {more, 16#53, 16#03};
+dec_huffman_lookup(16#37, 16#1) -> {more, 16#53, 16#06};
+dec_huffman_lookup(16#37, 16#2) -> {more, 16#53, 16#0a};
+dec_huffman_lookup(16#37, 16#3) -> {more, 16#53, 16#0f};
+dec_huffman_lookup(16#37, 16#4) -> {more, 16#53, 16#18};
+dec_huffman_lookup(16#37, 16#5) -> {more, 16#53, 16#1f};
+dec_huffman_lookup(16#37, 16#6) -> {more, 16#53, 16#29};
+dec_huffman_lookup(16#37, 16#7) -> {ok, 16#53, 16#38};
+dec_huffman_lookup(16#37, 16#8) -> {more, 16#54, 16#03};
+dec_huffman_lookup(16#37, 16#9) -> {more, 16#54, 16#06};
+dec_huffman_lookup(16#37, 16#a) -> {more, 16#54, 16#0a};
+dec_huffman_lookup(16#37, 16#b) -> {more, 16#54, 16#0f};
+dec_huffman_lookup(16#37, 16#c) -> {more, 16#54, 16#18};
+dec_huffman_lookup(16#37, 16#d) -> {more, 16#54, 16#1f};
+dec_huffman_lookup(16#37, 16#e) -> {more, 16#54, 16#29};
+dec_huffman_lookup(16#37, 16#f) -> {ok, 16#54, 16#38};
+dec_huffman_lookup(16#38, 16#0) -> {ok, 16#55, 16#00};
+dec_huffman_lookup(16#38, 16#1) -> {ok, 16#56, 16#00};
+dec_huffman_lookup(16#38, 16#2) -> {ok, 16#57, 16#00};
+dec_huffman_lookup(16#38, 16#3) -> {ok, 16#59, 16#00};
+dec_huffman_lookup(16#38, 16#4) -> {ok, 16#6a, 16#00};
+dec_huffman_lookup(16#38, 16#5) -> {ok, 16#6b, 16#00};
+dec_huffman_lookup(16#38, 16#6) -> {ok, 16#71, 16#00};
+dec_huffman_lookup(16#38, 16#7) -> {ok, 16#76, 16#00};
+dec_huffman_lookup(16#38, 16#8) -> {ok, 16#77, 16#00};
+dec_huffman_lookup(16#38, 16#9) -> {ok, 16#78, 16#00};
+dec_huffman_lookup(16#38, 16#a) -> {ok, 16#79, 16#00};
+dec_huffman_lookup(16#38, 16#b) -> {ok, 16#7a, 16#00};
+dec_huffman_lookup(16#38, 16#c) -> {more, undefined, 16#46};
+dec_huffman_lookup(16#38, 16#d) -> {more, undefined, 16#47};
+dec_huffman_lookup(16#38, 16#e) -> {more, undefined, 16#49};
+dec_huffman_lookup(16#38, 16#f) -> {ok, undefined, 16#4a};
+dec_huffman_lookup(16#39, 16#0) -> {more, 16#55, 16#01};
+dec_huffman_lookup(16#39, 16#1) -> {ok, 16#55, 16#16};
+dec_huffman_lookup(16#39, 16#2) -> {more, 16#56, 16#01};
+dec_huffman_lookup(16#39, 16#3) -> {ok, 16#56, 16#16};
+dec_huffman_lookup(16#39, 16#4) -> {more, 16#57, 16#01};
+dec_huffman_lookup(16#39, 16#5) -> {ok, 16#57, 16#16};
+dec_huffman_lookup(16#39, 16#6) -> {more, 16#59, 16#01};
+dec_huffman_lookup(16#39, 16#7) -> {ok, 16#59, 16#16};
+dec_huffman_lookup(16#39, 16#8) -> {more, 16#6a, 16#01};
+dec_huffman_lookup(16#39, 16#9) -> {ok, 16#6a, 16#16};
+dec_huffman_lookup(16#39, 16#a) -> {more, 16#6b, 16#01};
+dec_huffman_lookup(16#39, 16#b) -> {ok, 16#6b, 16#16};
+dec_huffman_lookup(16#39, 16#c) -> {more, 16#71, 16#01};
+dec_huffman_lookup(16#39, 16#d) -> {ok, 16#71, 16#16};
+dec_huffman_lookup(16#39, 16#e) -> {more, 16#76, 16#01};
+dec_huffman_lookup(16#39, 16#f) -> {ok, 16#76, 16#16};
+dec_huffman_lookup(16#3a, 16#0) -> {more, 16#55, 16#02};
+dec_huffman_lookup(16#3a, 16#1) -> {more, 16#55, 16#09};
+dec_huffman_lookup(16#3a, 16#2) -> {more, 16#55, 16#17};
+dec_huffman_lookup(16#3a, 16#3) -> {ok, 16#55, 16#28};
+dec_huffman_lookup(16#3a, 16#4) -> {more, 16#56, 16#02};
+dec_huffman_lookup(16#3a, 16#5) -> {more, 16#56, 16#09};
+dec_huffman_lookup(16#3a, 16#6) -> {more, 16#56, 16#17};
+dec_huffman_lookup(16#3a, 16#7) -> {ok, 16#56, 16#28};
+dec_huffman_lookup(16#3a, 16#8) -> {more, 16#57, 16#02};
+dec_huffman_lookup(16#3a, 16#9) -> {more, 16#57, 16#09};
+dec_huffman_lookup(16#3a, 16#a) -> {more, 16#57, 16#17};
+dec_huffman_lookup(16#3a, 16#b) -> {ok, 16#57, 16#28};
+dec_huffman_lookup(16#3a, 16#c) -> {more, 16#59, 16#02};
+dec_huffman_lookup(16#3a, 16#d) -> {more, 16#59, 16#09};
+dec_huffman_lookup(16#3a, 16#e) -> {more, 16#59, 16#17};
+dec_huffman_lookup(16#3a, 16#f) -> {ok, 16#59, 16#28};
+dec_huffman_lookup(16#3b, 16#0) -> {more, 16#55, 16#03};
+dec_huffman_lookup(16#3b, 16#1) -> {more, 16#55, 16#06};
+dec_huffman_lookup(16#3b, 16#2) -> {more, 16#55, 16#0a};
+dec_huffman_lookup(16#3b, 16#3) -> {more, 16#55, 16#0f};
+dec_huffman_lookup(16#3b, 16#4) -> {more, 16#55, 16#18};
+dec_huffman_lookup(16#3b, 16#5) -> {more, 16#55, 16#1f};
+dec_huffman_lookup(16#3b, 16#6) -> {more, 16#55, 16#29};
+dec_huffman_lookup(16#3b, 16#7) -> {ok, 16#55, 16#38};
+dec_huffman_lookup(16#3b, 16#8) -> {more, 16#56, 16#03};
+dec_huffman_lookup(16#3b, 16#9) -> {more, 16#56, 16#06};
+dec_huffman_lookup(16#3b, 16#a) -> {more, 16#56, 16#0a};
+dec_huffman_lookup(16#3b, 16#b) -> {more, 16#56, 16#0f};
+dec_huffman_lookup(16#3b, 16#c) -> {more, 16#56, 16#18};
+dec_huffman_lookup(16#3b, 16#d) -> {more, 16#56, 16#1f};
+dec_huffman_lookup(16#3b, 16#e) -> {more, 16#56, 16#29};
+dec_huffman_lookup(16#3b, 16#f) -> {ok, 16#56, 16#38};
+dec_huffman_lookup(16#3c, 16#0) -> {more, 16#57, 16#03};
+dec_huffman_lookup(16#3c, 16#1) -> {more, 16#57, 16#06};
+dec_huffman_lookup(16#3c, 16#2) -> {more, 16#57, 16#0a};
+dec_huffman_lookup(16#3c, 16#3) -> {more, 16#57, 16#0f};
+dec_huffman_lookup(16#3c, 16#4) -> {more, 16#57, 16#18};
+dec_huffman_lookup(16#3c, 16#5) -> {more, 16#57, 16#1f};
+dec_huffman_lookup(16#3c, 16#6) -> {more, 16#57, 16#29};
+dec_huffman_lookup(16#3c, 16#7) -> {ok, 16#57, 16#38};
+dec_huffman_lookup(16#3c, 16#8) -> {more, 16#59, 16#03};
+dec_huffman_lookup(16#3c, 16#9) -> {more, 16#59, 16#06};
+dec_huffman_lookup(16#3c, 16#a) -> {more, 16#59, 16#0a};
+dec_huffman_lookup(16#3c, 16#b) -> {more, 16#59, 16#0f};
+dec_huffman_lookup(16#3c, 16#c) -> {more, 16#59, 16#18};
+dec_huffman_lookup(16#3c, 16#d) -> {more, 16#59, 16#1f};
+dec_huffman_lookup(16#3c, 16#e) -> {more, 16#59, 16#29};
+dec_huffman_lookup(16#3c, 16#f) -> {ok, 16#59, 16#38};
+dec_huffman_lookup(16#3d, 16#0) -> {more, 16#6a, 16#02};
+dec_huffman_lookup(16#3d, 16#1) -> {more, 16#6a, 16#09};
+dec_huffman_lookup(16#3d, 16#2) -> {more, 16#6a, 16#17};
+dec_huffman_lookup(16#3d, 16#3) -> {ok, 16#6a, 16#28};
+dec_huffman_lookup(16#3d, 16#4) -> {more, 16#6b, 16#02};
+dec_huffman_lookup(16#3d, 16#5) -> {more, 16#6b, 16#09};
+dec_huffman_lookup(16#3d, 16#6) -> {more, 16#6b, 16#17};
+dec_huffman_lookup(16#3d, 16#7) -> {ok, 16#6b, 16#28};
+dec_huffman_lookup(16#3d, 16#8) -> {more, 16#71, 16#02};
+dec_huffman_lookup(16#3d, 16#9) -> {more, 16#71, 16#09};
+dec_huffman_lookup(16#3d, 16#a) -> {more, 16#71, 16#17};
+dec_huffman_lookup(16#3d, 16#b) -> {ok, 16#71, 16#28};
+dec_huffman_lookup(16#3d, 16#c) -> {more, 16#76, 16#02};
+dec_huffman_lookup(16#3d, 16#d) -> {more, 16#76, 16#09};
+dec_huffman_lookup(16#3d, 16#e) -> {more, 16#76, 16#17};
+dec_huffman_lookup(16#3d, 16#f) -> {ok, 16#76, 16#28};
+dec_huffman_lookup(16#3e, 16#0) -> {more, 16#6a, 16#03};
+dec_huffman_lookup(16#3e, 16#1) -> {more, 16#6a, 16#06};
+dec_huffman_lookup(16#3e, 16#2) -> {more, 16#6a, 16#0a};
+dec_huffman_lookup(16#3e, 16#3) -> {more, 16#6a, 16#0f};
+dec_huffman_lookup(16#3e, 16#4) -> {more, 16#6a, 16#18};
+dec_huffman_lookup(16#3e, 16#5) -> {more, 16#6a, 16#1f};
+dec_huffman_lookup(16#3e, 16#6) -> {more, 16#6a, 16#29};
+dec_huffman_lookup(16#3e, 16#7) -> {ok, 16#6a, 16#38};
+dec_huffman_lookup(16#3e, 16#8) -> {more, 16#6b, 16#03};
+dec_huffman_lookup(16#3e, 16#9) -> {more, 16#6b, 16#06};
+dec_huffman_lookup(16#3e, 16#a) -> {more, 16#6b, 16#0a};
+dec_huffman_lookup(16#3e, 16#b) -> {more, 16#6b, 16#0f};
+dec_huffman_lookup(16#3e, 16#c) -> {more, 16#6b, 16#18};
+dec_huffman_lookup(16#3e, 16#d) -> {more, 16#6b, 16#1f};
+dec_huffman_lookup(16#3e, 16#e) -> {more, 16#6b, 16#29};
+dec_huffman_lookup(16#3e, 16#f) -> {ok, 16#6b, 16#38};
+dec_huffman_lookup(16#3f, 16#0) -> {more, 16#71, 16#03};
+dec_huffman_lookup(16#3f, 16#1) -> {more, 16#71, 16#06};
+dec_huffman_lookup(16#3f, 16#2) -> {more, 16#71, 16#0a};
+dec_huffman_lookup(16#3f, 16#3) -> {more, 16#71, 16#0f};
+dec_huffman_lookup(16#3f, 16#4) -> {more, 16#71, 16#18};
+dec_huffman_lookup(16#3f, 16#5) -> {more, 16#71, 16#1f};
+dec_huffman_lookup(16#3f, 16#6) -> {more, 16#71, 16#29};
+dec_huffman_lookup(16#3f, 16#7) -> {ok, 16#71, 16#38};
+dec_huffman_lookup(16#3f, 16#8) -> {more, 16#76, 16#03};
+dec_huffman_lookup(16#3f, 16#9) -> {more, 16#76, 16#06};
+dec_huffman_lookup(16#3f, 16#a) -> {more, 16#76, 16#0a};
+dec_huffman_lookup(16#3f, 16#b) -> {more, 16#76, 16#0f};
+dec_huffman_lookup(16#3f, 16#c) -> {more, 16#76, 16#18};
+dec_huffman_lookup(16#3f, 16#d) -> {more, 16#76, 16#1f};
+dec_huffman_lookup(16#3f, 16#e) -> {more, 16#76, 16#29};
+dec_huffman_lookup(16#3f, 16#f) -> {ok, 16#76, 16#38};
+dec_huffman_lookup(16#40, 16#0) -> {more, 16#77, 16#01};
+dec_huffman_lookup(16#40, 16#1) -> {ok, 16#77, 16#16};
+dec_huffman_lookup(16#40, 16#2) -> {more, 16#78, 16#01};
+dec_huffman_lookup(16#40, 16#3) -> {ok, 16#78, 16#16};
+dec_huffman_lookup(16#40, 16#4) -> {more, 16#79, 16#01};
+dec_huffman_lookup(16#40, 16#5) -> {ok, 16#79, 16#16};
+dec_huffman_lookup(16#40, 16#6) -> {more, 16#7a, 16#01};
+dec_huffman_lookup(16#40, 16#7) -> {ok, 16#7a, 16#16};
+dec_huffman_lookup(16#40, 16#8) -> {ok, 16#26, 16#00};
+dec_huffman_lookup(16#40, 16#9) -> {ok, 16#2a, 16#00};
+dec_huffman_lookup(16#40, 16#a) -> {ok, 16#2c, 16#00};
+dec_huffman_lookup(16#40, 16#b) -> {ok, 16#3b, 16#00};
+dec_huffman_lookup(16#40, 16#c) -> {ok, 16#58, 16#00};
+dec_huffman_lookup(16#40, 16#d) -> {ok, 16#5a, 16#00};
+dec_huffman_lookup(16#40, 16#e) -> {more, undefined, 16#4b};
+dec_huffman_lookup(16#40, 16#f) -> {ok, undefined, 16#4e};
+dec_huffman_lookup(16#41, 16#0) -> {more, 16#77, 16#02};
+dec_huffman_lookup(16#41, 16#1) -> {more, 16#77, 16#09};
+dec_huffman_lookup(16#41, 16#2) -> {more, 16#77, 16#17};
+dec_huffman_lookup(16#41, 16#3) -> {ok, 16#77, 16#28};
+dec_huffman_lookup(16#41, 16#4) -> {more, 16#78, 16#02};
+dec_huffman_lookup(16#41, 16#5) -> {more, 16#78, 16#09};
+dec_huffman_lookup(16#41, 16#6) -> {more, 16#78, 16#17};
+dec_huffman_lookup(16#41, 16#7) -> {ok, 16#78, 16#28};
+dec_huffman_lookup(16#41, 16#8) -> {more, 16#79, 16#02};
+dec_huffman_lookup(16#41, 16#9) -> {more, 16#79, 16#09};
+dec_huffman_lookup(16#41, 16#a) -> {more, 16#79, 16#17};
+dec_huffman_lookup(16#41, 16#b) -> {ok, 16#79, 16#28};
+dec_huffman_lookup(16#41, 16#c) -> {more, 16#7a, 16#02};
+dec_huffman_lookup(16#41, 16#d) -> {more, 16#7a, 16#09};
+dec_huffman_lookup(16#41, 16#e) -> {more, 16#7a, 16#17};
+dec_huffman_lookup(16#41, 16#f) -> {ok, 16#7a, 16#28};
+dec_huffman_lookup(16#42, 16#0) -> {more, 16#77, 16#03};
+dec_huffman_lookup(16#42, 16#1) -> {more, 16#77, 16#06};
+dec_huffman_lookup(16#42, 16#2) -> {more, 16#77, 16#0a};
+dec_huffman_lookup(16#42, 16#3) -> {more, 16#77, 16#0f};
+dec_huffman_lookup(16#42, 16#4) -> {more, 16#77, 16#18};
+dec_huffman_lookup(16#42, 16#5) -> {more, 16#77, 16#1f};
+dec_huffman_lookup(16#42, 16#6) -> {more, 16#77, 16#29};
+dec_huffman_lookup(16#42, 16#7) -> {ok, 16#77, 16#38};
+dec_huffman_lookup(16#42, 16#8) -> {more, 16#78, 16#03};
+dec_huffman_lookup(16#42, 16#9) -> {more, 16#78, 16#06};
+dec_huffman_lookup(16#42, 16#a) -> {more, 16#78, 16#0a};
+dec_huffman_lookup(16#42, 16#b) -> {more, 16#78, 16#0f};
+dec_huffman_lookup(16#42, 16#c) -> {more, 16#78, 16#18};
+dec_huffman_lookup(16#42, 16#d) -> {more, 16#78, 16#1f};
+dec_huffman_lookup(16#42, 16#e) -> {more, 16#78, 16#29};
+dec_huffman_lookup(16#42, 16#f) -> {ok, 16#78, 16#38};
+dec_huffman_lookup(16#43, 16#0) -> {more, 16#79, 16#03};
+dec_huffman_lookup(16#43, 16#1) -> {more, 16#79, 16#06};
+dec_huffman_lookup(16#43, 16#2) -> {more, 16#79, 16#0a};
+dec_huffman_lookup(16#43, 16#3) -> {more, 16#79, 16#0f};
+dec_huffman_lookup(16#43, 16#4) -> {more, 16#79, 16#18};
+dec_huffman_lookup(16#43, 16#5) -> {more, 16#79, 16#1f};
+dec_huffman_lookup(16#43, 16#6) -> {more, 16#79, 16#29};
+dec_huffman_lookup(16#43, 16#7) -> {ok, 16#79, 16#38};
+dec_huffman_lookup(16#43, 16#8) -> {more, 16#7a, 16#03};
+dec_huffman_lookup(16#43, 16#9) -> {more, 16#7a, 16#06};
+dec_huffman_lookup(16#43, 16#a) -> {more, 16#7a, 16#0a};
+dec_huffman_lookup(16#43, 16#b) -> {more, 16#7a, 16#0f};
+dec_huffman_lookup(16#43, 16#c) -> {more, 16#7a, 16#18};
+dec_huffman_lookup(16#43, 16#d) -> {more, 16#7a, 16#1f};
+dec_huffman_lookup(16#43, 16#e) -> {more, 16#7a, 16#29};
+dec_huffman_lookup(16#43, 16#f) -> {ok, 16#7a, 16#38};
+dec_huffman_lookup(16#44, 16#0) -> {more, 16#26, 16#01};
+dec_huffman_lookup(16#44, 16#1) -> {ok, 16#26, 16#16};
+dec_huffman_lookup(16#44, 16#2) -> {more, 16#2a, 16#01};
+dec_huffman_lookup(16#44, 16#3) -> {ok, 16#2a, 16#16};
+dec_huffman_lookup(16#44, 16#4) -> {more, 16#2c, 16#01};
+dec_huffman_lookup(16#44, 16#5) -> {ok, 16#2c, 16#16};
+dec_huffman_lookup(16#44, 16#6) -> {more, 16#3b, 16#01};
+dec_huffman_lookup(16#44, 16#7) -> {ok, 16#3b, 16#16};
+dec_huffman_lookup(16#44, 16#8) -> {more, 16#58, 16#01};
+dec_huffman_lookup(16#44, 16#9) -> {ok, 16#58, 16#16};
+dec_huffman_lookup(16#44, 16#a) -> {more, 16#5a, 16#01};
+dec_huffman_lookup(16#44, 16#b) -> {ok, 16#5a, 16#16};
+dec_huffman_lookup(16#44, 16#c) -> {more, undefined, 16#4c};
+dec_huffman_lookup(16#44, 16#d) -> {more, undefined, 16#4d};
+dec_huffman_lookup(16#44, 16#e) -> {more, undefined, 16#4f};
+dec_huffman_lookup(16#44, 16#f) -> {ok, undefined, 16#51};
+dec_huffman_lookup(16#45, 16#0) -> {more, 16#26, 16#02};
+dec_huffman_lookup(16#45, 16#1) -> {more, 16#26, 16#09};
+dec_huffman_lookup(16#45, 16#2) -> {more, 16#26, 16#17};
+dec_huffman_lookup(16#45, 16#3) -> {ok, 16#26, 16#28};
+dec_huffman_lookup(16#45, 16#4) -> {more, 16#2a, 16#02};
+dec_huffman_lookup(16#45, 16#5) -> {more, 16#2a, 16#09};
+dec_huffman_lookup(16#45, 16#6) -> {more, 16#2a, 16#17};
+dec_huffman_lookup(16#45, 16#7) -> {ok, 16#2a, 16#28};
+dec_huffman_lookup(16#45, 16#8) -> {more, 16#2c, 16#02};
+dec_huffman_lookup(16#45, 16#9) -> {more, 16#2c, 16#09};
+dec_huffman_lookup(16#45, 16#a) -> {more, 16#2c, 16#17};
+dec_huffman_lookup(16#45, 16#b) -> {ok, 16#2c, 16#28};
+dec_huffman_lookup(16#45, 16#c) -> {more, 16#3b, 16#02};
+dec_huffman_lookup(16#45, 16#d) -> {more, 16#3b, 16#09};
+dec_huffman_lookup(16#45, 16#e) -> {more, 16#3b, 16#17};
+dec_huffman_lookup(16#45, 16#f) -> {ok, 16#3b, 16#28};
+dec_huffman_lookup(16#46, 16#0) -> {more, 16#26, 16#03};
+dec_huffman_lookup(16#46, 16#1) -> {more, 16#26, 16#06};
+dec_huffman_lookup(16#46, 16#2) -> {more, 16#26, 16#0a};
+dec_huffman_lookup(16#46, 16#3) -> {more, 16#26, 16#0f};
+dec_huffman_lookup(16#46, 16#4) -> {more, 16#26, 16#18};
+dec_huffman_lookup(16#46, 16#5) -> {more, 16#26, 16#1f};
+dec_huffman_lookup(16#46, 16#6) -> {more, 16#26, 16#29};
+dec_huffman_lookup(16#46, 16#7) -> {ok, 16#26, 16#38};
+dec_huffman_lookup(16#46, 16#8) -> {more, 16#2a, 16#03};
+dec_huffman_lookup(16#46, 16#9) -> {more, 16#2a, 16#06};
+dec_huffman_lookup(16#46, 16#a) -> {more, 16#2a, 16#0a};
+dec_huffman_lookup(16#46, 16#b) -> {more, 16#2a, 16#0f};
+dec_huffman_lookup(16#46, 16#c) -> {more, 16#2a, 16#18};
+dec_huffman_lookup(16#46, 16#d) -> {more, 16#2a, 16#1f};
+dec_huffman_lookup(16#46, 16#e) -> {more, 16#2a, 16#29};
+dec_huffman_lookup(16#46, 16#f) -> {ok, 16#2a, 16#38};
+dec_huffman_lookup(16#47, 16#0) -> {more, 16#2c, 16#03};
+dec_huffman_lookup(16#47, 16#1) -> {more, 16#2c, 16#06};
+dec_huffman_lookup(16#47, 16#2) -> {more, 16#2c, 16#0a};
+dec_huffman_lookup(16#47, 16#3) -> {more, 16#2c, 16#0f};
+dec_huffman_lookup(16#47, 16#4) -> {more, 16#2c, 16#18};
+dec_huffman_lookup(16#47, 16#5) -> {more, 16#2c, 16#1f};
+dec_huffman_lookup(16#47, 16#6) -> {more, 16#2c, 16#29};
+dec_huffman_lookup(16#47, 16#7) -> {ok, 16#2c, 16#38};
+dec_huffman_lookup(16#47, 16#8) -> {more, 16#3b, 16#03};
+dec_huffman_lookup(16#47, 16#9) -> {more, 16#3b, 16#06};
+dec_huffman_lookup(16#47, 16#a) -> {more, 16#3b, 16#0a};
+dec_huffman_lookup(16#47, 16#b) -> {more, 16#3b, 16#0f};
+dec_huffman_lookup(16#47, 16#c) -> {more, 16#3b, 16#18};
+dec_huffman_lookup(16#47, 16#d) -> {more, 16#3b, 16#1f};
+dec_huffman_lookup(16#47, 16#e) -> {more, 16#3b, 16#29};
+dec_huffman_lookup(16#47, 16#f) -> {ok, 16#3b, 16#38};
+dec_huffman_lookup(16#48, 16#0) -> {more, 16#58, 16#02};
+dec_huffman_lookup(16#48, 16#1) -> {more, 16#58, 16#09};
+dec_huffman_lookup(16#48, 16#2) -> {more, 16#58, 16#17};
+dec_huffman_lookup(16#48, 16#3) -> {ok, 16#58, 16#28};
+dec_huffman_lookup(16#48, 16#4) -> {more, 16#5a, 16#02};
+dec_huffman_lookup(16#48, 16#5) -> {more, 16#5a, 16#09};
+dec_huffman_lookup(16#48, 16#6) -> {more, 16#5a, 16#17};
+dec_huffman_lookup(16#48, 16#7) -> {ok, 16#5a, 16#28};
+dec_huffman_lookup(16#48, 16#8) -> {ok, 16#21, 16#00};
+dec_huffman_lookup(16#48, 16#9) -> {ok, 16#22, 16#00};
+dec_huffman_lookup(16#48, 16#a) -> {ok, 16#28, 16#00};
+dec_huffman_lookup(16#48, 16#b) -> {ok, 16#29, 16#00};
+dec_huffman_lookup(16#48, 16#c) -> {ok, 16#3f, 16#00};
+dec_huffman_lookup(16#48, 16#d) -> {more, undefined, 16#50};
+dec_huffman_lookup(16#48, 16#e) -> {more, undefined, 16#52};
+dec_huffman_lookup(16#48, 16#f) -> {ok, undefined, 16#54};
+dec_huffman_lookup(16#49, 16#0) -> {more, 16#58, 16#03};
+dec_huffman_lookup(16#49, 16#1) -> {more, 16#58, 16#06};
+dec_huffman_lookup(16#49, 16#2) -> {more, 16#58, 16#0a};
+dec_huffman_lookup(16#49, 16#3) -> {more, 16#58, 16#0f};
+dec_huffman_lookup(16#49, 16#4) -> {more, 16#58, 16#18};
+dec_huffman_lookup(16#49, 16#5) -> {more, 16#58, 16#1f};
+dec_huffman_lookup(16#49, 16#6) -> {more, 16#58, 16#29};
+dec_huffman_lookup(16#49, 16#7) -> {ok, 16#58, 16#38};
+dec_huffman_lookup(16#49, 16#8) -> {more, 16#5a, 16#03};
+dec_huffman_lookup(16#49, 16#9) -> {more, 16#5a, 16#06};
+dec_huffman_lookup(16#49, 16#a) -> {more, 16#5a, 16#0a};
+dec_huffman_lookup(16#49, 16#b) -> {more, 16#5a, 16#0f};
+dec_huffman_lookup(16#49, 16#c) -> {more, 16#5a, 16#18};
+dec_huffman_lookup(16#49, 16#d) -> {more, 16#5a, 16#1f};
+dec_huffman_lookup(16#49, 16#e) -> {more, 16#5a, 16#29};
+dec_huffman_lookup(16#49, 16#f) -> {ok, 16#5a, 16#38};
+dec_huffman_lookup(16#4a, 16#0) -> {more, 16#21, 16#01};
+dec_huffman_lookup(16#4a, 16#1) -> {ok, 16#21, 16#16};
+dec_huffman_lookup(16#4a, 16#2) -> {more, 16#22, 16#01};
+dec_huffman_lookup(16#4a, 16#3) -> {ok, 16#22, 16#16};
+dec_huffman_lookup(16#4a, 16#4) -> {more, 16#28, 16#01};
+dec_huffman_lookup(16#4a, 16#5) -> {ok, 16#28, 16#16};
+dec_huffman_lookup(16#4a, 16#6) -> {more, 16#29, 16#01};
+dec_huffman_lookup(16#4a, 16#7) -> {ok, 16#29, 16#16};
+dec_huffman_lookup(16#4a, 16#8) -> {more, 16#3f, 16#01};
+dec_huffman_lookup(16#4a, 16#9) -> {ok, 16#3f, 16#16};
+dec_huffman_lookup(16#4a, 16#a) -> {ok, 16#27, 16#00};
+dec_huffman_lookup(16#4a, 16#b) -> {ok, 16#2b, 16#00};
+dec_huffman_lookup(16#4a, 16#c) -> {ok, 16#7c, 16#00};
+dec_huffman_lookup(16#4a, 16#d) -> {more, undefined, 16#53};
+dec_huffman_lookup(16#4a, 16#e) -> {more, undefined, 16#55};
+dec_huffman_lookup(16#4a, 16#f) -> {ok, undefined, 16#58};
+dec_huffman_lookup(16#4b, 16#0) -> {more, 16#21, 16#02};
+dec_huffman_lookup(16#4b, 16#1) -> {more, 16#21, 16#09};
+dec_huffman_lookup(16#4b, 16#2) -> {more, 16#21, 16#17};
+dec_huffman_lookup(16#4b, 16#3) -> {ok, 16#21, 16#28};
+dec_huffman_lookup(16#4b, 16#4) -> {more, 16#22, 16#02};
+dec_huffman_lookup(16#4b, 16#5) -> {more, 16#22, 16#09};
+dec_huffman_lookup(16#4b, 16#6) -> {more, 16#22, 16#17};
+dec_huffman_lookup(16#4b, 16#7) -> {ok, 16#22, 16#28};
+dec_huffman_lookup(16#4b, 16#8) -> {more, 16#28, 16#02};
+dec_huffman_lookup(16#4b, 16#9) -> {more, 16#28, 16#09};
+dec_huffman_lookup(16#4b, 16#a) -> {more, 16#28, 16#17};
+dec_huffman_lookup(16#4b, 16#b) -> {ok, 16#28, 16#28};
+dec_huffman_lookup(16#4b, 16#c) -> {more, 16#29, 16#02};
+dec_huffman_lookup(16#4b, 16#d) -> {more, 16#29, 16#09};
+dec_huffman_lookup(16#4b, 16#e) -> {more, 16#29, 16#17};
+dec_huffman_lookup(16#4b, 16#f) -> {ok, 16#29, 16#28};
+dec_huffman_lookup(16#4c, 16#0) -> {more, 16#21, 16#03};
+dec_huffman_lookup(16#4c, 16#1) -> {more, 16#21, 16#06};
+dec_huffman_lookup(16#4c, 16#2) -> {more, 16#21, 16#0a};
+dec_huffman_lookup(16#4c, 16#3) -> {more, 16#21, 16#0f};
+dec_huffman_lookup(16#4c, 16#4) -> {more, 16#21, 16#18};
+dec_huffman_lookup(16#4c, 16#5) -> {more, 16#21, 16#1f};
+dec_huffman_lookup(16#4c, 16#6) -> {more, 16#21, 16#29};
+dec_huffman_lookup(16#4c, 16#7) -> {ok, 16#21, 16#38};
+dec_huffman_lookup(16#4c, 16#8) -> {more, 16#22, 16#03};
+dec_huffman_lookup(16#4c, 16#9) -> {more, 16#22, 16#06};
+dec_huffman_lookup(16#4c, 16#a) -> {more, 16#22, 16#0a};
+dec_huffman_lookup(16#4c, 16#b) -> {more, 16#22, 16#0f};
+dec_huffman_lookup(16#4c, 16#c) -> {more, 16#22, 16#18};
+dec_huffman_lookup(16#4c, 16#d) -> {more, 16#22, 16#1f};
+dec_huffman_lookup(16#4c, 16#e) -> {more, 16#22, 16#29};
+dec_huffman_lookup(16#4c, 16#f) -> {ok, 16#22, 16#38};
+dec_huffman_lookup(16#4d, 16#0) -> {more, 16#28, 16#03};
+dec_huffman_lookup(16#4d, 16#1) -> {more, 16#28, 16#06};
+dec_huffman_lookup(16#4d, 16#2) -> {more, 16#28, 16#0a};
+dec_huffman_lookup(16#4d, 16#3) -> {more, 16#28, 16#0f};
+dec_huffman_lookup(16#4d, 16#4) -> {more, 16#28, 16#18};
+dec_huffman_lookup(16#4d, 16#5) -> {more, 16#28, 16#1f};
+dec_huffman_lookup(16#4d, 16#6) -> {more, 16#28, 16#29};
+dec_huffman_lookup(16#4d, 16#7) -> {ok, 16#28, 16#38};
+dec_huffman_lookup(16#4d, 16#8) -> {more, 16#29, 16#03};
+dec_huffman_lookup(16#4d, 16#9) -> {more, 16#29, 16#06};
+dec_huffman_lookup(16#4d, 16#a) -> {more, 16#29, 16#0a};
+dec_huffman_lookup(16#4d, 16#b) -> {more, 16#29, 16#0f};
+dec_huffman_lookup(16#4d, 16#c) -> {more, 16#29, 16#18};
+dec_huffman_lookup(16#4d, 16#d) -> {more, 16#29, 16#1f};
+dec_huffman_lookup(16#4d, 16#e) -> {more, 16#29, 16#29};
+dec_huffman_lookup(16#4d, 16#f) -> {ok, 16#29, 16#38};
+dec_huffman_lookup(16#4e, 16#0) -> {more, 16#3f, 16#02};
+dec_huffman_lookup(16#4e, 16#1) -> {more, 16#3f, 16#09};
+dec_huffman_lookup(16#4e, 16#2) -> {more, 16#3f, 16#17};
+dec_huffman_lookup(16#4e, 16#3) -> {ok, 16#3f, 16#28};
+dec_huffman_lookup(16#4e, 16#4) -> {more, 16#27, 16#01};
+dec_huffman_lookup(16#4e, 16#5) -> {ok, 16#27, 16#16};
+dec_huffman_lookup(16#4e, 16#6) -> {more, 16#2b, 16#01};
+dec_huffman_lookup(16#4e, 16#7) -> {ok, 16#2b, 16#16};
+dec_huffman_lookup(16#4e, 16#8) -> {more, 16#7c, 16#01};
+dec_huffman_lookup(16#4e, 16#9) -> {ok, 16#7c, 16#16};
+dec_huffman_lookup(16#4e, 16#a) -> {ok, 16#23, 16#00};
+dec_huffman_lookup(16#4e, 16#b) -> {ok, 16#3e, 16#00};
+dec_huffman_lookup(16#4e, 16#c) -> {more, undefined, 16#56};
+dec_huffman_lookup(16#4e, 16#d) -> {more, undefined, 16#57};
+dec_huffman_lookup(16#4e, 16#e) -> {more, undefined, 16#59};
+dec_huffman_lookup(16#4e, 16#f) -> {ok, undefined, 16#5a};
+dec_huffman_lookup(16#4f, 16#0) -> {more, 16#3f, 16#03};
+dec_huffman_lookup(16#4f, 16#1) -> {more, 16#3f, 16#06};
+dec_huffman_lookup(16#4f, 16#2) -> {more, 16#3f, 16#0a};
+dec_huffman_lookup(16#4f, 16#3) -> {more, 16#3f, 16#0f};
+dec_huffman_lookup(16#4f, 16#4) -> {more, 16#3f, 16#18};
+dec_huffman_lookup(16#4f, 16#5) -> {more, 16#3f, 16#1f};
+dec_huffman_lookup(16#4f, 16#6) -> {more, 16#3f, 16#29};
+dec_huffman_lookup(16#4f, 16#7) -> {ok, 16#3f, 16#38};
+dec_huffman_lookup(16#4f, 16#8) -> {more, 16#27, 16#02};
+dec_huffman_lookup(16#4f, 16#9) -> {more, 16#27, 16#09};
+dec_huffman_lookup(16#4f, 16#a) -> {more, 16#27, 16#17};
+dec_huffman_lookup(16#4f, 16#b) -> {ok, 16#27, 16#28};
+dec_huffman_lookup(16#4f, 16#c) -> {more, 16#2b, 16#02};
+dec_huffman_lookup(16#4f, 16#d) -> {more, 16#2b, 16#09};
+dec_huffman_lookup(16#4f, 16#e) -> {more, 16#2b, 16#17};
+dec_huffman_lookup(16#4f, 16#f) -> {ok, 16#2b, 16#28};
+dec_huffman_lookup(16#50, 16#0) -> {more, 16#27, 16#03};
+dec_huffman_lookup(16#50, 16#1) -> {more, 16#27, 16#06};
+dec_huffman_lookup(16#50, 16#2) -> {more, 16#27, 16#0a};
+dec_huffman_lookup(16#50, 16#3) -> {more, 16#27, 16#0f};
+dec_huffman_lookup(16#50, 16#4) -> {more, 16#27, 16#18};
+dec_huffman_lookup(16#50, 16#5) -> {more, 16#27, 16#1f};
+dec_huffman_lookup(16#50, 16#6) -> {more, 16#27, 16#29};
+dec_huffman_lookup(16#50, 16#7) -> {ok, 16#27, 16#38};
+dec_huffman_lookup(16#50, 16#8) -> {more, 16#2b, 16#03};
+dec_huffman_lookup(16#50, 16#9) -> {more, 16#2b, 16#06};
+dec_huffman_lookup(16#50, 16#a) -> {more, 16#2b, 16#0a};
+dec_huffman_lookup(16#50, 16#b) -> {more, 16#2b, 16#0f};
+dec_huffman_lookup(16#50, 16#c) -> {more, 16#2b, 16#18};
+dec_huffman_lookup(16#50, 16#d) -> {more, 16#2b, 16#1f};
+dec_huffman_lookup(16#50, 16#e) -> {more, 16#2b, 16#29};
+dec_huffman_lookup(16#50, 16#f) -> {ok, 16#2b, 16#38};
+dec_huffman_lookup(16#51, 16#0) -> {more, 16#7c, 16#02};
+dec_huffman_lookup(16#51, 16#1) -> {more, 16#7c, 16#09};
+dec_huffman_lookup(16#51, 16#2) -> {more, 16#7c, 16#17};
+dec_huffman_lookup(16#51, 16#3) -> {ok, 16#7c, 16#28};
+dec_huffman_lookup(16#51, 16#4) -> {more, 16#23, 16#01};
+dec_huffman_lookup(16#51, 16#5) -> {ok, 16#23, 16#16};
+dec_huffman_lookup(16#51, 16#6) -> {more, 16#3e, 16#01};
+dec_huffman_lookup(16#51, 16#7) -> {ok, 16#3e, 16#16};
+dec_huffman_lookup(16#51, 16#8) -> {ok, 16#00, 16#00};
+dec_huffman_lookup(16#51, 16#9) -> {ok, 16#24, 16#00};
+dec_huffman_lookup(16#51, 16#a) -> {ok, 16#40, 16#00};
+dec_huffman_lookup(16#51, 16#b) -> {ok, 16#5b, 16#00};
+dec_huffman_lookup(16#51, 16#c) -> {ok, 16#5d, 16#00};
+dec_huffman_lookup(16#51, 16#d) -> {ok, 16#7e, 16#00};
+dec_huffman_lookup(16#51, 16#e) -> {more, undefined, 16#5b};
+dec_huffman_lookup(16#51, 16#f) -> {ok, undefined, 16#5c};
+dec_huffman_lookup(16#52, 16#0) -> {more, 16#7c, 16#03};
+dec_huffman_lookup(16#52, 16#1) -> {more, 16#7c, 16#06};
+dec_huffman_lookup(16#52, 16#2) -> {more, 16#7c, 16#0a};
+dec_huffman_lookup(16#52, 16#3) -> {more, 16#7c, 16#0f};
+dec_huffman_lookup(16#52, 16#4) -> {more, 16#7c, 16#18};
+dec_huffman_lookup(16#52, 16#5) -> {more, 16#7c, 16#1f};
+dec_huffman_lookup(16#52, 16#6) -> {more, 16#7c, 16#29};
+dec_huffman_lookup(16#52, 16#7) -> {ok, 16#7c, 16#38};
+dec_huffman_lookup(16#52, 16#8) -> {more, 16#23, 16#02};
+dec_huffman_lookup(16#52, 16#9) -> {more, 16#23, 16#09};
+dec_huffman_lookup(16#52, 16#a) -> {more, 16#23, 16#17};
+dec_huffman_lookup(16#52, 16#b) -> {ok, 16#23, 16#28};
+dec_huffman_lookup(16#52, 16#c) -> {more, 16#3e, 16#02};
+dec_huffman_lookup(16#52, 16#d) -> {more, 16#3e, 16#09};
+dec_huffman_lookup(16#52, 16#e) -> {more, 16#3e, 16#17};
+dec_huffman_lookup(16#52, 16#f) -> {ok, 16#3e, 16#28};
+dec_huffman_lookup(16#53, 16#0) -> {more, 16#23, 16#03};
+dec_huffman_lookup(16#53, 16#1) -> {more, 16#23, 16#06};
+dec_huffman_lookup(16#53, 16#2) -> {more, 16#23, 16#0a};
+dec_huffman_lookup(16#53, 16#3) -> {more, 16#23, 16#0f};
+dec_huffman_lookup(16#53, 16#4) -> {more, 16#23, 16#18};
+dec_huffman_lookup(16#53, 16#5) -> {more, 16#23, 16#1f};
+dec_huffman_lookup(16#53, 16#6) -> {more, 16#23, 16#29};
+dec_huffman_lookup(16#53, 16#7) -> {ok, 16#23, 16#38};
+dec_huffman_lookup(16#53, 16#8) -> {more, 16#3e, 16#03};
+dec_huffman_lookup(16#53, 16#9) -> {more, 16#3e, 16#06};
+dec_huffman_lookup(16#53, 16#a) -> {more, 16#3e, 16#0a};
+dec_huffman_lookup(16#53, 16#b) -> {more, 16#3e, 16#0f};
+dec_huffman_lookup(16#53, 16#c) -> {more, 16#3e, 16#18};
+dec_huffman_lookup(16#53, 16#d) -> {more, 16#3e, 16#1f};
+dec_huffman_lookup(16#53, 16#e) -> {more, 16#3e, 16#29};
+dec_huffman_lookup(16#53, 16#f) -> {ok, 16#3e, 16#38};
+dec_huffman_lookup(16#54, 16#0) -> {more, 16#00, 16#01};
+dec_huffman_lookup(16#54, 16#1) -> {ok, 16#00, 16#16};
+dec_huffman_lookup(16#54, 16#2) -> {more, 16#24, 16#01};
+dec_huffman_lookup(16#54, 16#3) -> {ok, 16#24, 16#16};
+dec_huffman_lookup(16#54, 16#4) -> {more, 16#40, 16#01};
+dec_huffman_lookup(16#54, 16#5) -> {ok, 16#40, 16#16};
+dec_huffman_lookup(16#54, 16#6) -> {more, 16#5b, 16#01};
+dec_huffman_lookup(16#54, 16#7) -> {ok, 16#5b, 16#16};
+dec_huffman_lookup(16#54, 16#8) -> {more, 16#5d, 16#01};
+dec_huffman_lookup(16#54, 16#9) -> {ok, 16#5d, 16#16};
+dec_huffman_lookup(16#54, 16#a) -> {more, 16#7e, 16#01};
+dec_huffman_lookup(16#54, 16#b) -> {ok, 16#7e, 16#16};
+dec_huffman_lookup(16#54, 16#c) -> {ok, 16#5e, 16#00};
+dec_huffman_lookup(16#54, 16#d) -> {ok, 16#7d, 16#00};
+dec_huffman_lookup(16#54, 16#e) -> {more, undefined, 16#5d};
+dec_huffman_lookup(16#54, 16#f) -> {ok, undefined, 16#5e};
+dec_huffman_lookup(16#55, 16#0) -> {more, 16#00, 16#02};
+dec_huffman_lookup(16#55, 16#1) -> {more, 16#00, 16#09};
+dec_huffman_lookup(16#55, 16#2) -> {more, 16#00, 16#17};
+dec_huffman_lookup(16#55, 16#3) -> {ok, 16#00, 16#28};
+dec_huffman_lookup(16#55, 16#4) -> {more, 16#24, 16#02};
+dec_huffman_lookup(16#55, 16#5) -> {more, 16#24, 16#09};
+dec_huffman_lookup(16#55, 16#6) -> {more, 16#24, 16#17};
+dec_huffman_lookup(16#55, 16#7) -> {ok, 16#24, 16#28};
+dec_huffman_lookup(16#55, 16#8) -> {more, 16#40, 16#02};
+dec_huffman_lookup(16#55, 16#9) -> {more, 16#40, 16#09};
+dec_huffman_lookup(16#55, 16#a) -> {more, 16#40, 16#17};
+dec_huffman_lookup(16#55, 16#b) -> {ok, 16#40, 16#28};
+dec_huffman_lookup(16#55, 16#c) -> {more, 16#5b, 16#02};
+dec_huffman_lookup(16#55, 16#d) -> {more, 16#5b, 16#09};
+dec_huffman_lookup(16#55, 16#e) -> {more, 16#5b, 16#17};
+dec_huffman_lookup(16#55, 16#f) -> {ok, 16#5b, 16#28};
+dec_huffman_lookup(16#56, 16#0) -> {more, 16#00, 16#03};
+dec_huffman_lookup(16#56, 16#1) -> {more, 16#00, 16#06};
+dec_huffman_lookup(16#56, 16#2) -> {more, 16#00, 16#0a};
+dec_huffman_lookup(16#56, 16#3) -> {more, 16#00, 16#0f};
+dec_huffman_lookup(16#56, 16#4) -> {more, 16#00, 16#18};
+dec_huffman_lookup(16#56, 16#5) -> {more, 16#00, 16#1f};
+dec_huffman_lookup(16#56, 16#6) -> {more, 16#00, 16#29};
+dec_huffman_lookup(16#56, 16#7) -> {ok, 16#00, 16#38};
+dec_huffman_lookup(16#56, 16#8) -> {more, 16#24, 16#03};
+dec_huffman_lookup(16#56, 16#9) -> {more, 16#24, 16#06};
+dec_huffman_lookup(16#56, 16#a) -> {more, 16#24, 16#0a};
+dec_huffman_lookup(16#56, 16#b) -> {more, 16#24, 16#0f};
+dec_huffman_lookup(16#56, 16#c) -> {more, 16#24, 16#18};
+dec_huffman_lookup(16#56, 16#d) -> {more, 16#24, 16#1f};
+dec_huffman_lookup(16#56, 16#e) -> {more, 16#24, 16#29};
+dec_huffman_lookup(16#56, 16#f) -> {ok, 16#24, 16#38};
+dec_huffman_lookup(16#57, 16#0) -> {more, 16#40, 16#03};
+dec_huffman_lookup(16#57, 16#1) -> {more, 16#40, 16#06};
+dec_huffman_lookup(16#57, 16#2) -> {more, 16#40, 16#0a};
+dec_huffman_lookup(16#57, 16#3) -> {more, 16#40, 16#0f};
+dec_huffman_lookup(16#57, 16#4) -> {more, 16#40, 16#18};
+dec_huffman_lookup(16#57, 16#5) -> {more, 16#40, 16#1f};
+dec_huffman_lookup(16#57, 16#6) -> {more, 16#40, 16#29};
+dec_huffman_lookup(16#57, 16#7) -> {ok, 16#40, 16#38};
+dec_huffman_lookup(16#57, 16#8) -> {more, 16#5b, 16#03};
+dec_huffman_lookup(16#57, 16#9) -> {more, 16#5b, 16#06};
+dec_huffman_lookup(16#57, 16#a) -> {more, 16#5b, 16#0a};
+dec_huffman_lookup(16#57, 16#b) -> {more, 16#5b, 16#0f};
+dec_huffman_lookup(16#57, 16#c) -> {more, 16#5b, 16#18};
+dec_huffman_lookup(16#57, 16#d) -> {more, 16#5b, 16#1f};
+dec_huffman_lookup(16#57, 16#e) -> {more, 16#5b, 16#29};
+dec_huffman_lookup(16#57, 16#f) -> {ok, 16#5b, 16#38};
+dec_huffman_lookup(16#58, 16#0) -> {more, 16#5d, 16#02};
+dec_huffman_lookup(16#58, 16#1) -> {more, 16#5d, 16#09};
+dec_huffman_lookup(16#58, 16#2) -> {more, 16#5d, 16#17};
+dec_huffman_lookup(16#58, 16#3) -> {ok, 16#5d, 16#28};
+dec_huffman_lookup(16#58, 16#4) -> {more, 16#7e, 16#02};
+dec_huffman_lookup(16#58, 16#5) -> {more, 16#7e, 16#09};
+dec_huffman_lookup(16#58, 16#6) -> {more, 16#7e, 16#17};
+dec_huffman_lookup(16#58, 16#7) -> {ok, 16#7e, 16#28};
+dec_huffman_lookup(16#58, 16#8) -> {more, 16#5e, 16#01};
+dec_huffman_lookup(16#58, 16#9) -> {ok, 16#5e, 16#16};
+dec_huffman_lookup(16#58, 16#a) -> {more, 16#7d, 16#01};
+dec_huffman_lookup(16#58, 16#b) -> {ok, 16#7d, 16#16};
+dec_huffman_lookup(16#58, 16#c) -> {ok, 16#3c, 16#00};
+dec_huffman_lookup(16#58, 16#d) -> {ok, 16#60, 16#00};
+dec_huffman_lookup(16#58, 16#e) -> {ok, 16#7b, 16#00};
+dec_huffman_lookup(16#58, 16#f) -> {ok, undefined, 16#5f};
+dec_huffman_lookup(16#59, 16#0) -> {more, 16#5d, 16#03};
+dec_huffman_lookup(16#59, 16#1) -> {more, 16#5d, 16#06};
+dec_huffman_lookup(16#59, 16#2) -> {more, 16#5d, 16#0a};
+dec_huffman_lookup(16#59, 16#3) -> {more, 16#5d, 16#0f};
+dec_huffman_lookup(16#59, 16#4) -> {more, 16#5d, 16#18};
+dec_huffman_lookup(16#59, 16#5) -> {more, 16#5d, 16#1f};
+dec_huffman_lookup(16#59, 16#6) -> {more, 16#5d, 16#29};
+dec_huffman_lookup(16#59, 16#7) -> {ok, 16#5d, 16#38};
+dec_huffman_lookup(16#59, 16#8) -> {more, 16#7e, 16#03};
+dec_huffman_lookup(16#59, 16#9) -> {more, 16#7e, 16#06};
+dec_huffman_lookup(16#59, 16#a) -> {more, 16#7e, 16#0a};
+dec_huffman_lookup(16#59, 16#b) -> {more, 16#7e, 16#0f};
+dec_huffman_lookup(16#59, 16#c) -> {more, 16#7e, 16#18};
+dec_huffman_lookup(16#59, 16#d) -> {more, 16#7e, 16#1f};
+dec_huffman_lookup(16#59, 16#e) -> {more, 16#7e, 16#29};
+dec_huffman_lookup(16#59, 16#f) -> {ok, 16#7e, 16#38};
+dec_huffman_lookup(16#5a, 16#0) -> {more, 16#5e, 16#02};
+dec_huffman_lookup(16#5a, 16#1) -> {more, 16#5e, 16#09};
+dec_huffman_lookup(16#5a, 16#2) -> {more, 16#5e, 16#17};
+dec_huffman_lookup(16#5a, 16#3) -> {ok, 16#5e, 16#28};
+dec_huffman_lookup(16#5a, 16#4) -> {more, 16#7d, 16#02};
+dec_huffman_lookup(16#5a, 16#5) -> {more, 16#7d, 16#09};
+dec_huffman_lookup(16#5a, 16#6) -> {more, 16#7d, 16#17};
+dec_huffman_lookup(16#5a, 16#7) -> {ok, 16#7d, 16#28};
+dec_huffman_lookup(16#5a, 16#8) -> {more, 16#3c, 16#01};
+dec_huffman_lookup(16#5a, 16#9) -> {ok, 16#3c, 16#16};
+dec_huffman_lookup(16#5a, 16#a) -> {more, 16#60, 16#01};
+dec_huffman_lookup(16#5a, 16#b) -> {ok, 16#60, 16#16};
+dec_huffman_lookup(16#5a, 16#c) -> {more, 16#7b, 16#01};
+dec_huffman_lookup(16#5a, 16#d) -> {ok, 16#7b, 16#16};
+dec_huffman_lookup(16#5a, 16#e) -> {more, undefined, 16#60};
+dec_huffman_lookup(16#5a, 16#f) -> {ok, undefined, 16#6e};
+dec_huffman_lookup(16#5b, 16#0) -> {more, 16#5e, 16#03};
+dec_huffman_lookup(16#5b, 16#1) -> {more, 16#5e, 16#06};
+dec_huffman_lookup(16#5b, 16#2) -> {more, 16#5e, 16#0a};
+dec_huffman_lookup(16#5b, 16#3) -> {more, 16#5e, 16#0f};
+dec_huffman_lookup(16#5b, 16#4) -> {more, 16#5e, 16#18};
+dec_huffman_lookup(16#5b, 16#5) -> {more, 16#5e, 16#1f};
+dec_huffman_lookup(16#5b, 16#6) -> {more, 16#5e, 16#29};
+dec_huffman_lookup(16#5b, 16#7) -> {ok, 16#5e, 16#38};
+dec_huffman_lookup(16#5b, 16#8) -> {more, 16#7d, 16#03};
+dec_huffman_lookup(16#5b, 16#9) -> {more, 16#7d, 16#06};
+dec_huffman_lookup(16#5b, 16#a) -> {more, 16#7d, 16#0a};
+dec_huffman_lookup(16#5b, 16#b) -> {more, 16#7d, 16#0f};
+dec_huffman_lookup(16#5b, 16#c) -> {more, 16#7d, 16#18};
+dec_huffman_lookup(16#5b, 16#d) -> {more, 16#7d, 16#1f};
+dec_huffman_lookup(16#5b, 16#e) -> {more, 16#7d, 16#29};
+dec_huffman_lookup(16#5b, 16#f) -> {ok, 16#7d, 16#38};
+dec_huffman_lookup(16#5c, 16#0) -> {more, 16#3c, 16#02};
+dec_huffman_lookup(16#5c, 16#1) -> {more, 16#3c, 16#09};
+dec_huffman_lookup(16#5c, 16#2) -> {more, 16#3c, 16#17};
+dec_huffman_lookup(16#5c, 16#3) -> {ok, 16#3c, 16#28};
+dec_huffman_lookup(16#5c, 16#4) -> {more, 16#60, 16#02};
+dec_huffman_lookup(16#5c, 16#5) -> {more, 16#60, 16#09};
+dec_huffman_lookup(16#5c, 16#6) -> {more, 16#60, 16#17};
+dec_huffman_lookup(16#5c, 16#7) -> {ok, 16#60, 16#28};
+dec_huffman_lookup(16#5c, 16#8) -> {more, 16#7b, 16#02};
+dec_huffman_lookup(16#5c, 16#9) -> {more, 16#7b, 16#09};
+dec_huffman_lookup(16#5c, 16#a) -> {more, 16#7b, 16#17};
+dec_huffman_lookup(16#5c, 16#b) -> {ok, 16#7b, 16#28};
+dec_huffman_lookup(16#5c, 16#c) -> {more, undefined, 16#61};
+dec_huffman_lookup(16#5c, 16#d) -> {more, undefined, 16#65};
+dec_huffman_lookup(16#5c, 16#e) -> {more, undefined, 16#6f};
+dec_huffman_lookup(16#5c, 16#f) -> {ok, undefined, 16#85};
+dec_huffman_lookup(16#5d, 16#0) -> {more, 16#3c, 16#03};
+dec_huffman_lookup(16#5d, 16#1) -> {more, 16#3c, 16#06};
+dec_huffman_lookup(16#5d, 16#2) -> {more, 16#3c, 16#0a};
+dec_huffman_lookup(16#5d, 16#3) -> {more, 16#3c, 16#0f};
+dec_huffman_lookup(16#5d, 16#4) -> {more, 16#3c, 16#18};
+dec_huffman_lookup(16#5d, 16#5) -> {more, 16#3c, 16#1f};
+dec_huffman_lookup(16#5d, 16#6) -> {more, 16#3c, 16#29};
+dec_huffman_lookup(16#5d, 16#7) -> {ok, 16#3c, 16#38};
+dec_huffman_lookup(16#5d, 16#8) -> {more, 16#60, 16#03};
+dec_huffman_lookup(16#5d, 16#9) -> {more, 16#60, 16#06};
+dec_huffman_lookup(16#5d, 16#a) -> {more, 16#60, 16#0a};
+dec_huffman_lookup(16#5d, 16#b) -> {more, 16#60, 16#0f};
+dec_huffman_lookup(16#5d, 16#c) -> {more, 16#60, 16#18};
+dec_huffman_lookup(16#5d, 16#d) -> {more, 16#60, 16#1f};
+dec_huffman_lookup(16#5d, 16#e) -> {more, 16#60, 16#29};
+dec_huffman_lookup(16#5d, 16#f) -> {ok, 16#60, 16#38};
+dec_huffman_lookup(16#5e, 16#0) -> {more, 16#7b, 16#03};
+dec_huffman_lookup(16#5e, 16#1) -> {more, 16#7b, 16#06};
+dec_huffman_lookup(16#5e, 16#2) -> {more, 16#7b, 16#0a};
+dec_huffman_lookup(16#5e, 16#3) -> {more, 16#7b, 16#0f};
+dec_huffman_lookup(16#5e, 16#4) -> {more, 16#7b, 16#18};
+dec_huffman_lookup(16#5e, 16#5) -> {more, 16#7b, 16#1f};
+dec_huffman_lookup(16#5e, 16#6) -> {more, 16#7b, 16#29};
+dec_huffman_lookup(16#5e, 16#7) -> {ok, 16#7b, 16#38};
+dec_huffman_lookup(16#5e, 16#8) -> {more, undefined, 16#62};
+dec_huffman_lookup(16#5e, 16#9) -> {more, undefined, 16#63};
+dec_huffman_lookup(16#5e, 16#a) -> {more, undefined, 16#66};
+dec_huffman_lookup(16#5e, 16#b) -> {more, undefined, 16#69};
+dec_huffman_lookup(16#5e, 16#c) -> {more, undefined, 16#70};
+dec_huffman_lookup(16#5e, 16#d) -> {more, undefined, 16#77};
+dec_huffman_lookup(16#5e, 16#e) -> {more, undefined, 16#86};
+dec_huffman_lookup(16#5e, 16#f) -> {ok, undefined, 16#99};
+dec_huffman_lookup(16#5f, 16#0) -> {ok, 16#5c, 16#00};
+dec_huffman_lookup(16#5f, 16#1) -> {ok, 16#c3, 16#00};
+dec_huffman_lookup(16#5f, 16#2) -> {ok, 16#d0, 16#00};
+dec_huffman_lookup(16#5f, 16#3) -> {more, undefined, 16#64};
+dec_huffman_lookup(16#5f, 16#4) -> {more, undefined, 16#67};
+dec_huffman_lookup(16#5f, 16#5) -> {more, undefined, 16#68};
+dec_huffman_lookup(16#5f, 16#6) -> {more, undefined, 16#6a};
+dec_huffman_lookup(16#5f, 16#7) -> {more, undefined, 16#6b};
+dec_huffman_lookup(16#5f, 16#8) -> {more, undefined, 16#71};
+dec_huffman_lookup(16#5f, 16#9) -> {more, undefined, 16#74};
+dec_huffman_lookup(16#5f, 16#a) -> {more, undefined, 16#78};
+dec_huffman_lookup(16#5f, 16#b) -> {more, undefined, 16#7e};
+dec_huffman_lookup(16#5f, 16#c) -> {more, undefined, 16#87};
+dec_huffman_lookup(16#5f, 16#d) -> {more, undefined, 16#8e};
+dec_huffman_lookup(16#5f, 16#e) -> {more, undefined, 16#9a};
+dec_huffman_lookup(16#5f, 16#f) -> {ok, undefined, 16#a9};
+dec_huffman_lookup(16#60, 16#0) -> {more, 16#5c, 16#01};
+dec_huffman_lookup(16#60, 16#1) -> {ok, 16#5c, 16#16};
+dec_huffman_lookup(16#60, 16#2) -> {more, 16#c3, 16#01};
+dec_huffman_lookup(16#60, 16#3) -> {ok, 16#c3, 16#16};
+dec_huffman_lookup(16#60, 16#4) -> {more, 16#d0, 16#01};
+dec_huffman_lookup(16#60, 16#5) -> {ok, 16#d0, 16#16};
+dec_huffman_lookup(16#60, 16#6) -> {ok, 16#80, 16#00};
+dec_huffman_lookup(16#60, 16#7) -> {ok, 16#82, 16#00};
+dec_huffman_lookup(16#60, 16#8) -> {ok, 16#83, 16#00};
+dec_huffman_lookup(16#60, 16#9) -> {ok, 16#a2, 16#00};
+dec_huffman_lookup(16#60, 16#a) -> {ok, 16#b8, 16#00};
+dec_huffman_lookup(16#60, 16#b) -> {ok, 16#c2, 16#00};
+dec_huffman_lookup(16#60, 16#c) -> {ok, 16#e0, 16#00};
+dec_huffman_lookup(16#60, 16#d) -> {ok, 16#e2, 16#00};
+dec_huffman_lookup(16#60, 16#e) -> {more, undefined, 16#6c};
+dec_huffman_lookup(16#60, 16#f) -> {more, undefined, 16#6d};
+dec_huffman_lookup(16#61, 16#0) -> {more, 16#5c, 16#02};
+dec_huffman_lookup(16#61, 16#1) -> {more, 16#5c, 16#09};
+dec_huffman_lookup(16#61, 16#2) -> {more, 16#5c, 16#17};
+dec_huffman_lookup(16#61, 16#3) -> {ok, 16#5c, 16#28};
+dec_huffman_lookup(16#61, 16#4) -> {more, 16#c3, 16#02};
+dec_huffman_lookup(16#61, 16#5) -> {more, 16#c3, 16#09};
+dec_huffman_lookup(16#61, 16#6) -> {more, 16#c3, 16#17};
+dec_huffman_lookup(16#61, 16#7) -> {ok, 16#c3, 16#28};
+dec_huffman_lookup(16#61, 16#8) -> {more, 16#d0, 16#02};
+dec_huffman_lookup(16#61, 16#9) -> {more, 16#d0, 16#09};
+dec_huffman_lookup(16#61, 16#a) -> {more, 16#d0, 16#17};
+dec_huffman_lookup(16#61, 16#b) -> {ok, 16#d0, 16#28};
+dec_huffman_lookup(16#61, 16#c) -> {more, 16#80, 16#01};
+dec_huffman_lookup(16#61, 16#d) -> {ok, 16#80, 16#16};
+dec_huffman_lookup(16#61, 16#e) -> {more, 16#82, 16#01};
+dec_huffman_lookup(16#61, 16#f) -> {ok, 16#82, 16#16};
+dec_huffman_lookup(16#62, 16#0) -> {more, 16#5c, 16#03};
+dec_huffman_lookup(16#62, 16#1) -> {more, 16#5c, 16#06};
+dec_huffman_lookup(16#62, 16#2) -> {more, 16#5c, 16#0a};
+dec_huffman_lookup(16#62, 16#3) -> {more, 16#5c, 16#0f};
+dec_huffman_lookup(16#62, 16#4) -> {more, 16#5c, 16#18};
+dec_huffman_lookup(16#62, 16#5) -> {more, 16#5c, 16#1f};
+dec_huffman_lookup(16#62, 16#6) -> {more, 16#5c, 16#29};
+dec_huffman_lookup(16#62, 16#7) -> {ok, 16#5c, 16#38};
+dec_huffman_lookup(16#62, 16#8) -> {more, 16#c3, 16#03};
+dec_huffman_lookup(16#62, 16#9) -> {more, 16#c3, 16#06};
+dec_huffman_lookup(16#62, 16#a) -> {more, 16#c3, 16#0a};
+dec_huffman_lookup(16#62, 16#b) -> {more, 16#c3, 16#0f};
+dec_huffman_lookup(16#62, 16#c) -> {more, 16#c3, 16#18};
+dec_huffman_lookup(16#62, 16#d) -> {more, 16#c3, 16#1f};
+dec_huffman_lookup(16#62, 16#e) -> {more, 16#c3, 16#29};
+dec_huffman_lookup(16#62, 16#f) -> {ok, 16#c3, 16#38};
+dec_huffman_lookup(16#63, 16#0) -> {more, 16#d0, 16#03};
+dec_huffman_lookup(16#63, 16#1) -> {more, 16#d0, 16#06};
+dec_huffman_lookup(16#63, 16#2) -> {more, 16#d0, 16#0a};
+dec_huffman_lookup(16#63, 16#3) -> {more, 16#d0, 16#0f};
+dec_huffman_lookup(16#63, 16#4) -> {more, 16#d0, 16#18};
+dec_huffman_lookup(16#63, 16#5) -> {more, 16#d0, 16#1f};
+dec_huffman_lookup(16#63, 16#6) -> {more, 16#d0, 16#29};
+dec_huffman_lookup(16#63, 16#7) -> {ok, 16#d0, 16#38};
+dec_huffman_lookup(16#63, 16#8) -> {more, 16#80, 16#02};
+dec_huffman_lookup(16#63, 16#9) -> {more, 16#80, 16#09};
+dec_huffman_lookup(16#63, 16#a) -> {more, 16#80, 16#17};
+dec_huffman_lookup(16#63, 16#b) -> {ok, 16#80, 16#28};
+dec_huffman_lookup(16#63, 16#c) -> {more, 16#82, 16#02};
+dec_huffman_lookup(16#63, 16#d) -> {more, 16#82, 16#09};
+dec_huffman_lookup(16#63, 16#e) -> {more, 16#82, 16#17};
+dec_huffman_lookup(16#63, 16#f) -> {ok, 16#82, 16#28};
+dec_huffman_lookup(16#64, 16#0) -> {more, 16#80, 16#03};
+dec_huffman_lookup(16#64, 16#1) -> {more, 16#80, 16#06};
+dec_huffman_lookup(16#64, 16#2) -> {more, 16#80, 16#0a};
+dec_huffman_lookup(16#64, 16#3) -> {more, 16#80, 16#0f};
+dec_huffman_lookup(16#64, 16#4) -> {more, 16#80, 16#18};
+dec_huffman_lookup(16#64, 16#5) -> {more, 16#80, 16#1f};
+dec_huffman_lookup(16#64, 16#6) -> {more, 16#80, 16#29};
+dec_huffman_lookup(16#64, 16#7) -> {ok, 16#80, 16#38};
+dec_huffman_lookup(16#64, 16#8) -> {more, 16#82, 16#03};
+dec_huffman_lookup(16#64, 16#9) -> {more, 16#82, 16#06};
+dec_huffman_lookup(16#64, 16#a) -> {more, 16#82, 16#0a};
+dec_huffman_lookup(16#64, 16#b) -> {more, 16#82, 16#0f};
+dec_huffman_lookup(16#64, 16#c) -> {more, 16#82, 16#18};
+dec_huffman_lookup(16#64, 16#d) -> {more, 16#82, 16#1f};
+dec_huffman_lookup(16#64, 16#e) -> {more, 16#82, 16#29};
+dec_huffman_lookup(16#64, 16#f) -> {ok, 16#82, 16#38};
+dec_huffman_lookup(16#65, 16#0) -> {more, 16#83, 16#01};
+dec_huffman_lookup(16#65, 16#1) -> {ok, 16#83, 16#16};
+dec_huffman_lookup(16#65, 16#2) -> {more, 16#a2, 16#01};
+dec_huffman_lookup(16#65, 16#3) -> {ok, 16#a2, 16#16};
+dec_huffman_lookup(16#65, 16#4) -> {more, 16#b8, 16#01};
+dec_huffman_lookup(16#65, 16#5) -> {ok, 16#b8, 16#16};
+dec_huffman_lookup(16#65, 16#6) -> {more, 16#c2, 16#01};
+dec_huffman_lookup(16#65, 16#7) -> {ok, 16#c2, 16#16};
+dec_huffman_lookup(16#65, 16#8) -> {more, 16#e0, 16#01};
+dec_huffman_lookup(16#65, 16#9) -> {ok, 16#e0, 16#16};
+dec_huffman_lookup(16#65, 16#a) -> {more, 16#e2, 16#01};
+dec_huffman_lookup(16#65, 16#b) -> {ok, 16#e2, 16#16};
+dec_huffman_lookup(16#65, 16#c) -> {ok, 16#99, 16#00};
+dec_huffman_lookup(16#65, 16#d) -> {ok, 16#a1, 16#00};
+dec_huffman_lookup(16#65, 16#e) -> {ok, 16#a7, 16#00};
+dec_huffman_lookup(16#65, 16#f) -> {ok, 16#ac, 16#00};
+dec_huffman_lookup(16#66, 16#0) -> {more, 16#83, 16#02};
+dec_huffman_lookup(16#66, 16#1) -> {more, 16#83, 16#09};
+dec_huffman_lookup(16#66, 16#2) -> {more, 16#83, 16#17};
+dec_huffman_lookup(16#66, 16#3) -> {ok, 16#83, 16#28};
+dec_huffman_lookup(16#66, 16#4) -> {more, 16#a2, 16#02};
+dec_huffman_lookup(16#66, 16#5) -> {more, 16#a2, 16#09};
+dec_huffman_lookup(16#66, 16#6) -> {more, 16#a2, 16#17};
+dec_huffman_lookup(16#66, 16#7) -> {ok, 16#a2, 16#28};
+dec_huffman_lookup(16#66, 16#8) -> {more, 16#b8, 16#02};
+dec_huffman_lookup(16#66, 16#9) -> {more, 16#b8, 16#09};
+dec_huffman_lookup(16#66, 16#a) -> {more, 16#b8, 16#17};
+dec_huffman_lookup(16#66, 16#b) -> {ok, 16#b8, 16#28};
+dec_huffman_lookup(16#66, 16#c) -> {more, 16#c2, 16#02};
+dec_huffman_lookup(16#66, 16#d) -> {more, 16#c2, 16#09};
+dec_huffman_lookup(16#66, 16#e) -> {more, 16#c2, 16#17};
+dec_huffman_lookup(16#66, 16#f) -> {ok, 16#c2, 16#28};
+dec_huffman_lookup(16#67, 16#0) -> {more, 16#83, 16#03};
+dec_huffman_lookup(16#67, 16#1) -> {more, 16#83, 16#06};
+dec_huffman_lookup(16#67, 16#2) -> {more, 16#83, 16#0a};
+dec_huffman_lookup(16#67, 16#3) -> {more, 16#83, 16#0f};
+dec_huffman_lookup(16#67, 16#4) -> {more, 16#83, 16#18};
+dec_huffman_lookup(16#67, 16#5) -> {more, 16#83, 16#1f};
+dec_huffman_lookup(16#67, 16#6) -> {more, 16#83, 16#29};
+dec_huffman_lookup(16#67, 16#7) -> {ok, 16#83, 16#38};
+dec_huffman_lookup(16#67, 16#8) -> {more, 16#a2, 16#03};
+dec_huffman_lookup(16#67, 16#9) -> {more, 16#a2, 16#06};
+dec_huffman_lookup(16#67, 16#a) -> {more, 16#a2, 16#0a};
+dec_huffman_lookup(16#67, 16#b) -> {more, 16#a2, 16#0f};
+dec_huffman_lookup(16#67, 16#c) -> {more, 16#a2, 16#18};
+dec_huffman_lookup(16#67, 16#d) -> {more, 16#a2, 16#1f};
+dec_huffman_lookup(16#67, 16#e) -> {more, 16#a2, 16#29};
+dec_huffman_lookup(16#67, 16#f) -> {ok, 16#a2, 16#38};
+dec_huffman_lookup(16#68, 16#0) -> {more, 16#b8, 16#03};
+dec_huffman_lookup(16#68, 16#1) -> {more, 16#b8, 16#06};
+dec_huffman_lookup(16#68, 16#2) -> {more, 16#b8, 16#0a};
+dec_huffman_lookup(16#68, 16#3) -> {more, 16#b8, 16#0f};
+dec_huffman_lookup(16#68, 16#4) -> {more, 16#b8, 16#18};
+dec_huffman_lookup(16#68, 16#5) -> {more, 16#b8, 16#1f};
+dec_huffman_lookup(16#68, 16#6) -> {more, 16#b8, 16#29};
+dec_huffman_lookup(16#68, 16#7) -> {ok, 16#b8, 16#38};
+dec_huffman_lookup(16#68, 16#8) -> {more, 16#c2, 16#03};
+dec_huffman_lookup(16#68, 16#9) -> {more, 16#c2, 16#06};
+dec_huffman_lookup(16#68, 16#a) -> {more, 16#c2, 16#0a};
+dec_huffman_lookup(16#68, 16#b) -> {more, 16#c2, 16#0f};
+dec_huffman_lookup(16#68, 16#c) -> {more, 16#c2, 16#18};
+dec_huffman_lookup(16#68, 16#d) -> {more, 16#c2, 16#1f};
+dec_huffman_lookup(16#68, 16#e) -> {more, 16#c2, 16#29};
+dec_huffman_lookup(16#68, 16#f) -> {ok, 16#c2, 16#38};
+dec_huffman_lookup(16#69, 16#0) -> {more, 16#e0, 16#02};
+dec_huffman_lookup(16#69, 16#1) -> {more, 16#e0, 16#09};
+dec_huffman_lookup(16#69, 16#2) -> {more, 16#e0, 16#17};
+dec_huffman_lookup(16#69, 16#3) -> {ok, 16#e0, 16#28};
+dec_huffman_lookup(16#69, 16#4) -> {more, 16#e2, 16#02};
+dec_huffman_lookup(16#69, 16#5) -> {more, 16#e2, 16#09};
+dec_huffman_lookup(16#69, 16#6) -> {more, 16#e2, 16#17};
+dec_huffman_lookup(16#69, 16#7) -> {ok, 16#e2, 16#28};
+dec_huffman_lookup(16#69, 16#8) -> {more, 16#99, 16#01};
+dec_huffman_lookup(16#69, 16#9) -> {ok, 16#99, 16#16};
+dec_huffman_lookup(16#69, 16#a) -> {more, 16#a1, 16#01};
+dec_huffman_lookup(16#69, 16#b) -> {ok, 16#a1, 16#16};
+dec_huffman_lookup(16#69, 16#c) -> {more, 16#a7, 16#01};
+dec_huffman_lookup(16#69, 16#d) -> {ok, 16#a7, 16#16};
+dec_huffman_lookup(16#69, 16#e) -> {more, 16#ac, 16#01};
+dec_huffman_lookup(16#69, 16#f) -> {ok, 16#ac, 16#16};
+dec_huffman_lookup(16#6a, 16#0) -> {more, 16#e0, 16#03};
+dec_huffman_lookup(16#6a, 16#1) -> {more, 16#e0, 16#06};
+dec_huffman_lookup(16#6a, 16#2) -> {more, 16#e0, 16#0a};
+dec_huffman_lookup(16#6a, 16#3) -> {more, 16#e0, 16#0f};
+dec_huffman_lookup(16#6a, 16#4) -> {more, 16#e0, 16#18};
+dec_huffman_lookup(16#6a, 16#5) -> {more, 16#e0, 16#1f};
+dec_huffman_lookup(16#6a, 16#6) -> {more, 16#e0, 16#29};
+dec_huffman_lookup(16#6a, 16#7) -> {ok, 16#e0, 16#38};
+dec_huffman_lookup(16#6a, 16#8) -> {more, 16#e2, 16#03};
+dec_huffman_lookup(16#6a, 16#9) -> {more, 16#e2, 16#06};
+dec_huffman_lookup(16#6a, 16#a) -> {more, 16#e2, 16#0a};
+dec_huffman_lookup(16#6a, 16#b) -> {more, 16#e2, 16#0f};
+dec_huffman_lookup(16#6a, 16#c) -> {more, 16#e2, 16#18};
+dec_huffman_lookup(16#6a, 16#d) -> {more, 16#e2, 16#1f};
+dec_huffman_lookup(16#6a, 16#e) -> {more, 16#e2, 16#29};
+dec_huffman_lookup(16#6a, 16#f) -> {ok, 16#e2, 16#38};
+dec_huffman_lookup(16#6b, 16#0) -> {more, 16#99, 16#02};
+dec_huffman_lookup(16#6b, 16#1) -> {more, 16#99, 16#09};
+dec_huffman_lookup(16#6b, 16#2) -> {more, 16#99, 16#17};
+dec_huffman_lookup(16#6b, 16#3) -> {ok, 16#99, 16#28};
+dec_huffman_lookup(16#6b, 16#4) -> {more, 16#a1, 16#02};
+dec_huffman_lookup(16#6b, 16#5) -> {more, 16#a1, 16#09};
+dec_huffman_lookup(16#6b, 16#6) -> {more, 16#a1, 16#17};
+dec_huffman_lookup(16#6b, 16#7) -> {ok, 16#a1, 16#28};
+dec_huffman_lookup(16#6b, 16#8) -> {more, 16#a7, 16#02};
+dec_huffman_lookup(16#6b, 16#9) -> {more, 16#a7, 16#09};
+dec_huffman_lookup(16#6b, 16#a) -> {more, 16#a7, 16#17};
+dec_huffman_lookup(16#6b, 16#b) -> {ok, 16#a7, 16#28};
+dec_huffman_lookup(16#6b, 16#c) -> {more, 16#ac, 16#02};
+dec_huffman_lookup(16#6b, 16#d) -> {more, 16#ac, 16#09};
+dec_huffman_lookup(16#6b, 16#e) -> {more, 16#ac, 16#17};
+dec_huffman_lookup(16#6b, 16#f) -> {ok, 16#ac, 16#28};
+dec_huffman_lookup(16#6c, 16#0) -> {more, 16#99, 16#03};
+dec_huffman_lookup(16#6c, 16#1) -> {more, 16#99, 16#06};
+dec_huffman_lookup(16#6c, 16#2) -> {more, 16#99, 16#0a};
+dec_huffman_lookup(16#6c, 16#3) -> {more, 16#99, 16#0f};
+dec_huffman_lookup(16#6c, 16#4) -> {more, 16#99, 16#18};
+dec_huffman_lookup(16#6c, 16#5) -> {more, 16#99, 16#1f};
+dec_huffman_lookup(16#6c, 16#6) -> {more, 16#99, 16#29};
+dec_huffman_lookup(16#6c, 16#7) -> {ok, 16#99, 16#38};
+dec_huffman_lookup(16#6c, 16#8) -> {more, 16#a1, 16#03};
+dec_huffman_lookup(16#6c, 16#9) -> {more, 16#a1, 16#06};
+dec_huffman_lookup(16#6c, 16#a) -> {more, 16#a1, 16#0a};
+dec_huffman_lookup(16#6c, 16#b) -> {more, 16#a1, 16#0f};
+dec_huffman_lookup(16#6c, 16#c) -> {more, 16#a1, 16#18};
+dec_huffman_lookup(16#6c, 16#d) -> {more, 16#a1, 16#1f};
+dec_huffman_lookup(16#6c, 16#e) -> {more, 16#a1, 16#29};
+dec_huffman_lookup(16#6c, 16#f) -> {ok, 16#a1, 16#38};
+dec_huffman_lookup(16#6d, 16#0) -> {more, 16#a7, 16#03};
+dec_huffman_lookup(16#6d, 16#1) -> {more, 16#a7, 16#06};
+dec_huffman_lookup(16#6d, 16#2) -> {more, 16#a7, 16#0a};
+dec_huffman_lookup(16#6d, 16#3) -> {more, 16#a7, 16#0f};
+dec_huffman_lookup(16#6d, 16#4) -> {more, 16#a7, 16#18};
+dec_huffman_lookup(16#6d, 16#5) -> {more, 16#a7, 16#1f};
+dec_huffman_lookup(16#6d, 16#6) -> {more, 16#a7, 16#29};
+dec_huffman_lookup(16#6d, 16#7) -> {ok, 16#a7, 16#38};
+dec_huffman_lookup(16#6d, 16#8) -> {more, 16#ac, 16#03};
+dec_huffman_lookup(16#6d, 16#9) -> {more, 16#ac, 16#06};
+dec_huffman_lookup(16#6d, 16#a) -> {more, 16#ac, 16#0a};
+dec_huffman_lookup(16#6d, 16#b) -> {more, 16#ac, 16#0f};
+dec_huffman_lookup(16#6d, 16#c) -> {more, 16#ac, 16#18};
+dec_huffman_lookup(16#6d, 16#d) -> {more, 16#ac, 16#1f};
+dec_huffman_lookup(16#6d, 16#e) -> {more, 16#ac, 16#29};
+dec_huffman_lookup(16#6d, 16#f) -> {ok, 16#ac, 16#38};
+dec_huffman_lookup(16#6e, 16#0) -> {more, undefined, 16#72};
+dec_huffman_lookup(16#6e, 16#1) -> {more, undefined, 16#73};
+dec_huffman_lookup(16#6e, 16#2) -> {more, undefined, 16#75};
+dec_huffman_lookup(16#6e, 16#3) -> {more, undefined, 16#76};
+dec_huffman_lookup(16#6e, 16#4) -> {more, undefined, 16#79};
+dec_huffman_lookup(16#6e, 16#5) -> {more, undefined, 16#7b};
+dec_huffman_lookup(16#6e, 16#6) -> {more, undefined, 16#7f};
+dec_huffman_lookup(16#6e, 16#7) -> {more, undefined, 16#82};
+dec_huffman_lookup(16#6e, 16#8) -> {more, undefined, 16#88};
+dec_huffman_lookup(16#6e, 16#9) -> {more, undefined, 16#8b};
+dec_huffman_lookup(16#6e, 16#a) -> {more, undefined, 16#8f};
+dec_huffman_lookup(16#6e, 16#b) -> {more, undefined, 16#92};
+dec_huffman_lookup(16#6e, 16#c) -> {more, undefined, 16#9b};
+dec_huffman_lookup(16#6e, 16#d) -> {more, undefined, 16#a2};
+dec_huffman_lookup(16#6e, 16#e) -> {more, undefined, 16#aa};
+dec_huffman_lookup(16#6e, 16#f) -> {ok, undefined, 16#b4};
+dec_huffman_lookup(16#6f, 16#0) -> {ok, 16#b0, 16#00};
+dec_huffman_lookup(16#6f, 16#1) -> {ok, 16#b1, 16#00};
+dec_huffman_lookup(16#6f, 16#2) -> {ok, 16#b3, 16#00};
+dec_huffman_lookup(16#6f, 16#3) -> {ok, 16#d1, 16#00};
+dec_huffman_lookup(16#6f, 16#4) -> {ok, 16#d8, 16#00};
+dec_huffman_lookup(16#6f, 16#5) -> {ok, 16#d9, 16#00};
+dec_huffman_lookup(16#6f, 16#6) -> {ok, 16#e3, 16#00};
+dec_huffman_lookup(16#6f, 16#7) -> {ok, 16#e5, 16#00};
+dec_huffman_lookup(16#6f, 16#8) -> {ok, 16#e6, 16#00};
+dec_huffman_lookup(16#6f, 16#9) -> {more, undefined, 16#7a};
+dec_huffman_lookup(16#6f, 16#a) -> {more, undefined, 16#7c};
+dec_huffman_lookup(16#6f, 16#b) -> {more, undefined, 16#7d};
+dec_huffman_lookup(16#6f, 16#c) -> {more, undefined, 16#80};
+dec_huffman_lookup(16#6f, 16#d) -> {more, undefined, 16#81};
+dec_huffman_lookup(16#6f, 16#e) -> {more, undefined, 16#83};
+dec_huffman_lookup(16#6f, 16#f) -> {more, undefined, 16#84};
+dec_huffman_lookup(16#70, 16#0) -> {more, 16#b0, 16#01};
+dec_huffman_lookup(16#70, 16#1) -> {ok, 16#b0, 16#16};
+dec_huffman_lookup(16#70, 16#2) -> {more, 16#b1, 16#01};
+dec_huffman_lookup(16#70, 16#3) -> {ok, 16#b1, 16#16};
+dec_huffman_lookup(16#70, 16#4) -> {more, 16#b3, 16#01};
+dec_huffman_lookup(16#70, 16#5) -> {ok, 16#b3, 16#16};
+dec_huffman_lookup(16#70, 16#6) -> {more, 16#d1, 16#01};
+dec_huffman_lookup(16#70, 16#7) -> {ok, 16#d1, 16#16};
+dec_huffman_lookup(16#70, 16#8) -> {more, 16#d8, 16#01};
+dec_huffman_lookup(16#70, 16#9) -> {ok, 16#d8, 16#16};
+dec_huffman_lookup(16#70, 16#a) -> {more, 16#d9, 16#01};
+dec_huffman_lookup(16#70, 16#b) -> {ok, 16#d9, 16#16};
+dec_huffman_lookup(16#70, 16#c) -> {more, 16#e3, 16#01};
+dec_huffman_lookup(16#70, 16#d) -> {ok, 16#e3, 16#16};
+dec_huffman_lookup(16#70, 16#e) -> {more, 16#e5, 16#01};
+dec_huffman_lookup(16#70, 16#f) -> {ok, 16#e5, 16#16};
+dec_huffman_lookup(16#71, 16#0) -> {more, 16#b0, 16#02};
+dec_huffman_lookup(16#71, 16#1) -> {more, 16#b0, 16#09};
+dec_huffman_lookup(16#71, 16#2) -> {more, 16#b0, 16#17};
+dec_huffman_lookup(16#71, 16#3) -> {ok, 16#b0, 16#28};
+dec_huffman_lookup(16#71, 16#4) -> {more, 16#b1, 16#02};
+dec_huffman_lookup(16#71, 16#5) -> {more, 16#b1, 16#09};
+dec_huffman_lookup(16#71, 16#6) -> {more, 16#b1, 16#17};
+dec_huffman_lookup(16#71, 16#7) -> {ok, 16#b1, 16#28};
+dec_huffman_lookup(16#71, 16#8) -> {more, 16#b3, 16#02};
+dec_huffman_lookup(16#71, 16#9) -> {more, 16#b3, 16#09};
+dec_huffman_lookup(16#71, 16#a) -> {more, 16#b3, 16#17};
+dec_huffman_lookup(16#71, 16#b) -> {ok, 16#b3, 16#28};
+dec_huffman_lookup(16#71, 16#c) -> {more, 16#d1, 16#02};
+dec_huffman_lookup(16#71, 16#d) -> {more, 16#d1, 16#09};
+dec_huffman_lookup(16#71, 16#e) -> {more, 16#d1, 16#17};
+dec_huffman_lookup(16#71, 16#f) -> {ok, 16#d1, 16#28};
+dec_huffman_lookup(16#72, 16#0) -> {more, 16#b0, 16#03};
+dec_huffman_lookup(16#72, 16#1) -> {more, 16#b0, 16#06};
+dec_huffman_lookup(16#72, 16#2) -> {more, 16#b0, 16#0a};
+dec_huffman_lookup(16#72, 16#3) -> {more, 16#b0, 16#0f};
+dec_huffman_lookup(16#72, 16#4) -> {more, 16#b0, 16#18};
+dec_huffman_lookup(16#72, 16#5) -> {more, 16#b0, 16#1f};
+dec_huffman_lookup(16#72, 16#6) -> {more, 16#b0, 16#29};
+dec_huffman_lookup(16#72, 16#7) -> {ok, 16#b0, 16#38};
+dec_huffman_lookup(16#72, 16#8) -> {more, 16#b1, 16#03};
+dec_huffman_lookup(16#72, 16#9) -> {more, 16#b1, 16#06};
+dec_huffman_lookup(16#72, 16#a) -> {more, 16#b1, 16#0a};
+dec_huffman_lookup(16#72, 16#b) -> {more, 16#b1, 16#0f};
+dec_huffman_lookup(16#72, 16#c) -> {more, 16#b1, 16#18};
+dec_huffman_lookup(16#72, 16#d) -> {more, 16#b1, 16#1f};
+dec_huffman_lookup(16#72, 16#e) -> {more, 16#b1, 16#29};
+dec_huffman_lookup(16#72, 16#f) -> {ok, 16#b1, 16#38};
+dec_huffman_lookup(16#73, 16#0) -> {more, 16#b3, 16#03};
+dec_huffman_lookup(16#73, 16#1) -> {more, 16#b3, 16#06};
+dec_huffman_lookup(16#73, 16#2) -> {more, 16#b3, 16#0a};
+dec_huffman_lookup(16#73, 16#3) -> {more, 16#b3, 16#0f};
+dec_huffman_lookup(16#73, 16#4) -> {more, 16#b3, 16#18};
+dec_huffman_lookup(16#73, 16#5) -> {more, 16#b3, 16#1f};
+dec_huffman_lookup(16#73, 16#6) -> {more, 16#b3, 16#29};
+dec_huffman_lookup(16#73, 16#7) -> {ok, 16#b3, 16#38};
+dec_huffman_lookup(16#73, 16#8) -> {more, 16#d1, 16#03};
+dec_huffman_lookup(16#73, 16#9) -> {more, 16#d1, 16#06};
+dec_huffman_lookup(16#73, 16#a) -> {more, 16#d1, 16#0a};
+dec_huffman_lookup(16#73, 16#b) -> {more, 16#d1, 16#0f};
+dec_huffman_lookup(16#73, 16#c) -> {more, 16#d1, 16#18};
+dec_huffman_lookup(16#73, 16#d) -> {more, 16#d1, 16#1f};
+dec_huffman_lookup(16#73, 16#e) -> {more, 16#d1, 16#29};
+dec_huffman_lookup(16#73, 16#f) -> {ok, 16#d1, 16#38};
+dec_huffman_lookup(16#74, 16#0) -> {more, 16#d8, 16#02};
+dec_huffman_lookup(16#74, 16#1) -> {more, 16#d8, 16#09};
+dec_huffman_lookup(16#74, 16#2) -> {more, 16#d8, 16#17};
+dec_huffman_lookup(16#74, 16#3) -> {ok, 16#d8, 16#28};
+dec_huffman_lookup(16#74, 16#4) -> {more, 16#d9, 16#02};
+dec_huffman_lookup(16#74, 16#5) -> {more, 16#d9, 16#09};
+dec_huffman_lookup(16#74, 16#6) -> {more, 16#d9, 16#17};
+dec_huffman_lookup(16#74, 16#7) -> {ok, 16#d9, 16#28};
+dec_huffman_lookup(16#74, 16#8) -> {more, 16#e3, 16#02};
+dec_huffman_lookup(16#74, 16#9) -> {more, 16#e3, 16#09};
+dec_huffman_lookup(16#74, 16#a) -> {more, 16#e3, 16#17};
+dec_huffman_lookup(16#74, 16#b) -> {ok, 16#e3, 16#28};
+dec_huffman_lookup(16#74, 16#c) -> {more, 16#e5, 16#02};
+dec_huffman_lookup(16#74, 16#d) -> {more, 16#e5, 16#09};
+dec_huffman_lookup(16#74, 16#e) -> {more, 16#e5, 16#17};
+dec_huffman_lookup(16#74, 16#f) -> {ok, 16#e5, 16#28};
+dec_huffman_lookup(16#75, 16#0) -> {more, 16#d8, 16#03};
+dec_huffman_lookup(16#75, 16#1) -> {more, 16#d8, 16#06};
+dec_huffman_lookup(16#75, 16#2) -> {more, 16#d8, 16#0a};
+dec_huffman_lookup(16#75, 16#3) -> {more, 16#d8, 16#0f};
+dec_huffman_lookup(16#75, 16#4) -> {more, 16#d8, 16#18};
+dec_huffman_lookup(16#75, 16#5) -> {more, 16#d8, 16#1f};
+dec_huffman_lookup(16#75, 16#6) -> {more, 16#d8, 16#29};
+dec_huffman_lookup(16#75, 16#7) -> {ok, 16#d8, 16#38};
+dec_huffman_lookup(16#75, 16#8) -> {more, 16#d9, 16#03};
+dec_huffman_lookup(16#75, 16#9) -> {more, 16#d9, 16#06};
+dec_huffman_lookup(16#75, 16#a) -> {more, 16#d9, 16#0a};
+dec_huffman_lookup(16#75, 16#b) -> {more, 16#d9, 16#0f};
+dec_huffman_lookup(16#75, 16#c) -> {more, 16#d9, 16#18};
+dec_huffman_lookup(16#75, 16#d) -> {more, 16#d9, 16#1f};
+dec_huffman_lookup(16#75, 16#e) -> {more, 16#d9, 16#29};
+dec_huffman_lookup(16#75, 16#f) -> {ok, 16#d9, 16#38};
+dec_huffman_lookup(16#76, 16#0) -> {more, 16#e3, 16#03};
+dec_huffman_lookup(16#76, 16#1) -> {more, 16#e3, 16#06};
+dec_huffman_lookup(16#76, 16#2) -> {more, 16#e3, 16#0a};
+dec_huffman_lookup(16#76, 16#3) -> {more, 16#e3, 16#0f};
+dec_huffman_lookup(16#76, 16#4) -> {more, 16#e3, 16#18};
+dec_huffman_lookup(16#76, 16#5) -> {more, 16#e3, 16#1f};
+dec_huffman_lookup(16#76, 16#6) -> {more, 16#e3, 16#29};
+dec_huffman_lookup(16#76, 16#7) -> {ok, 16#e3, 16#38};
+dec_huffman_lookup(16#76, 16#8) -> {more, 16#e5, 16#03};
+dec_huffman_lookup(16#76, 16#9) -> {more, 16#e5, 16#06};
+dec_huffman_lookup(16#76, 16#a) -> {more, 16#e5, 16#0a};
+dec_huffman_lookup(16#76, 16#b) -> {more, 16#e5, 16#0f};
+dec_huffman_lookup(16#76, 16#c) -> {more, 16#e5, 16#18};
+dec_huffman_lookup(16#76, 16#d) -> {more, 16#e5, 16#1f};
+dec_huffman_lookup(16#76, 16#e) -> {more, 16#e5, 16#29};
+dec_huffman_lookup(16#76, 16#f) -> {ok, 16#e5, 16#38};
+dec_huffman_lookup(16#77, 16#0) -> {more, 16#e6, 16#01};
+dec_huffman_lookup(16#77, 16#1) -> {ok, 16#e6, 16#16};
+dec_huffman_lookup(16#77, 16#2) -> {ok, 16#81, 16#00};
+dec_huffman_lookup(16#77, 16#3) -> {ok, 16#84, 16#00};
+dec_huffman_lookup(16#77, 16#4) -> {ok, 16#85, 16#00};
+dec_huffman_lookup(16#77, 16#5) -> {ok, 16#86, 16#00};
+dec_huffman_lookup(16#77, 16#6) -> {ok, 16#88, 16#00};
+dec_huffman_lookup(16#77, 16#7) -> {ok, 16#92, 16#00};
+dec_huffman_lookup(16#77, 16#8) -> {ok, 16#9a, 16#00};
+dec_huffman_lookup(16#77, 16#9) -> {ok, 16#9c, 16#00};
+dec_huffman_lookup(16#77, 16#a) -> {ok, 16#a0, 16#00};
+dec_huffman_lookup(16#77, 16#b) -> {ok, 16#a3, 16#00};
+dec_huffman_lookup(16#77, 16#c) -> {ok, 16#a4, 16#00};
+dec_huffman_lookup(16#77, 16#d) -> {ok, 16#a9, 16#00};
+dec_huffman_lookup(16#77, 16#e) -> {ok, 16#aa, 16#00};
+dec_huffman_lookup(16#77, 16#f) -> {ok, 16#ad, 16#00};
+dec_huffman_lookup(16#78, 16#0) -> {more, 16#e6, 16#02};
+dec_huffman_lookup(16#78, 16#1) -> {more, 16#e6, 16#09};
+dec_huffman_lookup(16#78, 16#2) -> {more, 16#e6, 16#17};
+dec_huffman_lookup(16#78, 16#3) -> {ok, 16#e6, 16#28};
+dec_huffman_lookup(16#78, 16#4) -> {more, 16#81, 16#01};
+dec_huffman_lookup(16#78, 16#5) -> {ok, 16#81, 16#16};
+dec_huffman_lookup(16#78, 16#6) -> {more, 16#84, 16#01};
+dec_huffman_lookup(16#78, 16#7) -> {ok, 16#84, 16#16};
+dec_huffman_lookup(16#78, 16#8) -> {more, 16#85, 16#01};
+dec_huffman_lookup(16#78, 16#9) -> {ok, 16#85, 16#16};
+dec_huffman_lookup(16#78, 16#a) -> {more, 16#86, 16#01};
+dec_huffman_lookup(16#78, 16#b) -> {ok, 16#86, 16#16};
+dec_huffman_lookup(16#78, 16#c) -> {more, 16#88, 16#01};
+dec_huffman_lookup(16#78, 16#d) -> {ok, 16#88, 16#16};
+dec_huffman_lookup(16#78, 16#e) -> {more, 16#92, 16#01};
+dec_huffman_lookup(16#78, 16#f) -> {ok, 16#92, 16#16};
+dec_huffman_lookup(16#79, 16#0) -> {more, 16#e6, 16#03};
+dec_huffman_lookup(16#79, 16#1) -> {more, 16#e6, 16#06};
+dec_huffman_lookup(16#79, 16#2) -> {more, 16#e6, 16#0a};
+dec_huffman_lookup(16#79, 16#3) -> {more, 16#e6, 16#0f};
+dec_huffman_lookup(16#79, 16#4) -> {more, 16#e6, 16#18};
+dec_huffman_lookup(16#79, 16#5) -> {more, 16#e6, 16#1f};
+dec_huffman_lookup(16#79, 16#6) -> {more, 16#e6, 16#29};
+dec_huffman_lookup(16#79, 16#7) -> {ok, 16#e6, 16#38};
+dec_huffman_lookup(16#79, 16#8) -> {more, 16#81, 16#02};
+dec_huffman_lookup(16#79, 16#9) -> {more, 16#81, 16#09};
+dec_huffman_lookup(16#79, 16#a) -> {more, 16#81, 16#17};
+dec_huffman_lookup(16#79, 16#b) -> {ok, 16#81, 16#28};
+dec_huffman_lookup(16#79, 16#c) -> {more, 16#84, 16#02};
+dec_huffman_lookup(16#79, 16#d) -> {more, 16#84, 16#09};
+dec_huffman_lookup(16#79, 16#e) -> {more, 16#84, 16#17};
+dec_huffman_lookup(16#79, 16#f) -> {ok, 16#84, 16#28};
+dec_huffman_lookup(16#7a, 16#0) -> {more, 16#81, 16#03};
+dec_huffman_lookup(16#7a, 16#1) -> {more, 16#81, 16#06};
+dec_huffman_lookup(16#7a, 16#2) -> {more, 16#81, 16#0a};
+dec_huffman_lookup(16#7a, 16#3) -> {more, 16#81, 16#0f};
+dec_huffman_lookup(16#7a, 16#4) -> {more, 16#81, 16#18};
+dec_huffman_lookup(16#7a, 16#5) -> {more, 16#81, 16#1f};
+dec_huffman_lookup(16#7a, 16#6) -> {more, 16#81, 16#29};
+dec_huffman_lookup(16#7a, 16#7) -> {ok, 16#81, 16#38};
+dec_huffman_lookup(16#7a, 16#8) -> {more, 16#84, 16#03};
+dec_huffman_lookup(16#7a, 16#9) -> {more, 16#84, 16#06};
+dec_huffman_lookup(16#7a, 16#a) -> {more, 16#84, 16#0a};
+dec_huffman_lookup(16#7a, 16#b) -> {more, 16#84, 16#0f};
+dec_huffman_lookup(16#7a, 16#c) -> {more, 16#84, 16#18};
+dec_huffman_lookup(16#7a, 16#d) -> {more, 16#84, 16#1f};
+dec_huffman_lookup(16#7a, 16#e) -> {more, 16#84, 16#29};
+dec_huffman_lookup(16#7a, 16#f) -> {ok, 16#84, 16#38};
+dec_huffman_lookup(16#7b, 16#0) -> {more, 16#85, 16#02};
+dec_huffman_lookup(16#7b, 16#1) -> {more, 16#85, 16#09};
+dec_huffman_lookup(16#7b, 16#2) -> {more, 16#85, 16#17};
+dec_huffman_lookup(16#7b, 16#3) -> {ok, 16#85, 16#28};
+dec_huffman_lookup(16#7b, 16#4) -> {more, 16#86, 16#02};
+dec_huffman_lookup(16#7b, 16#5) -> {more, 16#86, 16#09};
+dec_huffman_lookup(16#7b, 16#6) -> {more, 16#86, 16#17};
+dec_huffman_lookup(16#7b, 16#7) -> {ok, 16#86, 16#28};
+dec_huffman_lookup(16#7b, 16#8) -> {more, 16#88, 16#02};
+dec_huffman_lookup(16#7b, 16#9) -> {more, 16#88, 16#09};
+dec_huffman_lookup(16#7b, 16#a) -> {more, 16#88, 16#17};
+dec_huffman_lookup(16#7b, 16#b) -> {ok, 16#88, 16#28};
+dec_huffman_lookup(16#7b, 16#c) -> {more, 16#92, 16#02};
+dec_huffman_lookup(16#7b, 16#d) -> {more, 16#92, 16#09};
+dec_huffman_lookup(16#7b, 16#e) -> {more, 16#92, 16#17};
+dec_huffman_lookup(16#7b, 16#f) -> {ok, 16#92, 16#28};
+dec_huffman_lookup(16#7c, 16#0) -> {more, 16#85, 16#03};
+dec_huffman_lookup(16#7c, 16#1) -> {more, 16#85, 16#06};
+dec_huffman_lookup(16#7c, 16#2) -> {more, 16#85, 16#0a};
+dec_huffman_lookup(16#7c, 16#3) -> {more, 16#85, 16#0f};
+dec_huffman_lookup(16#7c, 16#4) -> {more, 16#85, 16#18};
+dec_huffman_lookup(16#7c, 16#5) -> {more, 16#85, 16#1f};
+dec_huffman_lookup(16#7c, 16#6) -> {more, 16#85, 16#29};
+dec_huffman_lookup(16#7c, 16#7) -> {ok, 16#85, 16#38};
+dec_huffman_lookup(16#7c, 16#8) -> {more, 16#86, 16#03};
+dec_huffman_lookup(16#7c, 16#9) -> {more, 16#86, 16#06};
+dec_huffman_lookup(16#7c, 16#a) -> {more, 16#86, 16#0a};
+dec_huffman_lookup(16#7c, 16#b) -> {more, 16#86, 16#0f};
+dec_huffman_lookup(16#7c, 16#c) -> {more, 16#86, 16#18};
+dec_huffman_lookup(16#7c, 16#d) -> {more, 16#86, 16#1f};
+dec_huffman_lookup(16#7c, 16#e) -> {more, 16#86, 16#29};
+dec_huffman_lookup(16#7c, 16#f) -> {ok, 16#86, 16#38};
+dec_huffman_lookup(16#7d, 16#0) -> {more, 16#88, 16#03};
+dec_huffman_lookup(16#7d, 16#1) -> {more, 16#88, 16#06};
+dec_huffman_lookup(16#7d, 16#2) -> {more, 16#88, 16#0a};
+dec_huffman_lookup(16#7d, 16#3) -> {more, 16#88, 16#0f};
+dec_huffman_lookup(16#7d, 16#4) -> {more, 16#88, 16#18};
+dec_huffman_lookup(16#7d, 16#5) -> {more, 16#88, 16#1f};
+dec_huffman_lookup(16#7d, 16#6) -> {more, 16#88, 16#29};
+dec_huffman_lookup(16#7d, 16#7) -> {ok, 16#88, 16#38};
+dec_huffman_lookup(16#7d, 16#8) -> {more, 16#92, 16#03};
+dec_huffman_lookup(16#7d, 16#9) -> {more, 16#92, 16#06};
+dec_huffman_lookup(16#7d, 16#a) -> {more, 16#92, 16#0a};
+dec_huffman_lookup(16#7d, 16#b) -> {more, 16#92, 16#0f};
+dec_huffman_lookup(16#7d, 16#c) -> {more, 16#92, 16#18};
+dec_huffman_lookup(16#7d, 16#d) -> {more, 16#92, 16#1f};
+dec_huffman_lookup(16#7d, 16#e) -> {more, 16#92, 16#29};
+dec_huffman_lookup(16#7d, 16#f) -> {ok, 16#92, 16#38};
+dec_huffman_lookup(16#7e, 16#0) -> {more, 16#9a, 16#01};
+dec_huffman_lookup(16#7e, 16#1) -> {ok, 16#9a, 16#16};
+dec_huffman_lookup(16#7e, 16#2) -> {more, 16#9c, 16#01};
+dec_huffman_lookup(16#7e, 16#3) -> {ok, 16#9c, 16#16};
+dec_huffman_lookup(16#7e, 16#4) -> {more, 16#a0, 16#01};
+dec_huffman_lookup(16#7e, 16#5) -> {ok, 16#a0, 16#16};
+dec_huffman_lookup(16#7e, 16#6) -> {more, 16#a3, 16#01};
+dec_huffman_lookup(16#7e, 16#7) -> {ok, 16#a3, 16#16};
+dec_huffman_lookup(16#7e, 16#8) -> {more, 16#a4, 16#01};
+dec_huffman_lookup(16#7e, 16#9) -> {ok, 16#a4, 16#16};
+dec_huffman_lookup(16#7e, 16#a) -> {more, 16#a9, 16#01};
+dec_huffman_lookup(16#7e, 16#b) -> {ok, 16#a9, 16#16};
+dec_huffman_lookup(16#7e, 16#c) -> {more, 16#aa, 16#01};
+dec_huffman_lookup(16#7e, 16#d) -> {ok, 16#aa, 16#16};
+dec_huffman_lookup(16#7e, 16#e) -> {more, 16#ad, 16#01};
+dec_huffman_lookup(16#7e, 16#f) -> {ok, 16#ad, 16#16};
+dec_huffman_lookup(16#7f, 16#0) -> {more, 16#9a, 16#02};
+dec_huffman_lookup(16#7f, 16#1) -> {more, 16#9a, 16#09};
+dec_huffman_lookup(16#7f, 16#2) -> {more, 16#9a, 16#17};
+dec_huffman_lookup(16#7f, 16#3) -> {ok, 16#9a, 16#28};
+dec_huffman_lookup(16#7f, 16#4) -> {more, 16#9c, 16#02};
+dec_huffman_lookup(16#7f, 16#5) -> {more, 16#9c, 16#09};
+dec_huffman_lookup(16#7f, 16#6) -> {more, 16#9c, 16#17};
+dec_huffman_lookup(16#7f, 16#7) -> {ok, 16#9c, 16#28};
+dec_huffman_lookup(16#7f, 16#8) -> {more, 16#a0, 16#02};
+dec_huffman_lookup(16#7f, 16#9) -> {more, 16#a0, 16#09};
+dec_huffman_lookup(16#7f, 16#a) -> {more, 16#a0, 16#17};
+dec_huffman_lookup(16#7f, 16#b) -> {ok, 16#a0, 16#28};
+dec_huffman_lookup(16#7f, 16#c) -> {more, 16#a3, 16#02};
+dec_huffman_lookup(16#7f, 16#d) -> {more, 16#a3, 16#09};
+dec_huffman_lookup(16#7f, 16#e) -> {more, 16#a3, 16#17};
+dec_huffman_lookup(16#7f, 16#f) -> {ok, 16#a3, 16#28};
+dec_huffman_lookup(16#80, 16#0) -> {more, 16#9a, 16#03};
+dec_huffman_lookup(16#80, 16#1) -> {more, 16#9a, 16#06};
+dec_huffman_lookup(16#80, 16#2) -> {more, 16#9a, 16#0a};
+dec_huffman_lookup(16#80, 16#3) -> {more, 16#9a, 16#0f};
+dec_huffman_lookup(16#80, 16#4) -> {more, 16#9a, 16#18};
+dec_huffman_lookup(16#80, 16#5) -> {more, 16#9a, 16#1f};
+dec_huffman_lookup(16#80, 16#6) -> {more, 16#9a, 16#29};
+dec_huffman_lookup(16#80, 16#7) -> {ok, 16#9a, 16#38};
+dec_huffman_lookup(16#80, 16#8) -> {more, 16#9c, 16#03};
+dec_huffman_lookup(16#80, 16#9) -> {more, 16#9c, 16#06};
+dec_huffman_lookup(16#80, 16#a) -> {more, 16#9c, 16#0a};
+dec_huffman_lookup(16#80, 16#b) -> {more, 16#9c, 16#0f};
+dec_huffman_lookup(16#80, 16#c) -> {more, 16#9c, 16#18};
+dec_huffman_lookup(16#80, 16#d) -> {more, 16#9c, 16#1f};
+dec_huffman_lookup(16#80, 16#e) -> {more, 16#9c, 16#29};
+dec_huffman_lookup(16#80, 16#f) -> {ok, 16#9c, 16#38};
+dec_huffman_lookup(16#81, 16#0) -> {more, 16#a0, 16#03};
+dec_huffman_lookup(16#81, 16#1) -> {more, 16#a0, 16#06};
+dec_huffman_lookup(16#81, 16#2) -> {more, 16#a0, 16#0a};
+dec_huffman_lookup(16#81, 16#3) -> {more, 16#a0, 16#0f};
+dec_huffman_lookup(16#81, 16#4) -> {more, 16#a0, 16#18};
+dec_huffman_lookup(16#81, 16#5) -> {more, 16#a0, 16#1f};
+dec_huffman_lookup(16#81, 16#6) -> {more, 16#a0, 16#29};
+dec_huffman_lookup(16#81, 16#7) -> {ok, 16#a0, 16#38};
+dec_huffman_lookup(16#81, 16#8) -> {more, 16#a3, 16#03};
+dec_huffman_lookup(16#81, 16#9) -> {more, 16#a3, 16#06};
+dec_huffman_lookup(16#81, 16#a) -> {more, 16#a3, 16#0a};
+dec_huffman_lookup(16#81, 16#b) -> {more, 16#a3, 16#0f};
+dec_huffman_lookup(16#81, 16#c) -> {more, 16#a3, 16#18};
+dec_huffman_lookup(16#81, 16#d) -> {more, 16#a3, 16#1f};
+dec_huffman_lookup(16#81, 16#e) -> {more, 16#a3, 16#29};
+dec_huffman_lookup(16#81, 16#f) -> {ok, 16#a3, 16#38};
+dec_huffman_lookup(16#82, 16#0) -> {more, 16#a4, 16#02};
+dec_huffman_lookup(16#82, 16#1) -> {more, 16#a4, 16#09};
+dec_huffman_lookup(16#82, 16#2) -> {more, 16#a4, 16#17};
+dec_huffman_lookup(16#82, 16#3) -> {ok, 16#a4, 16#28};
+dec_huffman_lookup(16#82, 16#4) -> {more, 16#a9, 16#02};
+dec_huffman_lookup(16#82, 16#5) -> {more, 16#a9, 16#09};
+dec_huffman_lookup(16#82, 16#6) -> {more, 16#a9, 16#17};
+dec_huffman_lookup(16#82, 16#7) -> {ok, 16#a9, 16#28};
+dec_huffman_lookup(16#82, 16#8) -> {more, 16#aa, 16#02};
+dec_huffman_lookup(16#82, 16#9) -> {more, 16#aa, 16#09};
+dec_huffman_lookup(16#82, 16#a) -> {more, 16#aa, 16#17};
+dec_huffman_lookup(16#82, 16#b) -> {ok, 16#aa, 16#28};
+dec_huffman_lookup(16#82, 16#c) -> {more, 16#ad, 16#02};
+dec_huffman_lookup(16#82, 16#d) -> {more, 16#ad, 16#09};
+dec_huffman_lookup(16#82, 16#e) -> {more, 16#ad, 16#17};
+dec_huffman_lookup(16#82, 16#f) -> {ok, 16#ad, 16#28};
+dec_huffman_lookup(16#83, 16#0) -> {more, 16#a4, 16#03};
+dec_huffman_lookup(16#83, 16#1) -> {more, 16#a4, 16#06};
+dec_huffman_lookup(16#83, 16#2) -> {more, 16#a4, 16#0a};
+dec_huffman_lookup(16#83, 16#3) -> {more, 16#a4, 16#0f};
+dec_huffman_lookup(16#83, 16#4) -> {more, 16#a4, 16#18};
+dec_huffman_lookup(16#83, 16#5) -> {more, 16#a4, 16#1f};
+dec_huffman_lookup(16#83, 16#6) -> {more, 16#a4, 16#29};
+dec_huffman_lookup(16#83, 16#7) -> {ok, 16#a4, 16#38};
+dec_huffman_lookup(16#83, 16#8) -> {more, 16#a9, 16#03};
+dec_huffman_lookup(16#83, 16#9) -> {more, 16#a9, 16#06};
+dec_huffman_lookup(16#83, 16#a) -> {more, 16#a9, 16#0a};
+dec_huffman_lookup(16#83, 16#b) -> {more, 16#a9, 16#0f};
+dec_huffman_lookup(16#83, 16#c) -> {more, 16#a9, 16#18};
+dec_huffman_lookup(16#83, 16#d) -> {more, 16#a9, 16#1f};
+dec_huffman_lookup(16#83, 16#e) -> {more, 16#a9, 16#29};
+dec_huffman_lookup(16#83, 16#f) -> {ok, 16#a9, 16#38};
+dec_huffman_lookup(16#84, 16#0) -> {more, 16#aa, 16#03};
+dec_huffman_lookup(16#84, 16#1) -> {more, 16#aa, 16#06};
+dec_huffman_lookup(16#84, 16#2) -> {more, 16#aa, 16#0a};
+dec_huffman_lookup(16#84, 16#3) -> {more, 16#aa, 16#0f};
+dec_huffman_lookup(16#84, 16#4) -> {more, 16#aa, 16#18};
+dec_huffman_lookup(16#84, 16#5) -> {more, 16#aa, 16#1f};
+dec_huffman_lookup(16#84, 16#6) -> {more, 16#aa, 16#29};
+dec_huffman_lookup(16#84, 16#7) -> {ok, 16#aa, 16#38};
+dec_huffman_lookup(16#84, 16#8) -> {more, 16#ad, 16#03};
+dec_huffman_lookup(16#84, 16#9) -> {more, 16#ad, 16#06};
+dec_huffman_lookup(16#84, 16#a) -> {more, 16#ad, 16#0a};
+dec_huffman_lookup(16#84, 16#b) -> {more, 16#ad, 16#0f};
+dec_huffman_lookup(16#84, 16#c) -> {more, 16#ad, 16#18};
+dec_huffman_lookup(16#84, 16#d) -> {more, 16#ad, 16#1f};
+dec_huffman_lookup(16#84, 16#e) -> {more, 16#ad, 16#29};
+dec_huffman_lookup(16#84, 16#f) -> {ok, 16#ad, 16#38};
+dec_huffman_lookup(16#85, 16#0) -> {more, undefined, 16#89};
+dec_huffman_lookup(16#85, 16#1) -> {more, undefined, 16#8a};
+dec_huffman_lookup(16#85, 16#2) -> {more, undefined, 16#8c};
+dec_huffman_lookup(16#85, 16#3) -> {more, undefined, 16#8d};
+dec_huffman_lookup(16#85, 16#4) -> {more, undefined, 16#90};
+dec_huffman_lookup(16#85, 16#5) -> {more, undefined, 16#91};
+dec_huffman_lookup(16#85, 16#6) -> {more, undefined, 16#93};
+dec_huffman_lookup(16#85, 16#7) -> {more, undefined, 16#96};
+dec_huffman_lookup(16#85, 16#8) -> {more, undefined, 16#9c};
+dec_huffman_lookup(16#85, 16#9) -> {more, undefined, 16#9f};
+dec_huffman_lookup(16#85, 16#a) -> {more, undefined, 16#a3};
+dec_huffman_lookup(16#85, 16#b) -> {more, undefined, 16#a6};
+dec_huffman_lookup(16#85, 16#c) -> {more, undefined, 16#ab};
+dec_huffman_lookup(16#85, 16#d) -> {more, undefined, 16#ae};
+dec_huffman_lookup(16#85, 16#e) -> {more, undefined, 16#b5};
+dec_huffman_lookup(16#85, 16#f) -> {ok, undefined, 16#be};
+dec_huffman_lookup(16#86, 16#0) -> {ok, 16#b2, 16#00};
+dec_huffman_lookup(16#86, 16#1) -> {ok, 16#b5, 16#00};
+dec_huffman_lookup(16#86, 16#2) -> {ok, 16#b9, 16#00};
+dec_huffman_lookup(16#86, 16#3) -> {ok, 16#ba, 16#00};
+dec_huffman_lookup(16#86, 16#4) -> {ok, 16#bb, 16#00};
+dec_huffman_lookup(16#86, 16#5) -> {ok, 16#bd, 16#00};
+dec_huffman_lookup(16#86, 16#6) -> {ok, 16#be, 16#00};
+dec_huffman_lookup(16#86, 16#7) -> {ok, 16#c4, 16#00};
+dec_huffman_lookup(16#86, 16#8) -> {ok, 16#c6, 16#00};
+dec_huffman_lookup(16#86, 16#9) -> {ok, 16#e4, 16#00};
+dec_huffman_lookup(16#86, 16#a) -> {ok, 16#e8, 16#00};
+dec_huffman_lookup(16#86, 16#b) -> {ok, 16#e9, 16#00};
+dec_huffman_lookup(16#86, 16#c) -> {more, undefined, 16#94};
+dec_huffman_lookup(16#86, 16#d) -> {more, undefined, 16#95};
+dec_huffman_lookup(16#86, 16#e) -> {more, undefined, 16#97};
+dec_huffman_lookup(16#86, 16#f) -> {more, undefined, 16#98};
+dec_huffman_lookup(16#87, 16#0) -> {more, 16#b2, 16#01};
+dec_huffman_lookup(16#87, 16#1) -> {ok, 16#b2, 16#16};
+dec_huffman_lookup(16#87, 16#2) -> {more, 16#b5, 16#01};
+dec_huffman_lookup(16#87, 16#3) -> {ok, 16#b5, 16#16};
+dec_huffman_lookup(16#87, 16#4) -> {more, 16#b9, 16#01};
+dec_huffman_lookup(16#87, 16#5) -> {ok, 16#b9, 16#16};
+dec_huffman_lookup(16#87, 16#6) -> {more, 16#ba, 16#01};
+dec_huffman_lookup(16#87, 16#7) -> {ok, 16#ba, 16#16};
+dec_huffman_lookup(16#87, 16#8) -> {more, 16#bb, 16#01};
+dec_huffman_lookup(16#87, 16#9) -> {ok, 16#bb, 16#16};
+dec_huffman_lookup(16#87, 16#a) -> {more, 16#bd, 16#01};
+dec_huffman_lookup(16#87, 16#b) -> {ok, 16#bd, 16#16};
+dec_huffman_lookup(16#87, 16#c) -> {more, 16#be, 16#01};
+dec_huffman_lookup(16#87, 16#d) -> {ok, 16#be, 16#16};
+dec_huffman_lookup(16#87, 16#e) -> {more, 16#c4, 16#01};
+dec_huffman_lookup(16#87, 16#f) -> {ok, 16#c4, 16#16};
+dec_huffman_lookup(16#88, 16#0) -> {more, 16#b2, 16#02};
+dec_huffman_lookup(16#88, 16#1) -> {more, 16#b2, 16#09};
+dec_huffman_lookup(16#88, 16#2) -> {more, 16#b2, 16#17};
+dec_huffman_lookup(16#88, 16#3) -> {ok, 16#b2, 16#28};
+dec_huffman_lookup(16#88, 16#4) -> {more, 16#b5, 16#02};
+dec_huffman_lookup(16#88, 16#5) -> {more, 16#b5, 16#09};
+dec_huffman_lookup(16#88, 16#6) -> {more, 16#b5, 16#17};
+dec_huffman_lookup(16#88, 16#7) -> {ok, 16#b5, 16#28};
+dec_huffman_lookup(16#88, 16#8) -> {more, 16#b9, 16#02};
+dec_huffman_lookup(16#88, 16#9) -> {more, 16#b9, 16#09};
+dec_huffman_lookup(16#88, 16#a) -> {more, 16#b9, 16#17};
+dec_huffman_lookup(16#88, 16#b) -> {ok, 16#b9, 16#28};
+dec_huffman_lookup(16#88, 16#c) -> {more, 16#ba, 16#02};
+dec_huffman_lookup(16#88, 16#d) -> {more, 16#ba, 16#09};
+dec_huffman_lookup(16#88, 16#e) -> {more, 16#ba, 16#17};
+dec_huffman_lookup(16#88, 16#f) -> {ok, 16#ba, 16#28};
+dec_huffman_lookup(16#89, 16#0) -> {more, 16#b2, 16#03};
+dec_huffman_lookup(16#89, 16#1) -> {more, 16#b2, 16#06};
+dec_huffman_lookup(16#89, 16#2) -> {more, 16#b2, 16#0a};
+dec_huffman_lookup(16#89, 16#3) -> {more, 16#b2, 16#0f};
+dec_huffman_lookup(16#89, 16#4) -> {more, 16#b2, 16#18};
+dec_huffman_lookup(16#89, 16#5) -> {more, 16#b2, 16#1f};
+dec_huffman_lookup(16#89, 16#6) -> {more, 16#b2, 16#29};
+dec_huffman_lookup(16#89, 16#7) -> {ok, 16#b2, 16#38};
+dec_huffman_lookup(16#89, 16#8) -> {more, 16#b5, 16#03};
+dec_huffman_lookup(16#89, 16#9) -> {more, 16#b5, 16#06};
+dec_huffman_lookup(16#89, 16#a) -> {more, 16#b5, 16#0a};
+dec_huffman_lookup(16#89, 16#b) -> {more, 16#b5, 16#0f};
+dec_huffman_lookup(16#89, 16#c) -> {more, 16#b5, 16#18};
+dec_huffman_lookup(16#89, 16#d) -> {more, 16#b5, 16#1f};
+dec_huffman_lookup(16#89, 16#e) -> {more, 16#b5, 16#29};
+dec_huffman_lookup(16#89, 16#f) -> {ok, 16#b5, 16#38};
+dec_huffman_lookup(16#8a, 16#0) -> {more, 16#b9, 16#03};
+dec_huffman_lookup(16#8a, 16#1) -> {more, 16#b9, 16#06};
+dec_huffman_lookup(16#8a, 16#2) -> {more, 16#b9, 16#0a};
+dec_huffman_lookup(16#8a, 16#3) -> {more, 16#b9, 16#0f};
+dec_huffman_lookup(16#8a, 16#4) -> {more, 16#b9, 16#18};
+dec_huffman_lookup(16#8a, 16#5) -> {more, 16#b9, 16#1f};
+dec_huffman_lookup(16#8a, 16#6) -> {more, 16#b9, 16#29};
+dec_huffman_lookup(16#8a, 16#7) -> {ok, 16#b9, 16#38};
+dec_huffman_lookup(16#8a, 16#8) -> {more, 16#ba, 16#03};
+dec_huffman_lookup(16#8a, 16#9) -> {more, 16#ba, 16#06};
+dec_huffman_lookup(16#8a, 16#a) -> {more, 16#ba, 16#0a};
+dec_huffman_lookup(16#8a, 16#b) -> {more, 16#ba, 16#0f};
+dec_huffman_lookup(16#8a, 16#c) -> {more, 16#ba, 16#18};
+dec_huffman_lookup(16#8a, 16#d) -> {more, 16#ba, 16#1f};
+dec_huffman_lookup(16#8a, 16#e) -> {more, 16#ba, 16#29};
+dec_huffman_lookup(16#8a, 16#f) -> {ok, 16#ba, 16#38};
+dec_huffman_lookup(16#8b, 16#0) -> {more, 16#bb, 16#02};
+dec_huffman_lookup(16#8b, 16#1) -> {more, 16#bb, 16#09};
+dec_huffman_lookup(16#8b, 16#2) -> {more, 16#bb, 16#17};
+dec_huffman_lookup(16#8b, 16#3) -> {ok, 16#bb, 16#28};
+dec_huffman_lookup(16#8b, 16#4) -> {more, 16#bd, 16#02};
+dec_huffman_lookup(16#8b, 16#5) -> {more, 16#bd, 16#09};
+dec_huffman_lookup(16#8b, 16#6) -> {more, 16#bd, 16#17};
+dec_huffman_lookup(16#8b, 16#7) -> {ok, 16#bd, 16#28};
+dec_huffman_lookup(16#8b, 16#8) -> {more, 16#be, 16#02};
+dec_huffman_lookup(16#8b, 16#9) -> {more, 16#be, 16#09};
+dec_huffman_lookup(16#8b, 16#a) -> {more, 16#be, 16#17};
+dec_huffman_lookup(16#8b, 16#b) -> {ok, 16#be, 16#28};
+dec_huffman_lookup(16#8b, 16#c) -> {more, 16#c4, 16#02};
+dec_huffman_lookup(16#8b, 16#d) -> {more, 16#c4, 16#09};
+dec_huffman_lookup(16#8b, 16#e) -> {more, 16#c4, 16#17};
+dec_huffman_lookup(16#8b, 16#f) -> {ok, 16#c4, 16#28};
+dec_huffman_lookup(16#8c, 16#0) -> {more, 16#bb, 16#03};
+dec_huffman_lookup(16#8c, 16#1) -> {more, 16#bb, 16#06};
+dec_huffman_lookup(16#8c, 16#2) -> {more, 16#bb, 16#0a};
+dec_huffman_lookup(16#8c, 16#3) -> {more, 16#bb, 16#0f};
+dec_huffman_lookup(16#8c, 16#4) -> {more, 16#bb, 16#18};
+dec_huffman_lookup(16#8c, 16#5) -> {more, 16#bb, 16#1f};
+dec_huffman_lookup(16#8c, 16#6) -> {more, 16#bb, 16#29};
+dec_huffman_lookup(16#8c, 16#7) -> {ok, 16#bb, 16#38};
+dec_huffman_lookup(16#8c, 16#8) -> {more, 16#bd, 16#03};
+dec_huffman_lookup(16#8c, 16#9) -> {more, 16#bd, 16#06};
+dec_huffman_lookup(16#8c, 16#a) -> {more, 16#bd, 16#0a};
+dec_huffman_lookup(16#8c, 16#b) -> {more, 16#bd, 16#0f};
+dec_huffman_lookup(16#8c, 16#c) -> {more, 16#bd, 16#18};
+dec_huffman_lookup(16#8c, 16#d) -> {more, 16#bd, 16#1f};
+dec_huffman_lookup(16#8c, 16#e) -> {more, 16#bd, 16#29};
+dec_huffman_lookup(16#8c, 16#f) -> {ok, 16#bd, 16#38};
+dec_huffman_lookup(16#8d, 16#0) -> {more, 16#be, 16#03};
+dec_huffman_lookup(16#8d, 16#1) -> {more, 16#be, 16#06};
+dec_huffman_lookup(16#8d, 16#2) -> {more, 16#be, 16#0a};
+dec_huffman_lookup(16#8d, 16#3) -> {more, 16#be, 16#0f};
+dec_huffman_lookup(16#8d, 16#4) -> {more, 16#be, 16#18};
+dec_huffman_lookup(16#8d, 16#5) -> {more, 16#be, 16#1f};
+dec_huffman_lookup(16#8d, 16#6) -> {more, 16#be, 16#29};
+dec_huffman_lookup(16#8d, 16#7) -> {ok, 16#be, 16#38};
+dec_huffman_lookup(16#8d, 16#8) -> {more, 16#c4, 16#03};
+dec_huffman_lookup(16#8d, 16#9) -> {more, 16#c4, 16#06};
+dec_huffman_lookup(16#8d, 16#a) -> {more, 16#c4, 16#0a};
+dec_huffman_lookup(16#8d, 16#b) -> {more, 16#c4, 16#0f};
+dec_huffman_lookup(16#8d, 16#c) -> {more, 16#c4, 16#18};
+dec_huffman_lookup(16#8d, 16#d) -> {more, 16#c4, 16#1f};
+dec_huffman_lookup(16#8d, 16#e) -> {more, 16#c4, 16#29};
+dec_huffman_lookup(16#8d, 16#f) -> {ok, 16#c4, 16#38};
+dec_huffman_lookup(16#8e, 16#0) -> {more, 16#c6, 16#01};
+dec_huffman_lookup(16#8e, 16#1) -> {ok, 16#c6, 16#16};
+dec_huffman_lookup(16#8e, 16#2) -> {more, 16#e4, 16#01};
+dec_huffman_lookup(16#8e, 16#3) -> {ok, 16#e4, 16#16};
+dec_huffman_lookup(16#8e, 16#4) -> {more, 16#e8, 16#01};
+dec_huffman_lookup(16#8e, 16#5) -> {ok, 16#e8, 16#16};
+dec_huffman_lookup(16#8e, 16#6) -> {more, 16#e9, 16#01};
+dec_huffman_lookup(16#8e, 16#7) -> {ok, 16#e9, 16#16};
+dec_huffman_lookup(16#8e, 16#8) -> {ok, 16#01, 16#00};
+dec_huffman_lookup(16#8e, 16#9) -> {ok, 16#87, 16#00};
+dec_huffman_lookup(16#8e, 16#a) -> {ok, 16#89, 16#00};
+dec_huffman_lookup(16#8e, 16#b) -> {ok, 16#8a, 16#00};
+dec_huffman_lookup(16#8e, 16#c) -> {ok, 16#8b, 16#00};
+dec_huffman_lookup(16#8e, 16#d) -> {ok, 16#8c, 16#00};
+dec_huffman_lookup(16#8e, 16#e) -> {ok, 16#8d, 16#00};
+dec_huffman_lookup(16#8e, 16#f) -> {ok, 16#8f, 16#00};
+dec_huffman_lookup(16#8f, 16#0) -> {more, 16#c6, 16#02};
+dec_huffman_lookup(16#8f, 16#1) -> {more, 16#c6, 16#09};
+dec_huffman_lookup(16#8f, 16#2) -> {more, 16#c6, 16#17};
+dec_huffman_lookup(16#8f, 16#3) -> {ok, 16#c6, 16#28};
+dec_huffman_lookup(16#8f, 16#4) -> {more, 16#e4, 16#02};
+dec_huffman_lookup(16#8f, 16#5) -> {more, 16#e4, 16#09};
+dec_huffman_lookup(16#8f, 16#6) -> {more, 16#e4, 16#17};
+dec_huffman_lookup(16#8f, 16#7) -> {ok, 16#e4, 16#28};
+dec_huffman_lookup(16#8f, 16#8) -> {more, 16#e8, 16#02};
+dec_huffman_lookup(16#8f, 16#9) -> {more, 16#e8, 16#09};
+dec_huffman_lookup(16#8f, 16#a) -> {more, 16#e8, 16#17};
+dec_huffman_lookup(16#8f, 16#b) -> {ok, 16#e8, 16#28};
+dec_huffman_lookup(16#8f, 16#c) -> {more, 16#e9, 16#02};
+dec_huffman_lookup(16#8f, 16#d) -> {more, 16#e9, 16#09};
+dec_huffman_lookup(16#8f, 16#e) -> {more, 16#e9, 16#17};
+dec_huffman_lookup(16#8f, 16#f) -> {ok, 16#e9, 16#28};
+dec_huffman_lookup(16#90, 16#0) -> {more, 16#c6, 16#03};
+dec_huffman_lookup(16#90, 16#1) -> {more, 16#c6, 16#06};
+dec_huffman_lookup(16#90, 16#2) -> {more, 16#c6, 16#0a};
+dec_huffman_lookup(16#90, 16#3) -> {more, 16#c6, 16#0f};
+dec_huffman_lookup(16#90, 16#4) -> {more, 16#c6, 16#18};
+dec_huffman_lookup(16#90, 16#5) -> {more, 16#c6, 16#1f};
+dec_huffman_lookup(16#90, 16#6) -> {more, 16#c6, 16#29};
+dec_huffman_lookup(16#90, 16#7) -> {ok, 16#c6, 16#38};
+dec_huffman_lookup(16#90, 16#8) -> {more, 16#e4, 16#03};
+dec_huffman_lookup(16#90, 16#9) -> {more, 16#e4, 16#06};
+dec_huffman_lookup(16#90, 16#a) -> {more, 16#e4, 16#0a};
+dec_huffman_lookup(16#90, 16#b) -> {more, 16#e4, 16#0f};
+dec_huffman_lookup(16#90, 16#c) -> {more, 16#e4, 16#18};
+dec_huffman_lookup(16#90, 16#d) -> {more, 16#e4, 16#1f};
+dec_huffman_lookup(16#90, 16#e) -> {more, 16#e4, 16#29};
+dec_huffman_lookup(16#90, 16#f) -> {ok, 16#e4, 16#38};
+dec_huffman_lookup(16#91, 16#0) -> {more, 16#e8, 16#03};
+dec_huffman_lookup(16#91, 16#1) -> {more, 16#e8, 16#06};
+dec_huffman_lookup(16#91, 16#2) -> {more, 16#e8, 16#0a};
+dec_huffman_lookup(16#91, 16#3) -> {more, 16#e8, 16#0f};
+dec_huffman_lookup(16#91, 16#4) -> {more, 16#e8, 16#18};
+dec_huffman_lookup(16#91, 16#5) -> {more, 16#e8, 16#1f};
+dec_huffman_lookup(16#91, 16#6) -> {more, 16#e8, 16#29};
+dec_huffman_lookup(16#91, 16#7) -> {ok, 16#e8, 16#38};
+dec_huffman_lookup(16#91, 16#8) -> {more, 16#e9, 16#03};
+dec_huffman_lookup(16#91, 16#9) -> {more, 16#e9, 16#06};
+dec_huffman_lookup(16#91, 16#a) -> {more, 16#e9, 16#0a};
+dec_huffman_lookup(16#91, 16#b) -> {more, 16#e9, 16#0f};
+dec_huffman_lookup(16#91, 16#c) -> {more, 16#e9, 16#18};
+dec_huffman_lookup(16#91, 16#d) -> {more, 16#e9, 16#1f};
+dec_huffman_lookup(16#91, 16#e) -> {more, 16#e9, 16#29};
+dec_huffman_lookup(16#91, 16#f) -> {ok, 16#e9, 16#38};
+dec_huffman_lookup(16#92, 16#0) -> {more, 16#01, 16#01};
+dec_huffman_lookup(16#92, 16#1) -> {ok, 16#01, 16#16};
+dec_huffman_lookup(16#92, 16#2) -> {more, 16#87, 16#01};
+dec_huffman_lookup(16#92, 16#3) -> {ok, 16#87, 16#16};
+dec_huffman_lookup(16#92, 16#4) -> {more, 16#89, 16#01};
+dec_huffman_lookup(16#92, 16#5) -> {ok, 16#89, 16#16};
+dec_huffman_lookup(16#92, 16#6) -> {more, 16#8a, 16#01};
+dec_huffman_lookup(16#92, 16#7) -> {ok, 16#8a, 16#16};
+dec_huffman_lookup(16#92, 16#8) -> {more, 16#8b, 16#01};
+dec_huffman_lookup(16#92, 16#9) -> {ok, 16#8b, 16#16};
+dec_huffman_lookup(16#92, 16#a) -> {more, 16#8c, 16#01};
+dec_huffman_lookup(16#92, 16#b) -> {ok, 16#8c, 16#16};
+dec_huffman_lookup(16#92, 16#c) -> {more, 16#8d, 16#01};
+dec_huffman_lookup(16#92, 16#d) -> {ok, 16#8d, 16#16};
+dec_huffman_lookup(16#92, 16#e) -> {more, 16#8f, 16#01};
+dec_huffman_lookup(16#92, 16#f) -> {ok, 16#8f, 16#16};
+dec_huffman_lookup(16#93, 16#0) -> {more, 16#01, 16#02};
+dec_huffman_lookup(16#93, 16#1) -> {more, 16#01, 16#09};
+dec_huffman_lookup(16#93, 16#2) -> {more, 16#01, 16#17};
+dec_huffman_lookup(16#93, 16#3) -> {ok, 16#01, 16#28};
+dec_huffman_lookup(16#93, 16#4) -> {more, 16#87, 16#02};
+dec_huffman_lookup(16#93, 16#5) -> {more, 16#87, 16#09};
+dec_huffman_lookup(16#93, 16#6) -> {more, 16#87, 16#17};
+dec_huffman_lookup(16#93, 16#7) -> {ok, 16#87, 16#28};
+dec_huffman_lookup(16#93, 16#8) -> {more, 16#89, 16#02};
+dec_huffman_lookup(16#93, 16#9) -> {more, 16#89, 16#09};
+dec_huffman_lookup(16#93, 16#a) -> {more, 16#89, 16#17};
+dec_huffman_lookup(16#93, 16#b) -> {ok, 16#89, 16#28};
+dec_huffman_lookup(16#93, 16#c) -> {more, 16#8a, 16#02};
+dec_huffman_lookup(16#93, 16#d) -> {more, 16#8a, 16#09};
+dec_huffman_lookup(16#93, 16#e) -> {more, 16#8a, 16#17};
+dec_huffman_lookup(16#93, 16#f) -> {ok, 16#8a, 16#28};
+dec_huffman_lookup(16#94, 16#0) -> {more, 16#01, 16#03};
+dec_huffman_lookup(16#94, 16#1) -> {more, 16#01, 16#06};
+dec_huffman_lookup(16#94, 16#2) -> {more, 16#01, 16#0a};
+dec_huffman_lookup(16#94, 16#3) -> {more, 16#01, 16#0f};
+dec_huffman_lookup(16#94, 16#4) -> {more, 16#01, 16#18};
+dec_huffman_lookup(16#94, 16#5) -> {more, 16#01, 16#1f};
+dec_huffman_lookup(16#94, 16#6) -> {more, 16#01, 16#29};
+dec_huffman_lookup(16#94, 16#7) -> {ok, 16#01, 16#38};
+dec_huffman_lookup(16#94, 16#8) -> {more, 16#87, 16#03};
+dec_huffman_lookup(16#94, 16#9) -> {more, 16#87, 16#06};
+dec_huffman_lookup(16#94, 16#a) -> {more, 16#87, 16#0a};
+dec_huffman_lookup(16#94, 16#b) -> {more, 16#87, 16#0f};
+dec_huffman_lookup(16#94, 16#c) -> {more, 16#87, 16#18};
+dec_huffman_lookup(16#94, 16#d) -> {more, 16#87, 16#1f};
+dec_huffman_lookup(16#94, 16#e) -> {more, 16#87, 16#29};
+dec_huffman_lookup(16#94, 16#f) -> {ok, 16#87, 16#38};
+dec_huffman_lookup(16#95, 16#0) -> {more, 16#89, 16#03};
+dec_huffman_lookup(16#95, 16#1) -> {more, 16#89, 16#06};
+dec_huffman_lookup(16#95, 16#2) -> {more, 16#89, 16#0a};
+dec_huffman_lookup(16#95, 16#3) -> {more, 16#89, 16#0f};
+dec_huffman_lookup(16#95, 16#4) -> {more, 16#89, 16#18};
+dec_huffman_lookup(16#95, 16#5) -> {more, 16#89, 16#1f};
+dec_huffman_lookup(16#95, 16#6) -> {more, 16#89, 16#29};
+dec_huffman_lookup(16#95, 16#7) -> {ok, 16#89, 16#38};
+dec_huffman_lookup(16#95, 16#8) -> {more, 16#8a, 16#03};
+dec_huffman_lookup(16#95, 16#9) -> {more, 16#8a, 16#06};
+dec_huffman_lookup(16#95, 16#a) -> {more, 16#8a, 16#0a};
+dec_huffman_lookup(16#95, 16#b) -> {more, 16#8a, 16#0f};
+dec_huffman_lookup(16#95, 16#c) -> {more, 16#8a, 16#18};
+dec_huffman_lookup(16#95, 16#d) -> {more, 16#8a, 16#1f};
+dec_huffman_lookup(16#95, 16#e) -> {more, 16#8a, 16#29};
+dec_huffman_lookup(16#95, 16#f) -> {ok, 16#8a, 16#38};
+dec_huffman_lookup(16#96, 16#0) -> {more, 16#8b, 16#02};
+dec_huffman_lookup(16#96, 16#1) -> {more, 16#8b, 16#09};
+dec_huffman_lookup(16#96, 16#2) -> {more, 16#8b, 16#17};
+dec_huffman_lookup(16#96, 16#3) -> {ok, 16#8b, 16#28};
+dec_huffman_lookup(16#96, 16#4) -> {more, 16#8c, 16#02};
+dec_huffman_lookup(16#96, 16#5) -> {more, 16#8c, 16#09};
+dec_huffman_lookup(16#96, 16#6) -> {more, 16#8c, 16#17};
+dec_huffman_lookup(16#96, 16#7) -> {ok, 16#8c, 16#28};
+dec_huffman_lookup(16#96, 16#8) -> {more, 16#8d, 16#02};
+dec_huffman_lookup(16#96, 16#9) -> {more, 16#8d, 16#09};
+dec_huffman_lookup(16#96, 16#a) -> {more, 16#8d, 16#17};
+dec_huffman_lookup(16#96, 16#b) -> {ok, 16#8d, 16#28};
+dec_huffman_lookup(16#96, 16#c) -> {more, 16#8f, 16#02};
+dec_huffman_lookup(16#96, 16#d) -> {more, 16#8f, 16#09};
+dec_huffman_lookup(16#96, 16#e) -> {more, 16#8f, 16#17};
+dec_huffman_lookup(16#96, 16#f) -> {ok, 16#8f, 16#28};
+dec_huffman_lookup(16#97, 16#0) -> {more, 16#8b, 16#03};
+dec_huffman_lookup(16#97, 16#1) -> {more, 16#8b, 16#06};
+dec_huffman_lookup(16#97, 16#2) -> {more, 16#8b, 16#0a};
+dec_huffman_lookup(16#97, 16#3) -> {more, 16#8b, 16#0f};
+dec_huffman_lookup(16#97, 16#4) -> {more, 16#8b, 16#18};
+dec_huffman_lookup(16#97, 16#5) -> {more, 16#8b, 16#1f};
+dec_huffman_lookup(16#97, 16#6) -> {more, 16#8b, 16#29};
+dec_huffman_lookup(16#97, 16#7) -> {ok, 16#8b, 16#38};
+dec_huffman_lookup(16#97, 16#8) -> {more, 16#8c, 16#03};
+dec_huffman_lookup(16#97, 16#9) -> {more, 16#8c, 16#06};
+dec_huffman_lookup(16#97, 16#a) -> {more, 16#8c, 16#0a};
+dec_huffman_lookup(16#97, 16#b) -> {more, 16#8c, 16#0f};
+dec_huffman_lookup(16#97, 16#c) -> {more, 16#8c, 16#18};
+dec_huffman_lookup(16#97, 16#d) -> {more, 16#8c, 16#1f};
+dec_huffman_lookup(16#97, 16#e) -> {more, 16#8c, 16#29};
+dec_huffman_lookup(16#97, 16#f) -> {ok, 16#8c, 16#38};
+dec_huffman_lookup(16#98, 16#0) -> {more, 16#8d, 16#03};
+dec_huffman_lookup(16#98, 16#1) -> {more, 16#8d, 16#06};
+dec_huffman_lookup(16#98, 16#2) -> {more, 16#8d, 16#0a};
+dec_huffman_lookup(16#98, 16#3) -> {more, 16#8d, 16#0f};
+dec_huffman_lookup(16#98, 16#4) -> {more, 16#8d, 16#18};
+dec_huffman_lookup(16#98, 16#5) -> {more, 16#8d, 16#1f};
+dec_huffman_lookup(16#98, 16#6) -> {more, 16#8d, 16#29};
+dec_huffman_lookup(16#98, 16#7) -> {ok, 16#8d, 16#38};
+dec_huffman_lookup(16#98, 16#8) -> {more, 16#8f, 16#03};
+dec_huffman_lookup(16#98, 16#9) -> {more, 16#8f, 16#06};
+dec_huffman_lookup(16#98, 16#a) -> {more, 16#8f, 16#0a};
+dec_huffman_lookup(16#98, 16#b) -> {more, 16#8f, 16#0f};
+dec_huffman_lookup(16#98, 16#c) -> {more, 16#8f, 16#18};
+dec_huffman_lookup(16#98, 16#d) -> {more, 16#8f, 16#1f};
+dec_huffman_lookup(16#98, 16#e) -> {more, 16#8f, 16#29};
+dec_huffman_lookup(16#98, 16#f) -> {ok, 16#8f, 16#38};
+dec_huffman_lookup(16#99, 16#0) -> {more, undefined, 16#9d};
+dec_huffman_lookup(16#99, 16#1) -> {more, undefined, 16#9e};
+dec_huffman_lookup(16#99, 16#2) -> {more, undefined, 16#a0};
+dec_huffman_lookup(16#99, 16#3) -> {more, undefined, 16#a1};
+dec_huffman_lookup(16#99, 16#4) -> {more, undefined, 16#a4};
+dec_huffman_lookup(16#99, 16#5) -> {more, undefined, 16#a5};
+dec_huffman_lookup(16#99, 16#6) -> {more, undefined, 16#a7};
+dec_huffman_lookup(16#99, 16#7) -> {more, undefined, 16#a8};
+dec_huffman_lookup(16#99, 16#8) -> {more, undefined, 16#ac};
+dec_huffman_lookup(16#99, 16#9) -> {more, undefined, 16#ad};
+dec_huffman_lookup(16#99, 16#a) -> {more, undefined, 16#af};
+dec_huffman_lookup(16#99, 16#b) -> {more, undefined, 16#b1};
+dec_huffman_lookup(16#99, 16#c) -> {more, undefined, 16#b6};
+dec_huffman_lookup(16#99, 16#d) -> {more, undefined, 16#b9};
+dec_huffman_lookup(16#99, 16#e) -> {more, undefined, 16#bf};
+dec_huffman_lookup(16#99, 16#f) -> {ok, undefined, 16#cf};
+dec_huffman_lookup(16#9a, 16#0) -> {ok, 16#93, 16#00};
+dec_huffman_lookup(16#9a, 16#1) -> {ok, 16#95, 16#00};
+dec_huffman_lookup(16#9a, 16#2) -> {ok, 16#96, 16#00};
+dec_huffman_lookup(16#9a, 16#3) -> {ok, 16#97, 16#00};
+dec_huffman_lookup(16#9a, 16#4) -> {ok, 16#98, 16#00};
+dec_huffman_lookup(16#9a, 16#5) -> {ok, 16#9b, 16#00};
+dec_huffman_lookup(16#9a, 16#6) -> {ok, 16#9d, 16#00};
+dec_huffman_lookup(16#9a, 16#7) -> {ok, 16#9e, 16#00};
+dec_huffman_lookup(16#9a, 16#8) -> {ok, 16#a5, 16#00};
+dec_huffman_lookup(16#9a, 16#9) -> {ok, 16#a6, 16#00};
+dec_huffman_lookup(16#9a, 16#a) -> {ok, 16#a8, 16#00};
+dec_huffman_lookup(16#9a, 16#b) -> {ok, 16#ae, 16#00};
+dec_huffman_lookup(16#9a, 16#c) -> {ok, 16#af, 16#00};
+dec_huffman_lookup(16#9a, 16#d) -> {ok, 16#b4, 16#00};
+dec_huffman_lookup(16#9a, 16#e) -> {ok, 16#b6, 16#00};
+dec_huffman_lookup(16#9a, 16#f) -> {ok, 16#b7, 16#00};
+dec_huffman_lookup(16#9b, 16#0) -> {more, 16#93, 16#01};
+dec_huffman_lookup(16#9b, 16#1) -> {ok, 16#93, 16#16};
+dec_huffman_lookup(16#9b, 16#2) -> {more, 16#95, 16#01};
+dec_huffman_lookup(16#9b, 16#3) -> {ok, 16#95, 16#16};
+dec_huffman_lookup(16#9b, 16#4) -> {more, 16#96, 16#01};
+dec_huffman_lookup(16#9b, 16#5) -> {ok, 16#96, 16#16};
+dec_huffman_lookup(16#9b, 16#6) -> {more, 16#97, 16#01};
+dec_huffman_lookup(16#9b, 16#7) -> {ok, 16#97, 16#16};
+dec_huffman_lookup(16#9b, 16#8) -> {more, 16#98, 16#01};
+dec_huffman_lookup(16#9b, 16#9) -> {ok, 16#98, 16#16};
+dec_huffman_lookup(16#9b, 16#a) -> {more, 16#9b, 16#01};
+dec_huffman_lookup(16#9b, 16#b) -> {ok, 16#9b, 16#16};
+dec_huffman_lookup(16#9b, 16#c) -> {more, 16#9d, 16#01};
+dec_huffman_lookup(16#9b, 16#d) -> {ok, 16#9d, 16#16};
+dec_huffman_lookup(16#9b, 16#e) -> {more, 16#9e, 16#01};
+dec_huffman_lookup(16#9b, 16#f) -> {ok, 16#9e, 16#16};
+dec_huffman_lookup(16#9c, 16#0) -> {more, 16#93, 16#02};
+dec_huffman_lookup(16#9c, 16#1) -> {more, 16#93, 16#09};
+dec_huffman_lookup(16#9c, 16#2) -> {more, 16#93, 16#17};
+dec_huffman_lookup(16#9c, 16#3) -> {ok, 16#93, 16#28};
+dec_huffman_lookup(16#9c, 16#4) -> {more, 16#95, 16#02};
+dec_huffman_lookup(16#9c, 16#5) -> {more, 16#95, 16#09};
+dec_huffman_lookup(16#9c, 16#6) -> {more, 16#95, 16#17};
+dec_huffman_lookup(16#9c, 16#7) -> {ok, 16#95, 16#28};
+dec_huffman_lookup(16#9c, 16#8) -> {more, 16#96, 16#02};
+dec_huffman_lookup(16#9c, 16#9) -> {more, 16#96, 16#09};
+dec_huffman_lookup(16#9c, 16#a) -> {more, 16#96, 16#17};
+dec_huffman_lookup(16#9c, 16#b) -> {ok, 16#96, 16#28};
+dec_huffman_lookup(16#9c, 16#c) -> {more, 16#97, 16#02};
+dec_huffman_lookup(16#9c, 16#d) -> {more, 16#97, 16#09};
+dec_huffman_lookup(16#9c, 16#e) -> {more, 16#97, 16#17};
+dec_huffman_lookup(16#9c, 16#f) -> {ok, 16#97, 16#28};
+dec_huffman_lookup(16#9d, 16#0) -> {more, 16#93, 16#03};
+dec_huffman_lookup(16#9d, 16#1) -> {more, 16#93, 16#06};
+dec_huffman_lookup(16#9d, 16#2) -> {more, 16#93, 16#0a};
+dec_huffman_lookup(16#9d, 16#3) -> {more, 16#93, 16#0f};
+dec_huffman_lookup(16#9d, 16#4) -> {more, 16#93, 16#18};
+dec_huffman_lookup(16#9d, 16#5) -> {more, 16#93, 16#1f};
+dec_huffman_lookup(16#9d, 16#6) -> {more, 16#93, 16#29};
+dec_huffman_lookup(16#9d, 16#7) -> {ok, 16#93, 16#38};
+dec_huffman_lookup(16#9d, 16#8) -> {more, 16#95, 16#03};
+dec_huffman_lookup(16#9d, 16#9) -> {more, 16#95, 16#06};
+dec_huffman_lookup(16#9d, 16#a) -> {more, 16#95, 16#0a};
+dec_huffman_lookup(16#9d, 16#b) -> {more, 16#95, 16#0f};
+dec_huffman_lookup(16#9d, 16#c) -> {more, 16#95, 16#18};
+dec_huffman_lookup(16#9d, 16#d) -> {more, 16#95, 16#1f};
+dec_huffman_lookup(16#9d, 16#e) -> {more, 16#95, 16#29};
+dec_huffman_lookup(16#9d, 16#f) -> {ok, 16#95, 16#38};
+dec_huffman_lookup(16#9e, 16#0) -> {more, 16#96, 16#03};
+dec_huffman_lookup(16#9e, 16#1) -> {more, 16#96, 16#06};
+dec_huffman_lookup(16#9e, 16#2) -> {more, 16#96, 16#0a};
+dec_huffman_lookup(16#9e, 16#3) -> {more, 16#96, 16#0f};
+dec_huffman_lookup(16#9e, 16#4) -> {more, 16#96, 16#18};
+dec_huffman_lookup(16#9e, 16#5) -> {more, 16#96, 16#1f};
+dec_huffman_lookup(16#9e, 16#6) -> {more, 16#96, 16#29};
+dec_huffman_lookup(16#9e, 16#7) -> {ok, 16#96, 16#38};
+dec_huffman_lookup(16#9e, 16#8) -> {more, 16#97, 16#03};
+dec_huffman_lookup(16#9e, 16#9) -> {more, 16#97, 16#06};
+dec_huffman_lookup(16#9e, 16#a) -> {more, 16#97, 16#0a};
+dec_huffman_lookup(16#9e, 16#b) -> {more, 16#97, 16#0f};
+dec_huffman_lookup(16#9e, 16#c) -> {more, 16#97, 16#18};
+dec_huffman_lookup(16#9e, 16#d) -> {more, 16#97, 16#1f};
+dec_huffman_lookup(16#9e, 16#e) -> {more, 16#97, 16#29};
+dec_huffman_lookup(16#9e, 16#f) -> {ok, 16#97, 16#38};
+dec_huffman_lookup(16#9f, 16#0) -> {more, 16#98, 16#02};
+dec_huffman_lookup(16#9f, 16#1) -> {more, 16#98, 16#09};
+dec_huffman_lookup(16#9f, 16#2) -> {more, 16#98, 16#17};
+dec_huffman_lookup(16#9f, 16#3) -> {ok, 16#98, 16#28};
+dec_huffman_lookup(16#9f, 16#4) -> {more, 16#9b, 16#02};
+dec_huffman_lookup(16#9f, 16#5) -> {more, 16#9b, 16#09};
+dec_huffman_lookup(16#9f, 16#6) -> {more, 16#9b, 16#17};
+dec_huffman_lookup(16#9f, 16#7) -> {ok, 16#9b, 16#28};
+dec_huffman_lookup(16#9f, 16#8) -> {more, 16#9d, 16#02};
+dec_huffman_lookup(16#9f, 16#9) -> {more, 16#9d, 16#09};
+dec_huffman_lookup(16#9f, 16#a) -> {more, 16#9d, 16#17};
+dec_huffman_lookup(16#9f, 16#b) -> {ok, 16#9d, 16#28};
+dec_huffman_lookup(16#9f, 16#c) -> {more, 16#9e, 16#02};
+dec_huffman_lookup(16#9f, 16#d) -> {more, 16#9e, 16#09};
+dec_huffman_lookup(16#9f, 16#e) -> {more, 16#9e, 16#17};
+dec_huffman_lookup(16#9f, 16#f) -> {ok, 16#9e, 16#28};
+dec_huffman_lookup(16#a0, 16#0) -> {more, 16#98, 16#03};
+dec_huffman_lookup(16#a0, 16#1) -> {more, 16#98, 16#06};
+dec_huffman_lookup(16#a0, 16#2) -> {more, 16#98, 16#0a};
+dec_huffman_lookup(16#a0, 16#3) -> {more, 16#98, 16#0f};
+dec_huffman_lookup(16#a0, 16#4) -> {more, 16#98, 16#18};
+dec_huffman_lookup(16#a0, 16#5) -> {more, 16#98, 16#1f};
+dec_huffman_lookup(16#a0, 16#6) -> {more, 16#98, 16#29};
+dec_huffman_lookup(16#a0, 16#7) -> {ok, 16#98, 16#38};
+dec_huffman_lookup(16#a0, 16#8) -> {more, 16#9b, 16#03};
+dec_huffman_lookup(16#a0, 16#9) -> {more, 16#9b, 16#06};
+dec_huffman_lookup(16#a0, 16#a) -> {more, 16#9b, 16#0a};
+dec_huffman_lookup(16#a0, 16#b) -> {more, 16#9b, 16#0f};
+dec_huffman_lookup(16#a0, 16#c) -> {more, 16#9b, 16#18};
+dec_huffman_lookup(16#a0, 16#d) -> {more, 16#9b, 16#1f};
+dec_huffman_lookup(16#a0, 16#e) -> {more, 16#9b, 16#29};
+dec_huffman_lookup(16#a0, 16#f) -> {ok, 16#9b, 16#38};
+dec_huffman_lookup(16#a1, 16#0) -> {more, 16#9d, 16#03};
+dec_huffman_lookup(16#a1, 16#1) -> {more, 16#9d, 16#06};
+dec_huffman_lookup(16#a1, 16#2) -> {more, 16#9d, 16#0a};
+dec_huffman_lookup(16#a1, 16#3) -> {more, 16#9d, 16#0f};
+dec_huffman_lookup(16#a1, 16#4) -> {more, 16#9d, 16#18};
+dec_huffman_lookup(16#a1, 16#5) -> {more, 16#9d, 16#1f};
+dec_huffman_lookup(16#a1, 16#6) -> {more, 16#9d, 16#29};
+dec_huffman_lookup(16#a1, 16#7) -> {ok, 16#9d, 16#38};
+dec_huffman_lookup(16#a1, 16#8) -> {more, 16#9e, 16#03};
+dec_huffman_lookup(16#a1, 16#9) -> {more, 16#9e, 16#06};
+dec_huffman_lookup(16#a1, 16#a) -> {more, 16#9e, 16#0a};
+dec_huffman_lookup(16#a1, 16#b) -> {more, 16#9e, 16#0f};
+dec_huffman_lookup(16#a1, 16#c) -> {more, 16#9e, 16#18};
+dec_huffman_lookup(16#a1, 16#d) -> {more, 16#9e, 16#1f};
+dec_huffman_lookup(16#a1, 16#e) -> {more, 16#9e, 16#29};
+dec_huffman_lookup(16#a1, 16#f) -> {ok, 16#9e, 16#38};
+dec_huffman_lookup(16#a2, 16#0) -> {more, 16#a5, 16#01};
+dec_huffman_lookup(16#a2, 16#1) -> {ok, 16#a5, 16#16};
+dec_huffman_lookup(16#a2, 16#2) -> {more, 16#a6, 16#01};
+dec_huffman_lookup(16#a2, 16#3) -> {ok, 16#a6, 16#16};
+dec_huffman_lookup(16#a2, 16#4) -> {more, 16#a8, 16#01};
+dec_huffman_lookup(16#a2, 16#5) -> {ok, 16#a8, 16#16};
+dec_huffman_lookup(16#a2, 16#6) -> {more, 16#ae, 16#01};
+dec_huffman_lookup(16#a2, 16#7) -> {ok, 16#ae, 16#16};
+dec_huffman_lookup(16#a2, 16#8) -> {more, 16#af, 16#01};
+dec_huffman_lookup(16#a2, 16#9) -> {ok, 16#af, 16#16};
+dec_huffman_lookup(16#a2, 16#a) -> {more, 16#b4, 16#01};
+dec_huffman_lookup(16#a2, 16#b) -> {ok, 16#b4, 16#16};
+dec_huffman_lookup(16#a2, 16#c) -> {more, 16#b6, 16#01};
+dec_huffman_lookup(16#a2, 16#d) -> {ok, 16#b6, 16#16};
+dec_huffman_lookup(16#a2, 16#e) -> {more, 16#b7, 16#01};
+dec_huffman_lookup(16#a2, 16#f) -> {ok, 16#b7, 16#16};
+dec_huffman_lookup(16#a3, 16#0) -> {more, 16#a5, 16#02};
+dec_huffman_lookup(16#a3, 16#1) -> {more, 16#a5, 16#09};
+dec_huffman_lookup(16#a3, 16#2) -> {more, 16#a5, 16#17};
+dec_huffman_lookup(16#a3, 16#3) -> {ok, 16#a5, 16#28};
+dec_huffman_lookup(16#a3, 16#4) -> {more, 16#a6, 16#02};
+dec_huffman_lookup(16#a3, 16#5) -> {more, 16#a6, 16#09};
+dec_huffman_lookup(16#a3, 16#6) -> {more, 16#a6, 16#17};
+dec_huffman_lookup(16#a3, 16#7) -> {ok, 16#a6, 16#28};
+dec_huffman_lookup(16#a3, 16#8) -> {more, 16#a8, 16#02};
+dec_huffman_lookup(16#a3, 16#9) -> {more, 16#a8, 16#09};
+dec_huffman_lookup(16#a3, 16#a) -> {more, 16#a8, 16#17};
+dec_huffman_lookup(16#a3, 16#b) -> {ok, 16#a8, 16#28};
+dec_huffman_lookup(16#a3, 16#c) -> {more, 16#ae, 16#02};
+dec_huffman_lookup(16#a3, 16#d) -> {more, 16#ae, 16#09};
+dec_huffman_lookup(16#a3, 16#e) -> {more, 16#ae, 16#17};
+dec_huffman_lookup(16#a3, 16#f) -> {ok, 16#ae, 16#28};
+dec_huffman_lookup(16#a4, 16#0) -> {more, 16#a5, 16#03};
+dec_huffman_lookup(16#a4, 16#1) -> {more, 16#a5, 16#06};
+dec_huffman_lookup(16#a4, 16#2) -> {more, 16#a5, 16#0a};
+dec_huffman_lookup(16#a4, 16#3) -> {more, 16#a5, 16#0f};
+dec_huffman_lookup(16#a4, 16#4) -> {more, 16#a5, 16#18};
+dec_huffman_lookup(16#a4, 16#5) -> {more, 16#a5, 16#1f};
+dec_huffman_lookup(16#a4, 16#6) -> {more, 16#a5, 16#29};
+dec_huffman_lookup(16#a4, 16#7) -> {ok, 16#a5, 16#38};
+dec_huffman_lookup(16#a4, 16#8) -> {more, 16#a6, 16#03};
+dec_huffman_lookup(16#a4, 16#9) -> {more, 16#a6, 16#06};
+dec_huffman_lookup(16#a4, 16#a) -> {more, 16#a6, 16#0a};
+dec_huffman_lookup(16#a4, 16#b) -> {more, 16#a6, 16#0f};
+dec_huffman_lookup(16#a4, 16#c) -> {more, 16#a6, 16#18};
+dec_huffman_lookup(16#a4, 16#d) -> {more, 16#a6, 16#1f};
+dec_huffman_lookup(16#a4, 16#e) -> {more, 16#a6, 16#29};
+dec_huffman_lookup(16#a4, 16#f) -> {ok, 16#a6, 16#38};
+dec_huffman_lookup(16#a5, 16#0) -> {more, 16#a8, 16#03};
+dec_huffman_lookup(16#a5, 16#1) -> {more, 16#a8, 16#06};
+dec_huffman_lookup(16#a5, 16#2) -> {more, 16#a8, 16#0a};
+dec_huffman_lookup(16#a5, 16#3) -> {more, 16#a8, 16#0f};
+dec_huffman_lookup(16#a5, 16#4) -> {more, 16#a8, 16#18};
+dec_huffman_lookup(16#a5, 16#5) -> {more, 16#a8, 16#1f};
+dec_huffman_lookup(16#a5, 16#6) -> {more, 16#a8, 16#29};
+dec_huffman_lookup(16#a5, 16#7) -> {ok, 16#a8, 16#38};
+dec_huffman_lookup(16#a5, 16#8) -> {more, 16#ae, 16#03};
+dec_huffman_lookup(16#a5, 16#9) -> {more, 16#ae, 16#06};
+dec_huffman_lookup(16#a5, 16#a) -> {more, 16#ae, 16#0a};
+dec_huffman_lookup(16#a5, 16#b) -> {more, 16#ae, 16#0f};
+dec_huffman_lookup(16#a5, 16#c) -> {more, 16#ae, 16#18};
+dec_huffman_lookup(16#a5, 16#d) -> {more, 16#ae, 16#1f};
+dec_huffman_lookup(16#a5, 16#e) -> {more, 16#ae, 16#29};
+dec_huffman_lookup(16#a5, 16#f) -> {ok, 16#ae, 16#38};
+dec_huffman_lookup(16#a6, 16#0) -> {more, 16#af, 16#02};
+dec_huffman_lookup(16#a6, 16#1) -> {more, 16#af, 16#09};
+dec_huffman_lookup(16#a6, 16#2) -> {more, 16#af, 16#17};
+dec_huffman_lookup(16#a6, 16#3) -> {ok, 16#af, 16#28};
+dec_huffman_lookup(16#a6, 16#4) -> {more, 16#b4, 16#02};
+dec_huffman_lookup(16#a6, 16#5) -> {more, 16#b4, 16#09};
+dec_huffman_lookup(16#a6, 16#6) -> {more, 16#b4, 16#17};
+dec_huffman_lookup(16#a6, 16#7) -> {ok, 16#b4, 16#28};
+dec_huffman_lookup(16#a6, 16#8) -> {more, 16#b6, 16#02};
+dec_huffman_lookup(16#a6, 16#9) -> {more, 16#b6, 16#09};
+dec_huffman_lookup(16#a6, 16#a) -> {more, 16#b6, 16#17};
+dec_huffman_lookup(16#a6, 16#b) -> {ok, 16#b6, 16#28};
+dec_huffman_lookup(16#a6, 16#c) -> {more, 16#b7, 16#02};
+dec_huffman_lookup(16#a6, 16#d) -> {more, 16#b7, 16#09};
+dec_huffman_lookup(16#a6, 16#e) -> {more, 16#b7, 16#17};
+dec_huffman_lookup(16#a6, 16#f) -> {ok, 16#b7, 16#28};
+dec_huffman_lookup(16#a7, 16#0) -> {more, 16#af, 16#03};
+dec_huffman_lookup(16#a7, 16#1) -> {more, 16#af, 16#06};
+dec_huffman_lookup(16#a7, 16#2) -> {more, 16#af, 16#0a};
+dec_huffman_lookup(16#a7, 16#3) -> {more, 16#af, 16#0f};
+dec_huffman_lookup(16#a7, 16#4) -> {more, 16#af, 16#18};
+dec_huffman_lookup(16#a7, 16#5) -> {more, 16#af, 16#1f};
+dec_huffman_lookup(16#a7, 16#6) -> {more, 16#af, 16#29};
+dec_huffman_lookup(16#a7, 16#7) -> {ok, 16#af, 16#38};
+dec_huffman_lookup(16#a7, 16#8) -> {more, 16#b4, 16#03};
+dec_huffman_lookup(16#a7, 16#9) -> {more, 16#b4, 16#06};
+dec_huffman_lookup(16#a7, 16#a) -> {more, 16#b4, 16#0a};
+dec_huffman_lookup(16#a7, 16#b) -> {more, 16#b4, 16#0f};
+dec_huffman_lookup(16#a7, 16#c) -> {more, 16#b4, 16#18};
+dec_huffman_lookup(16#a7, 16#d) -> {more, 16#b4, 16#1f};
+dec_huffman_lookup(16#a7, 16#e) -> {more, 16#b4, 16#29};
+dec_huffman_lookup(16#a7, 16#f) -> {ok, 16#b4, 16#38};
+dec_huffman_lookup(16#a8, 16#0) -> {more, 16#b6, 16#03};
+dec_huffman_lookup(16#a8, 16#1) -> {more, 16#b6, 16#06};
+dec_huffman_lookup(16#a8, 16#2) -> {more, 16#b6, 16#0a};
+dec_huffman_lookup(16#a8, 16#3) -> {more, 16#b6, 16#0f};
+dec_huffman_lookup(16#a8, 16#4) -> {more, 16#b6, 16#18};
+dec_huffman_lookup(16#a8, 16#5) -> {more, 16#b6, 16#1f};
+dec_huffman_lookup(16#a8, 16#6) -> {more, 16#b6, 16#29};
+dec_huffman_lookup(16#a8, 16#7) -> {ok, 16#b6, 16#38};
+dec_huffman_lookup(16#a8, 16#8) -> {more, 16#b7, 16#03};
+dec_huffman_lookup(16#a8, 16#9) -> {more, 16#b7, 16#06};
+dec_huffman_lookup(16#a8, 16#a) -> {more, 16#b7, 16#0a};
+dec_huffman_lookup(16#a8, 16#b) -> {more, 16#b7, 16#0f};
+dec_huffman_lookup(16#a8, 16#c) -> {more, 16#b7, 16#18};
+dec_huffman_lookup(16#a8, 16#d) -> {more, 16#b7, 16#1f};
+dec_huffman_lookup(16#a8, 16#e) -> {more, 16#b7, 16#29};
+dec_huffman_lookup(16#a8, 16#f) -> {ok, 16#b7, 16#38};
+dec_huffman_lookup(16#a9, 16#0) -> {ok, 16#bc, 16#00};
+dec_huffman_lookup(16#a9, 16#1) -> {ok, 16#bf, 16#00};
+dec_huffman_lookup(16#a9, 16#2) -> {ok, 16#c5, 16#00};
+dec_huffman_lookup(16#a9, 16#3) -> {ok, 16#e7, 16#00};
+dec_huffman_lookup(16#a9, 16#4) -> {ok, 16#ef, 16#00};
+dec_huffman_lookup(16#a9, 16#5) -> {more, undefined, 16#b0};
+dec_huffman_lookup(16#a9, 16#6) -> {more, undefined, 16#b2};
+dec_huffman_lookup(16#a9, 16#7) -> {more, undefined, 16#b3};
+dec_huffman_lookup(16#a9, 16#8) -> {more, undefined, 16#b7};
+dec_huffman_lookup(16#a9, 16#9) -> {more, undefined, 16#b8};
+dec_huffman_lookup(16#a9, 16#a) -> {more, undefined, 16#ba};
+dec_huffman_lookup(16#a9, 16#b) -> {more, undefined, 16#bb};
+dec_huffman_lookup(16#a9, 16#c) -> {more, undefined, 16#c0};
+dec_huffman_lookup(16#a9, 16#d) -> {more, undefined, 16#c7};
+dec_huffman_lookup(16#a9, 16#e) -> {more, undefined, 16#d0};
+dec_huffman_lookup(16#a9, 16#f) -> {ok, undefined, 16#df};
+dec_huffman_lookup(16#aa, 16#0) -> {more, 16#bc, 16#01};
+dec_huffman_lookup(16#aa, 16#1) -> {ok, 16#bc, 16#16};
+dec_huffman_lookup(16#aa, 16#2) -> {more, 16#bf, 16#01};
+dec_huffman_lookup(16#aa, 16#3) -> {ok, 16#bf, 16#16};
+dec_huffman_lookup(16#aa, 16#4) -> {more, 16#c5, 16#01};
+dec_huffman_lookup(16#aa, 16#5) -> {ok, 16#c5, 16#16};
+dec_huffman_lookup(16#aa, 16#6) -> {more, 16#e7, 16#01};
+dec_huffman_lookup(16#aa, 16#7) -> {ok, 16#e7, 16#16};
+dec_huffman_lookup(16#aa, 16#8) -> {more, 16#ef, 16#01};
+dec_huffman_lookup(16#aa, 16#9) -> {ok, 16#ef, 16#16};
+dec_huffman_lookup(16#aa, 16#a) -> {ok, 16#09, 16#00};
+dec_huffman_lookup(16#aa, 16#b) -> {ok, 16#8e, 16#00};
+dec_huffman_lookup(16#aa, 16#c) -> {ok, 16#90, 16#00};
+dec_huffman_lookup(16#aa, 16#d) -> {ok, 16#91, 16#00};
+dec_huffman_lookup(16#aa, 16#e) -> {ok, 16#94, 16#00};
+dec_huffman_lookup(16#aa, 16#f) -> {ok, 16#9f, 16#00};
+dec_huffman_lookup(16#ab, 16#0) -> {more, 16#bc, 16#02};
+dec_huffman_lookup(16#ab, 16#1) -> {more, 16#bc, 16#09};
+dec_huffman_lookup(16#ab, 16#2) -> {more, 16#bc, 16#17};
+dec_huffman_lookup(16#ab, 16#3) -> {ok, 16#bc, 16#28};
+dec_huffman_lookup(16#ab, 16#4) -> {more, 16#bf, 16#02};
+dec_huffman_lookup(16#ab, 16#5) -> {more, 16#bf, 16#09};
+dec_huffman_lookup(16#ab, 16#6) -> {more, 16#bf, 16#17};
+dec_huffman_lookup(16#ab, 16#7) -> {ok, 16#bf, 16#28};
+dec_huffman_lookup(16#ab, 16#8) -> {more, 16#c5, 16#02};
+dec_huffman_lookup(16#ab, 16#9) -> {more, 16#c5, 16#09};
+dec_huffman_lookup(16#ab, 16#a) -> {more, 16#c5, 16#17};
+dec_huffman_lookup(16#ab, 16#b) -> {ok, 16#c5, 16#28};
+dec_huffman_lookup(16#ab, 16#c) -> {more, 16#e7, 16#02};
+dec_huffman_lookup(16#ab, 16#d) -> {more, 16#e7, 16#09};
+dec_huffman_lookup(16#ab, 16#e) -> {more, 16#e7, 16#17};
+dec_huffman_lookup(16#ab, 16#f) -> {ok, 16#e7, 16#28};
+dec_huffman_lookup(16#ac, 16#0) -> {more, 16#bc, 16#03};
+dec_huffman_lookup(16#ac, 16#1) -> {more, 16#bc, 16#06};
+dec_huffman_lookup(16#ac, 16#2) -> {more, 16#bc, 16#0a};
+dec_huffman_lookup(16#ac, 16#3) -> {more, 16#bc, 16#0f};
+dec_huffman_lookup(16#ac, 16#4) -> {more, 16#bc, 16#18};
+dec_huffman_lookup(16#ac, 16#5) -> {more, 16#bc, 16#1f};
+dec_huffman_lookup(16#ac, 16#6) -> {more, 16#bc, 16#29};
+dec_huffman_lookup(16#ac, 16#7) -> {ok, 16#bc, 16#38};
+dec_huffman_lookup(16#ac, 16#8) -> {more, 16#bf, 16#03};
+dec_huffman_lookup(16#ac, 16#9) -> {more, 16#bf, 16#06};
+dec_huffman_lookup(16#ac, 16#a) -> {more, 16#bf, 16#0a};
+dec_huffman_lookup(16#ac, 16#b) -> {more, 16#bf, 16#0f};
+dec_huffman_lookup(16#ac, 16#c) -> {more, 16#bf, 16#18};
+dec_huffman_lookup(16#ac, 16#d) -> {more, 16#bf, 16#1f};
+dec_huffman_lookup(16#ac, 16#e) -> {more, 16#bf, 16#29};
+dec_huffman_lookup(16#ac, 16#f) -> {ok, 16#bf, 16#38};
+dec_huffman_lookup(16#ad, 16#0) -> {more, 16#c5, 16#03};
+dec_huffman_lookup(16#ad, 16#1) -> {more, 16#c5, 16#06};
+dec_huffman_lookup(16#ad, 16#2) -> {more, 16#c5, 16#0a};
+dec_huffman_lookup(16#ad, 16#3) -> {more, 16#c5, 16#0f};
+dec_huffman_lookup(16#ad, 16#4) -> {more, 16#c5, 16#18};
+dec_huffman_lookup(16#ad, 16#5) -> {more, 16#c5, 16#1f};
+dec_huffman_lookup(16#ad, 16#6) -> {more, 16#c5, 16#29};
+dec_huffman_lookup(16#ad, 16#7) -> {ok, 16#c5, 16#38};
+dec_huffman_lookup(16#ad, 16#8) -> {more, 16#e7, 16#03};
+dec_huffman_lookup(16#ad, 16#9) -> {more, 16#e7, 16#06};
+dec_huffman_lookup(16#ad, 16#a) -> {more, 16#e7, 16#0a};
+dec_huffman_lookup(16#ad, 16#b) -> {more, 16#e7, 16#0f};
+dec_huffman_lookup(16#ad, 16#c) -> {more, 16#e7, 16#18};
+dec_huffman_lookup(16#ad, 16#d) -> {more, 16#e7, 16#1f};
+dec_huffman_lookup(16#ad, 16#e) -> {more, 16#e7, 16#29};
+dec_huffman_lookup(16#ad, 16#f) -> {ok, 16#e7, 16#38};
+dec_huffman_lookup(16#ae, 16#0) -> {more, 16#ef, 16#02};
+dec_huffman_lookup(16#ae, 16#1) -> {more, 16#ef, 16#09};
+dec_huffman_lookup(16#ae, 16#2) -> {more, 16#ef, 16#17};
+dec_huffman_lookup(16#ae, 16#3) -> {ok, 16#ef, 16#28};
+dec_huffman_lookup(16#ae, 16#4) -> {more, 16#09, 16#01};
+dec_huffman_lookup(16#ae, 16#5) -> {ok, 16#09, 16#16};
+dec_huffman_lookup(16#ae, 16#6) -> {more, 16#8e, 16#01};
+dec_huffman_lookup(16#ae, 16#7) -> {ok, 16#8e, 16#16};
+dec_huffman_lookup(16#ae, 16#8) -> {more, 16#90, 16#01};
+dec_huffman_lookup(16#ae, 16#9) -> {ok, 16#90, 16#16};
+dec_huffman_lookup(16#ae, 16#a) -> {more, 16#91, 16#01};
+dec_huffman_lookup(16#ae, 16#b) -> {ok, 16#91, 16#16};
+dec_huffman_lookup(16#ae, 16#c) -> {more, 16#94, 16#01};
+dec_huffman_lookup(16#ae, 16#d) -> {ok, 16#94, 16#16};
+dec_huffman_lookup(16#ae, 16#e) -> {more, 16#9f, 16#01};
+dec_huffman_lookup(16#ae, 16#f) -> {ok, 16#9f, 16#16};
+dec_huffman_lookup(16#af, 16#0) -> {more, 16#ef, 16#03};
+dec_huffman_lookup(16#af, 16#1) -> {more, 16#ef, 16#06};
+dec_huffman_lookup(16#af, 16#2) -> {more, 16#ef, 16#0a};
+dec_huffman_lookup(16#af, 16#3) -> {more, 16#ef, 16#0f};
+dec_huffman_lookup(16#af, 16#4) -> {more, 16#ef, 16#18};
+dec_huffman_lookup(16#af, 16#5) -> {more, 16#ef, 16#1f};
+dec_huffman_lookup(16#af, 16#6) -> {more, 16#ef, 16#29};
+dec_huffman_lookup(16#af, 16#7) -> {ok, 16#ef, 16#38};
+dec_huffman_lookup(16#af, 16#8) -> {more, 16#09, 16#02};
+dec_huffman_lookup(16#af, 16#9) -> {more, 16#09, 16#09};
+dec_huffman_lookup(16#af, 16#a) -> {more, 16#09, 16#17};
+dec_huffman_lookup(16#af, 16#b) -> {ok, 16#09, 16#28};
+dec_huffman_lookup(16#af, 16#c) -> {more, 16#8e, 16#02};
+dec_huffman_lookup(16#af, 16#d) -> {more, 16#8e, 16#09};
+dec_huffman_lookup(16#af, 16#e) -> {more, 16#8e, 16#17};
+dec_huffman_lookup(16#af, 16#f) -> {ok, 16#8e, 16#28};
+dec_huffman_lookup(16#b0, 16#0) -> {more, 16#09, 16#03};
+dec_huffman_lookup(16#b0, 16#1) -> {more, 16#09, 16#06};
+dec_huffman_lookup(16#b0, 16#2) -> {more, 16#09, 16#0a};
+dec_huffman_lookup(16#b0, 16#3) -> {more, 16#09, 16#0f};
+dec_huffman_lookup(16#b0, 16#4) -> {more, 16#09, 16#18};
+dec_huffman_lookup(16#b0, 16#5) -> {more, 16#09, 16#1f};
+dec_huffman_lookup(16#b0, 16#6) -> {more, 16#09, 16#29};
+dec_huffman_lookup(16#b0, 16#7) -> {ok, 16#09, 16#38};
+dec_huffman_lookup(16#b0, 16#8) -> {more, 16#8e, 16#03};
+dec_huffman_lookup(16#b0, 16#9) -> {more, 16#8e, 16#06};
+dec_huffman_lookup(16#b0, 16#a) -> {more, 16#8e, 16#0a};
+dec_huffman_lookup(16#b0, 16#b) -> {more, 16#8e, 16#0f};
+dec_huffman_lookup(16#b0, 16#c) -> {more, 16#8e, 16#18};
+dec_huffman_lookup(16#b0, 16#d) -> {more, 16#8e, 16#1f};
+dec_huffman_lookup(16#b0, 16#e) -> {more, 16#8e, 16#29};
+dec_huffman_lookup(16#b0, 16#f) -> {ok, 16#8e, 16#38};
+dec_huffman_lookup(16#b1, 16#0) -> {more, 16#90, 16#02};
+dec_huffman_lookup(16#b1, 16#1) -> {more, 16#90, 16#09};
+dec_huffman_lookup(16#b1, 16#2) -> {more, 16#90, 16#17};
+dec_huffman_lookup(16#b1, 16#3) -> {ok, 16#90, 16#28};
+dec_huffman_lookup(16#b1, 16#4) -> {more, 16#91, 16#02};
+dec_huffman_lookup(16#b1, 16#5) -> {more, 16#91, 16#09};
+dec_huffman_lookup(16#b1, 16#6) -> {more, 16#91, 16#17};
+dec_huffman_lookup(16#b1, 16#7) -> {ok, 16#91, 16#28};
+dec_huffman_lookup(16#b1, 16#8) -> {more, 16#94, 16#02};
+dec_huffman_lookup(16#b1, 16#9) -> {more, 16#94, 16#09};
+dec_huffman_lookup(16#b1, 16#a) -> {more, 16#94, 16#17};
+dec_huffman_lookup(16#b1, 16#b) -> {ok, 16#94, 16#28};
+dec_huffman_lookup(16#b1, 16#c) -> {more, 16#9f, 16#02};
+dec_huffman_lookup(16#b1, 16#d) -> {more, 16#9f, 16#09};
+dec_huffman_lookup(16#b1, 16#e) -> {more, 16#9f, 16#17};
+dec_huffman_lookup(16#b1, 16#f) -> {ok, 16#9f, 16#28};
+dec_huffman_lookup(16#b2, 16#0) -> {more, 16#90, 16#03};
+dec_huffman_lookup(16#b2, 16#1) -> {more, 16#90, 16#06};
+dec_huffman_lookup(16#b2, 16#2) -> {more, 16#90, 16#0a};
+dec_huffman_lookup(16#b2, 16#3) -> {more, 16#90, 16#0f};
+dec_huffman_lookup(16#b2, 16#4) -> {more, 16#90, 16#18};
+dec_huffman_lookup(16#b2, 16#5) -> {more, 16#90, 16#1f};
+dec_huffman_lookup(16#b2, 16#6) -> {more, 16#90, 16#29};
+dec_huffman_lookup(16#b2, 16#7) -> {ok, 16#90, 16#38};
+dec_huffman_lookup(16#b2, 16#8) -> {more, 16#91, 16#03};
+dec_huffman_lookup(16#b2, 16#9) -> {more, 16#91, 16#06};
+dec_huffman_lookup(16#b2, 16#a) -> {more, 16#91, 16#0a};
+dec_huffman_lookup(16#b2, 16#b) -> {more, 16#91, 16#0f};
+dec_huffman_lookup(16#b2, 16#c) -> {more, 16#91, 16#18};
+dec_huffman_lookup(16#b2, 16#d) -> {more, 16#91, 16#1f};
+dec_huffman_lookup(16#b2, 16#e) -> {more, 16#91, 16#29};
+dec_huffman_lookup(16#b2, 16#f) -> {ok, 16#91, 16#38};
+dec_huffman_lookup(16#b3, 16#0) -> {more, 16#94, 16#03};
+dec_huffman_lookup(16#b3, 16#1) -> {more, 16#94, 16#06};
+dec_huffman_lookup(16#b3, 16#2) -> {more, 16#94, 16#0a};
+dec_huffman_lookup(16#b3, 16#3) -> {more, 16#94, 16#0f};
+dec_huffman_lookup(16#b3, 16#4) -> {more, 16#94, 16#18};
+dec_huffman_lookup(16#b3, 16#5) -> {more, 16#94, 16#1f};
+dec_huffman_lookup(16#b3, 16#6) -> {more, 16#94, 16#29};
+dec_huffman_lookup(16#b3, 16#7) -> {ok, 16#94, 16#38};
+dec_huffman_lookup(16#b3, 16#8) -> {more, 16#9f, 16#03};
+dec_huffman_lookup(16#b3, 16#9) -> {more, 16#9f, 16#06};
+dec_huffman_lookup(16#b3, 16#a) -> {more, 16#9f, 16#0a};
+dec_huffman_lookup(16#b3, 16#b) -> {more, 16#9f, 16#0f};
+dec_huffman_lookup(16#b3, 16#c) -> {more, 16#9f, 16#18};
+dec_huffman_lookup(16#b3, 16#d) -> {more, 16#9f, 16#1f};
+dec_huffman_lookup(16#b3, 16#e) -> {more, 16#9f, 16#29};
+dec_huffman_lookup(16#b3, 16#f) -> {ok, 16#9f, 16#38};
+dec_huffman_lookup(16#b4, 16#0) -> {ok, 16#ab, 16#00};
+dec_huffman_lookup(16#b4, 16#1) -> {ok, 16#ce, 16#00};
+dec_huffman_lookup(16#b4, 16#2) -> {ok, 16#d7, 16#00};
+dec_huffman_lookup(16#b4, 16#3) -> {ok, 16#e1, 16#00};
+dec_huffman_lookup(16#b4, 16#4) -> {ok, 16#ec, 16#00};
+dec_huffman_lookup(16#b4, 16#5) -> {ok, 16#ed, 16#00};
+dec_huffman_lookup(16#b4, 16#6) -> {more, undefined, 16#bc};
+dec_huffman_lookup(16#b4, 16#7) -> {more, undefined, 16#bd};
+dec_huffman_lookup(16#b4, 16#8) -> {more, undefined, 16#c1};
+dec_huffman_lookup(16#b4, 16#9) -> {more, undefined, 16#c4};
+dec_huffman_lookup(16#b4, 16#a) -> {more, undefined, 16#c8};
+dec_huffman_lookup(16#b4, 16#b) -> {more, undefined, 16#cb};
+dec_huffman_lookup(16#b4, 16#c) -> {more, undefined, 16#d1};
+dec_huffman_lookup(16#b4, 16#d) -> {more, undefined, 16#d8};
+dec_huffman_lookup(16#b4, 16#e) -> {more, undefined, 16#e0};
+dec_huffman_lookup(16#b4, 16#f) -> {ok, undefined, 16#ee};
+dec_huffman_lookup(16#b5, 16#0) -> {more, 16#ab, 16#01};
+dec_huffman_lookup(16#b5, 16#1) -> {ok, 16#ab, 16#16};
+dec_huffman_lookup(16#b5, 16#2) -> {more, 16#ce, 16#01};
+dec_huffman_lookup(16#b5, 16#3) -> {ok, 16#ce, 16#16};
+dec_huffman_lookup(16#b5, 16#4) -> {more, 16#d7, 16#01};
+dec_huffman_lookup(16#b5, 16#5) -> {ok, 16#d7, 16#16};
+dec_huffman_lookup(16#b5, 16#6) -> {more, 16#e1, 16#01};
+dec_huffman_lookup(16#b5, 16#7) -> {ok, 16#e1, 16#16};
+dec_huffman_lookup(16#b5, 16#8) -> {more, 16#ec, 16#01};
+dec_huffman_lookup(16#b5, 16#9) -> {ok, 16#ec, 16#16};
+dec_huffman_lookup(16#b5, 16#a) -> {more, 16#ed, 16#01};
+dec_huffman_lookup(16#b5, 16#b) -> {ok, 16#ed, 16#16};
+dec_huffman_lookup(16#b5, 16#c) -> {ok, 16#c7, 16#00};
+dec_huffman_lookup(16#b5, 16#d) -> {ok, 16#cf, 16#00};
+dec_huffman_lookup(16#b5, 16#e) -> {ok, 16#ea, 16#00};
+dec_huffman_lookup(16#b5, 16#f) -> {ok, 16#eb, 16#00};
+dec_huffman_lookup(16#b6, 16#0) -> {more, 16#ab, 16#02};
+dec_huffman_lookup(16#b6, 16#1) -> {more, 16#ab, 16#09};
+dec_huffman_lookup(16#b6, 16#2) -> {more, 16#ab, 16#17};
+dec_huffman_lookup(16#b6, 16#3) -> {ok, 16#ab, 16#28};
+dec_huffman_lookup(16#b6, 16#4) -> {more, 16#ce, 16#02};
+dec_huffman_lookup(16#b6, 16#5) -> {more, 16#ce, 16#09};
+dec_huffman_lookup(16#b6, 16#6) -> {more, 16#ce, 16#17};
+dec_huffman_lookup(16#b6, 16#7) -> {ok, 16#ce, 16#28};
+dec_huffman_lookup(16#b6, 16#8) -> {more, 16#d7, 16#02};
+dec_huffman_lookup(16#b6, 16#9) -> {more, 16#d7, 16#09};
+dec_huffman_lookup(16#b6, 16#a) -> {more, 16#d7, 16#17};
+dec_huffman_lookup(16#b6, 16#b) -> {ok, 16#d7, 16#28};
+dec_huffman_lookup(16#b6, 16#c) -> {more, 16#e1, 16#02};
+dec_huffman_lookup(16#b6, 16#d) -> {more, 16#e1, 16#09};
+dec_huffman_lookup(16#b6, 16#e) -> {more, 16#e1, 16#17};
+dec_huffman_lookup(16#b6, 16#f) -> {ok, 16#e1, 16#28};
+dec_huffman_lookup(16#b7, 16#0) -> {more, 16#ab, 16#03};
+dec_huffman_lookup(16#b7, 16#1) -> {more, 16#ab, 16#06};
+dec_huffman_lookup(16#b7, 16#2) -> {more, 16#ab, 16#0a};
+dec_huffman_lookup(16#b7, 16#3) -> {more, 16#ab, 16#0f};
+dec_huffman_lookup(16#b7, 16#4) -> {more, 16#ab, 16#18};
+dec_huffman_lookup(16#b7, 16#5) -> {more, 16#ab, 16#1f};
+dec_huffman_lookup(16#b7, 16#6) -> {more, 16#ab, 16#29};
+dec_huffman_lookup(16#b7, 16#7) -> {ok, 16#ab, 16#38};
+dec_huffman_lookup(16#b7, 16#8) -> {more, 16#ce, 16#03};
+dec_huffman_lookup(16#b7, 16#9) -> {more, 16#ce, 16#06};
+dec_huffman_lookup(16#b7, 16#a) -> {more, 16#ce, 16#0a};
+dec_huffman_lookup(16#b7, 16#b) -> {more, 16#ce, 16#0f};
+dec_huffman_lookup(16#b7, 16#c) -> {more, 16#ce, 16#18};
+dec_huffman_lookup(16#b7, 16#d) -> {more, 16#ce, 16#1f};
+dec_huffman_lookup(16#b7, 16#e) -> {more, 16#ce, 16#29};
+dec_huffman_lookup(16#b7, 16#f) -> {ok, 16#ce, 16#38};
+dec_huffman_lookup(16#b8, 16#0) -> {more, 16#d7, 16#03};
+dec_huffman_lookup(16#b8, 16#1) -> {more, 16#d7, 16#06};
+dec_huffman_lookup(16#b8, 16#2) -> {more, 16#d7, 16#0a};
+dec_huffman_lookup(16#b8, 16#3) -> {more, 16#d7, 16#0f};
+dec_huffman_lookup(16#b8, 16#4) -> {more, 16#d7, 16#18};
+dec_huffman_lookup(16#b8, 16#5) -> {more, 16#d7, 16#1f};
+dec_huffman_lookup(16#b8, 16#6) -> {more, 16#d7, 16#29};
+dec_huffman_lookup(16#b8, 16#7) -> {ok, 16#d7, 16#38};
+dec_huffman_lookup(16#b8, 16#8) -> {more, 16#e1, 16#03};
+dec_huffman_lookup(16#b8, 16#9) -> {more, 16#e1, 16#06};
+dec_huffman_lookup(16#b8, 16#a) -> {more, 16#e1, 16#0a};
+dec_huffman_lookup(16#b8, 16#b) -> {more, 16#e1, 16#0f};
+dec_huffman_lookup(16#b8, 16#c) -> {more, 16#e1, 16#18};
+dec_huffman_lookup(16#b8, 16#d) -> {more, 16#e1, 16#1f};
+dec_huffman_lookup(16#b8, 16#e) -> {more, 16#e1, 16#29};
+dec_huffman_lookup(16#b8, 16#f) -> {ok, 16#e1, 16#38};
+dec_huffman_lookup(16#b9, 16#0) -> {more, 16#ec, 16#02};
+dec_huffman_lookup(16#b9, 16#1) -> {more, 16#ec, 16#09};
+dec_huffman_lookup(16#b9, 16#2) -> {more, 16#ec, 16#17};
+dec_huffman_lookup(16#b9, 16#3) -> {ok, 16#ec, 16#28};
+dec_huffman_lookup(16#b9, 16#4) -> {more, 16#ed, 16#02};
+dec_huffman_lookup(16#b9, 16#5) -> {more, 16#ed, 16#09};
+dec_huffman_lookup(16#b9, 16#6) -> {more, 16#ed, 16#17};
+dec_huffman_lookup(16#b9, 16#7) -> {ok, 16#ed, 16#28};
+dec_huffman_lookup(16#b9, 16#8) -> {more, 16#c7, 16#01};
+dec_huffman_lookup(16#b9, 16#9) -> {ok, 16#c7, 16#16};
+dec_huffman_lookup(16#b9, 16#a) -> {more, 16#cf, 16#01};
+dec_huffman_lookup(16#b9, 16#b) -> {ok, 16#cf, 16#16};
+dec_huffman_lookup(16#b9, 16#c) -> {more, 16#ea, 16#01};
+dec_huffman_lookup(16#b9, 16#d) -> {ok, 16#ea, 16#16};
+dec_huffman_lookup(16#b9, 16#e) -> {more, 16#eb, 16#01};
+dec_huffman_lookup(16#b9, 16#f) -> {ok, 16#eb, 16#16};
+dec_huffman_lookup(16#ba, 16#0) -> {more, 16#ec, 16#03};
+dec_huffman_lookup(16#ba, 16#1) -> {more, 16#ec, 16#06};
+dec_huffman_lookup(16#ba, 16#2) -> {more, 16#ec, 16#0a};
+dec_huffman_lookup(16#ba, 16#3) -> {more, 16#ec, 16#0f};
+dec_huffman_lookup(16#ba, 16#4) -> {more, 16#ec, 16#18};
+dec_huffman_lookup(16#ba, 16#5) -> {more, 16#ec, 16#1f};
+dec_huffman_lookup(16#ba, 16#6) -> {more, 16#ec, 16#29};
+dec_huffman_lookup(16#ba, 16#7) -> {ok, 16#ec, 16#38};
+dec_huffman_lookup(16#ba, 16#8) -> {more, 16#ed, 16#03};
+dec_huffman_lookup(16#ba, 16#9) -> {more, 16#ed, 16#06};
+dec_huffman_lookup(16#ba, 16#a) -> {more, 16#ed, 16#0a};
+dec_huffman_lookup(16#ba, 16#b) -> {more, 16#ed, 16#0f};
+dec_huffman_lookup(16#ba, 16#c) -> {more, 16#ed, 16#18};
+dec_huffman_lookup(16#ba, 16#d) -> {more, 16#ed, 16#1f};
+dec_huffman_lookup(16#ba, 16#e) -> {more, 16#ed, 16#29};
+dec_huffman_lookup(16#ba, 16#f) -> {ok, 16#ed, 16#38};
+dec_huffman_lookup(16#bb, 16#0) -> {more, 16#c7, 16#02};
+dec_huffman_lookup(16#bb, 16#1) -> {more, 16#c7, 16#09};
+dec_huffman_lookup(16#bb, 16#2) -> {more, 16#c7, 16#17};
+dec_huffman_lookup(16#bb, 16#3) -> {ok, 16#c7, 16#28};
+dec_huffman_lookup(16#bb, 16#4) -> {more, 16#cf, 16#02};
+dec_huffman_lookup(16#bb, 16#5) -> {more, 16#cf, 16#09};
+dec_huffman_lookup(16#bb, 16#6) -> {more, 16#cf, 16#17};
+dec_huffman_lookup(16#bb, 16#7) -> {ok, 16#cf, 16#28};
+dec_huffman_lookup(16#bb, 16#8) -> {more, 16#ea, 16#02};
+dec_huffman_lookup(16#bb, 16#9) -> {more, 16#ea, 16#09};
+dec_huffman_lookup(16#bb, 16#a) -> {more, 16#ea, 16#17};
+dec_huffman_lookup(16#bb, 16#b) -> {ok, 16#ea, 16#28};
+dec_huffman_lookup(16#bb, 16#c) -> {more, 16#eb, 16#02};
+dec_huffman_lookup(16#bb, 16#d) -> {more, 16#eb, 16#09};
+dec_huffman_lookup(16#bb, 16#e) -> {more, 16#eb, 16#17};
+dec_huffman_lookup(16#bb, 16#f) -> {ok, 16#eb, 16#28};
+dec_huffman_lookup(16#bc, 16#0) -> {more, 16#c7, 16#03};
+dec_huffman_lookup(16#bc, 16#1) -> {more, 16#c7, 16#06};
+dec_huffman_lookup(16#bc, 16#2) -> {more, 16#c7, 16#0a};
+dec_huffman_lookup(16#bc, 16#3) -> {more, 16#c7, 16#0f};
+dec_huffman_lookup(16#bc, 16#4) -> {more, 16#c7, 16#18};
+dec_huffman_lookup(16#bc, 16#5) -> {more, 16#c7, 16#1f};
+dec_huffman_lookup(16#bc, 16#6) -> {more, 16#c7, 16#29};
+dec_huffman_lookup(16#bc, 16#7) -> {ok, 16#c7, 16#38};
+dec_huffman_lookup(16#bc, 16#8) -> {more, 16#cf, 16#03};
+dec_huffman_lookup(16#bc, 16#9) -> {more, 16#cf, 16#06};
+dec_huffman_lookup(16#bc, 16#a) -> {more, 16#cf, 16#0a};
+dec_huffman_lookup(16#bc, 16#b) -> {more, 16#cf, 16#0f};
+dec_huffman_lookup(16#bc, 16#c) -> {more, 16#cf, 16#18};
+dec_huffman_lookup(16#bc, 16#d) -> {more, 16#cf, 16#1f};
+dec_huffman_lookup(16#bc, 16#e) -> {more, 16#cf, 16#29};
+dec_huffman_lookup(16#bc, 16#f) -> {ok, 16#cf, 16#38};
+dec_huffman_lookup(16#bd, 16#0) -> {more, 16#ea, 16#03};
+dec_huffman_lookup(16#bd, 16#1) -> {more, 16#ea, 16#06};
+dec_huffman_lookup(16#bd, 16#2) -> {more, 16#ea, 16#0a};
+dec_huffman_lookup(16#bd, 16#3) -> {more, 16#ea, 16#0f};
+dec_huffman_lookup(16#bd, 16#4) -> {more, 16#ea, 16#18};
+dec_huffman_lookup(16#bd, 16#5) -> {more, 16#ea, 16#1f};
+dec_huffman_lookup(16#bd, 16#6) -> {more, 16#ea, 16#29};
+dec_huffman_lookup(16#bd, 16#7) -> {ok, 16#ea, 16#38};
+dec_huffman_lookup(16#bd, 16#8) -> {more, 16#eb, 16#03};
+dec_huffman_lookup(16#bd, 16#9) -> {more, 16#eb, 16#06};
+dec_huffman_lookup(16#bd, 16#a) -> {more, 16#eb, 16#0a};
+dec_huffman_lookup(16#bd, 16#b) -> {more, 16#eb, 16#0f};
+dec_huffman_lookup(16#bd, 16#c) -> {more, 16#eb, 16#18};
+dec_huffman_lookup(16#bd, 16#d) -> {more, 16#eb, 16#1f};
+dec_huffman_lookup(16#bd, 16#e) -> {more, 16#eb, 16#29};
+dec_huffman_lookup(16#bd, 16#f) -> {ok, 16#eb, 16#38};
+dec_huffman_lookup(16#be, 16#0) -> {more, undefined, 16#c2};
+dec_huffman_lookup(16#be, 16#1) -> {more, undefined, 16#c3};
+dec_huffman_lookup(16#be, 16#2) -> {more, undefined, 16#c5};
+dec_huffman_lookup(16#be, 16#3) -> {more, undefined, 16#c6};
+dec_huffman_lookup(16#be, 16#4) -> {more, undefined, 16#c9};
+dec_huffman_lookup(16#be, 16#5) -> {more, undefined, 16#ca};
+dec_huffman_lookup(16#be, 16#6) -> {more, undefined, 16#cc};
+dec_huffman_lookup(16#be, 16#7) -> {more, undefined, 16#cd};
+dec_huffman_lookup(16#be, 16#8) -> {more, undefined, 16#d2};
+dec_huffman_lookup(16#be, 16#9) -> {more, undefined, 16#d5};
+dec_huffman_lookup(16#be, 16#a) -> {more, undefined, 16#d9};
+dec_huffman_lookup(16#be, 16#b) -> {more, undefined, 16#dc};
+dec_huffman_lookup(16#be, 16#c) -> {more, undefined, 16#e1};
+dec_huffman_lookup(16#be, 16#d) -> {more, undefined, 16#e7};
+dec_huffman_lookup(16#be, 16#e) -> {more, undefined, 16#ef};
+dec_huffman_lookup(16#be, 16#f) -> {ok, undefined, 16#f6};
+dec_huffman_lookup(16#bf, 16#0) -> {ok, 16#c0, 16#00};
+dec_huffman_lookup(16#bf, 16#1) -> {ok, 16#c1, 16#00};
+dec_huffman_lookup(16#bf, 16#2) -> {ok, 16#c8, 16#00};
+dec_huffman_lookup(16#bf, 16#3) -> {ok, 16#c9, 16#00};
+dec_huffman_lookup(16#bf, 16#4) -> {ok, 16#ca, 16#00};
+dec_huffman_lookup(16#bf, 16#5) -> {ok, 16#cd, 16#00};
+dec_huffman_lookup(16#bf, 16#6) -> {ok, 16#d2, 16#00};
+dec_huffman_lookup(16#bf, 16#7) -> {ok, 16#d5, 16#00};
+dec_huffman_lookup(16#bf, 16#8) -> {ok, 16#da, 16#00};
+dec_huffman_lookup(16#bf, 16#9) -> {ok, 16#db, 16#00};
+dec_huffman_lookup(16#bf, 16#a) -> {ok, 16#ee, 16#00};
+dec_huffman_lookup(16#bf, 16#b) -> {ok, 16#f0, 16#00};
+dec_huffman_lookup(16#bf, 16#c) -> {ok, 16#f2, 16#00};
+dec_huffman_lookup(16#bf, 16#d) -> {ok, 16#f3, 16#00};
+dec_huffman_lookup(16#bf, 16#e) -> {ok, 16#ff, 16#00};
+dec_huffman_lookup(16#bf, 16#f) -> {more, undefined, 16#ce};
+dec_huffman_lookup(16#c0, 16#0) -> {more, 16#c0, 16#01};
+dec_huffman_lookup(16#c0, 16#1) -> {ok, 16#c0, 16#16};
+dec_huffman_lookup(16#c0, 16#2) -> {more, 16#c1, 16#01};
+dec_huffman_lookup(16#c0, 16#3) -> {ok, 16#c1, 16#16};
+dec_huffman_lookup(16#c0, 16#4) -> {more, 16#c8, 16#01};
+dec_huffman_lookup(16#c0, 16#5) -> {ok, 16#c8, 16#16};
+dec_huffman_lookup(16#c0, 16#6) -> {more, 16#c9, 16#01};
+dec_huffman_lookup(16#c0, 16#7) -> {ok, 16#c9, 16#16};
+dec_huffman_lookup(16#c0, 16#8) -> {more, 16#ca, 16#01};
+dec_huffman_lookup(16#c0, 16#9) -> {ok, 16#ca, 16#16};
+dec_huffman_lookup(16#c0, 16#a) -> {more, 16#cd, 16#01};
+dec_huffman_lookup(16#c0, 16#b) -> {ok, 16#cd, 16#16};
+dec_huffman_lookup(16#c0, 16#c) -> {more, 16#d2, 16#01};
+dec_huffman_lookup(16#c0, 16#d) -> {ok, 16#d2, 16#16};
+dec_huffman_lookup(16#c0, 16#e) -> {more, 16#d5, 16#01};
+dec_huffman_lookup(16#c0, 16#f) -> {ok, 16#d5, 16#16};
+dec_huffman_lookup(16#c1, 16#0) -> {more, 16#c0, 16#02};
+dec_huffman_lookup(16#c1, 16#1) -> {more, 16#c0, 16#09};
+dec_huffman_lookup(16#c1, 16#2) -> {more, 16#c0, 16#17};
+dec_huffman_lookup(16#c1, 16#3) -> {ok, 16#c0, 16#28};
+dec_huffman_lookup(16#c1, 16#4) -> {more, 16#c1, 16#02};
+dec_huffman_lookup(16#c1, 16#5) -> {more, 16#c1, 16#09};
+dec_huffman_lookup(16#c1, 16#6) -> {more, 16#c1, 16#17};
+dec_huffman_lookup(16#c1, 16#7) -> {ok, 16#c1, 16#28};
+dec_huffman_lookup(16#c1, 16#8) -> {more, 16#c8, 16#02};
+dec_huffman_lookup(16#c1, 16#9) -> {more, 16#c8, 16#09};
+dec_huffman_lookup(16#c1, 16#a) -> {more, 16#c8, 16#17};
+dec_huffman_lookup(16#c1, 16#b) -> {ok, 16#c8, 16#28};
+dec_huffman_lookup(16#c1, 16#c) -> {more, 16#c9, 16#02};
+dec_huffman_lookup(16#c1, 16#d) -> {more, 16#c9, 16#09};
+dec_huffman_lookup(16#c1, 16#e) -> {more, 16#c9, 16#17};
+dec_huffman_lookup(16#c1, 16#f) -> {ok, 16#c9, 16#28};
+dec_huffman_lookup(16#c2, 16#0) -> {more, 16#c0, 16#03};
+dec_huffman_lookup(16#c2, 16#1) -> {more, 16#c0, 16#06};
+dec_huffman_lookup(16#c2, 16#2) -> {more, 16#c0, 16#0a};
+dec_huffman_lookup(16#c2, 16#3) -> {more, 16#c0, 16#0f};
+dec_huffman_lookup(16#c2, 16#4) -> {more, 16#c0, 16#18};
+dec_huffman_lookup(16#c2, 16#5) -> {more, 16#c0, 16#1f};
+dec_huffman_lookup(16#c2, 16#6) -> {more, 16#c0, 16#29};
+dec_huffman_lookup(16#c2, 16#7) -> {ok, 16#c0, 16#38};
+dec_huffman_lookup(16#c2, 16#8) -> {more, 16#c1, 16#03};
+dec_huffman_lookup(16#c2, 16#9) -> {more, 16#c1, 16#06};
+dec_huffman_lookup(16#c2, 16#a) -> {more, 16#c1, 16#0a};
+dec_huffman_lookup(16#c2, 16#b) -> {more, 16#c1, 16#0f};
+dec_huffman_lookup(16#c2, 16#c) -> {more, 16#c1, 16#18};
+dec_huffman_lookup(16#c2, 16#d) -> {more, 16#c1, 16#1f};
+dec_huffman_lookup(16#c2, 16#e) -> {more, 16#c1, 16#29};
+dec_huffman_lookup(16#c2, 16#f) -> {ok, 16#c1, 16#38};
+dec_huffman_lookup(16#c3, 16#0) -> {more, 16#c8, 16#03};
+dec_huffman_lookup(16#c3, 16#1) -> {more, 16#c8, 16#06};
+dec_huffman_lookup(16#c3, 16#2) -> {more, 16#c8, 16#0a};
+dec_huffman_lookup(16#c3, 16#3) -> {more, 16#c8, 16#0f};
+dec_huffman_lookup(16#c3, 16#4) -> {more, 16#c8, 16#18};
+dec_huffman_lookup(16#c3, 16#5) -> {more, 16#c8, 16#1f};
+dec_huffman_lookup(16#c3, 16#6) -> {more, 16#c8, 16#29};
+dec_huffman_lookup(16#c3, 16#7) -> {ok, 16#c8, 16#38};
+dec_huffman_lookup(16#c3, 16#8) -> {more, 16#c9, 16#03};
+dec_huffman_lookup(16#c3, 16#9) -> {more, 16#c9, 16#06};
+dec_huffman_lookup(16#c3, 16#a) -> {more, 16#c9, 16#0a};
+dec_huffman_lookup(16#c3, 16#b) -> {more, 16#c9, 16#0f};
+dec_huffman_lookup(16#c3, 16#c) -> {more, 16#c9, 16#18};
+dec_huffman_lookup(16#c3, 16#d) -> {more, 16#c9, 16#1f};
+dec_huffman_lookup(16#c3, 16#e) -> {more, 16#c9, 16#29};
+dec_huffman_lookup(16#c3, 16#f) -> {ok, 16#c9, 16#38};
+dec_huffman_lookup(16#c4, 16#0) -> {more, 16#ca, 16#02};
+dec_huffman_lookup(16#c4, 16#1) -> {more, 16#ca, 16#09};
+dec_huffman_lookup(16#c4, 16#2) -> {more, 16#ca, 16#17};
+dec_huffman_lookup(16#c4, 16#3) -> {ok, 16#ca, 16#28};
+dec_huffman_lookup(16#c4, 16#4) -> {more, 16#cd, 16#02};
+dec_huffman_lookup(16#c4, 16#5) -> {more, 16#cd, 16#09};
+dec_huffman_lookup(16#c4, 16#6) -> {more, 16#cd, 16#17};
+dec_huffman_lookup(16#c4, 16#7) -> {ok, 16#cd, 16#28};
+dec_huffman_lookup(16#c4, 16#8) -> {more, 16#d2, 16#02};
+dec_huffman_lookup(16#c4, 16#9) -> {more, 16#d2, 16#09};
+dec_huffman_lookup(16#c4, 16#a) -> {more, 16#d2, 16#17};
+dec_huffman_lookup(16#c4, 16#b) -> {ok, 16#d2, 16#28};
+dec_huffman_lookup(16#c4, 16#c) -> {more, 16#d5, 16#02};
+dec_huffman_lookup(16#c4, 16#d) -> {more, 16#d5, 16#09};
+dec_huffman_lookup(16#c4, 16#e) -> {more, 16#d5, 16#17};
+dec_huffman_lookup(16#c4, 16#f) -> {ok, 16#d5, 16#28};
+dec_huffman_lookup(16#c5, 16#0) -> {more, 16#ca, 16#03};
+dec_huffman_lookup(16#c5, 16#1) -> {more, 16#ca, 16#06};
+dec_huffman_lookup(16#c5, 16#2) -> {more, 16#ca, 16#0a};
+dec_huffman_lookup(16#c5, 16#3) -> {more, 16#ca, 16#0f};
+dec_huffman_lookup(16#c5, 16#4) -> {more, 16#ca, 16#18};
+dec_huffman_lookup(16#c5, 16#5) -> {more, 16#ca, 16#1f};
+dec_huffman_lookup(16#c5, 16#6) -> {more, 16#ca, 16#29};
+dec_huffman_lookup(16#c5, 16#7) -> {ok, 16#ca, 16#38};
+dec_huffman_lookup(16#c5, 16#8) -> {more, 16#cd, 16#03};
+dec_huffman_lookup(16#c5, 16#9) -> {more, 16#cd, 16#06};
+dec_huffman_lookup(16#c5, 16#a) -> {more, 16#cd, 16#0a};
+dec_huffman_lookup(16#c5, 16#b) -> {more, 16#cd, 16#0f};
+dec_huffman_lookup(16#c5, 16#c) -> {more, 16#cd, 16#18};
+dec_huffman_lookup(16#c5, 16#d) -> {more, 16#cd, 16#1f};
+dec_huffman_lookup(16#c5, 16#e) -> {more, 16#cd, 16#29};
+dec_huffman_lookup(16#c5, 16#f) -> {ok, 16#cd, 16#38};
+dec_huffman_lookup(16#c6, 16#0) -> {more, 16#d2, 16#03};
+dec_huffman_lookup(16#c6, 16#1) -> {more, 16#d2, 16#06};
+dec_huffman_lookup(16#c6, 16#2) -> {more, 16#d2, 16#0a};
+dec_huffman_lookup(16#c6, 16#3) -> {more, 16#d2, 16#0f};
+dec_huffman_lookup(16#c6, 16#4) -> {more, 16#d2, 16#18};
+dec_huffman_lookup(16#c6, 16#5) -> {more, 16#d2, 16#1f};
+dec_huffman_lookup(16#c6, 16#6) -> {more, 16#d2, 16#29};
+dec_huffman_lookup(16#c6, 16#7) -> {ok, 16#d2, 16#38};
+dec_huffman_lookup(16#c6, 16#8) -> {more, 16#d5, 16#03};
+dec_huffman_lookup(16#c6, 16#9) -> {more, 16#d5, 16#06};
+dec_huffman_lookup(16#c6, 16#a) -> {more, 16#d5, 16#0a};
+dec_huffman_lookup(16#c6, 16#b) -> {more, 16#d5, 16#0f};
+dec_huffman_lookup(16#c6, 16#c) -> {more, 16#d5, 16#18};
+dec_huffman_lookup(16#c6, 16#d) -> {more, 16#d5, 16#1f};
+dec_huffman_lookup(16#c6, 16#e) -> {more, 16#d5, 16#29};
+dec_huffman_lookup(16#c6, 16#f) -> {ok, 16#d5, 16#38};
+dec_huffman_lookup(16#c7, 16#0) -> {more, 16#da, 16#01};
+dec_huffman_lookup(16#c7, 16#1) -> {ok, 16#da, 16#16};
+dec_huffman_lookup(16#c7, 16#2) -> {more, 16#db, 16#01};
+dec_huffman_lookup(16#c7, 16#3) -> {ok, 16#db, 16#16};
+dec_huffman_lookup(16#c7, 16#4) -> {more, 16#ee, 16#01};
+dec_huffman_lookup(16#c7, 16#5) -> {ok, 16#ee, 16#16};
+dec_huffman_lookup(16#c7, 16#6) -> {more, 16#f0, 16#01};
+dec_huffman_lookup(16#c7, 16#7) -> {ok, 16#f0, 16#16};
+dec_huffman_lookup(16#c7, 16#8) -> {more, 16#f2, 16#01};
+dec_huffman_lookup(16#c7, 16#9) -> {ok, 16#f2, 16#16};
+dec_huffman_lookup(16#c7, 16#a) -> {more, 16#f3, 16#01};
+dec_huffman_lookup(16#c7, 16#b) -> {ok, 16#f3, 16#16};
+dec_huffman_lookup(16#c7, 16#c) -> {more, 16#ff, 16#01};
+dec_huffman_lookup(16#c7, 16#d) -> {ok, 16#ff, 16#16};
+dec_huffman_lookup(16#c7, 16#e) -> {ok, 16#cb, 16#00};
+dec_huffman_lookup(16#c7, 16#f) -> {ok, 16#cc, 16#00};
+dec_huffman_lookup(16#c8, 16#0) -> {more, 16#da, 16#02};
+dec_huffman_lookup(16#c8, 16#1) -> {more, 16#da, 16#09};
+dec_huffman_lookup(16#c8, 16#2) -> {more, 16#da, 16#17};
+dec_huffman_lookup(16#c8, 16#3) -> {ok, 16#da, 16#28};
+dec_huffman_lookup(16#c8, 16#4) -> {more, 16#db, 16#02};
+dec_huffman_lookup(16#c8, 16#5) -> {more, 16#db, 16#09};
+dec_huffman_lookup(16#c8, 16#6) -> {more, 16#db, 16#17};
+dec_huffman_lookup(16#c8, 16#7) -> {ok, 16#db, 16#28};
+dec_huffman_lookup(16#c8, 16#8) -> {more, 16#ee, 16#02};
+dec_huffman_lookup(16#c8, 16#9) -> {more, 16#ee, 16#09};
+dec_huffman_lookup(16#c8, 16#a) -> {more, 16#ee, 16#17};
+dec_huffman_lookup(16#c8, 16#b) -> {ok, 16#ee, 16#28};
+dec_huffman_lookup(16#c8, 16#c) -> {more, 16#f0, 16#02};
+dec_huffman_lookup(16#c8, 16#d) -> {more, 16#f0, 16#09};
+dec_huffman_lookup(16#c8, 16#e) -> {more, 16#f0, 16#17};
+dec_huffman_lookup(16#c8, 16#f) -> {ok, 16#f0, 16#28};
+dec_huffman_lookup(16#c9, 16#0) -> {more, 16#da, 16#03};
+dec_huffman_lookup(16#c9, 16#1) -> {more, 16#da, 16#06};
+dec_huffman_lookup(16#c9, 16#2) -> {more, 16#da, 16#0a};
+dec_huffman_lookup(16#c9, 16#3) -> {more, 16#da, 16#0f};
+dec_huffman_lookup(16#c9, 16#4) -> {more, 16#da, 16#18};
+dec_huffman_lookup(16#c9, 16#5) -> {more, 16#da, 16#1f};
+dec_huffman_lookup(16#c9, 16#6) -> {more, 16#da, 16#29};
+dec_huffman_lookup(16#c9, 16#7) -> {ok, 16#da, 16#38};
+dec_huffman_lookup(16#c9, 16#8) -> {more, 16#db, 16#03};
+dec_huffman_lookup(16#c9, 16#9) -> {more, 16#db, 16#06};
+dec_huffman_lookup(16#c9, 16#a) -> {more, 16#db, 16#0a};
+dec_huffman_lookup(16#c9, 16#b) -> {more, 16#db, 16#0f};
+dec_huffman_lookup(16#c9, 16#c) -> {more, 16#db, 16#18};
+dec_huffman_lookup(16#c9, 16#d) -> {more, 16#db, 16#1f};
+dec_huffman_lookup(16#c9, 16#e) -> {more, 16#db, 16#29};
+dec_huffman_lookup(16#c9, 16#f) -> {ok, 16#db, 16#38};
+dec_huffman_lookup(16#ca, 16#0) -> {more, 16#ee, 16#03};
+dec_huffman_lookup(16#ca, 16#1) -> {more, 16#ee, 16#06};
+dec_huffman_lookup(16#ca, 16#2) -> {more, 16#ee, 16#0a};
+dec_huffman_lookup(16#ca, 16#3) -> {more, 16#ee, 16#0f};
+dec_huffman_lookup(16#ca, 16#4) -> {more, 16#ee, 16#18};
+dec_huffman_lookup(16#ca, 16#5) -> {more, 16#ee, 16#1f};
+dec_huffman_lookup(16#ca, 16#6) -> {more, 16#ee, 16#29};
+dec_huffman_lookup(16#ca, 16#7) -> {ok, 16#ee, 16#38};
+dec_huffman_lookup(16#ca, 16#8) -> {more, 16#f0, 16#03};
+dec_huffman_lookup(16#ca, 16#9) -> {more, 16#f0, 16#06};
+dec_huffman_lookup(16#ca, 16#a) -> {more, 16#f0, 16#0a};
+dec_huffman_lookup(16#ca, 16#b) -> {more, 16#f0, 16#0f};
+dec_huffman_lookup(16#ca, 16#c) -> {more, 16#f0, 16#18};
+dec_huffman_lookup(16#ca, 16#d) -> {more, 16#f0, 16#1f};
+dec_huffman_lookup(16#ca, 16#e) -> {more, 16#f0, 16#29};
+dec_huffman_lookup(16#ca, 16#f) -> {ok, 16#f0, 16#38};
+dec_huffman_lookup(16#cb, 16#0) -> {more, 16#f2, 16#02};
+dec_huffman_lookup(16#cb, 16#1) -> {more, 16#f2, 16#09};
+dec_huffman_lookup(16#cb, 16#2) -> {more, 16#f2, 16#17};
+dec_huffman_lookup(16#cb, 16#3) -> {ok, 16#f2, 16#28};
+dec_huffman_lookup(16#cb, 16#4) -> {more, 16#f3, 16#02};
+dec_huffman_lookup(16#cb, 16#5) -> {more, 16#f3, 16#09};
+dec_huffman_lookup(16#cb, 16#6) -> {more, 16#f3, 16#17};
+dec_huffman_lookup(16#cb, 16#7) -> {ok, 16#f3, 16#28};
+dec_huffman_lookup(16#cb, 16#8) -> {more, 16#ff, 16#02};
+dec_huffman_lookup(16#cb, 16#9) -> {more, 16#ff, 16#09};
+dec_huffman_lookup(16#cb, 16#a) -> {more, 16#ff, 16#17};
+dec_huffman_lookup(16#cb, 16#b) -> {ok, 16#ff, 16#28};
+dec_huffman_lookup(16#cb, 16#c) -> {more, 16#cb, 16#01};
+dec_huffman_lookup(16#cb, 16#d) -> {ok, 16#cb, 16#16};
+dec_huffman_lookup(16#cb, 16#e) -> {more, 16#cc, 16#01};
+dec_huffman_lookup(16#cb, 16#f) -> {ok, 16#cc, 16#16};
+dec_huffman_lookup(16#cc, 16#0) -> {more, 16#f2, 16#03};
+dec_huffman_lookup(16#cc, 16#1) -> {more, 16#f2, 16#06};
+dec_huffman_lookup(16#cc, 16#2) -> {more, 16#f2, 16#0a};
+dec_huffman_lookup(16#cc, 16#3) -> {more, 16#f2, 16#0f};
+dec_huffman_lookup(16#cc, 16#4) -> {more, 16#f2, 16#18};
+dec_huffman_lookup(16#cc, 16#5) -> {more, 16#f2, 16#1f};
+dec_huffman_lookup(16#cc, 16#6) -> {more, 16#f2, 16#29};
+dec_huffman_lookup(16#cc, 16#7) -> {ok, 16#f2, 16#38};
+dec_huffman_lookup(16#cc, 16#8) -> {more, 16#f3, 16#03};
+dec_huffman_lookup(16#cc, 16#9) -> {more, 16#f3, 16#06};
+dec_huffman_lookup(16#cc, 16#a) -> {more, 16#f3, 16#0a};
+dec_huffman_lookup(16#cc, 16#b) -> {more, 16#f3, 16#0f};
+dec_huffman_lookup(16#cc, 16#c) -> {more, 16#f3, 16#18};
+dec_huffman_lookup(16#cc, 16#d) -> {more, 16#f3, 16#1f};
+dec_huffman_lookup(16#cc, 16#e) -> {more, 16#f3, 16#29};
+dec_huffman_lookup(16#cc, 16#f) -> {ok, 16#f3, 16#38};
+dec_huffman_lookup(16#cd, 16#0) -> {more, 16#ff, 16#03};
+dec_huffman_lookup(16#cd, 16#1) -> {more, 16#ff, 16#06};
+dec_huffman_lookup(16#cd, 16#2) -> {more, 16#ff, 16#0a};
+dec_huffman_lookup(16#cd, 16#3) -> {more, 16#ff, 16#0f};
+dec_huffman_lookup(16#cd, 16#4) -> {more, 16#ff, 16#18};
+dec_huffman_lookup(16#cd, 16#5) -> {more, 16#ff, 16#1f};
+dec_huffman_lookup(16#cd, 16#6) -> {more, 16#ff, 16#29};
+dec_huffman_lookup(16#cd, 16#7) -> {ok, 16#ff, 16#38};
+dec_huffman_lookup(16#cd, 16#8) -> {more, 16#cb, 16#02};
+dec_huffman_lookup(16#cd, 16#9) -> {more, 16#cb, 16#09};
+dec_huffman_lookup(16#cd, 16#a) -> {more, 16#cb, 16#17};
+dec_huffman_lookup(16#cd, 16#b) -> {ok, 16#cb, 16#28};
+dec_huffman_lookup(16#cd, 16#c) -> {more, 16#cc, 16#02};
+dec_huffman_lookup(16#cd, 16#d) -> {more, 16#cc, 16#09};
+dec_huffman_lookup(16#cd, 16#e) -> {more, 16#cc, 16#17};
+dec_huffman_lookup(16#cd, 16#f) -> {ok, 16#cc, 16#28};
+dec_huffman_lookup(16#ce, 16#0) -> {more, 16#cb, 16#03};
+dec_huffman_lookup(16#ce, 16#1) -> {more, 16#cb, 16#06};
+dec_huffman_lookup(16#ce, 16#2) -> {more, 16#cb, 16#0a};
+dec_huffman_lookup(16#ce, 16#3) -> {more, 16#cb, 16#0f};
+dec_huffman_lookup(16#ce, 16#4) -> {more, 16#cb, 16#18};
+dec_huffman_lookup(16#ce, 16#5) -> {more, 16#cb, 16#1f};
+dec_huffman_lookup(16#ce, 16#6) -> {more, 16#cb, 16#29};
+dec_huffman_lookup(16#ce, 16#7) -> {ok, 16#cb, 16#38};
+dec_huffman_lookup(16#ce, 16#8) -> {more, 16#cc, 16#03};
+dec_huffman_lookup(16#ce, 16#9) -> {more, 16#cc, 16#06};
+dec_huffman_lookup(16#ce, 16#a) -> {more, 16#cc, 16#0a};
+dec_huffman_lookup(16#ce, 16#b) -> {more, 16#cc, 16#0f};
+dec_huffman_lookup(16#ce, 16#c) -> {more, 16#cc, 16#18};
+dec_huffman_lookup(16#ce, 16#d) -> {more, 16#cc, 16#1f};
+dec_huffman_lookup(16#ce, 16#e) -> {more, 16#cc, 16#29};
+dec_huffman_lookup(16#ce, 16#f) -> {ok, 16#cc, 16#38};
+dec_huffman_lookup(16#cf, 16#0) -> {more, undefined, 16#d3};
+dec_huffman_lookup(16#cf, 16#1) -> {more, undefined, 16#d4};
+dec_huffman_lookup(16#cf, 16#2) -> {more, undefined, 16#d6};
+dec_huffman_lookup(16#cf, 16#3) -> {more, undefined, 16#d7};
+dec_huffman_lookup(16#cf, 16#4) -> {more, undefined, 16#da};
+dec_huffman_lookup(16#cf, 16#5) -> {more, undefined, 16#db};
+dec_huffman_lookup(16#cf, 16#6) -> {more, undefined, 16#dd};
+dec_huffman_lookup(16#cf, 16#7) -> {more, undefined, 16#de};
+dec_huffman_lookup(16#cf, 16#8) -> {more, undefined, 16#e2};
+dec_huffman_lookup(16#cf, 16#9) -> {more, undefined, 16#e4};
+dec_huffman_lookup(16#cf, 16#a) -> {more, undefined, 16#e8};
+dec_huffman_lookup(16#cf, 16#b) -> {more, undefined, 16#eb};
+dec_huffman_lookup(16#cf, 16#c) -> {more, undefined, 16#f0};
+dec_huffman_lookup(16#cf, 16#d) -> {more, undefined, 16#f3};
+dec_huffman_lookup(16#cf, 16#e) -> {more, undefined, 16#f7};
+dec_huffman_lookup(16#cf, 16#f) -> {ok, undefined, 16#fa};
+dec_huffman_lookup(16#d0, 16#0) -> {ok, 16#d3, 16#00};
+dec_huffman_lookup(16#d0, 16#1) -> {ok, 16#d4, 16#00};
+dec_huffman_lookup(16#d0, 16#2) -> {ok, 16#d6, 16#00};
+dec_huffman_lookup(16#d0, 16#3) -> {ok, 16#dd, 16#00};
+dec_huffman_lookup(16#d0, 16#4) -> {ok, 16#de, 16#00};
+dec_huffman_lookup(16#d0, 16#5) -> {ok, 16#df, 16#00};
+dec_huffman_lookup(16#d0, 16#6) -> {ok, 16#f1, 16#00};
+dec_huffman_lookup(16#d0, 16#7) -> {ok, 16#f4, 16#00};
+dec_huffman_lookup(16#d0, 16#8) -> {ok, 16#f5, 16#00};
+dec_huffman_lookup(16#d0, 16#9) -> {ok, 16#f6, 16#00};
+dec_huffman_lookup(16#d0, 16#a) -> {ok, 16#f7, 16#00};
+dec_huffman_lookup(16#d0, 16#b) -> {ok, 16#f8, 16#00};
+dec_huffman_lookup(16#d0, 16#c) -> {ok, 16#fa, 16#00};
+dec_huffman_lookup(16#d0, 16#d) -> {ok, 16#fb, 16#00};
+dec_huffman_lookup(16#d0, 16#e) -> {ok, 16#fc, 16#00};
+dec_huffman_lookup(16#d0, 16#f) -> {ok, 16#fd, 16#00};
+dec_huffman_lookup(16#d1, 16#0) -> {more, 16#d3, 16#01};
+dec_huffman_lookup(16#d1, 16#1) -> {ok, 16#d3, 16#16};
+dec_huffman_lookup(16#d1, 16#2) -> {more, 16#d4, 16#01};
+dec_huffman_lookup(16#d1, 16#3) -> {ok, 16#d4, 16#16};
+dec_huffman_lookup(16#d1, 16#4) -> {more, 16#d6, 16#01};
+dec_huffman_lookup(16#d1, 16#5) -> {ok, 16#d6, 16#16};
+dec_huffman_lookup(16#d1, 16#6) -> {more, 16#dd, 16#01};
+dec_huffman_lookup(16#d1, 16#7) -> {ok, 16#dd, 16#16};
+dec_huffman_lookup(16#d1, 16#8) -> {more, 16#de, 16#01};
+dec_huffman_lookup(16#d1, 16#9) -> {ok, 16#de, 16#16};
+dec_huffman_lookup(16#d1, 16#a) -> {more, 16#df, 16#01};
+dec_huffman_lookup(16#d1, 16#b) -> {ok, 16#df, 16#16};
+dec_huffman_lookup(16#d1, 16#c) -> {more, 16#f1, 16#01};
+dec_huffman_lookup(16#d1, 16#d) -> {ok, 16#f1, 16#16};
+dec_huffman_lookup(16#d1, 16#e) -> {more, 16#f4, 16#01};
+dec_huffman_lookup(16#d1, 16#f) -> {ok, 16#f4, 16#16};
+dec_huffman_lookup(16#d2, 16#0) -> {more, 16#d3, 16#02};
+dec_huffman_lookup(16#d2, 16#1) -> {more, 16#d3, 16#09};
+dec_huffman_lookup(16#d2, 16#2) -> {more, 16#d3, 16#17};
+dec_huffman_lookup(16#d2, 16#3) -> {ok, 16#d3, 16#28};
+dec_huffman_lookup(16#d2, 16#4) -> {more, 16#d4, 16#02};
+dec_huffman_lookup(16#d2, 16#5) -> {more, 16#d4, 16#09};
+dec_huffman_lookup(16#d2, 16#6) -> {more, 16#d4, 16#17};
+dec_huffman_lookup(16#d2, 16#7) -> {ok, 16#d4, 16#28};
+dec_huffman_lookup(16#d2, 16#8) -> {more, 16#d6, 16#02};
+dec_huffman_lookup(16#d2, 16#9) -> {more, 16#d6, 16#09};
+dec_huffman_lookup(16#d2, 16#a) -> {more, 16#d6, 16#17};
+dec_huffman_lookup(16#d2, 16#b) -> {ok, 16#d6, 16#28};
+dec_huffman_lookup(16#d2, 16#c) -> {more, 16#dd, 16#02};
+dec_huffman_lookup(16#d2, 16#d) -> {more, 16#dd, 16#09};
+dec_huffman_lookup(16#d2, 16#e) -> {more, 16#dd, 16#17};
+dec_huffman_lookup(16#d2, 16#f) -> {ok, 16#dd, 16#28};
+dec_huffman_lookup(16#d3, 16#0) -> {more, 16#d3, 16#03};
+dec_huffman_lookup(16#d3, 16#1) -> {more, 16#d3, 16#06};
+dec_huffman_lookup(16#d3, 16#2) -> {more, 16#d3, 16#0a};
+dec_huffman_lookup(16#d3, 16#3) -> {more, 16#d3, 16#0f};
+dec_huffman_lookup(16#d3, 16#4) -> {more, 16#d3, 16#18};
+dec_huffman_lookup(16#d3, 16#5) -> {more, 16#d3, 16#1f};
+dec_huffman_lookup(16#d3, 16#6) -> {more, 16#d3, 16#29};
+dec_huffman_lookup(16#d3, 16#7) -> {ok, 16#d3, 16#38};
+dec_huffman_lookup(16#d3, 16#8) -> {more, 16#d4, 16#03};
+dec_huffman_lookup(16#d3, 16#9) -> {more, 16#d4, 16#06};
+dec_huffman_lookup(16#d3, 16#a) -> {more, 16#d4, 16#0a};
+dec_huffman_lookup(16#d3, 16#b) -> {more, 16#d4, 16#0f};
+dec_huffman_lookup(16#d3, 16#c) -> {more, 16#d4, 16#18};
+dec_huffman_lookup(16#d3, 16#d) -> {more, 16#d4, 16#1f};
+dec_huffman_lookup(16#d3, 16#e) -> {more, 16#d4, 16#29};
+dec_huffman_lookup(16#d3, 16#f) -> {ok, 16#d4, 16#38};
+dec_huffman_lookup(16#d4, 16#0) -> {more, 16#d6, 16#03};
+dec_huffman_lookup(16#d4, 16#1) -> {more, 16#d6, 16#06};
+dec_huffman_lookup(16#d4, 16#2) -> {more, 16#d6, 16#0a};
+dec_huffman_lookup(16#d4, 16#3) -> {more, 16#d6, 16#0f};
+dec_huffman_lookup(16#d4, 16#4) -> {more, 16#d6, 16#18};
+dec_huffman_lookup(16#d4, 16#5) -> {more, 16#d6, 16#1f};
+dec_huffman_lookup(16#d4, 16#6) -> {more, 16#d6, 16#29};
+dec_huffman_lookup(16#d4, 16#7) -> {ok, 16#d6, 16#38};
+dec_huffman_lookup(16#d4, 16#8) -> {more, 16#dd, 16#03};
+dec_huffman_lookup(16#d4, 16#9) -> {more, 16#dd, 16#06};
+dec_huffman_lookup(16#d4, 16#a) -> {more, 16#dd, 16#0a};
+dec_huffman_lookup(16#d4, 16#b) -> {more, 16#dd, 16#0f};
+dec_huffman_lookup(16#d4, 16#c) -> {more, 16#dd, 16#18};
+dec_huffman_lookup(16#d4, 16#d) -> {more, 16#dd, 16#1f};
+dec_huffman_lookup(16#d4, 16#e) -> {more, 16#dd, 16#29};
+dec_huffman_lookup(16#d4, 16#f) -> {ok, 16#dd, 16#38};
+dec_huffman_lookup(16#d5, 16#0) -> {more, 16#de, 16#02};
+dec_huffman_lookup(16#d5, 16#1) -> {more, 16#de, 16#09};
+dec_huffman_lookup(16#d5, 16#2) -> {more, 16#de, 16#17};
+dec_huffman_lookup(16#d5, 16#3) -> {ok, 16#de, 16#28};
+dec_huffman_lookup(16#d5, 16#4) -> {more, 16#df, 16#02};
+dec_huffman_lookup(16#d5, 16#5) -> {more, 16#df, 16#09};
+dec_huffman_lookup(16#d5, 16#6) -> {more, 16#df, 16#17};
+dec_huffman_lookup(16#d5, 16#7) -> {ok, 16#df, 16#28};
+dec_huffman_lookup(16#d5, 16#8) -> {more, 16#f1, 16#02};
+dec_huffman_lookup(16#d5, 16#9) -> {more, 16#f1, 16#09};
+dec_huffman_lookup(16#d5, 16#a) -> {more, 16#f1, 16#17};
+dec_huffman_lookup(16#d5, 16#b) -> {ok, 16#f1, 16#28};
+dec_huffman_lookup(16#d5, 16#c) -> {more, 16#f4, 16#02};
+dec_huffman_lookup(16#d5, 16#d) -> {more, 16#f4, 16#09};
+dec_huffman_lookup(16#d5, 16#e) -> {more, 16#f4, 16#17};
+dec_huffman_lookup(16#d5, 16#f) -> {ok, 16#f4, 16#28};
+dec_huffman_lookup(16#d6, 16#0) -> {more, 16#de, 16#03};
+dec_huffman_lookup(16#d6, 16#1) -> {more, 16#de, 16#06};
+dec_huffman_lookup(16#d6, 16#2) -> {more, 16#de, 16#0a};
+dec_huffman_lookup(16#d6, 16#3) -> {more, 16#de, 16#0f};
+dec_huffman_lookup(16#d6, 16#4) -> {more, 16#de, 16#18};
+dec_huffman_lookup(16#d6, 16#5) -> {more, 16#de, 16#1f};
+dec_huffman_lookup(16#d6, 16#6) -> {more, 16#de, 16#29};
+dec_huffman_lookup(16#d6, 16#7) -> {ok, 16#de, 16#38};
+dec_huffman_lookup(16#d6, 16#8) -> {more, 16#df, 16#03};
+dec_huffman_lookup(16#d6, 16#9) -> {more, 16#df, 16#06};
+dec_huffman_lookup(16#d6, 16#a) -> {more, 16#df, 16#0a};
+dec_huffman_lookup(16#d6, 16#b) -> {more, 16#df, 16#0f};
+dec_huffman_lookup(16#d6, 16#c) -> {more, 16#df, 16#18};
+dec_huffman_lookup(16#d6, 16#d) -> {more, 16#df, 16#1f};
+dec_huffman_lookup(16#d6, 16#e) -> {more, 16#df, 16#29};
+dec_huffman_lookup(16#d6, 16#f) -> {ok, 16#df, 16#38};
+dec_huffman_lookup(16#d7, 16#0) -> {more, 16#f1, 16#03};
+dec_huffman_lookup(16#d7, 16#1) -> {more, 16#f1, 16#06};
+dec_huffman_lookup(16#d7, 16#2) -> {more, 16#f1, 16#0a};
+dec_huffman_lookup(16#d7, 16#3) -> {more, 16#f1, 16#0f};
+dec_huffman_lookup(16#d7, 16#4) -> {more, 16#f1, 16#18};
+dec_huffman_lookup(16#d7, 16#5) -> {more, 16#f1, 16#1f};
+dec_huffman_lookup(16#d7, 16#6) -> {more, 16#f1, 16#29};
+dec_huffman_lookup(16#d7, 16#7) -> {ok, 16#f1, 16#38};
+dec_huffman_lookup(16#d7, 16#8) -> {more, 16#f4, 16#03};
+dec_huffman_lookup(16#d7, 16#9) -> {more, 16#f4, 16#06};
+dec_huffman_lookup(16#d7, 16#a) -> {more, 16#f4, 16#0a};
+dec_huffman_lookup(16#d7, 16#b) -> {more, 16#f4, 16#0f};
+dec_huffman_lookup(16#d7, 16#c) -> {more, 16#f4, 16#18};
+dec_huffman_lookup(16#d7, 16#d) -> {more, 16#f4, 16#1f};
+dec_huffman_lookup(16#d7, 16#e) -> {more, 16#f4, 16#29};
+dec_huffman_lookup(16#d7, 16#f) -> {ok, 16#f4, 16#38};
+dec_huffman_lookup(16#d8, 16#0) -> {more, 16#f5, 16#01};
+dec_huffman_lookup(16#d8, 16#1) -> {ok, 16#f5, 16#16};
+dec_huffman_lookup(16#d8, 16#2) -> {more, 16#f6, 16#01};
+dec_huffman_lookup(16#d8, 16#3) -> {ok, 16#f6, 16#16};
+dec_huffman_lookup(16#d8, 16#4) -> {more, 16#f7, 16#01};
+dec_huffman_lookup(16#d8, 16#5) -> {ok, 16#f7, 16#16};
+dec_huffman_lookup(16#d8, 16#6) -> {more, 16#f8, 16#01};
+dec_huffman_lookup(16#d8, 16#7) -> {ok, 16#f8, 16#16};
+dec_huffman_lookup(16#d8, 16#8) -> {more, 16#fa, 16#01};
+dec_huffman_lookup(16#d8, 16#9) -> {ok, 16#fa, 16#16};
+dec_huffman_lookup(16#d8, 16#a) -> {more, 16#fb, 16#01};
+dec_huffman_lookup(16#d8, 16#b) -> {ok, 16#fb, 16#16};
+dec_huffman_lookup(16#d8, 16#c) -> {more, 16#fc, 16#01};
+dec_huffman_lookup(16#d8, 16#d) -> {ok, 16#fc, 16#16};
+dec_huffman_lookup(16#d8, 16#e) -> {more, 16#fd, 16#01};
+dec_huffman_lookup(16#d8, 16#f) -> {ok, 16#fd, 16#16};
+dec_huffman_lookup(16#d9, 16#0) -> {more, 16#f5, 16#02};
+dec_huffman_lookup(16#d9, 16#1) -> {more, 16#f5, 16#09};
+dec_huffman_lookup(16#d9, 16#2) -> {more, 16#f5, 16#17};
+dec_huffman_lookup(16#d9, 16#3) -> {ok, 16#f5, 16#28};
+dec_huffman_lookup(16#d9, 16#4) -> {more, 16#f6, 16#02};
+dec_huffman_lookup(16#d9, 16#5) -> {more, 16#f6, 16#09};
+dec_huffman_lookup(16#d9, 16#6) -> {more, 16#f6, 16#17};
+dec_huffman_lookup(16#d9, 16#7) -> {ok, 16#f6, 16#28};
+dec_huffman_lookup(16#d9, 16#8) -> {more, 16#f7, 16#02};
+dec_huffman_lookup(16#d9, 16#9) -> {more, 16#f7, 16#09};
+dec_huffman_lookup(16#d9, 16#a) -> {more, 16#f7, 16#17};
+dec_huffman_lookup(16#d9, 16#b) -> {ok, 16#f7, 16#28};
+dec_huffman_lookup(16#d9, 16#c) -> {more, 16#f8, 16#02};
+dec_huffman_lookup(16#d9, 16#d) -> {more, 16#f8, 16#09};
+dec_huffman_lookup(16#d9, 16#e) -> {more, 16#f8, 16#17};
+dec_huffman_lookup(16#d9, 16#f) -> {ok, 16#f8, 16#28};
+dec_huffman_lookup(16#da, 16#0) -> {more, 16#f5, 16#03};
+dec_huffman_lookup(16#da, 16#1) -> {more, 16#f5, 16#06};
+dec_huffman_lookup(16#da, 16#2) -> {more, 16#f5, 16#0a};
+dec_huffman_lookup(16#da, 16#3) -> {more, 16#f5, 16#0f};
+dec_huffman_lookup(16#da, 16#4) -> {more, 16#f5, 16#18};
+dec_huffman_lookup(16#da, 16#5) -> {more, 16#f5, 16#1f};
+dec_huffman_lookup(16#da, 16#6) -> {more, 16#f5, 16#29};
+dec_huffman_lookup(16#da, 16#7) -> {ok, 16#f5, 16#38};
+dec_huffman_lookup(16#da, 16#8) -> {more, 16#f6, 16#03};
+dec_huffman_lookup(16#da, 16#9) -> {more, 16#f6, 16#06};
+dec_huffman_lookup(16#da, 16#a) -> {more, 16#f6, 16#0a};
+dec_huffman_lookup(16#da, 16#b) -> {more, 16#f6, 16#0f};
+dec_huffman_lookup(16#da, 16#c) -> {more, 16#f6, 16#18};
+dec_huffman_lookup(16#da, 16#d) -> {more, 16#f6, 16#1f};
+dec_huffman_lookup(16#da, 16#e) -> {more, 16#f6, 16#29};
+dec_huffman_lookup(16#da, 16#f) -> {ok, 16#f6, 16#38};
+dec_huffman_lookup(16#db, 16#0) -> {more, 16#f7, 16#03};
+dec_huffman_lookup(16#db, 16#1) -> {more, 16#f7, 16#06};
+dec_huffman_lookup(16#db, 16#2) -> {more, 16#f7, 16#0a};
+dec_huffman_lookup(16#db, 16#3) -> {more, 16#f7, 16#0f};
+dec_huffman_lookup(16#db, 16#4) -> {more, 16#f7, 16#18};
+dec_huffman_lookup(16#db, 16#5) -> {more, 16#f7, 16#1f};
+dec_huffman_lookup(16#db, 16#6) -> {more, 16#f7, 16#29};
+dec_huffman_lookup(16#db, 16#7) -> {ok, 16#f7, 16#38};
+dec_huffman_lookup(16#db, 16#8) -> {more, 16#f8, 16#03};
+dec_huffman_lookup(16#db, 16#9) -> {more, 16#f8, 16#06};
+dec_huffman_lookup(16#db, 16#a) -> {more, 16#f8, 16#0a};
+dec_huffman_lookup(16#db, 16#b) -> {more, 16#f8, 16#0f};
+dec_huffman_lookup(16#db, 16#c) -> {more, 16#f8, 16#18};
+dec_huffman_lookup(16#db, 16#d) -> {more, 16#f8, 16#1f};
+dec_huffman_lookup(16#db, 16#e) -> {more, 16#f8, 16#29};
+dec_huffman_lookup(16#db, 16#f) -> {ok, 16#f8, 16#38};
+dec_huffman_lookup(16#dc, 16#0) -> {more, 16#fa, 16#02};
+dec_huffman_lookup(16#dc, 16#1) -> {more, 16#fa, 16#09};
+dec_huffman_lookup(16#dc, 16#2) -> {more, 16#fa, 16#17};
+dec_huffman_lookup(16#dc, 16#3) -> {ok, 16#fa, 16#28};
+dec_huffman_lookup(16#dc, 16#4) -> {more, 16#fb, 16#02};
+dec_huffman_lookup(16#dc, 16#5) -> {more, 16#fb, 16#09};
+dec_huffman_lookup(16#dc, 16#6) -> {more, 16#fb, 16#17};
+dec_huffman_lookup(16#dc, 16#7) -> {ok, 16#fb, 16#28};
+dec_huffman_lookup(16#dc, 16#8) -> {more, 16#fc, 16#02};
+dec_huffman_lookup(16#dc, 16#9) -> {more, 16#fc, 16#09};
+dec_huffman_lookup(16#dc, 16#a) -> {more, 16#fc, 16#17};
+dec_huffman_lookup(16#dc, 16#b) -> {ok, 16#fc, 16#28};
+dec_huffman_lookup(16#dc, 16#c) -> {more, 16#fd, 16#02};
+dec_huffman_lookup(16#dc, 16#d) -> {more, 16#fd, 16#09};
+dec_huffman_lookup(16#dc, 16#e) -> {more, 16#fd, 16#17};
+dec_huffman_lookup(16#dc, 16#f) -> {ok, 16#fd, 16#28};
+dec_huffman_lookup(16#dd, 16#0) -> {more, 16#fa, 16#03};
+dec_huffman_lookup(16#dd, 16#1) -> {more, 16#fa, 16#06};
+dec_huffman_lookup(16#dd, 16#2) -> {more, 16#fa, 16#0a};
+dec_huffman_lookup(16#dd, 16#3) -> {more, 16#fa, 16#0f};
+dec_huffman_lookup(16#dd, 16#4) -> {more, 16#fa, 16#18};
+dec_huffman_lookup(16#dd, 16#5) -> {more, 16#fa, 16#1f};
+dec_huffman_lookup(16#dd, 16#6) -> {more, 16#fa, 16#29};
+dec_huffman_lookup(16#dd, 16#7) -> {ok, 16#fa, 16#38};
+dec_huffman_lookup(16#dd, 16#8) -> {more, 16#fb, 16#03};
+dec_huffman_lookup(16#dd, 16#9) -> {more, 16#fb, 16#06};
+dec_huffman_lookup(16#dd, 16#a) -> {more, 16#fb, 16#0a};
+dec_huffman_lookup(16#dd, 16#b) -> {more, 16#fb, 16#0f};
+dec_huffman_lookup(16#dd, 16#c) -> {more, 16#fb, 16#18};
+dec_huffman_lookup(16#dd, 16#d) -> {more, 16#fb, 16#1f};
+dec_huffman_lookup(16#dd, 16#e) -> {more, 16#fb, 16#29};
+dec_huffman_lookup(16#dd, 16#f) -> {ok, 16#fb, 16#38};
+dec_huffman_lookup(16#de, 16#0) -> {more, 16#fc, 16#03};
+dec_huffman_lookup(16#de, 16#1) -> {more, 16#fc, 16#06};
+dec_huffman_lookup(16#de, 16#2) -> {more, 16#fc, 16#0a};
+dec_huffman_lookup(16#de, 16#3) -> {more, 16#fc, 16#0f};
+dec_huffman_lookup(16#de, 16#4) -> {more, 16#fc, 16#18};
+dec_huffman_lookup(16#de, 16#5) -> {more, 16#fc, 16#1f};
+dec_huffman_lookup(16#de, 16#6) -> {more, 16#fc, 16#29};
+dec_huffman_lookup(16#de, 16#7) -> {ok, 16#fc, 16#38};
+dec_huffman_lookup(16#de, 16#8) -> {more, 16#fd, 16#03};
+dec_huffman_lookup(16#de, 16#9) -> {more, 16#fd, 16#06};
+dec_huffman_lookup(16#de, 16#a) -> {more, 16#fd, 16#0a};
+dec_huffman_lookup(16#de, 16#b) -> {more, 16#fd, 16#0f};
+dec_huffman_lookup(16#de, 16#c) -> {more, 16#fd, 16#18};
+dec_huffman_lookup(16#de, 16#d) -> {more, 16#fd, 16#1f};
+dec_huffman_lookup(16#de, 16#e) -> {more, 16#fd, 16#29};
+dec_huffman_lookup(16#de, 16#f) -> {ok, 16#fd, 16#38};
+dec_huffman_lookup(16#df, 16#0) -> {ok, 16#fe, 16#00};
+dec_huffman_lookup(16#df, 16#1) -> {more, undefined, 16#e3};
+dec_huffman_lookup(16#df, 16#2) -> {more, undefined, 16#e5};
+dec_huffman_lookup(16#df, 16#3) -> {more, undefined, 16#e6};
+dec_huffman_lookup(16#df, 16#4) -> {more, undefined, 16#e9};
+dec_huffman_lookup(16#df, 16#5) -> {more, undefined, 16#ea};
+dec_huffman_lookup(16#df, 16#6) -> {more, undefined, 16#ec};
+dec_huffman_lookup(16#df, 16#7) -> {more, undefined, 16#ed};
+dec_huffman_lookup(16#df, 16#8) -> {more, undefined, 16#f1};
+dec_huffman_lookup(16#df, 16#9) -> {more, undefined, 16#f2};
+dec_huffman_lookup(16#df, 16#a) -> {more, undefined, 16#f4};
+dec_huffman_lookup(16#df, 16#b) -> {more, undefined, 16#f5};
+dec_huffman_lookup(16#df, 16#c) -> {more, undefined, 16#f8};
+dec_huffman_lookup(16#df, 16#d) -> {more, undefined, 16#f9};
+dec_huffman_lookup(16#df, 16#e) -> {more, undefined, 16#fb};
+dec_huffman_lookup(16#df, 16#f) -> {ok, undefined, 16#fc};
+dec_huffman_lookup(16#e0, 16#0) -> {more, 16#fe, 16#01};
+dec_huffman_lookup(16#e0, 16#1) -> {ok, 16#fe, 16#16};
+dec_huffman_lookup(16#e0, 16#2) -> {ok, 16#02, 16#00};
+dec_huffman_lookup(16#e0, 16#3) -> {ok, 16#03, 16#00};
+dec_huffman_lookup(16#e0, 16#4) -> {ok, 16#04, 16#00};
+dec_huffman_lookup(16#e0, 16#5) -> {ok, 16#05, 16#00};
+dec_huffman_lookup(16#e0, 16#6) -> {ok, 16#06, 16#00};
+dec_huffman_lookup(16#e0, 16#7) -> {ok, 16#07, 16#00};
+dec_huffman_lookup(16#e0, 16#8) -> {ok, 16#08, 16#00};
+dec_huffman_lookup(16#e0, 16#9) -> {ok, 16#0b, 16#00};
+dec_huffman_lookup(16#e0, 16#a) -> {ok, 16#0c, 16#00};
+dec_huffman_lookup(16#e0, 16#b) -> {ok, 16#0e, 16#00};
+dec_huffman_lookup(16#e0, 16#c) -> {ok, 16#0f, 16#00};
+dec_huffman_lookup(16#e0, 16#d) -> {ok, 16#10, 16#00};
+dec_huffman_lookup(16#e0, 16#e) -> {ok, 16#11, 16#00};
+dec_huffman_lookup(16#e0, 16#f) -> {ok, 16#12, 16#00};
+dec_huffman_lookup(16#e1, 16#0) -> {more, 16#fe, 16#02};
+dec_huffman_lookup(16#e1, 16#1) -> {more, 16#fe, 16#09};
+dec_huffman_lookup(16#e1, 16#2) -> {more, 16#fe, 16#17};
+dec_huffman_lookup(16#e1, 16#3) -> {ok, 16#fe, 16#28};
+dec_huffman_lookup(16#e1, 16#4) -> {more, 16#02, 16#01};
+dec_huffman_lookup(16#e1, 16#5) -> {ok, 16#02, 16#16};
+dec_huffman_lookup(16#e1, 16#6) -> {more, 16#03, 16#01};
+dec_huffman_lookup(16#e1, 16#7) -> {ok, 16#03, 16#16};
+dec_huffman_lookup(16#e1, 16#8) -> {more, 16#04, 16#01};
+dec_huffman_lookup(16#e1, 16#9) -> {ok, 16#04, 16#16};
+dec_huffman_lookup(16#e1, 16#a) -> {more, 16#05, 16#01};
+dec_huffman_lookup(16#e1, 16#b) -> {ok, 16#05, 16#16};
+dec_huffman_lookup(16#e1, 16#c) -> {more, 16#06, 16#01};
+dec_huffman_lookup(16#e1, 16#d) -> {ok, 16#06, 16#16};
+dec_huffman_lookup(16#e1, 16#e) -> {more, 16#07, 16#01};
+dec_huffman_lookup(16#e1, 16#f) -> {ok, 16#07, 16#16};
+dec_huffman_lookup(16#e2, 16#0) -> {more, 16#fe, 16#03};
+dec_huffman_lookup(16#e2, 16#1) -> {more, 16#fe, 16#06};
+dec_huffman_lookup(16#e2, 16#2) -> {more, 16#fe, 16#0a};
+dec_huffman_lookup(16#e2, 16#3) -> {more, 16#fe, 16#0f};
+dec_huffman_lookup(16#e2, 16#4) -> {more, 16#fe, 16#18};
+dec_huffman_lookup(16#e2, 16#5) -> {more, 16#fe, 16#1f};
+dec_huffman_lookup(16#e2, 16#6) -> {more, 16#fe, 16#29};
+dec_huffman_lookup(16#e2, 16#7) -> {ok, 16#fe, 16#38};
+dec_huffman_lookup(16#e2, 16#8) -> {more, 16#02, 16#02};
+dec_huffman_lookup(16#e2, 16#9) -> {more, 16#02, 16#09};
+dec_huffman_lookup(16#e2, 16#a) -> {more, 16#02, 16#17};
+dec_huffman_lookup(16#e2, 16#b) -> {ok, 16#02, 16#28};
+dec_huffman_lookup(16#e2, 16#c) -> {more, 16#03, 16#02};
+dec_huffman_lookup(16#e2, 16#d) -> {more, 16#03, 16#09};
+dec_huffman_lookup(16#e2, 16#e) -> {more, 16#03, 16#17};
+dec_huffman_lookup(16#e2, 16#f) -> {ok, 16#03, 16#28};
+dec_huffman_lookup(16#e3, 16#0) -> {more, 16#02, 16#03};
+dec_huffman_lookup(16#e3, 16#1) -> {more, 16#02, 16#06};
+dec_huffman_lookup(16#e3, 16#2) -> {more, 16#02, 16#0a};
+dec_huffman_lookup(16#e3, 16#3) -> {more, 16#02, 16#0f};
+dec_huffman_lookup(16#e3, 16#4) -> {more, 16#02, 16#18};
+dec_huffman_lookup(16#e3, 16#5) -> {more, 16#02, 16#1f};
+dec_huffman_lookup(16#e3, 16#6) -> {more, 16#02, 16#29};
+dec_huffman_lookup(16#e3, 16#7) -> {ok, 16#02, 16#38};
+dec_huffman_lookup(16#e3, 16#8) -> {more, 16#03, 16#03};
+dec_huffman_lookup(16#e3, 16#9) -> {more, 16#03, 16#06};
+dec_huffman_lookup(16#e3, 16#a) -> {more, 16#03, 16#0a};
+dec_huffman_lookup(16#e3, 16#b) -> {more, 16#03, 16#0f};
+dec_huffman_lookup(16#e3, 16#c) -> {more, 16#03, 16#18};
+dec_huffman_lookup(16#e3, 16#d) -> {more, 16#03, 16#1f};
+dec_huffman_lookup(16#e3, 16#e) -> {more, 16#03, 16#29};
+dec_huffman_lookup(16#e3, 16#f) -> {ok, 16#03, 16#38};
+dec_huffman_lookup(16#e4, 16#0) -> {more, 16#04, 16#02};
+dec_huffman_lookup(16#e4, 16#1) -> {more, 16#04, 16#09};
+dec_huffman_lookup(16#e4, 16#2) -> {more, 16#04, 16#17};
+dec_huffman_lookup(16#e4, 16#3) -> {ok, 16#04, 16#28};
+dec_huffman_lookup(16#e4, 16#4) -> {more, 16#05, 16#02};
+dec_huffman_lookup(16#e4, 16#5) -> {more, 16#05, 16#09};
+dec_huffman_lookup(16#e4, 16#6) -> {more, 16#05, 16#17};
+dec_huffman_lookup(16#e4, 16#7) -> {ok, 16#05, 16#28};
+dec_huffman_lookup(16#e4, 16#8) -> {more, 16#06, 16#02};
+dec_huffman_lookup(16#e4, 16#9) -> {more, 16#06, 16#09};
+dec_huffman_lookup(16#e4, 16#a) -> {more, 16#06, 16#17};
+dec_huffman_lookup(16#e4, 16#b) -> {ok, 16#06, 16#28};
+dec_huffman_lookup(16#e4, 16#c) -> {more, 16#07, 16#02};
+dec_huffman_lookup(16#e4, 16#d) -> {more, 16#07, 16#09};
+dec_huffman_lookup(16#e4, 16#e) -> {more, 16#07, 16#17};
+dec_huffman_lookup(16#e4, 16#f) -> {ok, 16#07, 16#28};
+dec_huffman_lookup(16#e5, 16#0) -> {more, 16#04, 16#03};
+dec_huffman_lookup(16#e5, 16#1) -> {more, 16#04, 16#06};
+dec_huffman_lookup(16#e5, 16#2) -> {more, 16#04, 16#0a};
+dec_huffman_lookup(16#e5, 16#3) -> {more, 16#04, 16#0f};
+dec_huffman_lookup(16#e5, 16#4) -> {more, 16#04, 16#18};
+dec_huffman_lookup(16#e5, 16#5) -> {more, 16#04, 16#1f};
+dec_huffman_lookup(16#e5, 16#6) -> {more, 16#04, 16#29};
+dec_huffman_lookup(16#e5, 16#7) -> {ok, 16#04, 16#38};
+dec_huffman_lookup(16#e5, 16#8) -> {more, 16#05, 16#03};
+dec_huffman_lookup(16#e5, 16#9) -> {more, 16#05, 16#06};
+dec_huffman_lookup(16#e5, 16#a) -> {more, 16#05, 16#0a};
+dec_huffman_lookup(16#e5, 16#b) -> {more, 16#05, 16#0f};
+dec_huffman_lookup(16#e5, 16#c) -> {more, 16#05, 16#18};
+dec_huffman_lookup(16#e5, 16#d) -> {more, 16#05, 16#1f};
+dec_huffman_lookup(16#e5, 16#e) -> {more, 16#05, 16#29};
+dec_huffman_lookup(16#e5, 16#f) -> {ok, 16#05, 16#38};
+dec_huffman_lookup(16#e6, 16#0) -> {more, 16#06, 16#03};
+dec_huffman_lookup(16#e6, 16#1) -> {more, 16#06, 16#06};
+dec_huffman_lookup(16#e6, 16#2) -> {more, 16#06, 16#0a};
+dec_huffman_lookup(16#e6, 16#3) -> {more, 16#06, 16#0f};
+dec_huffman_lookup(16#e6, 16#4) -> {more, 16#06, 16#18};
+dec_huffman_lookup(16#e6, 16#5) -> {more, 16#06, 16#1f};
+dec_huffman_lookup(16#e6, 16#6) -> {more, 16#06, 16#29};
+dec_huffman_lookup(16#e6, 16#7) -> {ok, 16#06, 16#38};
+dec_huffman_lookup(16#e6, 16#8) -> {more, 16#07, 16#03};
+dec_huffman_lookup(16#e6, 16#9) -> {more, 16#07, 16#06};
+dec_huffman_lookup(16#e6, 16#a) -> {more, 16#07, 16#0a};
+dec_huffman_lookup(16#e6, 16#b) -> {more, 16#07, 16#0f};
+dec_huffman_lookup(16#e6, 16#c) -> {more, 16#07, 16#18};
+dec_huffman_lookup(16#e6, 16#d) -> {more, 16#07, 16#1f};
+dec_huffman_lookup(16#e6, 16#e) -> {more, 16#07, 16#29};
+dec_huffman_lookup(16#e6, 16#f) -> {ok, 16#07, 16#38};
+dec_huffman_lookup(16#e7, 16#0) -> {more, 16#08, 16#01};
+dec_huffman_lookup(16#e7, 16#1) -> {ok, 16#08, 16#16};
+dec_huffman_lookup(16#e7, 16#2) -> {more, 16#0b, 16#01};
+dec_huffman_lookup(16#e7, 16#3) -> {ok, 16#0b, 16#16};
+dec_huffman_lookup(16#e7, 16#4) -> {more, 16#0c, 16#01};
+dec_huffman_lookup(16#e7, 16#5) -> {ok, 16#0c, 16#16};
+dec_huffman_lookup(16#e7, 16#6) -> {more, 16#0e, 16#01};
+dec_huffman_lookup(16#e7, 16#7) -> {ok, 16#0e, 16#16};
+dec_huffman_lookup(16#e7, 16#8) -> {more, 16#0f, 16#01};
+dec_huffman_lookup(16#e7, 16#9) -> {ok, 16#0f, 16#16};
+dec_huffman_lookup(16#e7, 16#a) -> {more, 16#10, 16#01};
+dec_huffman_lookup(16#e7, 16#b) -> {ok, 16#10, 16#16};
+dec_huffman_lookup(16#e7, 16#c) -> {more, 16#11, 16#01};
+dec_huffman_lookup(16#e7, 16#d) -> {ok, 16#11, 16#16};
+dec_huffman_lookup(16#e7, 16#e) -> {more, 16#12, 16#01};
+dec_huffman_lookup(16#e7, 16#f) -> {ok, 16#12, 16#16};
+dec_huffman_lookup(16#e8, 16#0) -> {more, 16#08, 16#02};
+dec_huffman_lookup(16#e8, 16#1) -> {more, 16#08, 16#09};
+dec_huffman_lookup(16#e8, 16#2) -> {more, 16#08, 16#17};
+dec_huffman_lookup(16#e8, 16#3) -> {ok, 16#08, 16#28};
+dec_huffman_lookup(16#e8, 16#4) -> {more, 16#0b, 16#02};
+dec_huffman_lookup(16#e8, 16#5) -> {more, 16#0b, 16#09};
+dec_huffman_lookup(16#e8, 16#6) -> {more, 16#0b, 16#17};
+dec_huffman_lookup(16#e8, 16#7) -> {ok, 16#0b, 16#28};
+dec_huffman_lookup(16#e8, 16#8) -> {more, 16#0c, 16#02};
+dec_huffman_lookup(16#e8, 16#9) -> {more, 16#0c, 16#09};
+dec_huffman_lookup(16#e8, 16#a) -> {more, 16#0c, 16#17};
+dec_huffman_lookup(16#e8, 16#b) -> {ok, 16#0c, 16#28};
+dec_huffman_lookup(16#e8, 16#c) -> {more, 16#0e, 16#02};
+dec_huffman_lookup(16#e8, 16#d) -> {more, 16#0e, 16#09};
+dec_huffman_lookup(16#e8, 16#e) -> {more, 16#0e, 16#17};
+dec_huffman_lookup(16#e8, 16#f) -> {ok, 16#0e, 16#28};
+dec_huffman_lookup(16#e9, 16#0) -> {more, 16#08, 16#03};
+dec_huffman_lookup(16#e9, 16#1) -> {more, 16#08, 16#06};
+dec_huffman_lookup(16#e9, 16#2) -> {more, 16#08, 16#0a};
+dec_huffman_lookup(16#e9, 16#3) -> {more, 16#08, 16#0f};
+dec_huffman_lookup(16#e9, 16#4) -> {more, 16#08, 16#18};
+dec_huffman_lookup(16#e9, 16#5) -> {more, 16#08, 16#1f};
+dec_huffman_lookup(16#e9, 16#6) -> {more, 16#08, 16#29};
+dec_huffman_lookup(16#e9, 16#7) -> {ok, 16#08, 16#38};
+dec_huffman_lookup(16#e9, 16#8) -> {more, 16#0b, 16#03};
+dec_huffman_lookup(16#e9, 16#9) -> {more, 16#0b, 16#06};
+dec_huffman_lookup(16#e9, 16#a) -> {more, 16#0b, 16#0a};
+dec_huffman_lookup(16#e9, 16#b) -> {more, 16#0b, 16#0f};
+dec_huffman_lookup(16#e9, 16#c) -> {more, 16#0b, 16#18};
+dec_huffman_lookup(16#e9, 16#d) -> {more, 16#0b, 16#1f};
+dec_huffman_lookup(16#e9, 16#e) -> {more, 16#0b, 16#29};
+dec_huffman_lookup(16#e9, 16#f) -> {ok, 16#0b, 16#38};
+dec_huffman_lookup(16#ea, 16#0) -> {more, 16#0c, 16#03};
+dec_huffman_lookup(16#ea, 16#1) -> {more, 16#0c, 16#06};
+dec_huffman_lookup(16#ea, 16#2) -> {more, 16#0c, 16#0a};
+dec_huffman_lookup(16#ea, 16#3) -> {more, 16#0c, 16#0f};
+dec_huffman_lookup(16#ea, 16#4) -> {more, 16#0c, 16#18};
+dec_huffman_lookup(16#ea, 16#5) -> {more, 16#0c, 16#1f};
+dec_huffman_lookup(16#ea, 16#6) -> {more, 16#0c, 16#29};
+dec_huffman_lookup(16#ea, 16#7) -> {ok, 16#0c, 16#38};
+dec_huffman_lookup(16#ea, 16#8) -> {more, 16#0e, 16#03};
+dec_huffman_lookup(16#ea, 16#9) -> {more, 16#0e, 16#06};
+dec_huffman_lookup(16#ea, 16#a) -> {more, 16#0e, 16#0a};
+dec_huffman_lookup(16#ea, 16#b) -> {more, 16#0e, 16#0f};
+dec_huffman_lookup(16#ea, 16#c) -> {more, 16#0e, 16#18};
+dec_huffman_lookup(16#ea, 16#d) -> {more, 16#0e, 16#1f};
+dec_huffman_lookup(16#ea, 16#e) -> {more, 16#0e, 16#29};
+dec_huffman_lookup(16#ea, 16#f) -> {ok, 16#0e, 16#38};
+dec_huffman_lookup(16#eb, 16#0) -> {more, 16#0f, 16#02};
+dec_huffman_lookup(16#eb, 16#1) -> {more, 16#0f, 16#09};
+dec_huffman_lookup(16#eb, 16#2) -> {more, 16#0f, 16#17};
+dec_huffman_lookup(16#eb, 16#3) -> {ok, 16#0f, 16#28};
+dec_huffman_lookup(16#eb, 16#4) -> {more, 16#10, 16#02};
+dec_huffman_lookup(16#eb, 16#5) -> {more, 16#10, 16#09};
+dec_huffman_lookup(16#eb, 16#6) -> {more, 16#10, 16#17};
+dec_huffman_lookup(16#eb, 16#7) -> {ok, 16#10, 16#28};
+dec_huffman_lookup(16#eb, 16#8) -> {more, 16#11, 16#02};
+dec_huffman_lookup(16#eb, 16#9) -> {more, 16#11, 16#09};
+dec_huffman_lookup(16#eb, 16#a) -> {more, 16#11, 16#17};
+dec_huffman_lookup(16#eb, 16#b) -> {ok, 16#11, 16#28};
+dec_huffman_lookup(16#eb, 16#c) -> {more, 16#12, 16#02};
+dec_huffman_lookup(16#eb, 16#d) -> {more, 16#12, 16#09};
+dec_huffman_lookup(16#eb, 16#e) -> {more, 16#12, 16#17};
+dec_huffman_lookup(16#eb, 16#f) -> {ok, 16#12, 16#28};
+dec_huffman_lookup(16#ec, 16#0) -> {more, 16#0f, 16#03};
+dec_huffman_lookup(16#ec, 16#1) -> {more, 16#0f, 16#06};
+dec_huffman_lookup(16#ec, 16#2) -> {more, 16#0f, 16#0a};
+dec_huffman_lookup(16#ec, 16#3) -> {more, 16#0f, 16#0f};
+dec_huffman_lookup(16#ec, 16#4) -> {more, 16#0f, 16#18};
+dec_huffman_lookup(16#ec, 16#5) -> {more, 16#0f, 16#1f};
+dec_huffman_lookup(16#ec, 16#6) -> {more, 16#0f, 16#29};
+dec_huffman_lookup(16#ec, 16#7) -> {ok, 16#0f, 16#38};
+dec_huffman_lookup(16#ec, 16#8) -> {more, 16#10, 16#03};
+dec_huffman_lookup(16#ec, 16#9) -> {more, 16#10, 16#06};
+dec_huffman_lookup(16#ec, 16#a) -> {more, 16#10, 16#0a};
+dec_huffman_lookup(16#ec, 16#b) -> {more, 16#10, 16#0f};
+dec_huffman_lookup(16#ec, 16#c) -> {more, 16#10, 16#18};
+dec_huffman_lookup(16#ec, 16#d) -> {more, 16#10, 16#1f};
+dec_huffman_lookup(16#ec, 16#e) -> {more, 16#10, 16#29};
+dec_huffman_lookup(16#ec, 16#f) -> {ok, 16#10, 16#38};
+dec_huffman_lookup(16#ed, 16#0) -> {more, 16#11, 16#03};
+dec_huffman_lookup(16#ed, 16#1) -> {more, 16#11, 16#06};
+dec_huffman_lookup(16#ed, 16#2) -> {more, 16#11, 16#0a};
+dec_huffman_lookup(16#ed, 16#3) -> {more, 16#11, 16#0f};
+dec_huffman_lookup(16#ed, 16#4) -> {more, 16#11, 16#18};
+dec_huffman_lookup(16#ed, 16#5) -> {more, 16#11, 16#1f};
+dec_huffman_lookup(16#ed, 16#6) -> {more, 16#11, 16#29};
+dec_huffman_lookup(16#ed, 16#7) -> {ok, 16#11, 16#38};
+dec_huffman_lookup(16#ed, 16#8) -> {more, 16#12, 16#03};
+dec_huffman_lookup(16#ed, 16#9) -> {more, 16#12, 16#06};
+dec_huffman_lookup(16#ed, 16#a) -> {more, 16#12, 16#0a};
+dec_huffman_lookup(16#ed, 16#b) -> {more, 16#12, 16#0f};
+dec_huffman_lookup(16#ed, 16#c) -> {more, 16#12, 16#18};
+dec_huffman_lookup(16#ed, 16#d) -> {more, 16#12, 16#1f};
+dec_huffman_lookup(16#ed, 16#e) -> {more, 16#12, 16#29};
+dec_huffman_lookup(16#ed, 16#f) -> {ok, 16#12, 16#38};
+dec_huffman_lookup(16#ee, 16#0) -> {ok, 16#13, 16#00};
+dec_huffman_lookup(16#ee, 16#1) -> {ok, 16#14, 16#00};
+dec_huffman_lookup(16#ee, 16#2) -> {ok, 16#15, 16#00};
+dec_huffman_lookup(16#ee, 16#3) -> {ok, 16#17, 16#00};
+dec_huffman_lookup(16#ee, 16#4) -> {ok, 16#18, 16#00};
+dec_huffman_lookup(16#ee, 16#5) -> {ok, 16#19, 16#00};
+dec_huffman_lookup(16#ee, 16#6) -> {ok, 16#1a, 16#00};
+dec_huffman_lookup(16#ee, 16#7) -> {ok, 16#1b, 16#00};
+dec_huffman_lookup(16#ee, 16#8) -> {ok, 16#1c, 16#00};
+dec_huffman_lookup(16#ee, 16#9) -> {ok, 16#1d, 16#00};
+dec_huffman_lookup(16#ee, 16#a) -> {ok, 16#1e, 16#00};
+dec_huffman_lookup(16#ee, 16#b) -> {ok, 16#1f, 16#00};
+dec_huffman_lookup(16#ee, 16#c) -> {ok, 16#7f, 16#00};
+dec_huffman_lookup(16#ee, 16#d) -> {ok, 16#dc, 16#00};
+dec_huffman_lookup(16#ee, 16#e) -> {ok, 16#f9, 16#00};
+dec_huffman_lookup(16#ee, 16#f) -> {ok, undefined, 16#fd};
+dec_huffman_lookup(16#ef, 16#0) -> {more, 16#13, 16#01};
+dec_huffman_lookup(16#ef, 16#1) -> {ok, 16#13, 16#16};
+dec_huffman_lookup(16#ef, 16#2) -> {more, 16#14, 16#01};
+dec_huffman_lookup(16#ef, 16#3) -> {ok, 16#14, 16#16};
+dec_huffman_lookup(16#ef, 16#4) -> {more, 16#15, 16#01};
+dec_huffman_lookup(16#ef, 16#5) -> {ok, 16#15, 16#16};
+dec_huffman_lookup(16#ef, 16#6) -> {more, 16#17, 16#01};
+dec_huffman_lookup(16#ef, 16#7) -> {ok, 16#17, 16#16};
+dec_huffman_lookup(16#ef, 16#8) -> {more, 16#18, 16#01};
+dec_huffman_lookup(16#ef, 16#9) -> {ok, 16#18, 16#16};
+dec_huffman_lookup(16#ef, 16#a) -> {more, 16#19, 16#01};
+dec_huffman_lookup(16#ef, 16#b) -> {ok, 16#19, 16#16};
+dec_huffman_lookup(16#ef, 16#c) -> {more, 16#1a, 16#01};
+dec_huffman_lookup(16#ef, 16#d) -> {ok, 16#1a, 16#16};
+dec_huffman_lookup(16#ef, 16#e) -> {more, 16#1b, 16#01};
+dec_huffman_lookup(16#ef, 16#f) -> {ok, 16#1b, 16#16};
+dec_huffman_lookup(16#f0, 16#0) -> {more, 16#13, 16#02};
+dec_huffman_lookup(16#f0, 16#1) -> {more, 16#13, 16#09};
+dec_huffman_lookup(16#f0, 16#2) -> {more, 16#13, 16#17};
+dec_huffman_lookup(16#f0, 16#3) -> {ok, 16#13, 16#28};
+dec_huffman_lookup(16#f0, 16#4) -> {more, 16#14, 16#02};
+dec_huffman_lookup(16#f0, 16#5) -> {more, 16#14, 16#09};
+dec_huffman_lookup(16#f0, 16#6) -> {more, 16#14, 16#17};
+dec_huffman_lookup(16#f0, 16#7) -> {ok, 16#14, 16#28};
+dec_huffman_lookup(16#f0, 16#8) -> {more, 16#15, 16#02};
+dec_huffman_lookup(16#f0, 16#9) -> {more, 16#15, 16#09};
+dec_huffman_lookup(16#f0, 16#a) -> {more, 16#15, 16#17};
+dec_huffman_lookup(16#f0, 16#b) -> {ok, 16#15, 16#28};
+dec_huffman_lookup(16#f0, 16#c) -> {more, 16#17, 16#02};
+dec_huffman_lookup(16#f0, 16#d) -> {more, 16#17, 16#09};
+dec_huffman_lookup(16#f0, 16#e) -> {more, 16#17, 16#17};
+dec_huffman_lookup(16#f0, 16#f) -> {ok, 16#17, 16#28};
+dec_huffman_lookup(16#f1, 16#0) -> {more, 16#13, 16#03};
+dec_huffman_lookup(16#f1, 16#1) -> {more, 16#13, 16#06};
+dec_huffman_lookup(16#f1, 16#2) -> {more, 16#13, 16#0a};
+dec_huffman_lookup(16#f1, 16#3) -> {more, 16#13, 16#0f};
+dec_huffman_lookup(16#f1, 16#4) -> {more, 16#13, 16#18};
+dec_huffman_lookup(16#f1, 16#5) -> {more, 16#13, 16#1f};
+dec_huffman_lookup(16#f1, 16#6) -> {more, 16#13, 16#29};
+dec_huffman_lookup(16#f1, 16#7) -> {ok, 16#13, 16#38};
+dec_huffman_lookup(16#f1, 16#8) -> {more, 16#14, 16#03};
+dec_huffman_lookup(16#f1, 16#9) -> {more, 16#14, 16#06};
+dec_huffman_lookup(16#f1, 16#a) -> {more, 16#14, 16#0a};
+dec_huffman_lookup(16#f1, 16#b) -> {more, 16#14, 16#0f};
+dec_huffman_lookup(16#f1, 16#c) -> {more, 16#14, 16#18};
+dec_huffman_lookup(16#f1, 16#d) -> {more, 16#14, 16#1f};
+dec_huffman_lookup(16#f1, 16#e) -> {more, 16#14, 16#29};
+dec_huffman_lookup(16#f1, 16#f) -> {ok, 16#14, 16#38};
+dec_huffman_lookup(16#f2, 16#0) -> {more, 16#15, 16#03};
+dec_huffman_lookup(16#f2, 16#1) -> {more, 16#15, 16#06};
+dec_huffman_lookup(16#f2, 16#2) -> {more, 16#15, 16#0a};
+dec_huffman_lookup(16#f2, 16#3) -> {more, 16#15, 16#0f};
+dec_huffman_lookup(16#f2, 16#4) -> {more, 16#15, 16#18};
+dec_huffman_lookup(16#f2, 16#5) -> {more, 16#15, 16#1f};
+dec_huffman_lookup(16#f2, 16#6) -> {more, 16#15, 16#29};
+dec_huffman_lookup(16#f2, 16#7) -> {ok, 16#15, 16#38};
+dec_huffman_lookup(16#f2, 16#8) -> {more, 16#17, 16#03};
+dec_huffman_lookup(16#f2, 16#9) -> {more, 16#17, 16#06};
+dec_huffman_lookup(16#f2, 16#a) -> {more, 16#17, 16#0a};
+dec_huffman_lookup(16#f2, 16#b) -> {more, 16#17, 16#0f};
+dec_huffman_lookup(16#f2, 16#c) -> {more, 16#17, 16#18};
+dec_huffman_lookup(16#f2, 16#d) -> {more, 16#17, 16#1f};
+dec_huffman_lookup(16#f2, 16#e) -> {more, 16#17, 16#29};
+dec_huffman_lookup(16#f2, 16#f) -> {ok, 16#17, 16#38};
+dec_huffman_lookup(16#f3, 16#0) -> {more, 16#18, 16#02};
+dec_huffman_lookup(16#f3, 16#1) -> {more, 16#18, 16#09};
+dec_huffman_lookup(16#f3, 16#2) -> {more, 16#18, 16#17};
+dec_huffman_lookup(16#f3, 16#3) -> {ok, 16#18, 16#28};
+dec_huffman_lookup(16#f3, 16#4) -> {more, 16#19, 16#02};
+dec_huffman_lookup(16#f3, 16#5) -> {more, 16#19, 16#09};
+dec_huffman_lookup(16#f3, 16#6) -> {more, 16#19, 16#17};
+dec_huffman_lookup(16#f3, 16#7) -> {ok, 16#19, 16#28};
+dec_huffman_lookup(16#f3, 16#8) -> {more, 16#1a, 16#02};
+dec_huffman_lookup(16#f3, 16#9) -> {more, 16#1a, 16#09};
+dec_huffman_lookup(16#f3, 16#a) -> {more, 16#1a, 16#17};
+dec_huffman_lookup(16#f3, 16#b) -> {ok, 16#1a, 16#28};
+dec_huffman_lookup(16#f3, 16#c) -> {more, 16#1b, 16#02};
+dec_huffman_lookup(16#f3, 16#d) -> {more, 16#1b, 16#09};
+dec_huffman_lookup(16#f3, 16#e) -> {more, 16#1b, 16#17};
+dec_huffman_lookup(16#f3, 16#f) -> {ok, 16#1b, 16#28};
+dec_huffman_lookup(16#f4, 16#0) -> {more, 16#18, 16#03};
+dec_huffman_lookup(16#f4, 16#1) -> {more, 16#18, 16#06};
+dec_huffman_lookup(16#f4, 16#2) -> {more, 16#18, 16#0a};
+dec_huffman_lookup(16#f4, 16#3) -> {more, 16#18, 16#0f};
+dec_huffman_lookup(16#f4, 16#4) -> {more, 16#18, 16#18};
+dec_huffman_lookup(16#f4, 16#5) -> {more, 16#18, 16#1f};
+dec_huffman_lookup(16#f4, 16#6) -> {more, 16#18, 16#29};
+dec_huffman_lookup(16#f4, 16#7) -> {ok, 16#18, 16#38};
+dec_huffman_lookup(16#f4, 16#8) -> {more, 16#19, 16#03};
+dec_huffman_lookup(16#f4, 16#9) -> {more, 16#19, 16#06};
+dec_huffman_lookup(16#f4, 16#a) -> {more, 16#19, 16#0a};
+dec_huffman_lookup(16#f4, 16#b) -> {more, 16#19, 16#0f};
+dec_huffman_lookup(16#f4, 16#c) -> {more, 16#19, 16#18};
+dec_huffman_lookup(16#f4, 16#d) -> {more, 16#19, 16#1f};
+dec_huffman_lookup(16#f4, 16#e) -> {more, 16#19, 16#29};
+dec_huffman_lookup(16#f4, 16#f) -> {ok, 16#19, 16#38};
+dec_huffman_lookup(16#f5, 16#0) -> {more, 16#1a, 16#03};
+dec_huffman_lookup(16#f5, 16#1) -> {more, 16#1a, 16#06};
+dec_huffman_lookup(16#f5, 16#2) -> {more, 16#1a, 16#0a};
+dec_huffman_lookup(16#f5, 16#3) -> {more, 16#1a, 16#0f};
+dec_huffman_lookup(16#f5, 16#4) -> {more, 16#1a, 16#18};
+dec_huffman_lookup(16#f5, 16#5) -> {more, 16#1a, 16#1f};
+dec_huffman_lookup(16#f5, 16#6) -> {more, 16#1a, 16#29};
+dec_huffman_lookup(16#f5, 16#7) -> {ok, 16#1a, 16#38};
+dec_huffman_lookup(16#f5, 16#8) -> {more, 16#1b, 16#03};
+dec_huffman_lookup(16#f5, 16#9) -> {more, 16#1b, 16#06};
+dec_huffman_lookup(16#f5, 16#a) -> {more, 16#1b, 16#0a};
+dec_huffman_lookup(16#f5, 16#b) -> {more, 16#1b, 16#0f};
+dec_huffman_lookup(16#f5, 16#c) -> {more, 16#1b, 16#18};
+dec_huffman_lookup(16#f5, 16#d) -> {more, 16#1b, 16#1f};
+dec_huffman_lookup(16#f5, 16#e) -> {more, 16#1b, 16#29};
+dec_huffman_lookup(16#f5, 16#f) -> {ok, 16#1b, 16#38};
+dec_huffman_lookup(16#f6, 16#0) -> {more, 16#1c, 16#01};
+dec_huffman_lookup(16#f6, 16#1) -> {ok, 16#1c, 16#16};
+dec_huffman_lookup(16#f6, 16#2) -> {more, 16#1d, 16#01};
+dec_huffman_lookup(16#f6, 16#3) -> {ok, 16#1d, 16#16};
+dec_huffman_lookup(16#f6, 16#4) -> {more, 16#1e, 16#01};
+dec_huffman_lookup(16#f6, 16#5) -> {ok, 16#1e, 16#16};
+dec_huffman_lookup(16#f6, 16#6) -> {more, 16#1f, 16#01};
+dec_huffman_lookup(16#f6, 16#7) -> {ok, 16#1f, 16#16};
+dec_huffman_lookup(16#f6, 16#8) -> {more, 16#7f, 16#01};
+dec_huffman_lookup(16#f6, 16#9) -> {ok, 16#7f, 16#16};
+dec_huffman_lookup(16#f6, 16#a) -> {more, 16#dc, 16#01};
+dec_huffman_lookup(16#f6, 16#b) -> {ok, 16#dc, 16#16};
+dec_huffman_lookup(16#f6, 16#c) -> {more, 16#f9, 16#01};
+dec_huffman_lookup(16#f6, 16#d) -> {ok, 16#f9, 16#16};
+dec_huffman_lookup(16#f6, 16#e) -> {more, undefined, 16#fe};
+dec_huffman_lookup(16#f6, 16#f) -> {ok, undefined, 16#ff};
+dec_huffman_lookup(16#f7, 16#0) -> {more, 16#1c, 16#02};
+dec_huffman_lookup(16#f7, 16#1) -> {more, 16#1c, 16#09};
+dec_huffman_lookup(16#f7, 16#2) -> {more, 16#1c, 16#17};
+dec_huffman_lookup(16#f7, 16#3) -> {ok, 16#1c, 16#28};
+dec_huffman_lookup(16#f7, 16#4) -> {more, 16#1d, 16#02};
+dec_huffman_lookup(16#f7, 16#5) -> {more, 16#1d, 16#09};
+dec_huffman_lookup(16#f7, 16#6) -> {more, 16#1d, 16#17};
+dec_huffman_lookup(16#f7, 16#7) -> {ok, 16#1d, 16#28};
+dec_huffman_lookup(16#f7, 16#8) -> {more, 16#1e, 16#02};
+dec_huffman_lookup(16#f7, 16#9) -> {more, 16#1e, 16#09};
+dec_huffman_lookup(16#f7, 16#a) -> {more, 16#1e, 16#17};
+dec_huffman_lookup(16#f7, 16#b) -> {ok, 16#1e, 16#28};
+dec_huffman_lookup(16#f7, 16#c) -> {more, 16#1f, 16#02};
+dec_huffman_lookup(16#f7, 16#d) -> {more, 16#1f, 16#09};
+dec_huffman_lookup(16#f7, 16#e) -> {more, 16#1f, 16#17};
+dec_huffman_lookup(16#f7, 16#f) -> {ok, 16#1f, 16#28};
+dec_huffman_lookup(16#f8, 16#0) -> {more, 16#1c, 16#03};
+dec_huffman_lookup(16#f8, 16#1) -> {more, 16#1c, 16#06};
+dec_huffman_lookup(16#f8, 16#2) -> {more, 16#1c, 16#0a};
+dec_huffman_lookup(16#f8, 16#3) -> {more, 16#1c, 16#0f};
+dec_huffman_lookup(16#f8, 16#4) -> {more, 16#1c, 16#18};
+dec_huffman_lookup(16#f8, 16#5) -> {more, 16#1c, 16#1f};
+dec_huffman_lookup(16#f8, 16#6) -> {more, 16#1c, 16#29};
+dec_huffman_lookup(16#f8, 16#7) -> {ok, 16#1c, 16#38};
+dec_huffman_lookup(16#f8, 16#8) -> {more, 16#1d, 16#03};
+dec_huffman_lookup(16#f8, 16#9) -> {more, 16#1d, 16#06};
+dec_huffman_lookup(16#f8, 16#a) -> {more, 16#1d, 16#0a};
+dec_huffman_lookup(16#f8, 16#b) -> {more, 16#1d, 16#0f};
+dec_huffman_lookup(16#f8, 16#c) -> {more, 16#1d, 16#18};
+dec_huffman_lookup(16#f8, 16#d) -> {more, 16#1d, 16#1f};
+dec_huffman_lookup(16#f8, 16#e) -> {more, 16#1d, 16#29};
+dec_huffman_lookup(16#f8, 16#f) -> {ok, 16#1d, 16#38};
+dec_huffman_lookup(16#f9, 16#0) -> {more, 16#1e, 16#03};
+dec_huffman_lookup(16#f9, 16#1) -> {more, 16#1e, 16#06};
+dec_huffman_lookup(16#f9, 16#2) -> {more, 16#1e, 16#0a};
+dec_huffman_lookup(16#f9, 16#3) -> {more, 16#1e, 16#0f};
+dec_huffman_lookup(16#f9, 16#4) -> {more, 16#1e, 16#18};
+dec_huffman_lookup(16#f9, 16#5) -> {more, 16#1e, 16#1f};
+dec_huffman_lookup(16#f9, 16#6) -> {more, 16#1e, 16#29};
+dec_huffman_lookup(16#f9, 16#7) -> {ok, 16#1e, 16#38};
+dec_huffman_lookup(16#f9, 16#8) -> {more, 16#1f, 16#03};
+dec_huffman_lookup(16#f9, 16#9) -> {more, 16#1f, 16#06};
+dec_huffman_lookup(16#f9, 16#a) -> {more, 16#1f, 16#0a};
+dec_huffman_lookup(16#f9, 16#b) -> {more, 16#1f, 16#0f};
+dec_huffman_lookup(16#f9, 16#c) -> {more, 16#1f, 16#18};
+dec_huffman_lookup(16#f9, 16#d) -> {more, 16#1f, 16#1f};
+dec_huffman_lookup(16#f9, 16#e) -> {more, 16#1f, 16#29};
+dec_huffman_lookup(16#f9, 16#f) -> {ok, 16#1f, 16#38};
+dec_huffman_lookup(16#fa, 16#0) -> {more, 16#7f, 16#02};
+dec_huffman_lookup(16#fa, 16#1) -> {more, 16#7f, 16#09};
+dec_huffman_lookup(16#fa, 16#2) -> {more, 16#7f, 16#17};
+dec_huffman_lookup(16#fa, 16#3) -> {ok, 16#7f, 16#28};
+dec_huffman_lookup(16#fa, 16#4) -> {more, 16#dc, 16#02};
+dec_huffman_lookup(16#fa, 16#5) -> {more, 16#dc, 16#09};
+dec_huffman_lookup(16#fa, 16#6) -> {more, 16#dc, 16#17};
+dec_huffman_lookup(16#fa, 16#7) -> {ok, 16#dc, 16#28};
+dec_huffman_lookup(16#fa, 16#8) -> {more, 16#f9, 16#02};
+dec_huffman_lookup(16#fa, 16#9) -> {more, 16#f9, 16#09};
+dec_huffman_lookup(16#fa, 16#a) -> {more, 16#f9, 16#17};
+dec_huffman_lookup(16#fa, 16#b) -> {ok, 16#f9, 16#28};
+dec_huffman_lookup(16#fa, 16#c) -> {ok, 16#0a, 16#00};
+dec_huffman_lookup(16#fa, 16#d) -> {ok, 16#0d, 16#00};
+dec_huffman_lookup(16#fa, 16#e) -> {ok, 16#16, 16#00};
+dec_huffman_lookup(16#fa, 16#f) -> error;
+dec_huffman_lookup(16#fb, 16#0) -> {more, 16#7f, 16#03};
+dec_huffman_lookup(16#fb, 16#1) -> {more, 16#7f, 16#06};
+dec_huffman_lookup(16#fb, 16#2) -> {more, 16#7f, 16#0a};
+dec_huffman_lookup(16#fb, 16#3) -> {more, 16#7f, 16#0f};
+dec_huffman_lookup(16#fb, 16#4) -> {more, 16#7f, 16#18};
+dec_huffman_lookup(16#fb, 16#5) -> {more, 16#7f, 16#1f};
+dec_huffman_lookup(16#fb, 16#6) -> {more, 16#7f, 16#29};
+dec_huffman_lookup(16#fb, 16#7) -> {ok, 16#7f, 16#38};
+dec_huffman_lookup(16#fb, 16#8) -> {more, 16#dc, 16#03};
+dec_huffman_lookup(16#fb, 16#9) -> {more, 16#dc, 16#06};
+dec_huffman_lookup(16#fb, 16#a) -> {more, 16#dc, 16#0a};
+dec_huffman_lookup(16#fb, 16#b) -> {more, 16#dc, 16#0f};
+dec_huffman_lookup(16#fb, 16#c) -> {more, 16#dc, 16#18};
+dec_huffman_lookup(16#fb, 16#d) -> {more, 16#dc, 16#1f};
+dec_huffman_lookup(16#fb, 16#e) -> {more, 16#dc, 16#29};
+dec_huffman_lookup(16#fb, 16#f) -> {ok, 16#dc, 16#38};
+dec_huffman_lookup(16#fc, 16#0) -> {more, 16#f9, 16#03};
+dec_huffman_lookup(16#fc, 16#1) -> {more, 16#f9, 16#06};
+dec_huffman_lookup(16#fc, 16#2) -> {more, 16#f9, 16#0a};
+dec_huffman_lookup(16#fc, 16#3) -> {more, 16#f9, 16#0f};
+dec_huffman_lookup(16#fc, 16#4) -> {more, 16#f9, 16#18};
+dec_huffman_lookup(16#fc, 16#5) -> {more, 16#f9, 16#1f};
+dec_huffman_lookup(16#fc, 16#6) -> {more, 16#f9, 16#29};
+dec_huffman_lookup(16#fc, 16#7) -> {ok, 16#f9, 16#38};
+dec_huffman_lookup(16#fc, 16#8) -> {more, 16#0a, 16#01};
+dec_huffman_lookup(16#fc, 16#9) -> {ok, 16#0a, 16#16};
+dec_huffman_lookup(16#fc, 16#a) -> {more, 16#0d, 16#01};
+dec_huffman_lookup(16#fc, 16#b) -> {ok, 16#0d, 16#16};
+dec_huffman_lookup(16#fc, 16#c) -> {more, 16#16, 16#01};
+dec_huffman_lookup(16#fc, 16#d) -> {ok, 16#16, 16#16};
+dec_huffman_lookup(16#fc, 16#e) -> error;
+dec_huffman_lookup(16#fc, 16#f) -> error;
+dec_huffman_lookup(16#fd, 16#0) -> {more, 16#0a, 16#02};
+dec_huffman_lookup(16#fd, 16#1) -> {more, 16#0a, 16#09};
+dec_huffman_lookup(16#fd, 16#2) -> {more, 16#0a, 16#17};
+dec_huffman_lookup(16#fd, 16#3) -> {ok, 16#0a, 16#28};
+dec_huffman_lookup(16#fd, 16#4) -> {more, 16#0d, 16#02};
+dec_huffman_lookup(16#fd, 16#5) -> {more, 16#0d, 16#09};
+dec_huffman_lookup(16#fd, 16#6) -> {more, 16#0d, 16#17};
+dec_huffman_lookup(16#fd, 16#7) -> {ok, 16#0d, 16#28};
+dec_huffman_lookup(16#fd, 16#8) -> {more, 16#16, 16#02};
+dec_huffman_lookup(16#fd, 16#9) -> {more, 16#16, 16#09};
+dec_huffman_lookup(16#fd, 16#a) -> {more, 16#16, 16#17};
+dec_huffman_lookup(16#fd, 16#b) -> {ok, 16#16, 16#28};
+dec_huffman_lookup(16#fd, 16#c) -> error;
+dec_huffman_lookup(16#fd, 16#d) -> error;
+dec_huffman_lookup(16#fd, 16#e) -> error;
+dec_huffman_lookup(16#fd, 16#f) -> error;
+dec_huffman_lookup(16#fe, 16#0) -> {more, 16#0a, 16#03};
+dec_huffman_lookup(16#fe, 16#1) -> {more, 16#0a, 16#06};
+dec_huffman_lookup(16#fe, 16#2) -> {more, 16#0a, 16#0a};
+dec_huffman_lookup(16#fe, 16#3) -> {more, 16#0a, 16#0f};
+dec_huffman_lookup(16#fe, 16#4) -> {more, 16#0a, 16#18};
+dec_huffman_lookup(16#fe, 16#5) -> {more, 16#0a, 16#1f};
+dec_huffman_lookup(16#fe, 16#6) -> {more, 16#0a, 16#29};
+dec_huffman_lookup(16#fe, 16#7) -> {ok, 16#0a, 16#38};
+dec_huffman_lookup(16#fe, 16#8) -> {more, 16#0d, 16#03};
+dec_huffman_lookup(16#fe, 16#9) -> {more, 16#0d, 16#06};
+dec_huffman_lookup(16#fe, 16#a) -> {more, 16#0d, 16#0a};
+dec_huffman_lookup(16#fe, 16#b) -> {more, 16#0d, 16#0f};
+dec_huffman_lookup(16#fe, 16#c) -> {more, 16#0d, 16#18};
+dec_huffman_lookup(16#fe, 16#d) -> {more, 16#0d, 16#1f};
+dec_huffman_lookup(16#fe, 16#e) -> {more, 16#0d, 16#29};
+dec_huffman_lookup(16#fe, 16#f) -> {ok, 16#0d, 16#38};
+dec_huffman_lookup(16#ff, 16#0) -> {more, 16#16, 16#03};
+dec_huffman_lookup(16#ff, 16#1) -> {more, 16#16, 16#06};
+dec_huffman_lookup(16#ff, 16#2) -> {more, 16#16, 16#0a};
+dec_huffman_lookup(16#ff, 16#3) -> {more, 16#16, 16#0f};
+dec_huffman_lookup(16#ff, 16#4) -> {more, 16#16, 16#18};
+dec_huffman_lookup(16#ff, 16#5) -> {more, 16#16, 16#1f};
+dec_huffman_lookup(16#ff, 16#6) -> {more, 16#16, 16#29};
+dec_huffman_lookup(16#ff, 16#7) -> {ok, 16#16, 16#38};
+dec_huffman_lookup(16#ff, 16#8) -> error;
+dec_huffman_lookup(16#ff, 16#9) -> error;
+dec_huffman_lookup(16#ff, 16#a) -> error;
+dec_huffman_lookup(16#ff, 16#b) -> error;
+dec_huffman_lookup(16#ff, 16#c) -> error;
+dec_huffman_lookup(16#ff, 16#d) -> error;
+dec_huffman_lookup(16#ff, 16#e) -> error;
+dec_huffman_lookup(16#ff, 16#f) -> error.
diff --git a/server/_build/default/lib/cowlib/src/cow_http.erl b/server/_build/default/lib/cowlib/src/cow_http.erl
new file mode 100644
index 0000000..93e9193
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_http.erl
@@ -0,0 +1,426 @@
+%% Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_http).
+
+-export([parse_request_line/1]).
+-export([parse_status_line/1]).
+-export([status_to_integer/1]).
+-export([parse_headers/1]).
+
+-export([parse_fullpath/1]).
+-export([parse_version/1]).
+
+-export([request/4]).
+-export([response/3]).
+-export([headers/1]).
+-export([version/1]).
+
+-type version() :: 'HTTP/1.0' | 'HTTP/1.1'.
+-export_type([version/0]).
+
+-type status() :: 100..999.
+-export_type([status/0]).
+
+-type headers() :: [{binary(), iodata()}].
+-export_type([headers/0]).
+
+-include("cow_inline.hrl").
+
+%% @doc Parse the request line.
+
+-spec parse_request_line(binary()) -> {binary(), binary(), version(), binary()}.
+parse_request_line(Data) ->
+ {Pos, _} = binary:match(Data, <<"\r">>),
+ <<RequestLine:Pos/binary, "\r\n", Rest/bits>> = Data,
+ [Method, Target, Version0] = binary:split(RequestLine, <<$\s>>, [trim_all, global]),
+ Version = case Version0 of
+ <<"HTTP/1.1">> -> 'HTTP/1.1';
+ <<"HTTP/1.0">> -> 'HTTP/1.0'
+ end,
+ {Method, Target, Version, Rest}.
+
+-ifdef(TEST).
+parse_request_line_test_() ->
+ Tests = [
+ {<<"GET /path HTTP/1.0\r\nRest">>,
+ {<<"GET">>, <<"/path">>, 'HTTP/1.0', <<"Rest">>}},
+ {<<"GET /path HTTP/1.1\r\nRest">>,
+ {<<"GET">>, <<"/path">>, 'HTTP/1.1', <<"Rest">>}},
+ {<<"CONNECT proxy.example.org:1080 HTTP/1.1\r\nRest">>,
+ {<<"CONNECT">>, <<"proxy.example.org:1080">>, 'HTTP/1.1', <<"Rest">>}}
+ ],
+ [{V, fun() -> R = parse_request_line(V) end}
+ || {V, R} <- Tests].
+
+parse_request_line_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"GET">>,
+ <<"GET /path\r\n">>,
+ <<"GET /path HTTP/1.1">>,
+ <<"GET /path HTTP/1.1\r">>,
+ <<"GET /path HTTP/1.1\n">>,
+ <<"GET /path HTTP/0.9\r\n">>,
+ <<"content-type: text/plain\r\n">>,
+ <<0:80, "\r\n">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_request_line(V)) end}
+ || V <- Tests].
+
+horse_parse_request_line_get_path() ->
+ horse:repeat(200000,
+ parse_request_line(<<"GET /path HTTP/1.1\r\n">>)
+ ).
+-endif.
+
+%% @doc Parse the status line.
+
+-spec parse_status_line(binary()) -> {version(), status(), binary(), binary()}.
+parse_status_line(<< "HTTP/1.1 200 OK\r\n", Rest/bits >>) ->
+ {'HTTP/1.1', 200, <<"OK">>, Rest};
+parse_status_line(<< "HTTP/1.1 404 Not Found\r\n", Rest/bits >>) ->
+ {'HTTP/1.1', 404, <<"Not Found">>, Rest};
+parse_status_line(<< "HTTP/1.1 500 Internal Server Error\r\n", Rest/bits >>) ->
+ {'HTTP/1.1', 500, <<"Internal Server Error">>, Rest};
+parse_status_line(<< "HTTP/1.1 ", Status/bits >>) ->
+ parse_status_line(Status, 'HTTP/1.1');
+parse_status_line(<< "HTTP/1.0 ", Status/bits >>) ->
+ parse_status_line(Status, 'HTTP/1.0').
+
+parse_status_line(<<H, T, U, " ", Rest/bits>>, Version) ->
+ Status = status_to_integer(H, T, U),
+ {Pos, _} = binary:match(Rest, <<"\r">>),
+ << StatusStr:Pos/binary, "\r\n", Rest2/bits >> = Rest,
+ {Version, Status, StatusStr, Rest2}.
+
+-spec status_to_integer(status() | binary()) -> status().
+status_to_integer(Status) when is_integer(Status) ->
+ Status;
+status_to_integer(Status) ->
+ case Status of
+ <<H, T, U>> ->
+ status_to_integer(H, T, U);
+ <<H, T, U, " ", _/bits>> ->
+ status_to_integer(H, T, U)
+ end.
+
+status_to_integer(H, T, U)
+ when $0 =< H, H =< $9, $0 =< T, T =< $9, $0 =< U, U =< $9 ->
+ (H - $0) * 100 + (T - $0) * 10 + (U - $0).
+
+-ifdef(TEST).
+parse_status_line_test_() ->
+ Tests = [
+ {<<"HTTP/1.1 200 OK\r\nRest">>,
+ {'HTTP/1.1', 200, <<"OK">>, <<"Rest">>}},
+ {<<"HTTP/1.0 404 Not Found\r\nRest">>,
+ {'HTTP/1.0', 404, <<"Not Found">>, <<"Rest">>}},
+ {<<"HTTP/1.1 500 Something very funny here\r\nRest">>,
+ {'HTTP/1.1', 500, <<"Something very funny here">>, <<"Rest">>}},
+ {<<"HTTP/1.1 200 \r\nRest">>,
+ {'HTTP/1.1', 200, <<>>, <<"Rest">>}}
+ ],
+ [{V, fun() -> R = parse_status_line(V) end}
+ || {V, R} <- Tests].
+
+parse_status_line_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"HTTP/1.1">>,
+ <<"HTTP/1.1 200\r\n">>,
+ <<"HTTP/1.1 200 OK">>,
+ <<"HTTP/1.1 200 OK\r">>,
+ <<"HTTP/1.1 200 OK\n">>,
+ <<"HTTP/0.9 200 OK\r\n">>,
+ <<"HTTP/1.1 42 Answer\r\n">>,
+ <<"HTTP/1.1 999999999 More than OK\r\n">>,
+ <<"content-type: text/plain\r\n">>,
+ <<0:80, "\r\n">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_status_line(V)) end}
+ || V <- Tests].
+
+horse_parse_status_line_200() ->
+ horse:repeat(200000,
+ parse_status_line(<<"HTTP/1.1 200 OK\r\n">>)
+ ).
+
+horse_parse_status_line_404() ->
+ horse:repeat(200000,
+ parse_status_line(<<"HTTP/1.1 404 Not Found\r\n">>)
+ ).
+
+horse_parse_status_line_500() ->
+ horse:repeat(200000,
+ parse_status_line(<<"HTTP/1.1 500 Internal Server Error\r\n">>)
+ ).
+
+horse_parse_status_line_other() ->
+ horse:repeat(200000,
+ parse_status_line(<<"HTTP/1.1 416 Requested range not satisfiable\r\n">>)
+ ).
+-endif.
+
+%% @doc Parse the list of headers.
+
+-spec parse_headers(binary()) -> {[{binary(), binary()}], binary()}.
+parse_headers(Data) ->
+ parse_header(Data, []).
+
+parse_header(<< $\r, $\n, Rest/bits >>, Acc) ->
+ {lists:reverse(Acc), Rest};
+parse_header(Data, Acc) ->
+ parse_hd_name(Data, Acc, <<>>).
+
+parse_hd_name(<< C, Rest/bits >>, Acc, SoFar) ->
+ case C of
+ $: -> parse_hd_before_value(Rest, Acc, SoFar);
+ $\s -> parse_hd_name_ws(Rest, Acc, SoFar);
+ $\t -> parse_hd_name_ws(Rest, Acc, SoFar);
+ _ -> ?LOWER(parse_hd_name, Rest, Acc, SoFar)
+ end.
+
+parse_hd_name_ws(<< C, Rest/bits >>, Acc, Name) ->
+ case C of
+ $: -> parse_hd_before_value(Rest, Acc, Name);
+ $\s -> parse_hd_name_ws(Rest, Acc, Name);
+ $\t -> parse_hd_name_ws(Rest, Acc, Name)
+ end.
+
+parse_hd_before_value(<< $\s, Rest/bits >>, Acc, Name) ->
+ parse_hd_before_value(Rest, Acc, Name);
+parse_hd_before_value(<< $\t, Rest/bits >>, Acc, Name) ->
+ parse_hd_before_value(Rest, Acc, Name);
+parse_hd_before_value(Data, Acc, Name) ->
+ parse_hd_value(Data, Acc, Name, <<>>).
+
+parse_hd_value(<< $\r, Rest/bits >>, Acc, Name, SoFar) ->
+ case Rest of
+ << $\n, C, Rest2/bits >> when C =:= $\s; C =:= $\t ->
+ parse_hd_value(Rest2, Acc, Name, << SoFar/binary, C >>);
+ << $\n, Rest2/bits >> ->
+ Value = clean_value_ws_end(SoFar, byte_size(SoFar) - 1),
+ parse_header(Rest2, [{Name, Value}|Acc])
+ end;
+parse_hd_value(<< C, Rest/bits >>, Acc, Name, SoFar) ->
+ parse_hd_value(Rest, Acc, Name, << SoFar/binary, C >>).
+
+%% This function has been copied from cowboy_http.
+clean_value_ws_end(_, -1) ->
+ <<>>;
+clean_value_ws_end(Value, N) ->
+ case binary:at(Value, N) of
+ $\s -> clean_value_ws_end(Value, N - 1);
+ $\t -> clean_value_ws_end(Value, N - 1);
+ _ ->
+ S = N + 1,
+ << Value2:S/binary, _/bits >> = Value,
+ Value2
+ end.
+
+-ifdef(TEST).
+parse_headers_test_() ->
+ Tests = [
+ {<<"\r\nRest">>,
+ {[], <<"Rest">>}},
+ {<<"Server: Erlang/R17 \r\n\r\n">>,
+ {[{<<"server">>, <<"Erlang/R17">>}], <<>>}},
+ {<<"Server: Erlang/R17\r\n"
+ "Date: Sun, 23 Feb 2014 09:30:39 GMT\r\n"
+ "Multiline-Header: why hello!\r\n"
+ " I didn't see you all the way over there!\r\n"
+ "Content-Length: 12\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\nRest">>,
+ {[{<<"server">>, <<"Erlang/R17">>},
+ {<<"date">>, <<"Sun, 23 Feb 2014 09:30:39 GMT">>},
+ {<<"multiline-header">>,
+ <<"why hello! I didn't see you all the way over there!">>},
+ {<<"content-length">>, <<"12">>},
+ {<<"content-type">>, <<"text/plain">>}],
+ <<"Rest">>}}
+ ],
+ [{V, fun() -> R = parse_headers(V) end}
+ || {V, R} <- Tests].
+
+parse_headers_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"\r">>,
+ <<"Malformed\r\n\r\n">>,
+ <<"content-type: text/plain\r\nMalformed\r\n\r\n">>,
+ <<"HTTP/1.1 200 OK\r\n\r\n">>,
+ <<0:80, "\r\n\r\n">>,
+ <<"content-type: text/plain\r\ncontent-length: 12\r\n">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_headers(V)) end}
+ || V <- Tests].
+
+horse_parse_headers() ->
+ horse:repeat(50000,
+ parse_headers(<<"Server: Erlang/R17\r\n"
+ "Date: Sun, 23 Feb 2014 09:30:39 GMT\r\n"
+ "Multiline-Header: why hello!\r\n"
+ " I didn't see you all the way over there!\r\n"
+ "Content-Length: 12\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\nRest">>)
+ ).
+-endif.
+
+%% @doc Extract path and query string from a binary,
+%% removing any fragment component.
+
+-spec parse_fullpath(binary()) -> {binary(), binary()}.
+parse_fullpath(Fullpath) ->
+ parse_fullpath(Fullpath, <<>>).
+
+parse_fullpath(<<>>, Path) -> {Path, <<>>};
+parse_fullpath(<< $#, _/bits >>, Path) -> {Path, <<>>};
+parse_fullpath(<< $?, Qs/bits >>, Path) -> parse_fullpath_query(Qs, Path, <<>>);
+parse_fullpath(<< C, Rest/bits >>, SoFar) -> parse_fullpath(Rest, << SoFar/binary, C >>).
+
+parse_fullpath_query(<<>>, Path, Query) -> {Path, Query};
+parse_fullpath_query(<< $#, _/bits >>, Path, Query) -> {Path, Query};
+parse_fullpath_query(<< C, Rest/bits >>, Path, SoFar) ->
+ parse_fullpath_query(Rest, Path, << SoFar/binary, C >>).
+
+-ifdef(TEST).
+parse_fullpath_test() ->
+ {<<"*">>, <<>>} = parse_fullpath(<<"*">>),
+ {<<"/">>, <<>>} = parse_fullpath(<<"/">>),
+ {<<"/path/to/resource">>, <<>>} = parse_fullpath(<<"/path/to/resource#fragment">>),
+ {<<"/path/to/resource">>, <<>>} = parse_fullpath(<<"/path/to/resource">>),
+ {<<"/">>, <<>>} = parse_fullpath(<<"/?">>),
+ {<<"/">>, <<"q=cowboy">>} = parse_fullpath(<<"/?q=cowboy#fragment">>),
+ {<<"/">>, <<"q=cowboy">>} = parse_fullpath(<<"/?q=cowboy">>),
+ {<<"/path/to/resource">>, <<"q=cowboy">>}
+ = parse_fullpath(<<"/path/to/resource?q=cowboy">>),
+ ok.
+-endif.
+
+%% @doc Convert an HTTP version to atom.
+
+-spec parse_version(binary()) -> version().
+parse_version(<<"HTTP/1.1">>) -> 'HTTP/1.1';
+parse_version(<<"HTTP/1.0">>) -> 'HTTP/1.0'.
+
+-ifdef(TEST).
+parse_version_test() ->
+ 'HTTP/1.1' = parse_version(<<"HTTP/1.1">>),
+ 'HTTP/1.0' = parse_version(<<"HTTP/1.0">>),
+ {'EXIT', _} = (catch parse_version(<<"HTTP/1.2">>)),
+ ok.
+-endif.
+
+%% @doc Return formatted request-line and headers.
+%% @todo Add tests when the corresponding reverse functions are added.
+
+-spec request(binary(), iodata(), version(), headers()) -> iodata().
+request(Method, Path, Version, Headers) ->
+ [Method, <<" ">>, Path, <<" ">>, version(Version), <<"\r\n">>,
+ [[N, <<": ">>, V, <<"\r\n">>] || {N, V} <- Headers],
+ <<"\r\n">>].
+
+-spec response(status() | binary(), version(), headers()) -> iodata().
+response(Status, Version, Headers) ->
+ [version(Version), <<" ">>, status(Status), <<"\r\n">>,
+ headers(Headers), <<"\r\n">>].
+
+-spec headers(headers()) -> iodata().
+headers(Headers) ->
+ [[N, <<": ">>, V, <<"\r\n">>] || {N, V} <- Headers].
+
+%% @doc Return the version as a binary.
+
+-spec version(version()) -> binary().
+version('HTTP/1.1') -> <<"HTTP/1.1">>;
+version('HTTP/1.0') -> <<"HTTP/1.0">>.
+
+-ifdef(TEST).
+version_test() ->
+ <<"HTTP/1.1">> = version('HTTP/1.1'),
+ <<"HTTP/1.0">> = version('HTTP/1.0'),
+ {'EXIT', _} = (catch version('HTTP/1.2')),
+ ok.
+-endif.
+
+%% @doc Return the status code and string as binary.
+
+-spec status(status() | binary()) -> binary().
+status(100) -> <<"100 Continue">>;
+status(101) -> <<"101 Switching Protocols">>;
+status(102) -> <<"102 Processing">>;
+status(103) -> <<"103 Early Hints">>;
+status(200) -> <<"200 OK">>;
+status(201) -> <<"201 Created">>;
+status(202) -> <<"202 Accepted">>;
+status(203) -> <<"203 Non-Authoritative Information">>;
+status(204) -> <<"204 No Content">>;
+status(205) -> <<"205 Reset Content">>;
+status(206) -> <<"206 Partial Content">>;
+status(207) -> <<"207 Multi-Status">>;
+status(208) -> <<"208 Already Reported">>;
+status(226) -> <<"226 IM Used">>;
+status(300) -> <<"300 Multiple Choices">>;
+status(301) -> <<"301 Moved Permanently">>;
+status(302) -> <<"302 Found">>;
+status(303) -> <<"303 See Other">>;
+status(304) -> <<"304 Not Modified">>;
+status(305) -> <<"305 Use Proxy">>;
+status(306) -> <<"306 Switch Proxy">>;
+status(307) -> <<"307 Temporary Redirect">>;
+status(308) -> <<"308 Permanent Redirect">>;
+status(400) -> <<"400 Bad Request">>;
+status(401) -> <<"401 Unauthorized">>;
+status(402) -> <<"402 Payment Required">>;
+status(403) -> <<"403 Forbidden">>;
+status(404) -> <<"404 Not Found">>;
+status(405) -> <<"405 Method Not Allowed">>;
+status(406) -> <<"406 Not Acceptable">>;
+status(407) -> <<"407 Proxy Authentication Required">>;
+status(408) -> <<"408 Request Timeout">>;
+status(409) -> <<"409 Conflict">>;
+status(410) -> <<"410 Gone">>;
+status(411) -> <<"411 Length Required">>;
+status(412) -> <<"412 Precondition Failed">>;
+status(413) -> <<"413 Request Entity Too Large">>;
+status(414) -> <<"414 Request-URI Too Long">>;
+status(415) -> <<"415 Unsupported Media Type">>;
+status(416) -> <<"416 Requested Range Not Satisfiable">>;
+status(417) -> <<"417 Expectation Failed">>;
+status(418) -> <<"418 I'm a teapot">>;
+status(421) -> <<"421 Misdirected Request">>;
+status(422) -> <<"422 Unprocessable Entity">>;
+status(423) -> <<"423 Locked">>;
+status(424) -> <<"424 Failed Dependency">>;
+status(425) -> <<"425 Unordered Collection">>;
+status(426) -> <<"426 Upgrade Required">>;
+status(428) -> <<"428 Precondition Required">>;
+status(429) -> <<"429 Too Many Requests">>;
+status(431) -> <<"431 Request Header Fields Too Large">>;
+status(451) -> <<"451 Unavailable For Legal Reasons">>;
+status(500) -> <<"500 Internal Server Error">>;
+status(501) -> <<"501 Not Implemented">>;
+status(502) -> <<"502 Bad Gateway">>;
+status(503) -> <<"503 Service Unavailable">>;
+status(504) -> <<"504 Gateway Timeout">>;
+status(505) -> <<"505 HTTP Version Not Supported">>;
+status(506) -> <<"506 Variant Also Negotiates">>;
+status(507) -> <<"507 Insufficient Storage">>;
+status(508) -> <<"508 Loop Detected">>;
+status(510) -> <<"510 Not Extended">>;
+status(511) -> <<"511 Network Authentication Required">>;
+status(B) when is_binary(B) -> B.
diff --git a/server/_build/default/lib/cowlib/src/cow_http2.erl b/server/_build/default/lib/cowlib/src/cow_http2.erl
new file mode 100644
index 0000000..2925e37
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_http2.erl
@@ -0,0 +1,482 @@
+%% Copyright (c) 2015-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_http2).
+
+%% Parsing.
+-export([parse_sequence/1]).
+-export([parse/1]).
+-export([parse/2]).
+-export([parse_settings_payload/1]).
+
+%% Building.
+-export([data/3]).
+-export([data_header/3]).
+-export([headers/3]).
+-export([priority/4]).
+-export([rst_stream/2]).
+-export([settings/1]).
+-export([settings_payload/1]).
+-export([settings_ack/0]).
+-export([push_promise/3]).
+-export([ping/1]).
+-export([ping_ack/1]).
+-export([goaway/3]).
+-export([window_update/1]).
+-export([window_update/2]).
+
+-type streamid() :: pos_integer().
+-export_type([streamid/0]).
+
+-type fin() :: fin | nofin.
+-export_type([fin/0]).
+
+-type head_fin() :: head_fin | head_nofin.
+-export_type([head_fin/0]).
+
+-type exclusive() :: exclusive | shared.
+-type weight() :: 1..256.
+-type settings() :: map().
+
+-type error() :: no_error
+ | protocol_error
+ | internal_error
+ | flow_control_error
+ | settings_timeout
+ | stream_closed
+ | frame_size_error
+ | refused_stream
+ | cancel
+ | compression_error
+ | connect_error
+ | enhance_your_calm
+ | inadequate_security
+ | http_1_1_required
+ | unknown_error.
+-export_type([error/0]).
+
+-type frame() :: {data, streamid(), fin(), binary()}
+ | {headers, streamid(), fin(), head_fin(), binary()}
+ | {headers, streamid(), fin(), head_fin(), exclusive(), streamid(), weight(), binary()}
+ | {priority, streamid(), exclusive(), streamid(), weight()}
+ | {rst_stream, streamid(), error()}
+ | {settings, settings()}
+ | settings_ack
+ | {push_promise, streamid(), head_fin(), streamid(), binary()}
+ | {ping, integer()}
+ | {ping_ack, integer()}
+ | {goaway, streamid(), error(), binary()}
+ | {window_update, non_neg_integer()}
+ | {window_update, streamid(), non_neg_integer()}
+ | {continuation, streamid(), head_fin(), binary()}.
+-export_type([frame/0]).
+
+%% Parsing.
+
+-spec parse_sequence(binary())
+ -> {ok, binary()} | more | {connection_error, error(), atom()}.
+parse_sequence(<<"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", Rest/bits>>) ->
+ {ok, Rest};
+parse_sequence(Data) when byte_size(Data) >= 24 ->
+ {connection_error, protocol_error,
+ 'The connection preface was invalid. (RFC7540 3.5)'};
+parse_sequence(Data) ->
+ Len = byte_size(Data),
+ <<Preface:Len/binary, _/bits>> = <<"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n">>,
+ case Data of
+ Preface ->
+ more;
+ _ ->
+ {connection_error, protocol_error,
+ 'The connection preface was invalid. (RFC7540 3.5)'}
+ end.
+
+parse(<< Len:24, _/bits >>, MaxFrameSize) when Len > MaxFrameSize ->
+ {connection_error, frame_size_error, 'The frame size exceeded SETTINGS_MAX_FRAME_SIZE. (RFC7540 4.2)'};
+parse(Data, _) ->
+ parse(Data).
+
+%%
+%% DATA frames.
+%%
+parse(<< _:24, 0:8, _:9, 0:31, _/bits >>) ->
+ {connection_error, protocol_error, 'DATA frames MUST be associated with a stream. (RFC7540 6.1)'};
+parse(<< 0:24, 0:8, _:4, 1:1, _:35, _/bits >>) ->
+ {connection_error, frame_size_error, 'DATA frames with padding flag MUST have a length > 0. (RFC7540 6.1)'};
+parse(<< Len0:24, 0:8, _:4, 1:1, _:35, PadLen:8, _/bits >>) when PadLen >= Len0 ->
+ {connection_error, protocol_error, 'Length of padding MUST be less than length of payload. (RFC7540 6.1)'};
+%% No padding.
+parse(<< Len:24, 0:8, _:4, 0:1, _:2, FlagEndStream:1, _:1, StreamID:31, Data:Len/binary, Rest/bits >>) ->
+ {ok, {data, StreamID, parse_fin(FlagEndStream), Data}, Rest};
+%% Padding.
+parse(<< Len0:24, 0:8, _:4, 1:1, _:2, FlagEndStream:1, _:1, StreamID:31, PadLen:8, Rest0/bits >>)
+ when byte_size(Rest0) >= Len0 - 1 ->
+ Len = Len0 - PadLen - 1,
+ case Rest0 of
+ << Data:Len/binary, 0:PadLen/unit:8, Rest/bits >> ->
+ {ok, {data, StreamID, parse_fin(FlagEndStream), Data}, Rest};
+ _ ->
+ {connection_error, protocol_error, 'Padding octets MUST be set to zero. (RFC7540 6.1)'}
+ end;
+%%
+%% HEADERS frames.
+%%
+parse(<< _:24, 1:8, _:9, 0:31, _/bits >>) ->
+ {connection_error, protocol_error, 'HEADERS frames MUST be associated with a stream. (RFC7540 6.2)'};
+parse(<< 0:24, 1:8, _:4, 1:1, _:35, _/bits >>) ->
+ {connection_error, frame_size_error, 'HEADERS frames with padding flag MUST have a length > 0. (RFC7540 6.1)'};
+parse(<< Len:24, 1:8, _:2, 1:1, _:37, _/bits >>) when Len < 5 ->
+ {connection_error, frame_size_error, 'HEADERS frames with priority flag MUST have a length >= 5. (RFC7540 6.1)'};
+parse(<< Len:24, 1:8, _:2, 1:1, _:1, 1:1, _:35, _/bits >>) when Len < 6 ->
+ {connection_error, frame_size_error, 'HEADERS frames with padding and priority flags MUST have a length >= 6. (RFC7540 6.1)'};
+parse(<< Len0:24, 1:8, _:4, 1:1, _:35, PadLen:8, _/bits >>) when PadLen >= Len0 ->
+ {connection_error, protocol_error, 'Length of padding MUST be less than length of payload. (RFC7540 6.2)'};
+parse(<< Len0:24, 1:8, _:2, 1:1, _:1, 1:1, _:35, PadLen:8, _/bits >>) when PadLen >= Len0 - 5 ->
+ {connection_error, protocol_error, 'Length of padding MUST be less than length of payload. (RFC7540 6.2)'};
+%% No padding, no priority.
+parse(<< Len:24, 1:8, _:2, 0:1, _:1, 0:1, FlagEndHeaders:1, _:1, FlagEndStream:1, _:1, StreamID:31,
+ HeaderBlockFragment:Len/binary, Rest/bits >>) ->
+ {ok, {headers, StreamID, parse_fin(FlagEndStream), parse_head_fin(FlagEndHeaders), HeaderBlockFragment}, Rest};
+%% Padding, no priority.
+parse(<< Len0:24, 1:8, _:2, 0:1, _:1, 1:1, FlagEndHeaders:1, _:1, FlagEndStream:1, _:1, StreamID:31,
+ PadLen:8, Rest0/bits >>) when byte_size(Rest0) >= Len0 - 1 ->
+ Len = Len0 - PadLen - 1,
+ case Rest0 of
+ << HeaderBlockFragment:Len/binary, 0:PadLen/unit:8, Rest/bits >> ->
+ {ok, {headers, StreamID, parse_fin(FlagEndStream), parse_head_fin(FlagEndHeaders), HeaderBlockFragment}, Rest};
+ _ ->
+ {connection_error, protocol_error, 'Padding octets MUST be set to zero. (RFC7540 6.2)'}
+ end;
+%% No padding, priority.
+parse(<< _:24, 1:8, _:2, 1:1, _:1, 0:1, _:4, StreamID:31, _:1, StreamID:31, _/bits >>) ->
+ {connection_error, protocol_error,
+ 'HEADERS frames cannot define a stream that depends on itself. (RFC7540 5.3.1)'};
+parse(<< Len0:24, 1:8, _:2, 1:1, _:1, 0:1, FlagEndHeaders:1, _:1, FlagEndStream:1, _:1, StreamID:31,
+ E:1, DepStreamID:31, Weight:8, Rest0/bits >>) when byte_size(Rest0) >= Len0 - 5 ->
+ Len = Len0 - 5,
+ << HeaderBlockFragment:Len/binary, Rest/bits >> = Rest0,
+ {ok, {headers, StreamID, parse_fin(FlagEndStream), parse_head_fin(FlagEndHeaders),
+ parse_exclusive(E), DepStreamID, Weight + 1, HeaderBlockFragment}, Rest};
+%% Padding, priority.
+parse(<< _:24, 1:8, _:2, 1:1, _:1, 1:1, _:4, StreamID:31, _:9, StreamID:31, _/bits >>) ->
+ {connection_error, protocol_error,
+ 'HEADERS frames cannot define a stream that depends on itself. (RFC7540 5.3.1)'};
+parse(<< Len0:24, 1:8, _:2, 1:1, _:1, 1:1, FlagEndHeaders:1, _:1, FlagEndStream:1, _:1, StreamID:31,
+ PadLen:8, E:1, DepStreamID:31, Weight:8, Rest0/bits >>) when byte_size(Rest0) >= Len0 - 6 ->
+ Len = Len0 - PadLen - 6,
+ case Rest0 of
+ << HeaderBlockFragment:Len/binary, 0:PadLen/unit:8, Rest/bits >> ->
+ {ok, {headers, StreamID, parse_fin(FlagEndStream), parse_head_fin(FlagEndHeaders),
+ parse_exclusive(E), DepStreamID, Weight + 1, HeaderBlockFragment}, Rest};
+ _ ->
+ {connection_error, protocol_error, 'Padding octets MUST be set to zero. (RFC7540 6.2)'}
+ end;
+%%
+%% PRIORITY frames.
+%%
+parse(<< 5:24, 2:8, _:9, 0:31, _/bits >>) ->
+ {connection_error, protocol_error, 'PRIORITY frames MUST be associated with a stream. (RFC7540 6.3)'};
+parse(<< 5:24, 2:8, _:9, StreamID:31, _:1, StreamID:31, _:8, Rest/bits >>) ->
+ {stream_error, StreamID, protocol_error,
+ 'PRIORITY frames cannot make a stream depend on itself. (RFC7540 5.3.1)', Rest};
+parse(<< 5:24, 2:8, _:9, StreamID:31, E:1, DepStreamID:31, Weight:8, Rest/bits >>) ->
+ {ok, {priority, StreamID, parse_exclusive(E), DepStreamID, Weight + 1}, Rest};
+%% @todo Figure out how to best deal with non-fatal frame size errors; if we have everything
+%% then OK if not we might want to inform the caller how much he should expect so that it can
+%% decide if it should just close the connection
+parse(<< BadLen:24, 2:8, _:9, StreamID:31, _:BadLen/binary, Rest/bits >>) ->
+ {stream_error, StreamID, frame_size_error, 'PRIORITY frames MUST be 5 bytes wide. (RFC7540 6.3)', Rest};
+%%
+%% RST_STREAM frames.
+%%
+parse(<< 4:24, 3:8, _:9, 0:31, _/bits >>) ->
+ {connection_error, protocol_error, 'RST_STREAM frames MUST be associated with a stream. (RFC7540 6.4)'};
+parse(<< 4:24, 3:8, _:9, StreamID:31, ErrorCode:32, Rest/bits >>) ->
+ {ok, {rst_stream, StreamID, parse_error_code(ErrorCode)}, Rest};
+parse(<< BadLen:24, 3:8, _:9, _:31, _/bits >>) when BadLen =/= 4 ->
+ {connection_error, frame_size_error, 'RST_STREAM frames MUST be 4 bytes wide. (RFC7540 6.4)'};
+%%
+%% SETTINGS frames.
+%%
+parse(<< 0:24, 4:8, _:7, 1:1, _:1, 0:31, Rest/bits >>) ->
+ {ok, settings_ack, Rest};
+parse(<< _:24, 4:8, _:7, 1:1, _:1, 0:31, _/bits >>) ->
+ {connection_error, frame_size_error, 'SETTINGS frames with the ACK flag set MUST have a length of 0. (RFC7540 6.5)'};
+parse(<< Len:24, 4:8, _:7, 0:1, _:1, 0:31, _/bits >>) when Len rem 6 =/= 0 ->
+ {connection_error, frame_size_error, 'SETTINGS frames MUST have a length multiple of 6. (RFC7540 6.5)'};
+parse(<< Len:24, 4:8, _:7, 0:1, _:1, 0:31, Rest/bits >>) when byte_size(Rest) >= Len ->
+ parse_settings_payload(Rest, Len, #{});
+parse(<< _:24, 4:8, _:8, _:1, StreamID:31, _/bits >>) when StreamID =/= 0 ->
+ {connection_error, protocol_error, 'SETTINGS frames MUST NOT be associated with a stream. (RFC7540 6.5)'};
+%%
+%% PUSH_PROMISE frames.
+%%
+parse(<< Len:24, 5:8, _:40, _/bits >>) when Len < 4 ->
+ {connection_error, frame_size_error, 'PUSH_PROMISE frames MUST have a length >= 4. (RFC7540 4.2, RFC7540 6.6)'};
+parse(<< Len:24, 5:8, _:4, 1:1, _:35, _/bits >>) when Len < 5 ->
+ {connection_error, frame_size_error, 'PUSH_PROMISE frames with padding flag MUST have a length >= 5. (RFC7540 4.2, RFC7540 6.6)'};
+parse(<< _:24, 5:8, _:9, 0:31, _/bits >>) ->
+ {connection_error, protocol_error, 'PUSH_PROMISE frames MUST be associated with a stream. (RFC7540 6.6)'};
+parse(<< Len0:24, 5:8, _:4, 1:1, _:35, PadLen:8, _/bits >>) when PadLen >= Len0 - 4 ->
+ {connection_error, protocol_error, 'Length of padding MUST be less than length of payload. (RFC7540 6.6)'};
+parse(<< Len0:24, 5:8, _:4, 0:1, FlagEndHeaders:1, _:3, StreamID:31, _:1, PromisedStreamID:31, Rest0/bits >>)
+ when byte_size(Rest0) >= Len0 - 4 ->
+ Len = Len0 - 4,
+ << HeaderBlockFragment:Len/binary, Rest/bits >> = Rest0,
+ {ok, {push_promise, StreamID, parse_head_fin(FlagEndHeaders), PromisedStreamID, HeaderBlockFragment}, Rest};
+parse(<< Len0:24, 5:8, _:4, 1:1, FlagEndHeaders:1, _:2, StreamID:31, PadLen:8, _:1, PromisedStreamID:31, Rest0/bits >>)
+ when byte_size(Rest0) >= Len0 - 5 ->
+ Len = Len0 - 5,
+ case Rest0 of
+ << HeaderBlockFragment:Len/binary, 0:PadLen/unit:8, Rest/bits >> ->
+ {ok, {push_promise, StreamID, parse_head_fin(FlagEndHeaders), PromisedStreamID, HeaderBlockFragment}, Rest};
+ _ ->
+ {connection_error, protocol_error, 'Padding octets MUST be set to zero. (RFC7540 6.6)'}
+ end;
+%%
+%% PING frames.
+%%
+parse(<< 8:24, 6:8, _:7, 1:1, _:1, 0:31, Opaque:64, Rest/bits >>) ->
+ {ok, {ping_ack, Opaque}, Rest};
+parse(<< 8:24, 6:8, _:7, 0:1, _:1, 0:31, Opaque:64, Rest/bits >>) ->
+ {ok, {ping, Opaque}, Rest};
+parse(<< 8:24, 6:8, _:104, _/bits >>) ->
+ {connection_error, protocol_error, 'PING frames MUST NOT be associated with a stream. (RFC7540 6.7)'};
+parse(<< Len:24, 6:8, _/bits >>) when Len =/= 8 ->
+ {connection_error, frame_size_error, 'PING frames MUST be 8 bytes wide. (RFC7540 6.7)'};
+%%
+%% GOAWAY frames.
+%%
+parse(<< Len0:24, 7:8, _:9, 0:31, _:1, LastStreamID:31, ErrorCode:32, Rest0/bits >>) when byte_size(Rest0) >= Len0 - 8 ->
+ Len = Len0 - 8,
+ << DebugData:Len/binary, Rest/bits >> = Rest0,
+ {ok, {goaway, LastStreamID, parse_error_code(ErrorCode), DebugData}, Rest};
+parse(<< Len:24, 7:8, _:40, _/bits >>) when Len < 8 ->
+ {connection_error, frame_size_error, 'GOAWAY frames MUST have a length >= 8. (RFC7540 4.2, RFC7540 6.8)'};
+parse(<< _:24, 7:8, _:40, _/bits >>) ->
+ {connection_error, protocol_error, 'GOAWAY frames MUST NOT be associated with a stream. (RFC7540 6.8)'};
+%%
+%% WINDOW_UPDATE frames.
+%%
+parse(<< 4:24, 8:8, _:9, 0:31, _:1, 0:31, _/bits >>) ->
+ {connection_error, protocol_error, 'WINDOW_UPDATE frames MUST have a non-zero increment. (RFC7540 6.9)'};
+parse(<< 4:24, 8:8, _:9, 0:31, _:1, Increment:31, Rest/bits >>) ->
+ {ok, {window_update, Increment}, Rest};
+parse(<< 4:24, 8:8, _:9, StreamID:31, _:1, 0:31, Rest/bits >>) ->
+ {stream_error, StreamID, protocol_error, 'WINDOW_UPDATE frames MUST have a non-zero increment. (RFC7540 6.9)', Rest};
+parse(<< 4:24, 8:8, _:9, StreamID:31, _:1, Increment:31, Rest/bits >>) ->
+ {ok, {window_update, StreamID, Increment}, Rest};
+parse(<< Len:24, 8:8, _/bits >>) when Len =/= 4->
+ {connection_error, frame_size_error, 'WINDOW_UPDATE frames MUST be 4 bytes wide. (RFC7540 6.9)'};
+%%
+%% CONTINUATION frames.
+%%
+parse(<< _:24, 9:8, _:9, 0:31, _/bits >>) ->
+ {connection_error, protocol_error, 'CONTINUATION frames MUST be associated with a stream. (RFC7540 6.10)'};
+parse(<< Len:24, 9:8, _:5, FlagEndHeaders:1, _:3, StreamID:31, HeaderBlockFragment:Len/binary, Rest/bits >>) ->
+ {ok, {continuation, StreamID, parse_head_fin(FlagEndHeaders), HeaderBlockFragment}, Rest};
+%%
+%% Unknown frames are ignored.
+%%
+parse(<< Len:24, Type:8, _:40, _:Len/binary, Rest/bits >>) when Type > 9 ->
+ {ignore, Rest};
+%%
+%% Incomplete frames.
+%%
+parse(_) ->
+ more.
+
+-ifdef(TEST).
+parse_ping_test() ->
+ Ping = ping(1234567890),
+ _ = [more = parse(binary:part(Ping, 0, I)) || I <- lists:seq(1, byte_size(Ping) - 1)],
+ {ok, {ping, 1234567890}, <<>>} = parse(Ping),
+ {ok, {ping, 1234567890}, << 42 >>} = parse(<< Ping/binary, 42 >>),
+ ok.
+
+parse_windows_update_test() ->
+ WindowUpdate = << 4:24, 8:8, 0:9, 0:31, 0:1, 12345:31 >>,
+ _ = [more = parse(binary:part(WindowUpdate, 0, I)) || I <- lists:seq(1, byte_size(WindowUpdate) - 1)],
+ {ok, {window_update, 12345}, <<>>} = parse(WindowUpdate),
+ {ok, {window_update, 12345}, << 42 >>} = parse(<< WindowUpdate/binary, 42 >>),
+ ok.
+
+parse_settings_test() ->
+ more = parse(<< 0:24, 4:8, 1:8, 0:8 >>),
+ {ok, settings_ack, <<>>} = parse(<< 0:24, 4:8, 1:8, 0:32 >>),
+ {connection_error, protocol_error, _} = parse(<< 0:24, 4:8, 1:8, 0:1, 1:31 >>),
+ ok.
+-endif.
+
+parse_fin(0) -> nofin;
+parse_fin(1) -> fin.
+
+parse_head_fin(0) -> head_nofin;
+parse_head_fin(1) -> head_fin.
+
+parse_exclusive(0) -> shared;
+parse_exclusive(1) -> exclusive.
+
+parse_error_code( 0) -> no_error;
+parse_error_code( 1) -> protocol_error;
+parse_error_code( 2) -> internal_error;
+parse_error_code( 3) -> flow_control_error;
+parse_error_code( 4) -> settings_timeout;
+parse_error_code( 5) -> stream_closed;
+parse_error_code( 6) -> frame_size_error;
+parse_error_code( 7) -> refused_stream;
+parse_error_code( 8) -> cancel;
+parse_error_code( 9) -> compression_error;
+parse_error_code(10) -> connect_error;
+parse_error_code(11) -> enhance_your_calm;
+parse_error_code(12) -> inadequate_security;
+parse_error_code(13) -> http_1_1_required;
+parse_error_code(_) -> unknown_error.
+
+parse_settings_payload(SettingsPayload) ->
+ {ok, {settings, Settings}, <<>>}
+ = parse_settings_payload(SettingsPayload, byte_size(SettingsPayload), #{}),
+ Settings.
+
+parse_settings_payload(Rest, 0, Settings) ->
+ {ok, {settings, Settings}, Rest};
+%% SETTINGS_HEADER_TABLE_SIZE.
+parse_settings_payload(<< 1:16, Value:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{header_table_size => Value});
+%% SETTINGS_ENABLE_PUSH.
+parse_settings_payload(<< 2:16, 0:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{enable_push => false});
+parse_settings_payload(<< 2:16, 1:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{enable_push => true});
+parse_settings_payload(<< 2:16, _:32, _/bits >>, _, _) ->
+ {connection_error, protocol_error, 'The SETTINGS_ENABLE_PUSH value MUST be 0 or 1. (RFC7540 6.5.2)'};
+%% SETTINGS_MAX_CONCURRENT_STREAMS.
+parse_settings_payload(<< 3:16, Value:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{max_concurrent_streams => Value});
+%% SETTINGS_INITIAL_WINDOW_SIZE.
+parse_settings_payload(<< 4:16, Value:32, _/bits >>, _, _) when Value > 16#7fffffff ->
+ {connection_error, flow_control_error, 'The maximum SETTINGS_INITIAL_WINDOW_SIZE value is 0x7fffffff. (RFC7540 6.5.2)'};
+parse_settings_payload(<< 4:16, Value:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{initial_window_size => Value});
+%% SETTINGS_MAX_FRAME_SIZE.
+parse_settings_payload(<< 5:16, Value:32, _/bits >>, _, _) when Value =< 16#3fff ->
+ {connection_error, protocol_error, 'The SETTINGS_MAX_FRAME_SIZE value must be > 0x3fff. (RFC7540 6.5.2)'};
+parse_settings_payload(<< 5:16, Value:32, Rest/bits >>, Len, Settings) when Value =< 16#ffffff ->
+ parse_settings_payload(Rest, Len - 6, Settings#{max_frame_size => Value});
+parse_settings_payload(<< 5:16, _:32, _/bits >>, _, _) ->
+ {connection_error, protocol_error, 'The SETTINGS_MAX_FRAME_SIZE value must be =< 0xffffff. (RFC7540 6.5.2)'};
+%% SETTINGS_MAX_HEADER_LIST_SIZE.
+parse_settings_payload(<< 6:16, Value:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{max_header_list_size => Value});
+%% SETTINGS_ENABLE_CONNECT_PROTOCOL.
+parse_settings_payload(<< 8:16, 0:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{enable_connect_protocol => false});
+parse_settings_payload(<< 8:16, 1:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{enable_connect_protocol => true});
+parse_settings_payload(<< 8:16, _:32, _/bits >>, _, _) ->
+ {connection_error, protocol_error, 'The SETTINGS_ENABLE_CONNECT_PROTOCOL value MUST be 0 or 1. (draft-h2-websockets-01 3)'};
+%% Ignore unknown settings.
+parse_settings_payload(<< _:48, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings).
+
+%% Building.
+
+data(StreamID, IsFin, Data) ->
+ [data_header(StreamID, IsFin, iolist_size(Data)), Data].
+
+data_header(StreamID, IsFin, Len) ->
+ FlagEndStream = flag_fin(IsFin),
+ << Len:24, 0:15, FlagEndStream:1, 0:1, StreamID:31 >>.
+
+%% @todo Check size of HeaderBlock and use CONTINUATION frames if needed.
+headers(StreamID, IsFin, HeaderBlock) ->
+ Len = iolist_size(HeaderBlock),
+ FlagEndStream = flag_fin(IsFin),
+ FlagEndHeaders = 1,
+ [<< Len:24, 1:8, 0:5, FlagEndHeaders:1, 0:1, FlagEndStream:1, 0:1, StreamID:31 >>, HeaderBlock].
+
+priority(StreamID, E, DepStreamID, Weight) ->
+ FlagExclusive = exclusive(E),
+ << 5:24, 2:8, 0:9, StreamID:31, FlagExclusive:1, DepStreamID:31, Weight:8 >>.
+
+rst_stream(StreamID, Reason) ->
+ ErrorCode = error_code(Reason),
+ << 4:24, 3:8, 0:9, StreamID:31, ErrorCode:32 >>.
+
+settings(Settings) ->
+ Payload = settings_payload(Settings),
+ Len = iolist_size(Payload),
+ [<< Len:24, 4:8, 0:40 >>, Payload].
+
+settings_payload(Settings) ->
+ [case Key of
+ header_table_size -> <<1:16, Value:32>>;
+ enable_push when Value -> <<2:16, 1:32>>;
+ enable_push -> <<2:16, 0:32>>;
+ max_concurrent_streams when Value =:= infinity -> <<>>;
+ max_concurrent_streams -> <<3:16, Value:32>>;
+ initial_window_size -> <<4:16, Value:32>>;
+ max_frame_size -> <<5:16, Value:32>>;
+ max_header_list_size when Value =:= infinity -> <<>>;
+ max_header_list_size -> <<6:16, Value:32>>;
+ enable_connect_protocol when Value -> <<8:16, 1:32>>;
+ enable_connect_protocol -> <<8:16, 0:32>>
+ end || {Key, Value} <- maps:to_list(Settings)].
+
+settings_ack() ->
+ << 0:24, 4:8, 1:8, 0:32 >>.
+
+%% @todo Check size of HeaderBlock and use CONTINUATION frames if needed.
+push_promise(StreamID, PromisedStreamID, HeaderBlock) ->
+ Len = iolist_size(HeaderBlock) + 4,
+ FlagEndHeaders = 1,
+ [<< Len:24, 5:8, 0:5, FlagEndHeaders:1, 0:3, StreamID:31, 0:1, PromisedStreamID:31 >>, HeaderBlock].
+
+ping(Opaque) ->
+ << 8:24, 6:8, 0:40, Opaque:64 >>.
+
+ping_ack(Opaque) ->
+ << 8:24, 6:8, 0:7, 1:1, 0:32, Opaque:64 >>.
+
+goaway(LastStreamID, Reason, DebugData) ->
+ ErrorCode = error_code(Reason),
+ Len = iolist_size(DebugData) + 8,
+ [<< Len:24, 7:8, 0:41, LastStreamID:31, ErrorCode:32 >>, DebugData].
+
+window_update(Increment) ->
+ window_update(0, Increment).
+
+window_update(StreamID, Increment) when Increment =< 16#7fffffff ->
+ << 4:24, 8:8, 0:8, StreamID:32, 0:1, Increment:31 >>.
+
+flag_fin(nofin) -> 0;
+flag_fin(fin) -> 1.
+
+exclusive(shared) -> 0;
+exclusive(exclusive) -> 1.
+
+error_code(no_error) -> 0;
+error_code(protocol_error) -> 1;
+error_code(internal_error) -> 2;
+error_code(flow_control_error) -> 3;
+error_code(settings_timeout) -> 4;
+error_code(stream_closed) -> 5;
+error_code(frame_size_error) -> 6;
+error_code(refused_stream) -> 7;
+error_code(cancel) -> 8;
+error_code(compression_error) -> 9;
+error_code(connect_error) -> 10;
+error_code(enhance_your_calm) -> 11;
+error_code(inadequate_security) -> 12;
+error_code(http_1_1_required) -> 13.
diff --git a/server/_build/default/lib/cowlib/src/cow_http2_machine.erl b/server/_build/default/lib/cowlib/src/cow_http2_machine.erl
new file mode 100644
index 0000000..87c7b78
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_http2_machine.erl
@@ -0,0 +1,1647 @@
+%% Copyright (c) 2018-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_http2_machine).
+
+-export([init/2]).
+-export([init_stream/2]).
+-export([init_upgrade_stream/2]).
+-export([frame/2]).
+-export([ignored_frame/1]).
+-export([timeout/3]).
+-export([prepare_headers/5]).
+-export([prepare_push_promise/4]).
+-export([prepare_trailers/3]).
+-export([send_or_queue_data/4]).
+-export([ensure_window/2]).
+-export([ensure_window/3]).
+-export([update_window/2]).
+-export([update_window/3]).
+-export([reset_stream/2]).
+-export([get_connection_local_buffer_size/1]).
+-export([get_local_setting/2]).
+-export([get_remote_settings/1]).
+-export([get_last_streamid/1]).
+-export([set_last_streamid/1]).
+-export([get_stream_local_buffer_size/2]).
+-export([get_stream_local_state/2]).
+-export([get_stream_remote_state/2]).
+-export([is_lingering_stream/2]).
+
+-type opts() :: #{
+ connection_window_margin_size => 0..16#7fffffff,
+ connection_window_update_threshold => 0..16#7fffffff,
+ enable_connect_protocol => boolean(),
+ initial_connection_window_size => 65535..16#7fffffff,
+ initial_stream_window_size => 0..16#7fffffff,
+ max_connection_window_size => 0..16#7fffffff,
+ max_concurrent_streams => non_neg_integer() | infinity,
+ max_decode_table_size => non_neg_integer(),
+ max_encode_table_size => non_neg_integer(),
+ max_frame_size_received => 16384..16777215,
+ max_frame_size_sent => 16384..16777215 | infinity,
+ max_stream_window_size => 0..16#7fffffff,
+ message_tag => any(),
+ preface_timeout => timeout(),
+ settings_timeout => timeout(),
+ stream_window_data_threshold => 0..16#7fffffff,
+ stream_window_margin_size => 0..16#7fffffff,
+ stream_window_update_threshold => 0..16#7fffffff
+}.
+-export_type([opts/0]).
+
+%% The order of the fields is significant.
+-record(sendfile, {
+ offset :: non_neg_integer(),
+ bytes :: pos_integer(),
+ path :: file:name_all()
+}).
+
+-record(stream, {
+ id = undefined :: cow_http2:streamid(),
+
+ %% Request method.
+ method = undefined :: binary(),
+
+ %% Whether we finished sending data.
+ local = idle :: idle | cow_http2:fin(),
+
+ %% Local flow control window (how much we can send).
+ local_window :: integer(),
+
+ %% Buffered data waiting for the flow control window to increase.
+ local_buffer = queue:new() ::
+ queue:queue({cow_http2:fin(), non_neg_integer(), {data, iodata()} | #sendfile{}}),
+ local_buffer_size = 0 :: non_neg_integer(),
+ local_trailers = undefined :: undefined | cow_http:headers(),
+
+ %% Whether we finished receiving data.
+ remote = idle :: idle | cow_http2:fin(),
+
+ %% Remote flow control window (how much we accept to receive).
+ remote_window :: integer(),
+
+ %% Size expected and read from the request body.
+ remote_expected_size = undefined :: undefined | non_neg_integer(),
+ remote_read_size = 0 :: non_neg_integer(),
+
+ %% Unparsed te header. Used to know if we can send trailers.
+ %% Note that we can always send trailers to the server.
+ te :: undefined | binary()
+}).
+
+-type stream() :: #stream{}.
+
+-type continued_frame() ::
+ {headers, cow_http2:streamid(), cow_http2:fin(), cow_http2:head_fin(), binary()} |
+ {push_promise, cow_http2:streamid(), cow_http2:head_fin(), cow_http2:streamid(), binary()}.
+
+-record(http2_machine, {
+ %% Whether the HTTP/2 endpoint is a client or a server.
+ mode :: client | server,
+
+ %% HTTP/2 SETTINGS customization.
+ opts = #{} :: opts(),
+
+ %% Connection-wide frame processing state.
+ state = settings :: settings | normal
+ | {continuation, request | response | trailers | push_promise, continued_frame()},
+
+ %% Timer for the connection preface.
+ preface_timer = undefined :: undefined | reference(),
+
+ %% Timer for the ack for a SETTINGS frame we sent.
+ settings_timer = undefined :: undefined | reference(),
+
+ %% Settings are separate for each endpoint. In addition, settings
+ %% must be acknowledged before they can be expected to be applied.
+ local_settings = #{
+% header_table_size => 4096,
+% enable_push => true,
+% max_concurrent_streams => infinity,
+ initial_window_size => 65535
+% max_frame_size => 16384
+% max_header_list_size => infinity
+ } :: map(),
+ next_settings = undefined :: undefined | map(),
+ remote_settings = #{
+ initial_window_size => 65535
+ } :: map(),
+
+ %% Connection-wide flow control window.
+ local_window = 65535 :: integer(), %% How much we can send.
+ remote_window = 65535 :: integer(), %% How much we accept to receive.
+
+ %% Stream identifiers.
+ local_streamid :: pos_integer(), %% The next streamid to be used.
+ remote_streamid = 0 :: non_neg_integer(), %% The last streamid received.
+ last_remote_streamid = 16#7fffffff :: non_neg_integer(), %% Used in GOAWAY.
+
+ %% Currently active HTTP/2 streams. Streams may be initiated either
+ %% by the client or by the server through PUSH_PROMISE frames.
+ streams = #{} :: #{cow_http2:streamid() => stream()},
+
+ %% HTTP/2 streams that have recently been reset locally.
+ %% We are expected to keep receiving additional frames after
+ %% sending an RST_STREAM.
+ local_lingering_streams = [] :: [cow_http2:streamid()],
+
+ %% HTTP/2 streams that have recently been reset remotely.
+ %% We keep a few of these around in order to reject subsequent
+ %% frames on these streams.
+ remote_lingering_streams = [] :: [cow_http2:streamid()],
+
+ %% HPACK decoding and encoding state.
+ decode_state = cow_hpack:init() :: cow_hpack:state(),
+ encode_state = cow_hpack:init() :: cow_hpack:state()
+}).
+
+-opaque http2_machine() :: #http2_machine{}.
+-export_type([http2_machine/0]).
+
+-type pseudo_headers() :: #{} %% Trailers
+ | #{ %% Responses.
+ status := cow_http:status()
+ } | #{ %% Normal CONNECT requests.
+ method := binary(),
+ authority := binary()
+ } | #{ %% Other requests and extended CONNECT requests.
+ method := binary(),
+ scheme := binary(),
+ authority := binary(),
+ path := binary(),
+ protocol => binary()
+ }.
+
+%% Returns true when the given StreamID is for a local-initiated stream.
+-define(IS_SERVER_LOCAL(StreamID), ((StreamID rem 2) =:= 0)).
+-define(IS_CLIENT_LOCAL(StreamID), ((StreamID rem 2) =:= 1)).
+-define(IS_LOCAL(Mode, StreamID), (
+ ((Mode =:= server) andalso ?IS_SERVER_LOCAL(StreamID))
+ orelse
+ ((Mode =:= client) andalso ?IS_CLIENT_LOCAL(StreamID))
+)).
+
+-spec init(client | server, opts()) -> {ok, iodata(), http2_machine()}.
+init(client, Opts) ->
+ NextSettings = settings_init(Opts),
+ client_preface(#http2_machine{
+ mode=client,
+ opts=Opts,
+ preface_timer=start_timer(preface_timeout, Opts),
+ settings_timer=start_timer(settings_timeout, Opts),
+ next_settings=NextSettings,
+ local_streamid=1
+ });
+init(server, Opts) ->
+ NextSettings = settings_init(Opts),
+ common_preface(#http2_machine{
+ mode=server,
+ opts=Opts,
+ preface_timer=start_timer(preface_timeout, Opts),
+ settings_timer=start_timer(settings_timeout, Opts),
+ next_settings=NextSettings,
+ local_streamid=2
+ }).
+
+%% @todo In Cowlib 3.0 we should always include MessageTag in the message.
+%% It can be set to 'undefined' if the option is missing.
+start_timer(Name, Opts=#{message_tag := MessageTag}) ->
+ case maps:get(Name, Opts, 5000) of
+ infinity -> undefined;
+ Timeout -> erlang:start_timer(Timeout, self(), {?MODULE, MessageTag, Name})
+ end;
+start_timer(Name, Opts) ->
+ case maps:get(Name, Opts, 5000) of
+ infinity -> undefined;
+ Timeout -> erlang:start_timer(Timeout, self(), {?MODULE, Name})
+ end.
+
+client_preface(State0) ->
+ {ok, CommonPreface, State} = common_preface(State0),
+ {ok, [
+ <<"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n">>,
+ CommonPreface
+ ], State}.
+
+%% We send next_settings and use defaults until we get an ack.
+%%
+%% We also send a WINDOW_UPDATE frame for the connection when
+%% the user specified an initial_connection_window_size.
+common_preface(State=#http2_machine{opts=Opts, next_settings=NextSettings}) ->
+ case maps:get(initial_connection_window_size, Opts, 65535) of
+ 65535 ->
+ {ok, cow_http2:settings(NextSettings), State};
+ Size ->
+ {ok, [
+ cow_http2:settings(NextSettings),
+ cow_http2:window_update(Size - 65535)
+ ], update_window(Size - 65535, State)}
+ end.
+
+settings_init(Opts) ->
+ S0 = setting_from_opt(#{}, Opts, max_decode_table_size,
+ header_table_size, 4096),
+ S1 = setting_from_opt(S0, Opts, max_concurrent_streams,
+ max_concurrent_streams, infinity),
+ S2 = setting_from_opt(S1, Opts, initial_stream_window_size,
+ initial_window_size, 65535),
+ S3 = setting_from_opt(S2, Opts, max_frame_size_received,
+ max_frame_size, 16384),
+ %% @todo max_header_list_size
+ setting_from_opt(S3, Opts, enable_connect_protocol,
+ enable_connect_protocol, false).
+
+setting_from_opt(Settings, Opts, OptName, SettingName, Default) ->
+ case maps:get(OptName, Opts, Default) of
+ Default -> Settings;
+ Value -> Settings#{SettingName => Value}
+ end.
+
+-spec init_stream(binary(), State)
+ -> {ok, cow_http2:streamid(), State} when State::http2_machine().
+init_stream(Method, State=#http2_machine{mode=client, local_streamid=LocalStreamID,
+ local_settings=#{initial_window_size := RemoteWindow},
+ remote_settings=#{initial_window_size := LocalWindow}}) ->
+ Stream = #stream{id=LocalStreamID, method=Method,
+ local_window=LocalWindow, remote_window=RemoteWindow},
+ {ok, LocalStreamID, stream_store(Stream, State#http2_machine{
+ local_streamid=LocalStreamID + 2})}.
+
+-spec init_upgrade_stream(binary(), State)
+ -> {ok, cow_http2:streamid(), State} when State::http2_machine().
+init_upgrade_stream(Method, State=#http2_machine{mode=server, remote_streamid=0,
+ local_settings=#{initial_window_size := RemoteWindow},
+ remote_settings=#{initial_window_size := LocalWindow}}) ->
+ Stream = #stream{id=1, method=Method,
+ remote=fin, remote_expected_size=0,
+ local_window=LocalWindow, remote_window=RemoteWindow, te=undefined},
+ {ok, 1, stream_store(Stream, State#http2_machine{remote_streamid=1})}.
+
+-spec frame(cow_http2:frame(), State)
+ -> {ok, State}
+ | {ok, {data, cow_http2:streamid(), cow_http2:fin(), binary()}, State}
+ | {ok, {headers, cow_http2:streamid(), cow_http2:fin(),
+ cow_http:headers(), pseudo_headers(), non_neg_integer() | undefined}, State}
+ | {ok, {trailers, cow_http2:streamid(), cow_http:headers()}, State}
+ | {ok, {rst_stream, cow_http2:streamid(), cow_http2:error()}, State}
+ | {ok, {push_promise, cow_http2:streamid(), cow_http2:streamid(),
+ cow_http:headers(), pseudo_headers()}, State}
+ | {ok, {goaway, cow_http2:streamid(), cow_http2:error(), binary()}, State}
+ | {send, [{cow_http2:streamid(), cow_http2:fin(),
+ [{data, iodata()} | #sendfile{} | {trailers, cow_http:headers()}]}], State}
+ | {error, {stream_error, cow_http2:streamid(), cow_http2:error(), atom()}, State}
+ | {error, {connection_error, cow_http2:error(), atom()}, State}
+ when State::http2_machine().
+frame(Frame, State=#http2_machine{state=settings, preface_timer=TRef}) ->
+ ok = case TRef of
+ undefined -> ok;
+ _ -> erlang:cancel_timer(TRef, [{async, true}, {info, false}])
+ end,
+ settings_frame(Frame, State#http2_machine{state=normal, preface_timer=undefined});
+frame(Frame, State=#http2_machine{state={continuation, _, _}}) ->
+ maybe_discard_result(continuation_frame(Frame, State));
+frame(settings_ack, State=#http2_machine{state=normal}) ->
+ settings_ack_frame(State);
+frame(Frame, State=#http2_machine{state=normal}) ->
+ Result = case element(1, Frame) of
+ data -> data_frame(Frame, State);
+ headers -> headers_frame(Frame, State);
+ priority -> priority_frame(Frame, State);
+ rst_stream -> rst_stream_frame(Frame, State);
+ settings -> settings_frame(Frame, State);
+ push_promise -> push_promise_frame(Frame, State);
+ ping -> ping_frame(Frame, State);
+ ping_ack -> ping_ack_frame(Frame, State);
+ goaway -> goaway_frame(Frame, State);
+ window_update -> window_update_frame(Frame, State);
+ continuation -> unexpected_continuation_frame(Frame, State);
+ _ -> ignored_frame(State)
+ end,
+ maybe_discard_result(Result).
+
+%% RFC7540 6.9. After sending a GOAWAY frame, the sender can discard frames for
+%% streams initiated by the receiver with identifiers higher than the identified
+%% last stream. However, any frames that alter connection state cannot be
+%% completely ignored. For instance, HEADERS, PUSH_PROMISE, and CONTINUATION
+%% frames MUST be minimally processed to ensure the state maintained for header
+%% compression is consistent.
+maybe_discard_result(FrameResult={ok, Result, State=#http2_machine{mode=Mode,
+ last_remote_streamid=MaxID}})
+ when element(1, Result) =/= goaway ->
+ case element(2, Result) of
+ StreamID when StreamID > MaxID, not ?IS_LOCAL(Mode, StreamID) ->
+ {ok, State};
+ _StreamID ->
+ FrameResult
+ end;
+maybe_discard_result(FrameResult) ->
+ FrameResult.
+
+%% DATA frame.
+
+data_frame({data, StreamID, _, _}, State=#http2_machine{mode=Mode,
+ local_streamid=LocalStreamID, remote_streamid=RemoteStreamID})
+ when (?IS_LOCAL(Mode, StreamID) andalso (StreamID >= LocalStreamID))
+ orelse ((not ?IS_LOCAL(Mode, StreamID)) andalso (StreamID > RemoteStreamID)) ->
+ {error, {connection_error, protocol_error,
+ 'DATA frame received on a stream in idle state. (RFC7540 5.1)'},
+ State};
+data_frame({data, _, _, Data}, State=#http2_machine{remote_window=ConnWindow})
+ when byte_size(Data) > ConnWindow ->
+ {error, {connection_error, flow_control_error,
+ 'DATA frame overflowed the connection flow control window. (RFC7540 6.9, RFC7540 6.9.1)'},
+ State};
+data_frame(Frame={data, StreamID, _, Data}, State0=#http2_machine{
+ remote_window=ConnWindow, local_lingering_streams=Lingering}) ->
+ DataLen = byte_size(Data),
+ State = State0#http2_machine{remote_window=ConnWindow - DataLen},
+ case stream_get(StreamID, State) of
+ #stream{remote_window=StreamWindow} when StreamWindow < DataLen ->
+ stream_reset(StreamID, State, flow_control_error,
+ 'DATA frame overflowed the stream flow control window. (RFC7540 6.9, RFC7540 6.9.1)');
+ Stream = #stream{remote=nofin} ->
+ data_frame(Frame, State, Stream, DataLen);
+ #stream{remote=idle} ->
+ stream_reset(StreamID, State, protocol_error,
+ 'DATA frame received before a HEADERS frame. (RFC7540 8.1, RFC7540 8.1.2.6)');
+ #stream{remote=fin} ->
+ stream_reset(StreamID, State, stream_closed,
+ 'DATA frame received for a half-closed (remote) stream. (RFC7540 5.1)');
+ undefined ->
+ %% After we send an RST_STREAM frame and terminate a stream,
+ %% the remote endpoint might still be sending us some more
+ %% frames until it can process this RST_STREAM.
+ case lists:member(StreamID, Lingering) of
+ true ->
+ {ok, State};
+ false ->
+ {error, {connection_error, stream_closed,
+ 'DATA frame received for a closed stream. (RFC7540 5.1)'},
+ State}
+ end
+ end.
+
+data_frame(Frame={data, _, IsFin, _}, State0, Stream0=#stream{id=StreamID,
+ remote_window=StreamWindow, remote_read_size=StreamRead}, DataLen) ->
+ Stream = Stream0#stream{remote=IsFin,
+ remote_window=StreamWindow - DataLen,
+ remote_read_size=StreamRead + DataLen},
+ State = stream_store(Stream, State0),
+ case is_body_size_valid(Stream) of
+ true ->
+ {ok, Frame, State};
+ false ->
+ stream_reset(StreamID, State, protocol_error,
+ 'The total size of DATA frames is different than the content-length. (RFC7540 8.1.2.6)')
+ end.
+
+%% It's always valid when no content-length header was specified.
+is_body_size_valid(#stream{remote_expected_size=undefined}) ->
+ true;
+%% We didn't finish reading the body but the size is already larger than expected.
+is_body_size_valid(#stream{remote=nofin, remote_expected_size=Expected,
+ remote_read_size=Read}) when Read > Expected ->
+ false;
+is_body_size_valid(#stream{remote=nofin}) ->
+ true;
+is_body_size_valid(#stream{remote=fin, remote_expected_size=Expected,
+ remote_read_size=Expected}) ->
+ true;
+%% We finished reading the body and the size read is not the one expected.
+is_body_size_valid(_) ->
+ false.
+
+%% HEADERS frame.
+%%
+%% We always close the connection when we detect errors before
+%% decoding the headers to not waste resources on non-compliant
+%% endpoints, making us stricter than the RFC requires.
+
+%% Convenience record to manipulate the tuple.
+%% The order of the fields matter.
+-record(headers, {
+ id :: cow_http2:streamid(),
+ fin :: cow_http2:fin(),
+ head :: cow_http2:head_fin(),
+ data :: binary()
+}).
+
+headers_frame(Frame=#headers{}, State=#http2_machine{mode=Mode}) ->
+ case Mode of
+ server -> server_headers_frame(Frame, State);
+ client -> client_headers_frame(Frame, State)
+ end;
+%% @todo Handle the PRIORITY data, but only if this returns an ok tuple.
+%% @todo Do not lose the PRIORITY information if CONTINUATION frames follow.
+headers_frame({headers, StreamID, IsFin, IsHeadFin,
+ _IsExclusive, _DepStreamID, _Weight, HeaderData},
+ State=#http2_machine{mode=Mode}) ->
+ HeadersFrame = #headers{id=StreamID, fin=IsFin, head=IsHeadFin, data=HeaderData},
+ case Mode of
+ server -> server_headers_frame(HeadersFrame, State);
+ client -> client_headers_frame(HeadersFrame, State)
+ end.
+
+%% Reject HEADERS frames with even-numbered streamid.
+server_headers_frame(#headers{id=StreamID}, State)
+ when ?IS_SERVER_LOCAL(StreamID) ->
+ {error, {connection_error, protocol_error,
+ 'HEADERS frame received with even-numbered streamid. (RFC7540 5.1.1)'},
+ State};
+%% HEADERS frame on an idle stream: new request.
+server_headers_frame(Frame=#headers{id=StreamID, head=IsHeadFin},
+ State=#http2_machine{mode=server, remote_streamid=RemoteStreamID})
+ when StreamID > RemoteStreamID ->
+ case IsHeadFin of
+ head_fin ->
+ headers_decode(Frame, State, request, undefined);
+ head_nofin ->
+ {ok, State#http2_machine{state={continuation, request, Frame}}}
+ end;
+%% Either a HEADERS frame received on (half-)closed stream,
+%% or a HEADERS frame containing the trailers.
+server_headers_frame(Frame=#headers{id=StreamID, fin=IsFin, head=IsHeadFin}, State) ->
+ case stream_get(StreamID, State) of
+ %% Trailers.
+ Stream = #stream{remote=nofin} when IsFin =:= fin ->
+ case IsHeadFin of
+ head_fin ->
+ headers_decode(Frame, State, trailers, Stream);
+ head_nofin ->
+ {ok, State#http2_machine{state={continuation, trailers, Frame}}}
+ end;
+ #stream{remote=nofin} ->
+ {error, {connection_error, protocol_error,
+ 'Trailing HEADERS frame received without the END_STREAM flag set. (RFC7540 8.1, RFC7540 8.1.2.6)'},
+ State};
+ _ ->
+ {error, {connection_error, stream_closed,
+ 'HEADERS frame received on a stream in closed or half-closed state. (RFC7540 5.1)'},
+ State}
+ end.
+
+%% Either a HEADERS frame received on an (half-)closed stream,
+%% or a HEADERS frame containing the response or the trailers.
+client_headers_frame(Frame=#headers{id=StreamID, fin=IsFin, head=IsHeadFin},
+ State=#http2_machine{local_streamid=LocalStreamID, remote_streamid=RemoteStreamID})
+ when (?IS_CLIENT_LOCAL(StreamID) andalso (StreamID < LocalStreamID))
+ orelse ((not ?IS_CLIENT_LOCAL(StreamID)) andalso (StreamID =< RemoteStreamID)) ->
+ case stream_get(StreamID, State) of
+ Stream = #stream{remote=idle} ->
+ case IsHeadFin of
+ head_fin ->
+ headers_decode(Frame, State, response, Stream);
+ head_nofin ->
+ {ok, State#http2_machine{state={continuation, response, Frame}}}
+ end;
+ Stream = #stream{remote=nofin} when IsFin =:= fin ->
+ case IsHeadFin of
+ head_fin ->
+ headers_decode(Frame, State, trailers, Stream);
+ head_nofin ->
+ {ok, State#http2_machine{state={continuation, trailers, Frame}}}
+ end;
+ #stream{remote=nofin} ->
+ {error, {connection_error, protocol_error,
+ 'Trailing HEADERS frame received without the END_STREAM flag set. (RFC7540 8.1, RFC7540 8.1.2.6)'},
+ State};
+ _ ->
+ {error, {connection_error, stream_closed,
+ 'HEADERS frame received on a stream in closed or half-closed state. (RFC7540 5.1)'},
+ State}
+ end;
+%% Reject HEADERS frames received on idle streams.
+client_headers_frame(_, State) ->
+ {error, {connection_error, protocol_error,
+ 'HEADERS frame received on an idle stream. (RFC7540 5.1.1)'},
+ State}.
+
+headers_decode(Frame=#headers{head=head_fin, data=HeaderData},
+ State=#http2_machine{decode_state=DecodeState0}, Type, Stream) ->
+ try cow_hpack:decode(HeaderData, DecodeState0) of
+ {Headers, DecodeState} when Type =:= request ->
+ headers_enforce_concurrency_limit(Frame,
+ State#http2_machine{decode_state=DecodeState}, Type, Stream, Headers);
+ {Headers, DecodeState} ->
+ headers_pseudo_headers(Frame,
+ State#http2_machine{decode_state=DecodeState}, Type, Stream, Headers)
+ catch _:_ ->
+ {error, {connection_error, compression_error,
+ 'Error while trying to decode HPACK-encoded header block. (RFC7540 4.3)'},
+ State}
+ end.
+
+headers_enforce_concurrency_limit(Frame=#headers{id=StreamID},
+ State=#http2_machine{local_settings=LocalSettings, streams=Streams},
+ Type, Stream, Headers) ->
+ MaxConcurrentStreams = maps:get(max_concurrent_streams, LocalSettings, infinity),
+ %% Using < is correct because this new stream is not included
+ %% in the Streams variable yet and so we'll end up with +1 stream.
+ case map_size(Streams) < MaxConcurrentStreams of
+ true ->
+ headers_pseudo_headers(Frame, State, Type, Stream, Headers);
+ false ->
+ {error, {stream_error, StreamID, refused_stream,
+ 'Maximum number of concurrent streams has been reached. (RFC7540 5.1.2)'},
+ State}
+ end.
+
+headers_pseudo_headers(Frame, State=#http2_machine{local_settings=LocalSettings},
+ Type, Stream, Headers0) when Type =:= request; Type =:= push_promise ->
+ IsExtendedConnectEnabled = maps:get(enable_connect_protocol, LocalSettings, false),
+ case request_pseudo_headers(Headers0, #{}) of
+ %% Extended CONNECT method (RFC8441).
+ {ok, PseudoHeaders=#{method := <<"CONNECT">>, scheme := _,
+ authority := _, path := _, protocol := _}, Headers}
+ when IsExtendedConnectEnabled ->
+ headers_regular_headers(Frame, State, Type, Stream, PseudoHeaders, Headers);
+ {ok, #{method := <<"CONNECT">>, scheme := _,
+ authority := _, path := _}, _}
+ when IsExtendedConnectEnabled ->
+ headers_malformed(Frame, State,
+ 'The :protocol pseudo-header MUST be sent with an extended CONNECT. (RFC8441 4)');
+ {ok, #{protocol := _}, _} ->
+ headers_malformed(Frame, State,
+ 'The :protocol pseudo-header is only defined for the extended CONNECT. (RFC8441 4)');
+ %% Normal CONNECT (no scheme/path).
+ {ok, PseudoHeaders=#{method := <<"CONNECT">>, authority := _}, Headers}
+ when map_size(PseudoHeaders) =:= 2 ->
+ headers_regular_headers(Frame, State, Type, Stream, PseudoHeaders, Headers);
+ {ok, #{method := <<"CONNECT">>}, _} ->
+ headers_malformed(Frame, State,
+ 'CONNECT requests only use the :method and :authority pseudo-headers. (RFC7540 8.3)');
+ %% Other requests.
+ {ok, PseudoHeaders=#{method := _, scheme := _, path := _}, Headers} ->
+ headers_regular_headers(Frame, State, Type, Stream, PseudoHeaders, Headers);
+ {ok, _, _} ->
+ headers_malformed(Frame, State,
+ 'A required pseudo-header was not found. (RFC7540 8.1.2.3)');
+ {error, HumanReadable} ->
+ headers_malformed(Frame, State, HumanReadable)
+ end;
+headers_pseudo_headers(Frame=#headers{id=StreamID},
+ State, Type=response, Stream, Headers0) ->
+ case response_pseudo_headers(Headers0, #{}) of
+ {ok, PseudoHeaders=#{status := _}, Headers} ->
+ headers_regular_headers(Frame, State, Type, Stream, PseudoHeaders, Headers);
+ {ok, _, _} ->
+ stream_reset(StreamID, State, protocol_error,
+ 'A required pseudo-header was not found. (RFC7540 8.1.2.4)');
+ {error, HumanReadable} ->
+ stream_reset(StreamID, State, protocol_error, HumanReadable)
+ end;
+headers_pseudo_headers(Frame=#headers{id=StreamID},
+ State, Type=trailers, Stream, Headers) ->
+ case trailers_contain_pseudo_headers(Headers) of
+ false ->
+ headers_regular_headers(Frame, State, Type, Stream, #{}, Headers);
+ true ->
+ stream_reset(StreamID, State, protocol_error,
+ 'Trailer header blocks must not contain pseudo-headers. (RFC7540 8.1.2.1)')
+ end.
+
+headers_malformed(#headers{id=StreamID}, State, HumanReadable) ->
+ {error, {stream_error, StreamID, protocol_error, HumanReadable}, State}.
+
+request_pseudo_headers([{<<":method">>, _}|_], #{method := _}) ->
+ {error, 'Multiple :method pseudo-headers were found. (RFC7540 8.1.2.3)'};
+request_pseudo_headers([{<<":method">>, Method}|Tail], PseudoHeaders) ->
+ request_pseudo_headers(Tail, PseudoHeaders#{method => Method});
+request_pseudo_headers([{<<":scheme">>, _}|_], #{scheme := _}) ->
+ {error, 'Multiple :scheme pseudo-headers were found. (RFC7540 8.1.2.3)'};
+request_pseudo_headers([{<<":scheme">>, Scheme}|Tail], PseudoHeaders) ->
+ request_pseudo_headers(Tail, PseudoHeaders#{scheme => Scheme});
+request_pseudo_headers([{<<":authority">>, _}|_], #{authority := _}) ->
+ {error, 'Multiple :authority pseudo-headers were found. (RFC7540 8.1.2.3)'};
+request_pseudo_headers([{<<":authority">>, Authority}|Tail], PseudoHeaders) ->
+ request_pseudo_headers(Tail, PseudoHeaders#{authority => Authority});
+request_pseudo_headers([{<<":path">>, _}|_], #{path := _}) ->
+ {error, 'Multiple :path pseudo-headers were found. (RFC7540 8.1.2.3)'};
+request_pseudo_headers([{<<":path">>, Path}|Tail], PseudoHeaders) ->
+ request_pseudo_headers(Tail, PseudoHeaders#{path => Path});
+request_pseudo_headers([{<<":protocol">>, _}|_], #{protocol := _}) ->
+ {error, 'Multiple :protocol pseudo-headers were found. (RFC7540 8.1.2.3)'};
+request_pseudo_headers([{<<":protocol">>, Protocol}|Tail], PseudoHeaders) ->
+ request_pseudo_headers(Tail, PseudoHeaders#{protocol => Protocol});
+request_pseudo_headers([{<<":", _/bits>>, _}|_], _) ->
+ {error, 'An unknown or invalid pseudo-header was found. (RFC7540 8.1.2.1)'};
+request_pseudo_headers(Headers, PseudoHeaders) ->
+ {ok, PseudoHeaders, Headers}.
+
+response_pseudo_headers([{<<":status">>, _}|_], #{status := _}) ->
+ {error, 'Multiple :status pseudo-headers were found. (RFC7540 8.1.2.3)'};
+response_pseudo_headers([{<<":status">>, Status}|Tail], PseudoHeaders) ->
+ try cow_http:status_to_integer(Status) of
+ IntStatus ->
+ response_pseudo_headers(Tail, PseudoHeaders#{status => IntStatus})
+ catch _:_ ->
+ {error, 'The :status pseudo-header value is invalid. (RFC7540 8.1.2.4)'}
+ end;
+response_pseudo_headers([{<<":", _/bits>>, _}|_], _) ->
+ {error, 'An unknown or invalid pseudo-header was found. (RFC7540 8.1.2.1)'};
+response_pseudo_headers(Headers, PseudoHeaders) ->
+ {ok, PseudoHeaders, Headers}.
+
+trailers_contain_pseudo_headers([]) ->
+ false;
+trailers_contain_pseudo_headers([{<<":", _/bits>>, _}|_]) ->
+ true;
+trailers_contain_pseudo_headers([_|Tail]) ->
+ trailers_contain_pseudo_headers(Tail).
+
+%% Rejecting invalid regular headers might be a bit too strong for clients.
+headers_regular_headers(Frame=#headers{id=StreamID},
+ State, Type, Stream, PseudoHeaders, Headers) ->
+ case regular_headers(Headers, Type) of
+ ok when Type =:= request ->
+ request_expected_size(Frame, State, Type, Stream, PseudoHeaders, Headers);
+ ok when Type =:= push_promise ->
+ push_promise_frame(Frame, State, Stream, PseudoHeaders, Headers);
+ ok when Type =:= response ->
+ response_expected_size(Frame, State, Type, Stream, PseudoHeaders, Headers);
+ ok when Type =:= trailers ->
+ trailers_frame(Frame, State, Stream, Headers);
+ {error, HumanReadable} when Type =:= request ->
+ headers_malformed(Frame, State, HumanReadable);
+ {error, HumanReadable} ->
+ stream_reset(StreamID, State, protocol_error, HumanReadable)
+ end.
+
+regular_headers([{<<>>, _}|_], _) ->
+ {error, 'Empty header names are not valid regular headers. (CVE-2019-9516)'};
+regular_headers([{<<":", _/bits>>, _}|_], _) ->
+ {error, 'Pseudo-headers were found after regular headers. (RFC7540 8.1.2.1)'};
+regular_headers([{<<"connection">>, _}|_], _) ->
+ {error, 'The connection header is not allowed. (RFC7540 8.1.2.2)'};
+regular_headers([{<<"keep-alive">>, _}|_], _) ->
+ {error, 'The keep-alive header is not allowed. (RFC7540 8.1.2.2)'};
+regular_headers([{<<"proxy-authenticate">>, _}|_], _) ->
+ {error, 'The proxy-authenticate header is not allowed. (RFC7540 8.1.2.2)'};
+regular_headers([{<<"proxy-authorization">>, _}|_], _) ->
+ {error, 'The proxy-authorization header is not allowed. (RFC7540 8.1.2.2)'};
+regular_headers([{<<"transfer-encoding">>, _}|_], _) ->
+ {error, 'The transfer-encoding header is not allowed. (RFC7540 8.1.2.2)'};
+regular_headers([{<<"upgrade">>, _}|_], _) ->
+ {error, 'The upgrade header is not allowed. (RFC7540 8.1.2.2)'};
+regular_headers([{<<"te">>, Value}|_], request) when Value =/= <<"trailers">> ->
+ {error, 'The te header with a value other than "trailers" is not allowed. (RFC7540 8.1.2.2)'};
+regular_headers([{<<"te">>, _}|_], Type) when Type =/= request ->
+ {error, 'The te header is only allowed in request headers. (RFC7540 8.1.2.2)'};
+regular_headers([{Name, _}|Tail], Type) ->
+ Pattern = [
+ <<$A>>, <<$B>>, <<$C>>, <<$D>>, <<$E>>, <<$F>>, <<$G>>, <<$H>>, <<$I>>,
+ <<$J>>, <<$K>>, <<$L>>, <<$M>>, <<$N>>, <<$O>>, <<$P>>, <<$Q>>, <<$R>>,
+ <<$S>>, <<$T>>, <<$U>>, <<$V>>, <<$W>>, <<$X>>, <<$Y>>, <<$Z>>
+ ],
+ case binary:match(Name, Pattern) of
+ nomatch -> regular_headers(Tail, Type);
+ _ -> {error, 'Header names must be lowercase. (RFC7540 8.1.2)'}
+ end;
+regular_headers([], _) ->
+ ok.
+
+request_expected_size(Frame=#headers{fin=IsFin}, State, Type, Stream, PseudoHeaders, Headers) ->
+ case [CL || {<<"content-length">>, CL} <- Headers] of
+ [] when IsFin =:= fin ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
+ [] ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, undefined);
+ [<<"0">>] when IsFin =:= fin ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
+ [_] when IsFin =:= fin ->
+ headers_malformed(Frame, State,
+ 'HEADERS frame with the END_STREAM flag contains a non-zero content-length. (RFC7540 8.1.2.6)');
+ [BinLen] ->
+ headers_parse_expected_size(Frame, State, Type, Stream,
+ PseudoHeaders, Headers, BinLen);
+ _ ->
+ headers_malformed(Frame, State,
+ 'Multiple content-length headers were received. (RFC7230 3.3.2)')
+ end.
+
+response_expected_size(Frame=#headers{id=StreamID, fin=IsFin}, State, Type,
+ Stream=#stream{method=Method}, PseudoHeaders=#{status := Status}, Headers) ->
+ case [CL || {<<"content-length">>, CL} <- Headers] of
+ [] when IsFin =:= fin ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
+ [] ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, undefined);
+ [_] when Status >= 100, Status =< 199 ->
+ stream_reset(StreamID, State, protocol_error,
+ 'Content-length header received in a 1xx response. (RFC7230 3.3.2)');
+ [_] when Status =:= 204 ->
+ stream_reset(StreamID, State, protocol_error,
+ 'Content-length header received in a 204 response. (RFC7230 3.3.2)');
+ [_] when Status >= 200, Status =< 299, Method =:= <<"CONNECT">> ->
+ stream_reset(StreamID, State, protocol_error,
+ 'Content-length header received in a 2xx response to a CONNECT request. (RFC7230 3.3.2).');
+ %% Responses to HEAD requests, and 304 responses may contain
+ %% a content-length header that must be ignored. (RFC7230 3.3.2)
+ [_] when Method =:= <<"HEAD">> ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
+ [_] when Status =:= 304 ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
+ [<<"0">>] when IsFin =:= fin ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
+ [_] when IsFin =:= fin ->
+ stream_reset(StreamID, State, protocol_error,
+ 'HEADERS frame with the END_STREAM flag contains a non-zero content-length. (RFC7540 8.1.2.6)');
+ [BinLen] ->
+ headers_parse_expected_size(Frame, State, Type, Stream,
+ PseudoHeaders, Headers, BinLen);
+ _ ->
+ stream_reset(StreamID, State, protocol_error,
+ 'Multiple content-length headers were received. (RFC7230 3.3.2)')
+ end.
+
+headers_parse_expected_size(Frame=#headers{id=StreamID},
+ State, Type, Stream, PseudoHeaders, Headers, BinLen) ->
+ try cow_http_hd:parse_content_length(BinLen) of
+ Len ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, Len)
+ catch
+ _:_ ->
+ HumanReadable = 'The content-length header is invalid. (RFC7230 3.3.2)',
+ case Type of
+ request -> headers_malformed(Frame, State, HumanReadable);
+ response -> stream_reset(StreamID, State, protocol_error, HumanReadable)
+ end
+ end.
+
+headers_frame(#headers{id=StreamID, fin=IsFin}, State0=#http2_machine{
+ local_settings=#{initial_window_size := RemoteWindow},
+ remote_settings=#{initial_window_size := LocalWindow}},
+ Type, Stream0, PseudoHeaders, Headers, Len) ->
+ {Stream, State1} = case Type of
+ request ->
+ TE = case lists:keyfind(<<"te">>, 1, Headers) of
+ {_, TE0} -> TE0;
+ false -> undefined
+ end,
+ {#stream{id=StreamID, method=maps:get(method, PseudoHeaders),
+ remote=IsFin, remote_expected_size=Len,
+ local_window=LocalWindow, remote_window=RemoteWindow, te=TE},
+ State0#http2_machine{remote_streamid=StreamID}};
+ response ->
+ Stream1 = case PseudoHeaders of
+ #{status := Status} when Status >= 100, Status =< 199 -> Stream0;
+ _ -> Stream0#stream{remote=IsFin, remote_expected_size=Len}
+ end,
+ {Stream1, State0}
+ end,
+ State = stream_store(Stream, State1),
+ {ok, {headers, StreamID, IsFin, Headers, PseudoHeaders, Len}, State}.
+
+trailers_frame(#headers{id=StreamID}, State0, Stream0, Headers) ->
+ Stream = Stream0#stream{remote=fin},
+ State = stream_store(Stream, State0),
+ case is_body_size_valid(Stream) of
+ true ->
+ {ok, {trailers, StreamID, Headers}, State};
+ false ->
+ stream_reset(StreamID, State, protocol_error,
+ 'The total size of DATA frames is different than the content-length. (RFC7540 8.1.2.6)')
+ end.
+
+%% PRIORITY frame.
+%%
+%% @todo Handle PRIORITY frames.
+
+priority_frame(_Frame, State) ->
+ {ok, State}.
+
+%% RST_STREAM frame.
+
+rst_stream_frame({rst_stream, StreamID, _}, State=#http2_machine{mode=Mode,
+ local_streamid=LocalStreamID, remote_streamid=RemoteStreamID})
+ when (?IS_LOCAL(Mode, StreamID) andalso (StreamID >= LocalStreamID))
+ orelse ((not ?IS_LOCAL(Mode, StreamID)) andalso (StreamID > RemoteStreamID)) ->
+ {error, {connection_error, protocol_error,
+ 'RST_STREAM frame received on a stream in idle state. (RFC7540 5.1)'},
+ State};
+rst_stream_frame({rst_stream, StreamID, Reason}, State=#http2_machine{
+ streams=Streams0, remote_lingering_streams=Lingering0}) ->
+ Streams = maps:remove(StreamID, Streams0),
+ %% We only keep up to 10 streams in this state. @todo Make it configurable?
+ Lingering = [StreamID|lists:sublist(Lingering0, 10 - 1)],
+ {ok, {rst_stream, StreamID, Reason},
+ State#http2_machine{streams=Streams, remote_lingering_streams=Lingering}}.
+
+%% SETTINGS frame.
+
+settings_frame({settings, Settings}, State0=#http2_machine{
+ opts=Opts, remote_settings=Settings0}) ->
+ State1 = State0#http2_machine{remote_settings=maps:merge(Settings0, Settings)},
+ State2 = maps:fold(fun
+ (header_table_size, NewSize, State=#http2_machine{encode_state=EncodeState0}) ->
+ MaxSize = maps:get(max_encode_table_size, Opts, 4096),
+ EncodeState = cow_hpack:set_max_size(min(NewSize, MaxSize), EncodeState0),
+ State#http2_machine{encode_state=EncodeState};
+ (initial_window_size, NewWindowSize, State) ->
+ OldWindowSize = maps:get(initial_window_size, Settings0, 65535),
+ streams_update_local_window(State, NewWindowSize - OldWindowSize);
+ (_, _, State) ->
+ State
+ end, State1, Settings),
+ case Settings of
+ #{initial_window_size := _} -> send_data(State2);
+ _ -> {ok, State2}
+ end;
+%% We expect to receive a SETTINGS frame as part of the preface.
+settings_frame(_F, State=#http2_machine{mode=server}) ->
+ {error, {connection_error, protocol_error,
+ 'The preface sequence must be followed by a SETTINGS frame. (RFC7540 3.5)'},
+ State};
+settings_frame(_F, State) ->
+ {error, {connection_error, protocol_error,
+ 'The preface must begin with a SETTINGS frame. (RFC7540 3.5)'},
+ State}.
+
+%% When SETTINGS_INITIAL_WINDOW_SIZE changes we need to update
+%% the local stream windows for all active streams and perhaps
+%% resume sending data.
+streams_update_local_window(State=#http2_machine{streams=Streams0}, Increment) ->
+ Streams = maps:map(fun(_, S=#stream{local_window=StreamWindow}) ->
+ S#stream{local_window=StreamWindow + Increment}
+ end, Streams0),
+ State#http2_machine{streams=Streams}.
+
+%% Ack for a previously sent SETTINGS frame.
+
+settings_ack_frame(State0=#http2_machine{settings_timer=TRef,
+ local_settings=Local0, next_settings=NextSettings}) ->
+ ok = case TRef of
+ undefined -> ok;
+ _ -> erlang:cancel_timer(TRef, [{async, true}, {info, false}])
+ end,
+ Local = maps:merge(Local0, NextSettings),
+ State1 = State0#http2_machine{settings_timer=undefined,
+ local_settings=Local, next_settings=#{}},
+ {ok, maps:fold(fun
+ (header_table_size, MaxSize, State=#http2_machine{decode_state=DecodeState0}) ->
+ DecodeState = cow_hpack:set_max_size(MaxSize, DecodeState0),
+ State#http2_machine{decode_state=DecodeState};
+ (initial_window_size, NewWindowSize, State) ->
+ OldWindowSize = maps:get(initial_window_size, Local0, 65535),
+ streams_update_remote_window(State, NewWindowSize - OldWindowSize);
+ (_, _, State) ->
+ State
+ end, State1, NextSettings)}.
+
+%% When we receive an ack to a SETTINGS frame we sent we need to update
+%% the remote stream windows for all active streams.
+streams_update_remote_window(State=#http2_machine{streams=Streams0}, Increment) ->
+ Streams = maps:map(fun(_, S=#stream{remote_window=StreamWindow}) ->
+ S#stream{remote_window=StreamWindow + Increment}
+ end, Streams0),
+ State#http2_machine{streams=Streams}.
+
+%% PUSH_PROMISE frame.
+
+%% Convenience record to manipulate the tuple.
+%% The order of the fields matter.
+-record(push_promise, {
+ id :: cow_http2:streamid(),
+ head :: cow_http2:head_fin(),
+ promised_id :: cow_http2:streamid(),
+ data :: binary()
+}).
+
+push_promise_frame(_, State=#http2_machine{mode=server}) ->
+ {error, {connection_error, protocol_error,
+ 'PUSH_PROMISE frames MUST NOT be sent by the client. (RFC7540 6.6)'},
+ State};
+push_promise_frame(_, State=#http2_machine{local_settings=#{enable_push := false}}) ->
+ {error, {connection_error, protocol_error,
+ 'PUSH_PROMISE frame received despite SETTINGS_ENABLE_PUSH set to 0. (RFC7540 6.6)'},
+ State};
+push_promise_frame(#push_promise{promised_id=PromisedStreamID},
+ State=#http2_machine{remote_streamid=RemoteStreamID})
+ when PromisedStreamID =< RemoteStreamID ->
+ {error, {connection_error, protocol_error,
+ 'PUSH_PROMISE frame received for a promised stream in closed or half-closed state. (RFC7540 5.1, RFC7540 6.6)'},
+ State};
+push_promise_frame(#push_promise{id=StreamID}, State)
+ when not ?IS_CLIENT_LOCAL(StreamID) ->
+ {error, {connection_error, protocol_error,
+ 'PUSH_PROMISE frame received on a server-initiated stream. (RFC7540 6.6)'},
+ State};
+push_promise_frame(Frame=#push_promise{id=StreamID, head=IsHeadFin,
+ promised_id=PromisedStreamID, data=HeaderData}, State) ->
+ case stream_get(StreamID, State) of
+ Stream=#stream{remote=idle} ->
+ case IsHeadFin of
+ head_fin ->
+ headers_decode(#headers{id=PromisedStreamID,
+ fin=fin, head=IsHeadFin, data=HeaderData},
+ State, push_promise, Stream);
+ head_nofin ->
+ {ok, State#http2_machine{state={continuation, push_promise, Frame}}}
+ end;
+ _ ->
+%% @todo Check if the stream is lingering. If it is, decode the frame
+%% and do what? That's the big question and why it's not implemented yet.
+% However, an endpoint that
+% has sent RST_STREAM on the associated stream MUST handle PUSH_PROMISE
+% frames that might have been created before the RST_STREAM frame is
+% received and processed. (RFC7540 6.6)
+ {error, {connection_error, stream_closed,
+ 'PUSH_PROMISE frame received on a stream in closed or half-closed state. (RFC7540 5.1, RFC7540 6.6)'},
+ State}
+ end.
+
+push_promise_frame(#headers{id=PromisedStreamID},
+ State0=#http2_machine{
+ local_settings=#{initial_window_size := RemoteWindow},
+ remote_settings=#{initial_window_size := LocalWindow}},
+ #stream{id=StreamID}, PseudoHeaders=#{method := Method}, Headers) ->
+ TE = case lists:keyfind(<<"te">>, 1, Headers) of
+ {_, TE0} -> TE0;
+ false -> undefined
+ end,
+ PromisedStream = #stream{id=PromisedStreamID, method=Method,
+ local=fin, local_window=LocalWindow,
+ remote_window=RemoteWindow, te=TE},
+ State = stream_store(PromisedStream,
+ State0#http2_machine{remote_streamid=PromisedStreamID}),
+ {ok, {push_promise, StreamID, PromisedStreamID, Headers, PseudoHeaders}, State}.
+
+%% PING frame.
+
+ping_frame({ping, _}, State) ->
+ {ok, State}.
+
+%% Ack for a previously sent PING frame.
+%%
+%% @todo Might want to check contents but probably a waste of time.
+
+ping_ack_frame({ping_ack, _}, State) ->
+ {ok, State}.
+
+%% GOAWAY frame.
+
+goaway_frame(Frame={goaway, _, _, _}, State) ->
+ {ok, Frame, State}.
+
+%% WINDOW_UPDATE frame.
+
+%% Connection-wide WINDOW_UPDATE frame.
+window_update_frame({window_update, Increment}, State=#http2_machine{local_window=ConnWindow})
+ when ConnWindow + Increment > 16#7fffffff ->
+ {error, {connection_error, flow_control_error,
+ 'The flow control window must not be greater than 2^31-1. (RFC7540 6.9.1)'},
+ State};
+window_update_frame({window_update, Increment}, State=#http2_machine{local_window=ConnWindow}) ->
+ send_data(State#http2_machine{local_window=ConnWindow + Increment});
+%% Stream-specific WINDOW_UPDATE frame.
+window_update_frame({window_update, StreamID, _}, State=#http2_machine{mode=Mode,
+ local_streamid=LocalStreamID, remote_streamid=RemoteStreamID})
+ when (?IS_LOCAL(Mode, StreamID) andalso (StreamID >= LocalStreamID))
+ orelse ((not ?IS_LOCAL(Mode, StreamID)) andalso (StreamID > RemoteStreamID)) ->
+ {error, {connection_error, protocol_error,
+ 'WINDOW_UPDATE frame received on a stream in idle state. (RFC7540 5.1)'},
+ State};
+window_update_frame({window_update, StreamID, Increment},
+ State0=#http2_machine{remote_lingering_streams=Lingering}) ->
+ case stream_get(StreamID, State0) of
+ #stream{local_window=StreamWindow} when StreamWindow + Increment > 16#7fffffff ->
+ stream_reset(StreamID, State0, flow_control_error,
+ 'The flow control window must not be greater than 2^31-1. (RFC7540 6.9.1)');
+ Stream0 = #stream{local_window=StreamWindow} ->
+ send_data(Stream0#stream{local_window=StreamWindow + Increment}, State0);
+ undefined ->
+ %% WINDOW_UPDATE frames may be received for a short period of time
+ %% after a stream is closed. They must be ignored.
+ case lists:member(StreamID, Lingering) of
+ false -> {ok, State0};
+ true -> stream_reset(StreamID, State0, stream_closed,
+ 'WINDOW_UPDATE frame received after the stream was reset. (RFC7540 5.1)')
+ end
+ end.
+
+%% CONTINUATION frame.
+
+%% Convenience record to manipulate the tuple.
+%% The order of the fields matter.
+-record(continuation, {
+ id :: cow_http2:streamid(),
+ head :: cow_http2:head_fin(),
+ data :: binary()
+}).
+
+unexpected_continuation_frame(#continuation{}, State) ->
+ {error, {connection_error, protocol_error,
+ 'CONTINUATION frames MUST be preceded by a HEADERS or PUSH_PROMISE frame. (RFC7540 6.10)'},
+ State}.
+
+continuation_frame(#continuation{id=StreamID, head=head_fin, data=HeaderFragment1},
+ State=#http2_machine{state={continuation, Type,
+ Frame=#headers{id=StreamID, data=HeaderFragment0}}}) ->
+ HeaderData = <<HeaderFragment0/binary, HeaderFragment1/binary>>,
+ headers_decode(Frame#headers{head=head_fin, data=HeaderData},
+ State#http2_machine{state=normal}, Type, stream_get(StreamID, State));
+continuation_frame(#continuation{id=StreamID, head=head_fin, data=HeaderFragment1},
+ State=#http2_machine{state={continuation, Type, #push_promise{
+ id=StreamID, promised_id=PromisedStreamID, data=HeaderFragment0}}}) ->
+ HeaderData = <<HeaderFragment0/binary, HeaderFragment1/binary>>,
+ headers_decode(#headers{id=PromisedStreamID, fin=fin, head=head_fin, data=HeaderData},
+ State#http2_machine{state=normal}, Type, undefined);
+continuation_frame(#continuation{id=StreamID, data=HeaderFragment1},
+ State=#http2_machine{state={continuation, Type, ContinuedFrame0}})
+ when element(2, ContinuedFrame0) =:= StreamID ->
+ ContinuedFrame = case ContinuedFrame0 of
+ #headers{data=HeaderFragment0} ->
+ HeaderData = <<HeaderFragment0/binary, HeaderFragment1/binary>>,
+ ContinuedFrame0#headers{data=HeaderData};
+ #push_promise{data=HeaderFragment0} ->
+ HeaderData = <<HeaderFragment0/binary, HeaderFragment1/binary>>,
+ ContinuedFrame0#push_promise{data=HeaderData}
+ end,
+ {ok, State#http2_machine{state={continuation, Type, ContinuedFrame}}};
+continuation_frame(_F, State) ->
+ {error, {connection_error, protocol_error,
+ 'An invalid frame was received in the middle of a header block. (RFC7540 6.2)'},
+ State}.
+
+%% Ignored frames.
+
+-spec ignored_frame(State)
+ -> {ok, State}
+ | {error, {connection_error, protocol_error, atom()}, State}
+ when State::http2_machine().
+ignored_frame(State=#http2_machine{state={continuation, _, _}}) ->
+ {error, {connection_error, protocol_error,
+ 'An invalid frame was received in the middle of a header block. (RFC7540 6.2)'},
+ State};
+%% @todo It might be useful to error out when we receive
+%% too many unknown frames. (RFC7540 10.5)
+ignored_frame(State) ->
+ {ok, State}.
+
+%% Timeouts.
+
+-spec timeout(preface_timeout | settings_timeout, reference(), State)
+ -> {ok, State}
+ | {error, {connection_error, cow_http2:error(), atom()}, State}
+ when State::http2_machine().
+timeout(preface_timeout, TRef, State=#http2_machine{preface_timer=TRef}) ->
+ {error, {connection_error, protocol_error,
+ 'The preface was not received in a reasonable amount of time.'},
+ State};
+timeout(settings_timeout, TRef, State=#http2_machine{settings_timer=TRef}) ->
+ {error, {connection_error, settings_timeout,
+ 'The SETTINGS ack was not received within the configured time. (RFC7540 6.5.3)'},
+ State};
+timeout(_, _, State) ->
+ {ok, State}.
+
+%% Functions for sending a message header or body. Note that
+%% this module does not send data directly, instead it returns
+%% a value that can then be used to send the frames.
+
+-spec prepare_headers(cow_http2:streamid(), State, idle | cow_http2:fin(),
+ pseudo_headers(), cow_http:headers())
+ -> {ok, cow_http2:fin(), iodata(), State} when State::http2_machine().
+prepare_headers(StreamID, State=#http2_machine{encode_state=EncodeState0},
+ IsFin0, PseudoHeaders, Headers0) ->
+ Stream = #stream{method=Method, local=idle} = stream_get(StreamID, State),
+ IsFin = case {IsFin0, Method} of
+ {idle, _} -> nofin;
+ {_, <<"HEAD">>} -> fin;
+ _ -> IsFin0
+ end,
+ Headers = merge_pseudo_headers(PseudoHeaders, remove_http11_headers(Headers0)),
+ {HeaderBlock, EncodeState} = cow_hpack:encode(Headers, EncodeState0),
+ {ok, IsFin, HeaderBlock, stream_store(Stream#stream{local=IsFin0},
+ State#http2_machine{encode_state=EncodeState})}.
+
+-spec prepare_push_promise(cow_http2:streamid(), State, pseudo_headers(), cow_http:headers())
+ -> {ok, cow_http2:streamid(), iodata(), State}
+ | {error, no_push} when State::http2_machine().
+prepare_push_promise(_, #http2_machine{remote_settings=#{enable_push := false}}, _, _) ->
+ {error, no_push};
+prepare_push_promise(StreamID, State=#http2_machine{encode_state=EncodeState0,
+ local_settings=#{initial_window_size := RemoteWindow},
+ remote_settings=#{initial_window_size := LocalWindow},
+ local_streamid=LocalStreamID}, PseudoHeaders, Headers0) ->
+ #stream{local=idle} = stream_get(StreamID, State),
+ TE = case lists:keyfind(<<"te">>, 1, Headers0) of
+ {_, TE0} -> TE0;
+ false -> undefined
+ end,
+ Headers = merge_pseudo_headers(PseudoHeaders, remove_http11_headers(Headers0)),
+ {HeaderBlock, EncodeState} = cow_hpack:encode(Headers, EncodeState0),
+ {ok, LocalStreamID, HeaderBlock, stream_store(
+ #stream{id=LocalStreamID, method=maps:get(method, PseudoHeaders),
+ remote=fin, remote_expected_size=0,
+ local_window=LocalWindow, remote_window=RemoteWindow, te=TE},
+ State#http2_machine{encode_state=EncodeState, local_streamid=LocalStreamID + 2})}.
+
+remove_http11_headers(Headers) ->
+ RemoveHeaders0 = [
+ <<"keep-alive">>,
+ <<"proxy-connection">>,
+ <<"transfer-encoding">>,
+ <<"upgrade">>
+ ],
+ RemoveHeaders = case lists:keyfind(<<"connection">>, 1, Headers) of
+ false ->
+ RemoveHeaders0;
+ {_, ConnHd} ->
+ %% We do not need to worry about any "close" header because
+ %% that header name is reserved.
+ Connection = cow_http_hd:parse_connection(ConnHd),
+ Connection ++ [<<"connection">>|RemoveHeaders0]
+ end,
+ lists:filter(fun({Name, _}) ->
+ not lists:member(Name, RemoveHeaders)
+ end, Headers).
+
+merge_pseudo_headers(PseudoHeaders, Headers0) ->
+ lists:foldl(fun
+ ({status, Status}, Acc) when is_integer(Status) ->
+ [{<<":status">>, integer_to_binary(Status)}|Acc];
+ ({Name, Value}, Acc) ->
+ [{iolist_to_binary([$:, atom_to_binary(Name, latin1)]), Value}|Acc]
+ end, Headers0, maps:to_list(PseudoHeaders)).
+
+-spec prepare_trailers(cow_http2:streamid(), State, cow_http:headers())
+ -> {ok, iodata(), State} when State::http2_machine().
+prepare_trailers(StreamID, State=#http2_machine{encode_state=EncodeState0}, Trailers) ->
+ Stream = #stream{local=nofin} = stream_get(StreamID, State),
+ {HeaderBlock, EncodeState} = cow_hpack:encode(Trailers, EncodeState0),
+ {ok, HeaderBlock, stream_store(Stream#stream{local=fin},
+ State#http2_machine{encode_state=EncodeState})}.
+
+-spec send_or_queue_data(cow_http2:streamid(), State, cow_http2:fin(), DataOrFileOrTrailers)
+ -> {ok, State}
+ | {send, [{cow_http2:streamid(), cow_http2:fin(), [DataOrFileOrTrailers]}], State}
+ when State::http2_machine(), DataOrFileOrTrailers::
+ {data, iodata()} | #sendfile{} | {trailers, cow_http:headers()}.
+send_or_queue_data(StreamID, State0=#http2_machine{opts=Opts, local_window=ConnWindow},
+ IsFin0, DataOrFileOrTrailers0) ->
+ %% @todo Probably just ignore if the method was HEAD.
+ Stream0 = #stream{
+ local=nofin,
+ local_window=StreamWindow,
+ local_buffer_size=BufferSize,
+ te=TE0
+ } = stream_get(StreamID, State0),
+ DataOrFileOrTrailers = case DataOrFileOrTrailers0 of
+ {trailers, _} ->
+ %% We only accept TE headers containing exactly "trailers" (RFC7540 8.1.2.1).
+ TE = try cow_http_hd:parse_te(TE0) of
+ {trailers, []} -> trailers;
+ _ -> no_trailers
+ catch _:_ ->
+ %% If we can't parse the TE header, assume we can't send trailers.
+ no_trailers
+ end,
+ case TE of
+ trailers ->
+ DataOrFileOrTrailers0;
+ no_trailers ->
+ {data, <<>>}
+ end;
+ _ ->
+ DataOrFileOrTrailers0
+ end,
+ SendSize = case DataOrFileOrTrailers of
+ {data, D} -> BufferSize + iolist_size(D);
+ #sendfile{bytes=B} -> BufferSize + B;
+ {trailers, _} -> 0
+ end,
+ MinSendSize = maps:get(stream_window_data_threshold, Opts, 16384),
+ if
+ %% If we cannot send the data all at once and the window
+ %% is smaller than we are willing to send at a minimum,
+ %% we queue the data directly.
+ (StreamWindow < MinSendSize)
+ andalso ((StreamWindow < SendSize) orelse (ConnWindow < SendSize)) ->
+ {ok, stream_store(queue_data(Stream0, IsFin0, DataOrFileOrTrailers, in), State0)};
+ true ->
+ case send_or_queue_data(Stream0, State0, [], IsFin0, DataOrFileOrTrailers, in) of
+ {ok, Stream, State, []} ->
+ {ok, stream_store(Stream, State)};
+ {ok, Stream=#stream{local=IsFin}, State, SendData} ->
+ {send, [{StreamID, IsFin, lists:reverse(SendData)}], stream_store(Stream, State)}
+ end
+ end.
+
+%% Internal data sending/queuing functions.
+
+%% @todo Should we ever want to implement the PRIORITY mechanism,
+%% this would be the place to do it. Right now, we just go over
+%% all streams and send what we can until either everything is
+%% sent or we run out of space in the window.
+send_data(State0=#http2_machine{streams=Streams0}) ->
+ Iterator = maps:iterator(Streams0),
+ case send_data_for_all_streams(maps:next(Iterator), Streams0, State0, []) of
+ {ok, Streams, State, []} ->
+ {ok, State#http2_machine{streams=Streams}};
+ {ok, Streams, State, Send} ->
+ {send, Send, State#http2_machine{streams=Streams}}
+ end.
+
+send_data_for_all_streams(none, Streams, State, Send) ->
+ {ok, Streams, State, Send};
+%% While technically we should never get < 0 here, let's be on the safe side.
+send_data_for_all_streams(_, Streams, State=#http2_machine{local_window=ConnWindow}, Send)
+ when ConnWindow =< 0 ->
+ {ok, Streams, State, Send};
+%% We rely on send_data_for_one_stream/3 to do all the necessary checks about the stream.
+send_data_for_all_streams({StreamID, Stream0, Iterator}, Streams, State0, Send) ->
+ case send_data_for_one_stream(Stream0, State0, []) of
+ {ok, Stream, State, []} ->
+ send_data_for_all_streams(maps:next(Iterator),
+ Streams#{StreamID => Stream}, State, Send);
+ %% We need to remove the stream here because we do not use stream_store/2.
+ {ok, #stream{local=fin, remote=fin}, State, SendData} ->
+ send_data_for_all_streams(maps:next(Iterator),
+ maps:remove(StreamID, Streams), State, [{StreamID, fin, SendData}|Send]);
+ {ok, Stream=#stream{local=IsFin}, State, SendData} ->
+ send_data_for_all_streams(maps:next(Iterator),
+ Streams#{StreamID => Stream}, State, [{StreamID, IsFin, SendData}|Send])
+ end.
+
+send_data(Stream0, State0) ->
+ case send_data_for_one_stream(Stream0, State0, []) of
+ {ok, Stream, State, []} ->
+ {ok, stream_store(Stream, State)};
+ {ok, Stream=#stream{id=StreamID, local=IsFin}, State, SendData} ->
+ {send, [{StreamID, IsFin, SendData}], stream_store(Stream, State)}
+ end.
+
+send_data_for_one_stream(Stream=#stream{local=nofin, local_buffer_size=0,
+ local_trailers=Trailers}, State, SendAcc) when Trailers =/= undefined ->
+ {ok, Stream, State, lists:reverse([{trailers, Trailers}|SendAcc])};
+send_data_for_one_stream(Stream=#stream{local=nofin, local_buffer=Q0, local_buffer_size=0},
+ State, SendAcc) ->
+ case queue:len(Q0) of
+ 0 ->
+ {ok, Stream, State, lists:reverse(SendAcc)};
+ 1 ->
+ %% We know there is a final empty data frame in the queue.
+ %% We need to mark the stream as complete.
+ {{value, {fin, 0, _}}, Q} = queue:out(Q0),
+ {ok, Stream#stream{local=fin, local_buffer=Q}, State, lists:reverse(SendAcc)}
+ end;
+send_data_for_one_stream(Stream=#stream{local=IsFin, local_window=StreamWindow,
+ local_buffer_size=BufferSize}, State=#http2_machine{local_window=ConnWindow}, SendAcc)
+ when ConnWindow =< 0; IsFin =:= fin; StreamWindow =< 0; BufferSize =:= 0 ->
+ {ok, Stream, State, lists:reverse(SendAcc)};
+send_data_for_one_stream(Stream0=#stream{local_window=StreamWindow,
+ local_buffer=Q0, local_buffer_size=BufferSize},
+ State0=#http2_machine{opts=Opts, local_window=ConnWindow}, SendAcc0) ->
+ MinSendSize = maps:get(stream_window_data_threshold, Opts, 16384),
+ if
+ %% If we cannot send the entire buffer at once and the window
+ %% is smaller than we are willing to send at a minimum, do nothing.
+ %%
+ %% We only do this check the first time we go through this function;
+ %% we want to send as much data as possible IF we send some.
+ (SendAcc0 =:= []) andalso (StreamWindow < MinSendSize)
+ andalso ((StreamWindow < BufferSize) orelse (ConnWindow < BufferSize)) ->
+ {ok, Stream0, State0, []};
+ true ->
+ %% We know there is an item in the queue.
+ {{value, {IsFin, DataSize, Data}}, Q} = queue:out(Q0),
+ Stream1 = Stream0#stream{local_buffer=Q, local_buffer_size=BufferSize - DataSize},
+ {ok, Stream, State, SendAcc}
+ = send_or_queue_data(Stream1, State0, SendAcc0, IsFin, Data, in_r),
+ send_data_for_one_stream(Stream, State, SendAcc)
+ end.
+
+%% We can send trailers immediately if the queue is empty, otherwise we queue.
+%% We always send trailer frames even if the window is empty.
+send_or_queue_data(Stream=#stream{local_buffer_size=0},
+ State, SendAcc, fin, {trailers, Trailers}, _) ->
+ {ok, Stream, State, [{trailers, Trailers}|SendAcc]};
+send_or_queue_data(Stream, State, SendAcc, fin, {trailers, Trailers}, _) ->
+ {ok, Stream#stream{local_trailers=Trailers}, State, SendAcc};
+%% Send data immediately if we can, buffer otherwise.
+send_or_queue_data(Stream=#stream{local_window=StreamWindow},
+ State=#http2_machine{local_window=ConnWindow},
+ SendAcc, IsFin, Data, In)
+ when ConnWindow =< 0; StreamWindow =< 0 ->
+ {ok, queue_data(Stream, IsFin, Data, In), State, SendAcc};
+send_or_queue_data(Stream=#stream{local_window=StreamWindow},
+ State=#http2_machine{opts=Opts, remote_settings=RemoteSettings,
+ local_window=ConnWindow}, SendAcc, IsFin, Data, In) ->
+ RemoteMaxFrameSize = maps:get(max_frame_size, RemoteSettings, 16384),
+ ConfiguredMaxFrameSize = maps:get(max_frame_size_sent, Opts, infinity),
+ MaxSendSize = min(
+ min(ConnWindow, StreamWindow),
+ min(RemoteMaxFrameSize, ConfiguredMaxFrameSize)
+ ),
+ case Data of
+ File = #sendfile{bytes=Bytes} when Bytes =< MaxSendSize ->
+ {ok, Stream#stream{local=IsFin, local_window=StreamWindow - Bytes},
+ State#http2_machine{local_window=ConnWindow - Bytes},
+ [File|SendAcc]};
+ File = #sendfile{offset=Offset, bytes=Bytes} ->
+ send_or_queue_data(Stream#stream{local_window=StreamWindow - MaxSendSize},
+ State#http2_machine{local_window=ConnWindow - MaxSendSize},
+ [File#sendfile{bytes=MaxSendSize}|SendAcc], IsFin,
+ File#sendfile{offset=Offset + MaxSendSize, bytes=Bytes - MaxSendSize}, In);
+ {data, Iolist0} ->
+ IolistSize = iolist_size(Iolist0),
+ if
+ IolistSize =< MaxSendSize ->
+ {ok, Stream#stream{local=IsFin, local_window=StreamWindow - IolistSize},
+ State#http2_machine{local_window=ConnWindow - IolistSize},
+ [{data, Iolist0}|SendAcc]};
+ true ->
+ {Iolist, More} = cow_iolists:split(MaxSendSize, Iolist0),
+ send_or_queue_data(Stream#stream{local_window=StreamWindow - MaxSendSize},
+ State#http2_machine{local_window=ConnWindow - MaxSendSize},
+ [{data, Iolist}|SendAcc], IsFin, {data, More}, In)
+ end
+ end.
+
+queue_data(Stream=#stream{local_buffer=Q0, local_buffer_size=Size0}, IsFin, Data, In) ->
+ DataSize = case Data of
+ {sendfile, _, Bytes, _} -> Bytes;
+ {data, Iolist} -> iolist_size(Iolist)
+ end,
+ %% Never queue non-final empty data frames.
+ case {DataSize, IsFin} of
+ {0, nofin} ->
+ Stream;
+ _ ->
+ Q = queue:In({IsFin, DataSize, Data}, Q0),
+ Stream#stream{local_buffer=Q, local_buffer_size=Size0 + DataSize}
+ end.
+
+%% Public interface to update the flow control window.
+%%
+%% The ensure_window function applies heuristics to avoid updating the
+%% window when it is not necessary. The update_window function updates
+%% the window unconditionally.
+%%
+%% The ensure_window function should be called when requesting more
+%% data (for example when reading a request or response body) as well
+%% as when receiving new data. Failure to do so may result in the
+%% window being depleted.
+%%
+%% The heuristics dictating whether the window must be updated and
+%% what the window size is depends on three options (margin, max
+%% and threshold) along with the Size argument. The window increment
+%% returned by this function may therefore be smaller than the Size
+%% argument. On the other hand the total window allocated over many
+%% calls may end up being larger than the initial Size argument. As
+%% a result, it is the responsibility of the caller to ensure that
+%% the Size argument is never lower than 0.
+
+-spec ensure_window(non_neg_integer(), State)
+ -> ok | {ok, pos_integer(), State} when State::http2_machine().
+ensure_window(Size, State=#http2_machine{opts=Opts, remote_window=RemoteWindow}) ->
+ case ensure_window(Size, RemoteWindow, connection, Opts) of
+ ok ->
+ ok;
+ {ok, Increment} ->
+ {ok, Increment, State#http2_machine{remote_window=RemoteWindow + Increment}}
+ end.
+
+-spec ensure_window(cow_http2:streamid(), non_neg_integer(), State)
+ -> ok | {ok, pos_integer(), State} when State::http2_machine().
+ensure_window(StreamID, Size, State=#http2_machine{opts=Opts}) ->
+ case stream_get(StreamID, State) of
+ %% For simplicity's sake, we do not consider attempts to ensure the window
+ %% of a terminated stream to be errors. We simply act as if the stream
+ %% window is large enough.
+ undefined ->
+ ok;
+ Stream = #stream{remote_window=RemoteWindow} ->
+ case ensure_window(Size, RemoteWindow, stream, Opts) of
+ ok ->
+ ok;
+ {ok, Increment} ->
+ {ok, Increment, stream_store(Stream#stream{remote_window=RemoteWindow + Increment}, State)}
+ end
+ end.
+
+%% No need to update the window when we are not expecting data.
+ensure_window(0, _, _, _) ->
+ ok;
+%% No need to update the window when it is already high enough.
+ensure_window(Size, Window, _, _) when Size =< Window ->
+ ok;
+ensure_window(Size0, Window, Type, Opts) ->
+ Threshold = ensure_window_threshold(Type, Opts),
+ if
+ %% We do not update the window when it is higher than the threshold.
+ Window > Threshold ->
+ ok;
+ true ->
+ Margin = ensure_window_margin(Type, Opts),
+ Size = Size0 + Margin,
+ MaxWindow = ensure_window_max(Type, Opts),
+ Increment = if
+ %% We cannot go above the maximum window size.
+ Size > MaxWindow -> MaxWindow - Window;
+ true -> Size - Window
+ end,
+ case Increment of
+ 0 -> ok;
+ _ -> {ok, Increment}
+ end
+ end.
+
+%% Margin defaults to the default initial window size.
+ensure_window_margin(connection, Opts) ->
+ maps:get(connection_window_margin_size, Opts, 65535);
+ensure_window_margin(stream, Opts) ->
+ maps:get(stream_window_margin_size, Opts, 65535).
+
+%% Max window defaults to the max value allowed by the protocol.
+ensure_window_max(connection, Opts) ->
+ maps:get(max_connection_window_size, Opts, 16#7fffffff);
+ensure_window_max(stream, Opts) ->
+ maps:get(max_stream_window_size, Opts, 16#7fffffff).
+
+%% Threshold defaults to 10 times the default frame size.
+ensure_window_threshold(connection, Opts) ->
+ maps:get(connection_window_update_threshold, Opts, 163840);
+ensure_window_threshold(stream, Opts) ->
+ maps:get(stream_window_update_threshold, Opts, 163840).
+
+-spec update_window(1..16#7fffffff, State)
+ -> State when State::http2_machine().
+update_window(Size, State=#http2_machine{remote_window=RemoteWindow})
+ when Size > 0 ->
+ State#http2_machine{remote_window=RemoteWindow + Size}.
+
+-spec update_window(cow_http2:streamid(), 1..16#7fffffff, State)
+ -> State when State::http2_machine().
+update_window(StreamID, Size, State)
+ when Size > 0 ->
+ Stream = #stream{remote_window=RemoteWindow} = stream_get(StreamID, State),
+ stream_store(Stream#stream{remote_window=RemoteWindow + Size}, State).
+
+%% Public interface to reset streams.
+
+-spec reset_stream(cow_http2:streamid(), State)
+ -> {ok, State} | {error, not_found} when State::http2_machine().
+reset_stream(StreamID, State=#http2_machine{streams=Streams0}) ->
+ case maps:take(StreamID, Streams0) of
+ {_, Streams} ->
+ {ok, stream_linger(StreamID, State#http2_machine{streams=Streams})};
+ error ->
+ {error, not_found}
+ end.
+
+%% Retrieve the buffer size for all streams.
+
+-spec get_connection_local_buffer_size(http2_machine()) -> non_neg_integer().
+get_connection_local_buffer_size(#http2_machine{streams=Streams}) ->
+ maps:fold(fun(_, #stream{local_buffer_size=Size}, Acc) ->
+ Acc + Size
+ end, 0, Streams).
+
+%% Retrieve a setting value, or its default value if not set.
+
+-spec get_local_setting(atom(), http2_machine()) -> atom() | integer().
+get_local_setting(Key, #http2_machine{local_settings=Settings}) ->
+ maps:get(Key, Settings, default_setting_value(Key)).
+
+-spec get_remote_settings(http2_machine()) -> map().
+get_remote_settings(#http2_machine{mode=Mode, remote_settings=Settings}) ->
+ Defaults0 = #{
+ header_table_size => default_setting_value(header_table_size),
+ enable_push => default_setting_value(enable_push),
+ max_concurrent_streams => default_setting_value(max_concurrent_streams),
+ initial_window_size => default_setting_value(initial_window_size),
+ max_frame_size => default_setting_value(max_frame_size),
+ max_header_list_size => default_setting_value(max_header_list_size)
+ },
+ Defaults = case Mode of
+ server ->
+ Defaults0#{enable_connect_protocol => default_setting_value(enable_connect_protocol)};
+ client ->
+ Defaults0
+ end,
+ maps:merge(Defaults, Settings).
+
+default_setting_value(header_table_size) -> 4096;
+default_setting_value(enable_push) -> true;
+default_setting_value(max_concurrent_streams) -> infinity;
+default_setting_value(initial_window_size) -> 65535;
+default_setting_value(max_frame_size) -> 16384;
+default_setting_value(max_header_list_size) -> infinity;
+default_setting_value(enable_connect_protocol) -> false.
+
+%% Function to obtain the last known streamid received
+%% for the purposes of sending a GOAWAY frame and closing the connection.
+
+-spec get_last_streamid(http2_machine()) -> cow_http2:streamid().
+get_last_streamid(#http2_machine{remote_streamid=RemoteStreamID}) ->
+ RemoteStreamID.
+
+%% Set last accepted streamid to the last known streamid, for the purpose
+%% ignoring frames for remote streams created after sending GOAWAY.
+
+-spec set_last_streamid(http2_machine()) -> {cow_http2:streamid(), http2_machine()}.
+set_last_streamid(State=#http2_machine{remote_streamid=StreamID,
+ last_remote_streamid=LastStreamID}) when StreamID =< LastStreamID->
+ {StreamID, State#http2_machine{last_remote_streamid = StreamID}}.
+
+%% Retrieve the local buffer size for a stream.
+
+-spec get_stream_local_buffer_size(cow_http2:streamid(), http2_machine())
+ -> {ok, non_neg_integer()} | {error, not_found | closed}.
+get_stream_local_buffer_size(StreamID, State=#http2_machine{mode=Mode,
+ local_streamid=LocalStreamID, remote_streamid=RemoteStreamID}) ->
+ case stream_get(StreamID, State) of
+ #stream{local_buffer_size=Size} ->
+ {ok, Size};
+ undefined when (?IS_LOCAL(Mode, StreamID) andalso (StreamID < LocalStreamID))
+ orelse ((not ?IS_LOCAL(Mode, StreamID)) andalso (StreamID =< RemoteStreamID)) ->
+ {error, closed};
+ undefined ->
+ {error, not_found}
+ end.
+
+%% Retrieve the local state for a stream, including the state in the queue.
+
+-spec get_stream_local_state(cow_http2:streamid(), http2_machine())
+ -> {ok, idle | cow_http2:fin(), empty | nofin | fin} | {error, not_found | closed}.
+get_stream_local_state(StreamID, State=#http2_machine{mode=Mode,
+ local_streamid=LocalStreamID, remote_streamid=RemoteStreamID}) ->
+ case stream_get(StreamID, State) of
+ #stream{local=IsFin, local_buffer=Q, local_trailers=undefined} ->
+ IsQueueFin = case queue:peek_r(Q) of
+ empty -> empty;
+ {value, {IsQueueFin0, _, _}} -> IsQueueFin0
+ end,
+ {ok, IsFin, IsQueueFin};
+ %% Trailers are queued so the local state is fin after the queue is drained.
+ #stream{local=IsFin} ->
+ {ok, IsFin, fin};
+ undefined when (?IS_LOCAL(Mode, StreamID) andalso (StreamID < LocalStreamID))
+ orelse ((not ?IS_LOCAL(Mode, StreamID)) andalso (StreamID =< RemoteStreamID)) ->
+ {error, closed};
+ undefined ->
+ {error, not_found}
+ end.
+
+%% Retrieve the remote state for a stream.
+
+-spec get_stream_remote_state(cow_http2:streamid(), http2_machine())
+ -> {ok, idle | cow_http2:fin()} | {error, not_found | closed}.
+get_stream_remote_state(StreamID, State=#http2_machine{mode=Mode,
+ local_streamid=LocalStreamID, remote_streamid=RemoteStreamID}) ->
+ case stream_get(StreamID, State) of
+ #stream{remote=IsFin} ->
+ {ok, IsFin};
+ undefined when (?IS_LOCAL(Mode, StreamID) andalso (StreamID < LocalStreamID))
+ orelse ((not ?IS_LOCAL(Mode, StreamID)) andalso (StreamID =< RemoteStreamID)) ->
+ {error, closed};
+ undefined ->
+ {error, not_found}
+ end.
+
+%% Query whether the stream was reset recently by the remote endpoint.
+
+-spec is_lingering_stream(cow_http2:streamid(), http2_machine()) -> boolean().
+is_lingering_stream(StreamID, #http2_machine{
+ local_lingering_streams=Local, remote_lingering_streams=Remote}) ->
+ case lists:member(StreamID, Local) of
+ true -> true;
+ false -> lists:member(StreamID, Remote)
+ end.
+
+%% Stream-related functions.
+
+stream_get(StreamID, #http2_machine{streams=Streams}) ->
+ maps:get(StreamID, Streams, undefined).
+
+stream_store(#stream{id=StreamID, local=fin, remote=fin},
+ State=#http2_machine{streams=Streams0}) ->
+ Streams = maps:remove(StreamID, Streams0),
+ State#http2_machine{streams=Streams};
+stream_store(Stream=#stream{id=StreamID},
+ State=#http2_machine{streams=Streams}) ->
+ State#http2_machine{streams=Streams#{StreamID => Stream}}.
+
+%% @todo Don't send an RST_STREAM if one was already sent.
+stream_reset(StreamID, State, Reason, HumanReadable) ->
+ {error, {stream_error, StreamID, Reason, HumanReadable},
+ stream_linger(StreamID, State)}.
+
+stream_linger(StreamID, State=#http2_machine{local_lingering_streams=Lingering0}) ->
+ %% We only keep up to 100 streams in this state. @todo Make it configurable?
+ Lingering = [StreamID|lists:sublist(Lingering0, 100 - 1)],
+ State#http2_machine{local_lingering_streams=Lingering}.
diff --git a/server/_build/default/lib/cowlib/src/cow_http_hd.erl b/server/_build/default/lib/cowlib/src/cow_http_hd.erl
new file mode 100644
index 0000000..f0e4fba
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_http_hd.erl
@@ -0,0 +1,3642 @@
+%% Copyright (c) 2014-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_http_hd).
+
+%% Functions are ordered by header name, with the parse
+%% function before the build function.
+
+-export([parse_accept/1]).
+-export([parse_accept_charset/1]).
+% @todo -export([parse_accept_datetime/1]). RFC7089
+-export([parse_accept_encoding/1]).
+% @todo -export([parse_accept_features/1]). RFC2295
+-export([parse_accept_language/1]).
+-export([parse_accept_ranges/1]).
+% @todo -export([parse_access_control_allow_credentials/1]). CORS
+-export([access_control_allow_credentials/0]).
+% @todo -export([parse_access_control_allow_headers/1]). CORS
+-export([access_control_allow_headers/1]).
+% @todo -export([parse_access_control_allow_methods/1]). CORS
+-export([access_control_allow_methods/1]).
+% @todo -export([parse_access_control_allow_origin/1]). CORS
+-export([access_control_allow_origin/1]).
+% @todo -export([parse_access_control_expose_headers/1]). CORS
+-export([access_control_expose_headers/1]).
+% @todo -export([parse_access_control_max_age/1]). CORS
+-export([access_control_max_age/1]).
+-export([parse_access_control_request_headers/1]).
+-export([parse_access_control_request_method/1]).
+-export([parse_age/1]).
+-export([parse_allow/1]).
+% @todo -export([parse_alternates/1]). RFC2295
+% @todo -export([parse_authentication_info/1]). RFC2617
+-export([parse_authorization/1]).
+-export([parse_cache_control/1]).
+-export([parse_connection/1]).
+% @todo -export([parse_content_disposition/1]). RFC6266
+-export([parse_content_encoding/1]).
+-export([parse_content_language/1]).
+-export([parse_content_length/1]).
+% @todo -export([parse_content_location/1]). RFC7231
+% @todo -export([parse_content_md5/1]). RFC2616 (deprecated)
+-export([parse_content_range/1]).
+% @todo -export([parse_content_security_policy/1]). CSP
+% @todo -export([parse_content_security_policy_report_only/1]). CSP
+-export([parse_content_type/1]).
+-export([parse_cookie/1]).
+-export([parse_date/1]).
+% @todo -export([parse_digest/1]). RFC3230
+% @todo -export([parse_dnt/1]). http://donottrack.us/
+-export([parse_etag/1]).
+-export([parse_expect/1]).
+-export([parse_expires/1]).
+% @todo -export([parse_forwarded/1]). RFC7239
+% @todo -export([parse_from/1]). RFC7231
+-export([parse_host/1]).
+-export([parse_http2_settings/1]).
+-export([parse_if_match/1]).
+-export([parse_if_modified_since/1]).
+-export([parse_if_none_match/1]).
+-export([parse_if_range/1]).
+-export([parse_if_unmodified_since/1]).
+% @todo -export([parse_last_event_id/1]). eventsource
+-export([parse_last_modified/1]).
+-export([parse_link/1]).
+% @todo -export([parse_location/1]). RFC7231
+-export([parse_max_forwards/1]).
+% @todo -export([parse_memento_datetime/1]). RFC7089
+% @todo -export([parse_negotiate/1]). RFC2295
+-export([parse_origin/1]).
+-export([parse_pragma/1]).
+% @todo -export([parse_prefer/1]). RFC7240
+-export([parse_proxy_authenticate/1]).
+% @todo -export([parse_proxy_authentication_info/1]). RFC2617
+-export([parse_proxy_authorization/1]).
+% @todo -export([parse_proxy_support/1]). RFC4559
+% @todo -export([parse_public_key_pins/1]). Key Pinning (upcoming)
+% @todo -export([parse_public_key_pins_report_only/1]). Key Pinning (upcoming)
+-export([parse_range/1]).
+% @todo -export([parse_referer/1]). RFC7231
+% @todo -export([parse_refresh/1]). Non-standard (examples: "5", "5; url=http://example.com/")
+-export([parse_retry_after/1]).
+-export([parse_sec_websocket_accept/1]).
+-export([parse_sec_websocket_extensions/1]).
+-export([parse_sec_websocket_key/1]).
+% @todo -export([parse_sec_websocket_origin/1]). Websocket drafts 7 and 8
+-export([parse_sec_websocket_protocol_req/1]).
+-export([parse_sec_websocket_protocol_resp/1]).
+-export([parse_sec_websocket_version_req/1]).
+-export([parse_sec_websocket_version_resp/1]).
+% @todo -export([parse_server/1]). RFC7231
+-export([parse_set_cookie/1]).
+% @todo -export([parse_strict_transport_security/1]). RFC6797
+% @todo -export([parse_tcn/1]). RFC2295
+-export([parse_te/1]).
+-export([parse_trailer/1]).
+-export([parse_transfer_encoding/1]).
+-export([parse_upgrade/1]).
+% @todo -export([parse_user_agent/1]). RFC7231
+% @todo -export([parse_variant_vary/1]). RFC2295
+-export([parse_variant_key/2]).
+-export([variant_key/1]).
+-export([parse_variants/1]).
+-export([variants/1]).
+-export([parse_vary/1]).
+% @todo -export([parse_via/1]). RFC7230
+% @todo -export([parse_want_digest/1]). RFC3230
+% @todo -export([parse_warning/1]). RFC7234
+-export([parse_www_authenticate/1]).
+% @todo -export([parse_x_content_duration/1]). Gecko/MDN (value: float)
+% @todo -export([parse_x_dns_prefetch_control/1]). Various (value: "on"|"off")
+-export([parse_x_forwarded_for/1]).
+% @todo -export([parse_x_frame_options/1]). RFC7034
+
+-type etag() :: {weak | strong, binary()}.
+-export_type([etag/0]).
+
+-type media_type() :: {binary(), binary(), [{binary(), binary()}]}.
+-export_type([media_type/0]).
+
+-type qvalue() :: 0..1000.
+-export_type([qvalue/0]).
+
+-type websocket_version() :: 0..255.
+-export_type([websocket_version/0]).
+
+-include("cow_inline.hrl").
+-include("cow_parse.hrl").
+
+-ifdef(TEST).
+-include_lib("proper/include/proper.hrl").
+
+vector(Min, Max, Dom) -> ?LET(N, choose(Min, Max), vector(N, Dom)).
+small_list(Dom) -> vector(0, 10, Dom).
+small_non_empty_list(Dom) -> vector(1, 10, Dom).
+
+alpha_chars() -> "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".
+alphanum_chars() -> "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".
+digit_chars() -> "0123456789".
+
+ows() -> list(elements([$\s, $\t])).
+alpha() -> elements(alpha_chars()).
+alphanum() -> elements(alphanum_chars()).
+digit() -> elements(digit_chars()).
+
+tchar() ->
+ frequency([
+ {1, elements([$!, $#, $$, $%, $&, $', $*, $+, $-, $., $^, $_, $`, $|, $~])},
+ {99, elements(alphanum_chars())}
+ ]).
+
+token() ->
+ ?LET(T,
+ non_empty(list(tchar())),
+ list_to_binary(T)).
+
+abnf_char() ->
+ integer(1, 127).
+
+vchar() ->
+ integer(33, 126).
+
+obs_text() ->
+ integer(128, 255).
+
+qdtext() ->
+ frequency([
+ {99, elements("\t\s!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~")},
+ {1, obs_text()}
+ ]).
+
+quoted_pair() ->
+ [$\\, frequency([
+ {99, elements("\t\s!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~")},
+ {1, obs_text()}
+ ])].
+
+quoted_string() ->
+ [$", list(frequency([{100, qdtext()}, {1, quoted_pair()}])), $"].
+
+%% Helper function for ( token / quoted-string ) values.
+unquote([$", V, $"]) -> unquote(V, <<>>);
+unquote(V) -> V.
+
+unquote([], Acc) -> Acc;
+unquote([[$\\, C]|Tail], Acc) -> unquote(Tail, << Acc/binary, C >>);
+unquote([C|Tail], Acc) -> unquote(Tail, << Acc/binary, C >>).
+
+parameter() ->
+ ?SUCHTHAT({K, _, _, _},
+ {token(), oneof([token(), quoted_string()]), ows(), ows()},
+ K =/= <<"q">>).
+
+weight() ->
+ frequency([
+ {90, integer(0, 1000)},
+ {10, undefined}
+ ]).
+
+%% Helper function for weight's qvalue formatting.
+qvalue_to_iodata(0) -> <<"0">>;
+qvalue_to_iodata(Q) when Q < 10 -> [<<"0.00">>, integer_to_binary(Q)];
+qvalue_to_iodata(Q) when Q < 100 -> [<<"0.0">>, integer_to_binary(Q)];
+qvalue_to_iodata(Q) when Q < 1000 -> [<<"0.">>, integer_to_binary(Q)];
+qvalue_to_iodata(1000) -> <<"1">>.
+-endif.
+
+%% Accept header.
+
+-spec parse_accept(binary()) -> [{media_type(), qvalue(), [binary() | {binary(), binary()}]}].
+parse_accept(<<"*/*">>) ->
+ [{{<<"*">>, <<"*">>, []}, 1000, []}];
+parse_accept(Accept) ->
+ media_range_list(Accept, []).
+
+media_range_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) -> ?LOWER(media_range_type, R, Acc, <<>>);
+media_range_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> media_range_list(R, Acc);
+media_range_list(<<>>, Acc) -> lists:reverse(Acc).
+
+media_range_type(<< C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> ?LOWER(media_range_type, R, Acc, T);
+media_range_type(<< $/, C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> ?LOWER(media_range_subtype, R, Acc, T, <<>>);
+%% Special clause for badly behaving user agents that send * instead of */*.
+media_range_type(<< $;, R/bits >>, Acc, <<"*">>) -> media_range_before_param(R, Acc, <<"*">>, <<"*">>, []).
+
+media_range_subtype(<< C, R/bits >>, Acc, T, S) when ?IS_TOKEN(C) -> ?LOWER(media_range_subtype, R, Acc, T, S);
+media_range_subtype(R, Acc, T, S) -> media_range_param_sep(R, Acc, T, S, []).
+
+media_range_param_sep(<<>>, Acc, T, S, P) -> lists:reverse([{{T, S, lists:reverse(P)}, 1000, []}|Acc]);
+media_range_param_sep(<< $,, R/bits >>, Acc, T, S, P) -> media_range_list(R, [{{T, S, lists:reverse(P)}, 1000, []}|Acc]);
+media_range_param_sep(<< $;, R/bits >>, Acc, T, S, P) -> media_range_before_param(R, Acc, T, S, P);
+media_range_param_sep(<< C, R/bits >>, Acc, T, S, P) when ?IS_WS(C) -> media_range_param_sep(R, Acc, T, S, P).
+
+media_range_before_param(<< C, R/bits >>, Acc, T, S, P) when ?IS_WS(C) -> media_range_before_param(R, Acc, T, S, P);
+media_range_before_param(<< $q, $=, R/bits >>, Acc, T, S, P) -> media_range_weight(R, Acc, T, S, P);
+media_range_before_param(<< "charset=", $", R/bits >>, Acc, T, S, P) -> media_range_charset_quoted(R, Acc, T, S, P, <<>>);
+media_range_before_param(<< "charset=", R/bits >>, Acc, T, S, P) -> media_range_charset(R, Acc, T, S, P, <<>>);
+media_range_before_param(<< C, R/bits >>, Acc, T, S, P) when ?IS_TOKEN(C) -> ?LOWER(media_range_param, R, Acc, T, S, P, <<>>).
+
+media_range_charset_quoted(<< $", R/bits >>, Acc, T, S, P, V) ->
+ media_range_param_sep(R, Acc, T, S, [{<<"charset">>, V}|P]);
+media_range_charset_quoted(<< $\\, C, R/bits >>, Acc, T, S, P, V) when ?IS_VCHAR_OBS(C) ->
+ ?LOWER(media_range_charset_quoted, R, Acc, T, S, P, V);
+media_range_charset_quoted(<< C, R/bits >>, Acc, T, S, P, V) when ?IS_VCHAR_OBS(C) ->
+ ?LOWER(media_range_charset_quoted, R, Acc, T, S, P, V).
+
+media_range_charset(<< C, R/bits >>, Acc, T, S, P, V) when ?IS_TOKEN(C) ->
+ ?LOWER(media_range_charset, R, Acc, T, S, P, V);
+media_range_charset(R, Acc, T, S, P, V) ->
+ media_range_param_sep(R, Acc, T, S, [{<<"charset">>, V}|P]).
+
+media_range_param(<< $=, $", R/bits >>, Acc, T, S, P, K) -> media_range_quoted(R, Acc, T, S, P, K, <<>>);
+media_range_param(<< $=, C, R/bits >>, Acc, T, S, P, K) when ?IS_TOKEN(C) -> media_range_value(R, Acc, T, S, P, K, << C >>);
+media_range_param(<< C, R/bits >>, Acc, T, S, P, K) when ?IS_TOKEN(C) -> ?LOWER(media_range_param, R, Acc, T, S, P, K).
+
+media_range_quoted(<< $", R/bits >>, Acc, T, S, P, K, V) -> media_range_param_sep(R, Acc, T, S, [{K, V}|P]);
+media_range_quoted(<< $\\, C, R/bits >>, Acc, T, S, P, K, V) when ?IS_VCHAR_OBS(C) -> media_range_quoted(R, Acc, T, S, P, K, << V/binary, C >>);
+media_range_quoted(<< C, R/bits >>, Acc, T, S, P, K, V) when ?IS_VCHAR_OBS(C) -> media_range_quoted(R, Acc, T, S, P, K, << V/binary, C >>).
+
+media_range_value(<< C, R/bits >>, Acc, T, S, P, K, V) when ?IS_TOKEN(C) -> media_range_value(R, Acc, T, S, P, K, << V/binary, C >>);
+media_range_value(R, Acc, T, S, P, K, V) -> media_range_param_sep(R, Acc, T, S, [{K, V}|P]).
+
+media_range_weight(<< "1.000", R/bits >>, Acc, T, S, P) -> accept_ext_sep(R, Acc, T, S, P, 1000, []);
+media_range_weight(<< "1.00", R/bits >>, Acc, T, S, P) -> accept_ext_sep(R, Acc, T, S, P, 1000, []);
+media_range_weight(<< "1.0", R/bits >>, Acc, T, S, P) -> accept_ext_sep(R, Acc, T, S, P, 1000, []);
+media_range_weight(<< "1.", R/bits >>, Acc, T, S, P) -> accept_ext_sep(R, Acc, T, S, P, 1000, []);
+media_range_weight(<< "1", R/bits >>, Acc, T, S, P) -> accept_ext_sep(R, Acc, T, S, P, 1000, []);
+media_range_weight(<< "0.", A, B, C, R/bits >>, Acc, T, S, P) when ?IS_DIGIT(A), ?IS_DIGIT(B), ?IS_DIGIT(C) ->
+ accept_ext_sep(R, Acc, T, S, P, (A - $0) * 100 + (B - $0) * 10 + (C - $0), []);
+media_range_weight(<< "0.", A, B, R/bits >>, Acc, T, S, P) when ?IS_DIGIT(A), ?IS_DIGIT(B) ->
+ accept_ext_sep(R, Acc, T, S, P, (A - $0) * 100 + (B - $0) * 10, []);
+media_range_weight(<< "0.", A, R/bits >>, Acc, T, S, P) when ?IS_DIGIT(A) ->
+ accept_ext_sep(R, Acc, T, S, P, (A - $0) * 100, []);
+media_range_weight(<< "0.", R/bits >>, Acc, T, S, P) -> accept_ext_sep(R, Acc, T, S, P, 0, []);
+media_range_weight(<< "0", R/bits >>, Acc, T, S, P) -> accept_ext_sep(R, Acc, T, S, P, 0, []);
+%% Special clauses for badly behaving user agents that send .123 instead of 0.123.
+media_range_weight(<< ".", A, B, C, R/bits >>, Acc, T, S, P) when ?IS_DIGIT(A), ?IS_DIGIT(B), ?IS_DIGIT(C) ->
+ accept_ext_sep(R, Acc, T, S, P, (A - $0) * 100 + (B - $0) * 10 + (C - $0), []);
+media_range_weight(<< ".", A, B, R/bits >>, Acc, T, S, P) when ?IS_DIGIT(A), ?IS_DIGIT(B) ->
+ accept_ext_sep(R, Acc, T, S, P, (A - $0) * 100 + (B - $0) * 10, []);
+media_range_weight(<< ".", A, R/bits >>, Acc, T, S, P) when ?IS_DIGIT(A) ->
+ accept_ext_sep(R, Acc, T, S, P, (A - $0) * 100, []).
+
+accept_ext_sep(<<>>, Acc, T, S, P, Q, E) -> lists:reverse([{{T, S, lists:reverse(P)}, Q, lists:reverse(E)}|Acc]);
+accept_ext_sep(<< $,, R/bits >>, Acc, T, S, P, Q, E) -> media_range_list(R, [{{T, S, lists:reverse(P)}, Q, lists:reverse(E)}|Acc]);
+accept_ext_sep(<< $;, R/bits >>, Acc, T, S, P, Q, E) -> accept_before_ext(R, Acc, T, S, P, Q, E);
+accept_ext_sep(<< C, R/bits >>, Acc, T, S, P, Q, E) when ?IS_WS(C) -> accept_ext_sep(R, Acc, T, S, P, Q, E).
+
+accept_before_ext(<< C, R/bits >>, Acc, T, S, P, Q, E) when ?IS_WS(C) -> accept_before_ext(R, Acc, T, S, P, Q, E);
+accept_before_ext(<< C, R/bits >>, Acc, T, S, P, Q, E) when ?IS_TOKEN(C) -> ?LOWER(accept_ext, R, Acc, T, S, P, Q, E, <<>>).
+
+accept_ext(<< $=, $", R/bits >>, Acc, T, S, P, Q, E, K) -> accept_quoted(R, Acc, T, S, P, Q, E, K, <<>>);
+accept_ext(<< $=, C, R/bits >>, Acc, T, S, P, Q, E, K) when ?IS_TOKEN(C) -> accept_value(R, Acc, T, S, P, Q, E, K, << C >>);
+accept_ext(<< C, R/bits >>, Acc, T, S, P, Q, E, K) when ?IS_TOKEN(C) -> ?LOWER(accept_ext, R, Acc, T, S, P, Q, E, K);
+accept_ext(R, Acc, T, S, P, Q, E, K) -> accept_ext_sep(R, Acc, T, S, P, Q, [K|E]).
+
+accept_quoted(<< $", R/bits >>, Acc, T, S, P, Q, E, K, V) -> accept_ext_sep(R, Acc, T, S, P, Q, [{K, V}|E]);
+accept_quoted(<< $\\, C, R/bits >>, Acc, T, S, P, Q, E, K, V) when ?IS_VCHAR_OBS(C) -> accept_quoted(R, Acc, T, S, P, Q, E, K, << V/binary, C >>);
+accept_quoted(<< C, R/bits >>, Acc, T, S, P, Q, E, K, V) when ?IS_VCHAR_OBS(C) -> accept_quoted(R, Acc, T, S, P, Q, E, K, << V/binary, C >>).
+
+accept_value(<< C, R/bits >>, Acc, T, S, P, Q, E, K, V) when ?IS_TOKEN(C) -> accept_value(R, Acc, T, S, P, Q, E, K, << V/binary, C >>);
+accept_value(R, Acc, T, S, P, Q, E, K, V) -> accept_ext_sep(R, Acc, T, S, P, Q, [{K, V}|E]).
+
+-ifdef(TEST).
+accept_ext() ->
+ oneof([token(), parameter()]).
+
+accept_exts() ->
+ frequency([
+ {90, []},
+ {10, small_list(accept_ext())}
+ ]).
+
+accept_param() ->
+ frequency([
+ {90, parameter()},
+ {10, {<<"charset">>, oneof([token(), quoted_string()]), <<>>, <<>>}}
+ ]).
+
+accept_params() ->
+ small_list(accept_param()).
+
+accept() ->
+ ?LET({T, S, P, W, E},
+ {token(), token(), accept_params(), weight(), accept_exts()},
+ {T, S, P, W, E, iolist_to_binary([T, $/, S,
+ [[OWS1, $;, OWS2, K, $=, V] || {K, V, OWS1, OWS2} <- P],
+ case W of
+ undefined -> [];
+ _ -> [
+ [<<";q=">>, qvalue_to_iodata(W)],
+ [case Ext of
+ {K, V, OWS1, OWS2} -> [OWS1, $;, OWS2, K, $=, V];
+ K -> [$;, K]
+ end || Ext <- E]]
+ end])}
+ ).
+
+prop_parse_accept() ->
+ ?FORALL(L,
+ vector(1, 50, accept()),
+ begin
+ << _, Accept/binary >> = iolist_to_binary([[$,, A] || {_, _, _, _, _, A} <- L]),
+ ResL = parse_accept(Accept),
+ CheckedL = [begin
+ ExpectedP = [case ?LOWER(K) of
+ <<"charset">> -> {<<"charset">>, ?LOWER(unquote(V))};
+ LowK -> {LowK, unquote(V)}
+ end || {K, V, _, _} <- P],
+ ExpectedE = [case Ext of
+ {K, V, _, _} -> {?LOWER(K), unquote(V)};
+ K -> ?LOWER(K)
+ end || Ext <- E],
+ ResT =:= ?LOWER(T)
+ andalso ResS =:= ?LOWER(S)
+ andalso ResP =:= ExpectedP
+ andalso (ResW =:= W orelse (W =:= undefined andalso ResW =:= 1000))
+ andalso ((W =:= undefined andalso ResE =:= []) orelse (W =/= undefined andalso ResE =:= ExpectedE))
+ end || {{T, S, P, W, E, _}, {{ResT, ResS, ResP}, ResW, ResE}} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end
+ ).
+
+parse_accept_test_() ->
+ Tests = [
+ {<<>>, []},
+ {<<" ">>, []},
+ {<<"audio/*; q=0.2, audio/basic">>, [
+ {{<<"audio">>, <<"*">>, []}, 200, []},
+ {{<<"audio">>, <<"basic">>, []}, 1000, []}
+ ]},
+ {<<"text/plain; q=0.5, text/html, "
+ "text/x-dvi; q=0.8, text/x-c">>, [
+ {{<<"text">>, <<"plain">>, []}, 500, []},
+ {{<<"text">>, <<"html">>, []}, 1000, []},
+ {{<<"text">>, <<"x-dvi">>, []}, 800, []},
+ {{<<"text">>, <<"x-c">>, []}, 1000, []}
+ ]},
+ {<<"text/*, text/html, text/html;level=1, */*">>, [
+ {{<<"text">>, <<"*">>, []}, 1000, []},
+ {{<<"text">>, <<"html">>, []}, 1000, []},
+ {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []},
+ {{<<"*">>, <<"*">>, []}, 1000, []}
+ ]},
+ {<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
+ "text/html;level=2;q=0.4, */*;q=0.5">>, [
+ {{<<"text">>, <<"*">>, []}, 300, []},
+ {{<<"text">>, <<"html">>, []}, 700, []},
+ {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []},
+ {{<<"text">>, <<"html">>, [{<<"level">>, <<"2">>}]}, 400, []},
+ {{<<"*">>, <<"*">>, []}, 500, []}
+ ]},
+ {<<"text/html;level=1;quoted=\"hi hi hi\";"
+ "q=0.123;standalone;complex=gits, text/plain">>, [
+ {{<<"text">>, <<"html">>,
+ [{<<"level">>, <<"1">>}, {<<"quoted">>, <<"hi hi hi">>}]}, 123,
+ [<<"standalone">>, {<<"complex">>, <<"gits">>}]},
+ {{<<"text">>, <<"plain">>, []}, 1000, []}
+ ]},
+ {<<"text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2">>, [
+ {{<<"text">>, <<"html">>, []}, 1000, []},
+ {{<<"image">>, <<"gif">>, []}, 1000, []},
+ {{<<"image">>, <<"jpeg">>, []}, 1000, []},
+ {{<<"*">>, <<"*">>, []}, 200, []},
+ {{<<"*">>, <<"*">>, []}, 200, []}
+ ]},
+ {<<"text/plain; charset=UTF-8">>, [
+ {{<<"text">>, <<"plain">>, [{<<"charset">>, <<"utf-8">>}]}, 1000, []}
+ ]}
+ ],
+ [{V, fun() -> R = parse_accept(V) end} || {V, R} <- Tests].
+
+parse_accept_error_test_() ->
+ Tests = [
+ <<"audio/basic, */;q=0.5">>,
+ <<"audio/, audio/basic">>,
+ <<"aud\tio/basic">>,
+ <<"audio/basic;t=\"zero \\", 0, " woo\"">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_accept(V)) end} || V <- Tests].
+
+horse_parse_accept() ->
+ horse:repeat(20000,
+ parse_accept(<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
+ "text/html;level=2;q=0.4, */*;q=0.5">>)
+ ).
+-endif.
+
+%% Accept-Charset header.
+
+-spec parse_accept_charset(binary()) -> [{binary(), qvalue()}].
+parse_accept_charset(Charset) ->
+ nonempty(conneg_list(Charset, [])).
+
+conneg_list(<<>>, Acc) -> lists:reverse(Acc);
+conneg_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> conneg_list(R, Acc);
+conneg_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) -> ?LOWER(conneg, R, Acc, <<>>).
+
+conneg(<< C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> ?LOWER(conneg, R, Acc, T);
+conneg(R, Acc, T) -> conneg_param_sep(R, Acc, T).
+
+conneg_param_sep(<<>>, Acc, T) -> lists:reverse([{T, 1000}|Acc]);
+conneg_param_sep(<< $,, R/bits >>, Acc, T) -> conneg_list(R, [{T, 1000}|Acc]);
+conneg_param_sep(<< $;, R/bits >>, Acc, T) -> conneg_before_weight(R, Acc, T);
+conneg_param_sep(<< C, R/bits >>, Acc, T) when ?IS_WS(C) -> conneg_param_sep(R, Acc, T).
+
+conneg_before_weight(<< C, R/bits >>, Acc, T) when ?IS_WS(C) -> conneg_before_weight(R, Acc, T);
+conneg_before_weight(<< $q, $=, R/bits >>, Acc, T) -> conneg_weight(R, Acc, T);
+%% Special clause for broken user agents that confuse ; and , separators.
+conneg_before_weight(<< C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> ?LOWER(conneg, R, [{T, 1000}|Acc], <<>>).
+
+conneg_weight(<< "1.000", R/bits >>, Acc, T) -> conneg_list_sep(R, [{T, 1000}|Acc]);
+conneg_weight(<< "1.00", R/bits >>, Acc, T) -> conneg_list_sep(R, [{T, 1000}|Acc]);
+conneg_weight(<< "1.0", R/bits >>, Acc, T) -> conneg_list_sep(R, [{T, 1000}|Acc]);
+conneg_weight(<< "1.", R/bits >>, Acc, T) -> conneg_list_sep(R, [{T, 1000}|Acc]);
+conneg_weight(<< "1", R/bits >>, Acc, T) -> conneg_list_sep(R, [{T, 1000}|Acc]);
+conneg_weight(<< "0.", A, B, C, R/bits >>, Acc, T) when ?IS_DIGIT(A), ?IS_DIGIT(B), ?IS_DIGIT(C) ->
+ conneg_list_sep(R, [{T, (A - $0) * 100 + (B - $0) * 10 + (C - $0)}|Acc]);
+conneg_weight(<< "0.", A, B, R/bits >>, Acc, T) when ?IS_DIGIT(A), ?IS_DIGIT(B) ->
+ conneg_list_sep(R, [{T, (A - $0) * 100 + (B - $0) * 10}|Acc]);
+conneg_weight(<< "0.", A, R/bits >>, Acc, T) when ?IS_DIGIT(A) ->
+ conneg_list_sep(R, [{T, (A - $0) * 100}|Acc]);
+conneg_weight(<< "0.", R/bits >>, Acc, T) -> conneg_list_sep(R, [{T, 0}|Acc]);
+conneg_weight(<< "0", R/bits >>, Acc, T) -> conneg_list_sep(R, [{T, 0}|Acc]).
+
+conneg_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+conneg_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> conneg_list_sep(R, Acc);
+conneg_list_sep(<< $,, R/bits >>, Acc) -> conneg_list(R, Acc).
+
+-ifdef(TEST).
+accept_charset() ->
+ ?LET({C, W},
+ {token(), weight()},
+ {C, W, iolist_to_binary([C, case W of
+ undefined -> [];
+ _ -> [<<";q=">>, qvalue_to_iodata(W)]
+ end])}
+ ).
+
+prop_parse_accept_charset() ->
+ ?FORALL(L,
+ non_empty(list(accept_charset())),
+ begin
+ << _, AcceptCharset/binary >> = iolist_to_binary([[$,, A] || {_, _, A} <- L]),
+ ResL = parse_accept_charset(AcceptCharset),
+ CheckedL = [begin
+ ResC =:= ?LOWER(Ch)
+ andalso (ResW =:= W orelse (W =:= undefined andalso ResW =:= 1000))
+ end || {{Ch, W, _}, {ResC, ResW}} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_accept_charset_test_() ->
+ Tests = [
+ {<<"iso-8859-5, unicode-1-1;q=0.8">>, [
+ {<<"iso-8859-5">>, 1000},
+ {<<"unicode-1-1">>, 800}
+ ]},
+ %% Some user agents send this invalid value for the Accept-Charset header
+ {<<"ISO-8859-1;utf-8;q=0.7,*;q=0.7">>, [
+ {<<"iso-8859-1">>, 1000},
+ {<<"utf-8">>, 700},
+ {<<"*">>, 700}
+ ]}
+ ],
+ [{V, fun() -> R = parse_accept_charset(V) end} || {V, R} <- Tests].
+
+parse_accept_charset_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_accept_charset(V)) end} || V <- Tests].
+
+horse_parse_accept_charset() ->
+ horse:repeat(20000,
+ parse_accept_charset(<<"iso-8859-5, unicode-1-1;q=0.8">>)
+ ).
+-endif.
+
+%% Accept-Encoding header.
+
+-spec parse_accept_encoding(binary()) -> [{binary(), qvalue()}].
+parse_accept_encoding(Encoding) ->
+ conneg_list(Encoding, []).
+
+-ifdef(TEST).
+accept_encoding() ->
+ ?LET({E, W},
+ {token(), weight()},
+ {E, W, iolist_to_binary([E, case W of
+ undefined -> [];
+ _ -> [<<";q=">>, qvalue_to_iodata(W)]
+ end])}
+ ).
+
+%% @todo This property seems useless, see prop_accept_charset.
+prop_parse_accept_encoding() ->
+ ?FORALL(L,
+ non_empty(list(accept_encoding())),
+ begin
+ << _, AcceptEncoding/binary >> = iolist_to_binary([[$,, A] || {_, _, A} <- L]),
+ ResL = parse_accept_encoding(AcceptEncoding),
+ CheckedL = [begin
+ ResE =:= ?LOWER(E)
+ andalso (ResW =:= W orelse (W =:= undefined andalso ResW =:= 1000))
+ end || {{E, W, _}, {ResE, ResW}} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_accept_encoding_test_() ->
+ Tests = [
+ {<<>>, []},
+ {<<"*">>, [{<<"*">>, 1000}]},
+ {<<"compress, gzip">>, [
+ {<<"compress">>, 1000},
+ {<<"gzip">>, 1000}
+ ]},
+ {<<"compress;q=0.5, gzip;q=1.0">>, [
+ {<<"compress">>, 500},
+ {<<"gzip">>, 1000}
+ ]},
+ {<<"gzip;q=1.0, identity; q=0.5, *;q=0">>, [
+ {<<"gzip">>, 1000},
+ {<<"identity">>, 500},
+ {<<"*">>, 0}
+ ]}
+ ],
+ [{V, fun() -> R = parse_accept_encoding(V) end} || {V, R} <- Tests].
+
+horse_parse_accept_encoding() ->
+ horse:repeat(20000,
+ parse_accept_encoding(<<"gzip;q=1.0, identity; q=0.5, *;q=0">>)
+ ).
+-endif.
+
+%% Accept-Language header.
+
+-spec parse_accept_language(binary()) -> [{binary(), qvalue()}].
+parse_accept_language(LanguageRange) ->
+ nonempty(language_range_list(LanguageRange, [])).
+
+language_range_list(<<>>, Acc) -> lists:reverse(Acc);
+language_range_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> language_range_list(R, Acc);
+language_range_list(<< $*, R/bits >>, Acc) -> language_range_param_sep(R, Acc, <<"*">>);
+language_range_list(<< C, R/bits >>, Acc) when ?IS_ALPHA(C) ->
+ ?LOWER(language_range, R, Acc, 1, <<>>).
+
+language_range(<< $-, C, R/bits >>, Acc, _, T) when ?IS_ALPHANUM(C) ->
+ ?LOWER(language_range_sub, R, Acc, 1, << T/binary, $- >>);
+language_range(<< C, R/bits >>, Acc, N, T) when ?IS_ALPHA(C), N < 8 ->
+ ?LOWER(language_range, R, Acc, N + 1, T);
+language_range(R, Acc, _, T) -> language_range_param_sep(R, Acc, T).
+
+language_range_sub(<< $-, R/bits >>, Acc, _, T) -> language_range_sub(R, Acc, 0, << T/binary, $- >>);
+language_range_sub(<< C, R/bits >>, Acc, N, T) when ?IS_ALPHANUM(C), N < 8 ->
+ ?LOWER(language_range_sub, R, Acc, N + 1, T);
+language_range_sub(R, Acc, _, T) -> language_range_param_sep(R, Acc, T).
+
+language_range_param_sep(<<>>, Acc, T) -> lists:reverse([{T, 1000}|Acc]);
+language_range_param_sep(<< $,, R/bits >>, Acc, T) -> language_range_list(R, [{T, 1000}|Acc]);
+language_range_param_sep(<< $;, R/bits >>, Acc, T) -> language_range_before_weight(R, Acc, T);
+language_range_param_sep(<< C, R/bits >>, Acc, T) when ?IS_WS(C) -> language_range_param_sep(R, Acc, T).
+
+language_range_before_weight(<< C, R/bits >>, Acc, T) when ?IS_WS(C) -> language_range_before_weight(R, Acc, T);
+language_range_before_weight(<< $q, $=, R/bits >>, Acc, T) -> language_range_weight(R, Acc, T);
+%% Special clause for broken user agents that confuse ; and , separators.
+language_range_before_weight(<< C, R/bits >>, Acc, T) when ?IS_ALPHA(C) ->
+ ?LOWER(language_range, R, [{T, 1000}|Acc], 1, <<>>).
+
+language_range_weight(<< "1.000", R/bits >>, Acc, T) -> language_range_list_sep(R, [{T, 1000}|Acc]);
+language_range_weight(<< "1.00", R/bits >>, Acc, T) -> language_range_list_sep(R, [{T, 1000}|Acc]);
+language_range_weight(<< "1.0", R/bits >>, Acc, T) -> language_range_list_sep(R, [{T, 1000}|Acc]);
+language_range_weight(<< "1.", R/bits >>, Acc, T) -> language_range_list_sep(R, [{T, 1000}|Acc]);
+language_range_weight(<< "1", R/bits >>, Acc, T) -> language_range_list_sep(R, [{T, 1000}|Acc]);
+language_range_weight(<< "0.", A, B, C, R/bits >>, Acc, T) when ?IS_DIGIT(A), ?IS_DIGIT(B), ?IS_DIGIT(C) ->
+ language_range_list_sep(R, [{T, (A - $0) * 100 + (B - $0) * 10 + (C - $0)}|Acc]);
+language_range_weight(<< "0.", A, B, R/bits >>, Acc, T) when ?IS_DIGIT(A), ?IS_DIGIT(B) ->
+ language_range_list_sep(R, [{T, (A - $0) * 100 + (B - $0) * 10}|Acc]);
+language_range_weight(<< "0.", A, R/bits >>, Acc, T) when ?IS_DIGIT(A) ->
+ language_range_list_sep(R, [{T, (A - $0) * 100}|Acc]);
+language_range_weight(<< "0.", R/bits >>, Acc, T) -> language_range_list_sep(R, [{T, 0}|Acc]);
+language_range_weight(<< "0", R/bits >>, Acc, T) -> language_range_list_sep(R, [{T, 0}|Acc]).
+
+language_range_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+language_range_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> language_range_list_sep(R, Acc);
+language_range_list_sep(<< $,, R/bits >>, Acc) -> language_range_list(R, Acc).
+
+-ifdef(TEST).
+language_range_tag() ->
+ vector(1, 8, alpha()).
+
+language_range_subtag() ->
+ [$-, vector(1, 8, alphanum())].
+
+language_range() ->
+ [language_range_tag(), small_list(language_range_subtag())].
+
+accept_language() ->
+ ?LET({R, W},
+ {language_range(), weight()},
+ {iolist_to_binary(R), W, iolist_to_binary([R, case W of
+ undefined -> [];
+ _ -> [<<";q=">>, qvalue_to_iodata(W)]
+ end])}
+ ).
+
+prop_parse_accept_language() ->
+ ?FORALL(L,
+ non_empty(list(accept_language())),
+ begin
+ << _, AcceptLanguage/binary >> = iolist_to_binary([[$,, A] || {_, _, A} <- L]),
+ ResL = parse_accept_language(AcceptLanguage),
+ CheckedL = [begin
+ ResR =:= ?LOWER(R)
+ andalso (ResW =:= W orelse (W =:= undefined andalso ResW =:= 1000))
+ end || {{R, W, _}, {ResR, ResW}} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_accept_language_test_() ->
+ Tests = [
+ {<<"da, en-gb;q=0.8, en;q=0.7">>, [
+ {<<"da">>, 1000},
+ {<<"en-gb">>, 800},
+ {<<"en">>, 700}
+ ]},
+ {<<"en, en-US, en-cockney, i-cherokee, x-pig-latin, es-419">>, [
+ {<<"en">>, 1000},
+ {<<"en-us">>, 1000},
+ {<<"en-cockney">>, 1000},
+ {<<"i-cherokee">>, 1000},
+ {<<"x-pig-latin">>, 1000},
+ {<<"es-419">>, 1000}
+ ]}
+ ],
+ [{V, fun() -> R = parse_accept_language(V) end} || {V, R} <- Tests].
+
+parse_accept_language_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"loooooong">>,
+ <<"en-us-loooooong">>,
+ <<"419-en-us">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_accept_language(V)) end} || V <- Tests].
+
+horse_parse_accept_language() ->
+ horse:repeat(20000,
+ parse_accept_language(<<"da, en-gb;q=0.8, en;q=0.7">>)
+ ).
+-endif.
+
+%% Accept-Ranges header.
+
+-spec parse_accept_ranges(binary()) -> [binary()].
+parse_accept_ranges(<<"none">>) -> [];
+parse_accept_ranges(<<"bytes">>) -> [<<"bytes">>];
+parse_accept_ranges(AcceptRanges) ->
+ nonempty(token_ci_list(AcceptRanges, [])).
+
+-ifdef(TEST).
+parse_accept_ranges_test_() ->
+ Tests = [
+ {<<"bytes">>, [<<"bytes">>]},
+ {<<"none">>, []},
+ {<<"bytes, pages, kilos">>, [<<"bytes">>, <<"pages">>, <<"kilos">>]}
+ ],
+ [{V, fun() -> R = parse_accept_ranges(V) end} || {V, R} <- Tests].
+
+parse_accept_ranges_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_accept_ranges(V)) end} || V <- Tests].
+
+horse_parse_accept_ranges_none() ->
+ horse:repeat(200000,
+ parse_accept_ranges(<<"none">>)
+ ).
+
+horse_parse_accept_ranges_bytes() ->
+ horse:repeat(200000,
+ parse_accept_ranges(<<"bytes">>)
+ ).
+
+horse_parse_accept_ranges_other() ->
+ horse:repeat(200000,
+ parse_accept_ranges(<<"bytes, pages, kilos">>)
+ ).
+-endif.
+
+%% Access-Control-Allow-Credentials header.
+
+-spec access_control_allow_credentials() -> iodata().
+access_control_allow_credentials() -> <<"true">>.
+
+%% Access-Control-Allow-Headers header.
+
+-spec access_control_allow_headers([binary()]) -> iodata().
+access_control_allow_headers(Headers) ->
+ join_token_list(nonempty(Headers)).
+
+-ifdef(TEST).
+access_control_allow_headers_test_() ->
+ Tests = [
+ {[<<"accept">>], <<"accept">>},
+ {[<<"accept">>, <<"authorization">>, <<"content-type">>], <<"accept, authorization, content-type">>}
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> R = iolist_to_binary(access_control_allow_headers(V)) end} || {V, R} <- Tests].
+
+access_control_allow_headers_error_test_() ->
+ Tests = [
+ []
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> {'EXIT', _} = (catch access_control_allow_headers(V)) end} || V <- Tests].
+
+horse_access_control_allow_headers() ->
+ horse:repeat(200000,
+ access_control_allow_headers([<<"accept">>, <<"authorization">>, <<"content-type">>])
+ ).
+-endif.
+
+%% Access-Control-Allow-Methods header.
+
+-spec access_control_allow_methods([binary()]) -> iodata().
+access_control_allow_methods(Methods) ->
+ join_token_list(nonempty(Methods)).
+
+-ifdef(TEST).
+access_control_allow_methods_test_() ->
+ Tests = [
+ {[<<"GET">>], <<"GET">>},
+ {[<<"GET">>, <<"POST">>, <<"DELETE">>], <<"GET, POST, DELETE">>}
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> R = iolist_to_binary(access_control_allow_methods(V)) end} || {V, R} <- Tests].
+
+access_control_allow_methods_error_test_() ->
+ Tests = [
+ []
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> {'EXIT', _} = (catch access_control_allow_methods(V)) end} || V <- Tests].
+
+horse_access_control_allow_methods() ->
+ horse:repeat(200000,
+ access_control_allow_methods([<<"GET">>, <<"POST">>, <<"DELETE">>])
+ ).
+-endif.
+
+%% Access-Control-Allow-Origin header.
+
+-spec access_control_allow_origin({binary(), binary(), 0..65535} | reference() | '*') -> iodata().
+access_control_allow_origin({Scheme, Host, Port}) ->
+ case default_port(Scheme) of
+ Port -> [Scheme, <<"://">>, Host];
+ _ -> [Scheme, <<"://">>, Host, <<":">>, integer_to_binary(Port)]
+ end;
+access_control_allow_origin('*') -> <<$*>>;
+access_control_allow_origin(Ref) when is_reference(Ref) -> <<"null">>.
+
+-ifdef(TEST).
+access_control_allow_origin_test_() ->
+ Tests = [
+ {{<<"http">>, <<"www.example.org">>, 8080}, <<"http://www.example.org:8080">>},
+ {{<<"http">>, <<"www.example.org">>, 80}, <<"http://www.example.org">>},
+ {{<<"http">>, <<"192.0.2.1">>, 8080}, <<"http://192.0.2.1:8080">>},
+ {{<<"http">>, <<"192.0.2.1">>, 80}, <<"http://192.0.2.1">>},
+ {{<<"http">>, <<"[2001:db8::1]">>, 8080}, <<"http://[2001:db8::1]:8080">>},
+ {{<<"http">>, <<"[2001:db8::1]">>, 80}, <<"http://[2001:db8::1]">>},
+ {{<<"http">>, <<"[::ffff:192.0.2.1]">>, 8080}, <<"http://[::ffff:192.0.2.1]:8080">>},
+ {{<<"http">>, <<"[::ffff:192.0.2.1]">>, 80}, <<"http://[::ffff:192.0.2.1]">>},
+ {make_ref(), <<"null">>},
+ {'*', <<$*>>}
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> R = iolist_to_binary(access_control_allow_origin(V)) end} || {V, R} <- Tests].
+
+horse_access_control_allow_origin() ->
+ horse:repeat(200000,
+ access_control_allow_origin({<<"http">>, <<"example.org">>, 8080})
+ ).
+-endif.
+
+%% Access-Control-Expose-Headers header.
+
+-spec access_control_expose_headers([binary()]) -> iodata().
+access_control_expose_headers(Headers) ->
+ join_token_list(nonempty(Headers)).
+
+-ifdef(TEST).
+access_control_expose_headers_test_() ->
+ Tests = [
+ {[<<"accept">>], <<"accept">>},
+ {[<<"accept">>, <<"authorization">>, <<"content-type">>], <<"accept, authorization, content-type">>}
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> R = iolist_to_binary(access_control_expose_headers(V)) end} || {V, R} <- Tests].
+
+access_control_expose_headers_error_test_() ->
+ Tests = [
+ []
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> {'EXIT', _} = (catch access_control_expose_headers(V)) end} || V <- Tests].
+
+horse_access_control_expose_headers() ->
+ horse:repeat(200000,
+ access_control_expose_headers([<<"accept">>, <<"authorization">>, <<"content-type">>])
+ ).
+-endif.
+
+%% Access-Control-Max-Age header.
+
+-spec access_control_max_age(non_neg_integer()) -> iodata().
+access_control_max_age(MaxAge) -> integer_to_binary(MaxAge).
+
+-ifdef(TEST).
+access_control_max_age_test_() ->
+ Tests = [
+ {0, <<"0">>},
+ {42, <<"42">>},
+ {69, <<"69">>},
+ {1337, <<"1337">>},
+ {3495, <<"3495">>},
+ {1234567890, <<"1234567890">>}
+ ],
+ [{V, fun() -> R = access_control_max_age(V) end} || {V, R} <- Tests].
+-endif.
+
+%% Access-Control-Request-Headers header.
+
+-spec parse_access_control_request_headers(binary()) -> [binary()].
+parse_access_control_request_headers(Headers) ->
+ token_ci_list(Headers, []).
+
+-ifdef(TEST).
+headers() ->
+ ?LET(L,
+ list({ows(), ows(), token()}),
+ case L of
+ [] -> {[], <<>>};
+ _ ->
+ << _, Headers/binary >> = iolist_to_binary([[OWS1, $,, OWS2, M] || {OWS1, OWS2, M} <- L]),
+ {[?LOWER(M) || {_, _, M} <- L], Headers}
+ end).
+
+prop_parse_access_control_request_headers() ->
+ ?FORALL({L, Headers},
+ headers(),
+ L =:= parse_access_control_request_headers(Headers)).
+
+parse_access_control_request_headers_test_() ->
+ Tests = [
+ {<<>>, []},
+ {<<"Content-Type">>, [<<"content-type">>]},
+ {<<"accept, authorization, content-type">>, [<<"accept">>, <<"authorization">>, <<"content-type">>]},
+ {<<"accept,, , authorization,content-type">>, [<<"accept">>, <<"authorization">>, <<"content-type">>]}
+ ],
+ [{V, fun() -> R = parse_access_control_request_headers(V) end} || {V, R} <- Tests].
+
+horse_parse_access_control_request_headers() ->
+ horse:repeat(200000,
+ parse_access_control_request_headers(<<"accept, authorization, content-type">>)
+ ).
+-endif.
+
+%% Access-Control-Request-Method header.
+
+-spec parse_access_control_request_method(binary()) -> binary().
+parse_access_control_request_method(Method) ->
+ true = <<>> =/= Method,
+ ok = validate_token(Method),
+ Method.
+
+validate_token(<< C, R/bits >>) when ?IS_TOKEN(C) -> validate_token(R);
+validate_token(<<>>) -> ok.
+
+-ifdef(TEST).
+parse_access_control_request_method_test_() ->
+ Tests = [
+ <<"GET">>,
+ <<"HEAD">>,
+ <<"POST">>,
+ <<"PUT">>,
+ <<"DELETE">>,
+ <<"TRACE">>,
+ <<"CONNECT">>,
+ <<"whatever">>
+ ],
+ [{V, fun() -> R = parse_access_control_request_method(V) end} || {V, R} <- Tests].
+
+parse_access_control_request_method_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_access_control_request_method(V)) end} || V <- Tests].
+
+horse_parse_access_control_request_method() ->
+ horse:repeat(200000,
+ parse_access_control_request_method(<<"POST">>)
+ ).
+-endif.
+
+%% Age header.
+
+-spec parse_age(binary()) -> non_neg_integer().
+parse_age(Age) ->
+ I = binary_to_integer(Age),
+ true = I >= 0,
+ I.
+
+-ifdef(TEST).
+parse_age_test_() ->
+ Tests = [
+ {<<"0">>, 0},
+ {<<"42">>, 42},
+ {<<"69">>, 69},
+ {<<"1337">>, 1337},
+ {<<"3495">>, 3495},
+ {<<"1234567890">>, 1234567890}
+ ],
+ [{V, fun() -> R = parse_age(V) end} || {V, R} <- Tests].
+
+parse_age_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"123, 123">>,
+ <<"4.17">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_age(V)) end} || V <- Tests].
+-endif.
+
+%% Allow header.
+
+-spec parse_allow(binary()) -> [binary()].
+parse_allow(Allow) ->
+ token_list(Allow, []).
+
+-ifdef(TEST).
+allow() ->
+ ?LET(L,
+ list({ows(), ows(), token()}),
+ case L of
+ [] -> {[], <<>>};
+ _ ->
+ << _, Allow/binary >> = iolist_to_binary([[OWS1, $,, OWS2, M] || {OWS1, OWS2, M} <- L]),
+ {[M || {_, _, M} <- L], Allow}
+ end).
+
+prop_parse_allow() ->
+ ?FORALL({L, Allow},
+ allow(),
+ L =:= parse_allow(Allow)).
+
+parse_allow_test_() ->
+ Tests = [
+ {<<>>, []},
+ {<<"GET, HEAD, PUT">>, [<<"GET">>, <<"HEAD">>, <<"PUT">>]}
+ ],
+ [{V, fun() -> R = parse_allow(V) end} || {V, R} <- Tests].
+
+horse_parse_allow() ->
+ horse:repeat(200000,
+ parse_allow(<<"GET, HEAD, PUT">>)
+ ).
+-endif.
+
+%% Authorization header.
+%%
+%% We support Basic, Digest and Bearer schemes only.
+%%
+%% In the Digest case we do not validate that the mandatory
+%% fields are present. When parsing auth-params, we do not
+%% accept BWS characters around the "=".
+
+-spec parse_authorization(binary())
+ -> {basic, binary(), binary()}
+ | {bearer, binary()}
+ | {digest, [{binary(), binary()}]}.
+parse_authorization(<<B, A, S, I, C, " ", R/bits >>)
+ when ((B =:= $B) or (B =:= $b)), ((A =:= $A) or (A =:= $a)),
+ ((S =:= $S) or (S =:= $s)), ((I =:= $I) or (I =:= $i)),
+ ((C =:= $C) or (C =:= $c)) ->
+ auth_basic(base64:decode(R), <<>>);
+parse_authorization(<<B, E1, A, R1, E2, R2, " ", R/bits >>)
+ when (R =/= <<>>), ((B =:= $B) or (B =:= $b)),
+ ((E1 =:= $E) or (E1 =:= $e)), ((A =:= $A) or (A =:= $a)),
+ ((R1 =:= $R) or (R1 =:= $r)), ((E2 =:= $E) or (E2 =:= $e)),
+ ((R2 =:= $R) or (R2 =:= $r)) ->
+ validate_auth_bearer(R),
+ {bearer, R};
+parse_authorization(<<D, I, G, E, S, T, " ", R/bits >>)
+ when ((D =:= $D) or (D =:= $d)), ((I =:= $I) or (I =:= $i)),
+ ((G =:= $G) or (G =:= $g)), ((E =:= $E) or (E =:= $e)),
+ ((S =:= $S) or (S =:= $s)), ((T =:= $T) or (T =:= $t)) ->
+ {digest, nonempty(auth_digest_list(R, []))}.
+
+auth_basic(<< $:, Password/bits >>, UserID) -> {basic, UserID, Password};
+auth_basic(<< C, R/bits >>, UserID) -> auth_basic(R, << UserID/binary, C >>).
+
+validate_auth_bearer(<< C, R/bits >>) when ?IS_TOKEN68(C) -> validate_auth_bearer(R);
+validate_auth_bearer(<< $=, R/bits >>) -> validate_auth_bearer_eq(R);
+validate_auth_bearer(<<>>) -> ok.
+
+validate_auth_bearer_eq(<< $=, R/bits >>) -> validate_auth_bearer_eq(R);
+validate_auth_bearer_eq(<<>>) -> ok.
+
+auth_digest_list(<<>>, Acc) -> lists:reverse(Acc);
+auth_digest_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> auth_digest_list(R, Acc);
+auth_digest_list(<< "algorithm=", C, R/bits >>, Acc) when ?IS_TOKEN(C) -> auth_digest_token(R, Acc, <<"algorithm">>, << C >>);
+auth_digest_list(<< "cnonce=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"cnonce">>, <<>>);
+auth_digest_list(<< "nc=", A, B, C, D, E, F, G, H, R/bits >>, Acc)
+ when ?IS_LHEX(A), ?IS_LHEX(B), ?IS_LHEX(C), ?IS_LHEX(D),
+ ?IS_LHEX(E), ?IS_LHEX(F), ?IS_LHEX(G), ?IS_LHEX(H) ->
+ auth_digest_list_sep(R, [{<<"nc">>, << A, B, C, D, E, F, G, H >>}|Acc]);
+auth_digest_list(<< "nonce=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"nonce">>, <<>>);
+auth_digest_list(<< "opaque=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"opaque">>, <<>>);
+auth_digest_list(<< "qop=", C, R/bits >>, Acc) when ?IS_TOKEN(C) -> auth_digest_token(R, Acc, <<"qop">>, << C >>);
+auth_digest_list(<< "realm=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"realm">>, <<>>);
+auth_digest_list(<< "response=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"response">>, <<>>);
+auth_digest_list(<< "uri=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"uri">>, <<>>);
+auth_digest_list(<< "username=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"username">>, <<>>);
+auth_digest_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) ->
+ ?LOWER(auth_digest_param, R, Acc, <<>>).
+
+auth_digest_param(<< $=, $", R/bits >>, Acc, K) -> auth_digest_quoted(R, Acc, K, <<>>);
+auth_digest_param(<< $=, C, R/bits >>, Acc, K) when ?IS_TOKEN(C) -> auth_digest_token(R, Acc, K, << C >>);
+auth_digest_param(<< C, R/bits >>, Acc, K) when ?IS_TOKEN(C) ->
+ ?LOWER(auth_digest_param, R, Acc, K).
+
+auth_digest_token(<< C, R/bits >>, Acc, K, V) when ?IS_TOKEN(C) -> auth_digest_token(R, Acc, K, << V/binary, C >>);
+auth_digest_token(R, Acc, K, V) -> auth_digest_list_sep(R, [{K, V}|Acc]).
+
+auth_digest_quoted(<< $", R/bits >>, Acc, K, V) -> auth_digest_list_sep(R, [{K, V}|Acc]);
+auth_digest_quoted(<< $\\, C, R/bits >>, Acc, K, V) when ?IS_VCHAR_OBS(C) -> auth_digest_quoted(R, Acc, K, << V/binary, C >>);
+auth_digest_quoted(<< C, R/bits >>, Acc, K, V) when ?IS_VCHAR_OBS(C) -> auth_digest_quoted(R, Acc, K, << V/binary, C >>).
+
+auth_digest_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+auth_digest_list_sep(<< $,, R/bits >>, Acc) -> auth_digest_list(R, Acc);
+auth_digest_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> auth_digest_list_sep(R, Acc).
+
+-ifdef(TEST).
+parse_authorization_test_() ->
+ Tests = [
+ {<<"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==">>, {basic, <<"Aladdin">>, <<"open sesame">>}},
+ {<<"bAsIc QWxhZGRpbjpvcGVuIHNlc2FtZQ==">>, {basic, <<"Aladdin">>, <<"open sesame">>}},
+ {<<"Bearer mF_9.B5f-4.1JqM">>, {bearer, <<"mF_9.B5f-4.1JqM">>}},
+ {<<"bEaRer mF_9.B5f-4.1JqM">>, {bearer, <<"mF_9.B5f-4.1JqM">>}},
+ {<<"Digest username=\"Mufasa\","
+ "realm=\"testrealm@host.com\","
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
+ "uri=\"/dir/index.html\","
+ "qop=auth,"
+ "nc=00000001,"
+ "cnonce=\"0a4f113b\","
+ "response=\"6629fae49393a05397450978507c4ef1\","
+ "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"">>,
+ {digest, [
+ {<<"username">>, <<"Mufasa">>},
+ {<<"realm">>, <<"testrealm@host.com">>},
+ {<<"nonce">>, <<"dcd98b7102dd2f0e8b11d0f600bfb0c093">>},
+ {<<"uri">>, <<"/dir/index.html">>},
+ {<<"qop">>, <<"auth">>},
+ {<<"nc">>, <<"00000001">>},
+ {<<"cnonce">>, <<"0a4f113b">>},
+ {<<"response">>, <<"6629fae49393a05397450978507c4ef1">>},
+ {<<"opaque">>, <<"5ccc069c403ebaf9f0171e9517f40e41">>}]}}
+ ],
+ [{V, fun() -> R = parse_authorization(V) end} || {V, R} <- Tests].
+
+horse_parse_authorization_basic() ->
+ horse:repeat(20000,
+ parse_authorization(<<"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==">>)
+ ).
+
+horse_parse_authorization_bearer() ->
+ horse:repeat(20000,
+ parse_authorization(<<"Bearer mF_9.B5f-4.1JqM">>)
+ ).
+
+horse_parse_authorization_digest() ->
+ horse:repeat(20000,
+ parse_authorization(
+ <<"Digest username=\"Mufasa\","
+ "realm=\"testrealm@host.com\","
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
+ "uri=\"/dir/index.html\","
+ "qop=auth,"
+ "nc=00000001,"
+ "cnonce=\"0a4f113b\","
+ "response=\"6629fae49393a05397450978507c4ef1\","
+ "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"">>)
+ ).
+-endif.
+
+%% Cache-Control header.
+%%
+%% In the fields list case, we do not support escaping, which shouldn't be needed anyway.
+
+-spec parse_cache_control(binary())
+ -> [binary() | {binary(), binary()} | {binary(), non_neg_integer()} | {binary(), [binary()]}].
+parse_cache_control(<<"no-cache">>) ->
+ [<<"no-cache">>];
+parse_cache_control(<<"max-age=0">>) ->
+ [{<<"max-age">>, 0}];
+parse_cache_control(CacheControl) ->
+ nonempty(cache_directive_list(CacheControl, [])).
+
+cache_directive_list(<<>>, Acc) -> lists:reverse(Acc);
+cache_directive_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C)-> cache_directive_list(R, Acc);
+cache_directive_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) ->
+ ?LOWER(cache_directive, R, Acc, <<>>).
+
+cache_directive(<< $=, $", R/bits >>, Acc, T)
+ when (T =:= <<"no-cache">>) or (T =:= <<"private">>) ->
+ cache_directive_fields_list(R, Acc, T, []);
+cache_directive(<< $=, C, R/bits >>, Acc, T)
+ when ?IS_DIGIT(C), (T =:= <<"max-age">>) or (T =:= <<"max-stale">>)
+ or (T =:= <<"min-fresh">>) or (T =:= <<"s-maxage">>)
+ or (T =:= <<"stale-while-revalidate">>) or (T =:= <<"stale-if-error">>) ->
+ cache_directive_delta(R, Acc, T, (C - $0));
+cache_directive(<< $=, $", R/bits >>, Acc, T) -> cache_directive_quoted_string(R, Acc, T, <<>>);
+cache_directive(<< $=, C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> cache_directive_token(R, Acc, T, << C >>);
+cache_directive(<< C, R/bits >>, Acc, T) when ?IS_TOKEN(C) ->
+ ?LOWER(cache_directive, R, Acc, T);
+cache_directive(R, Acc, T) -> cache_directive_list_sep(R, [T|Acc]).
+
+cache_directive_delta(<< C, R/bits >>, Acc, K, V) when ?IS_DIGIT(C) -> cache_directive_delta(R, Acc, K, V * 10 + (C - $0));
+cache_directive_delta(R, Acc, K, V) -> cache_directive_list_sep(R, [{K, V}|Acc]).
+
+cache_directive_fields_list(<< C, R/bits >>, Acc, K, L) when ?IS_WS_COMMA(C) -> cache_directive_fields_list(R, Acc, K, L);
+cache_directive_fields_list(<< $", R/bits >>, Acc, K, L) -> cache_directive_list_sep(R, [{K, lists:reverse(L)}|Acc]);
+cache_directive_fields_list(<< C, R/bits >>, Acc, K, L) when ?IS_TOKEN(C) ->
+ ?LOWER(cache_directive_field, R, Acc, K, L, <<>>).
+
+cache_directive_field(<< C, R/bits >>, Acc, K, L, F) when ?IS_TOKEN(C) ->
+ ?LOWER(cache_directive_field, R, Acc, K, L, F);
+cache_directive_field(R, Acc, K, L, F) -> cache_directive_fields_list_sep(R, Acc, K, [F|L]).
+
+cache_directive_fields_list_sep(<< C, R/bits >>, Acc, K, L) when ?IS_WS(C) -> cache_directive_fields_list_sep(R, Acc, K, L);
+cache_directive_fields_list_sep(<< $,, R/bits >>, Acc, K, L) -> cache_directive_fields_list(R, Acc, K, L);
+cache_directive_fields_list_sep(<< $", R/bits >>, Acc, K, L) -> cache_directive_list_sep(R, [{K, lists:reverse(L)}|Acc]).
+
+cache_directive_token(<< C, R/bits >>, Acc, K, V) when ?IS_TOKEN(C) -> cache_directive_token(R, Acc, K, << V/binary, C >>);
+cache_directive_token(R, Acc, K, V) -> cache_directive_list_sep(R, [{K, V}|Acc]).
+
+cache_directive_quoted_string(<< $", R/bits >>, Acc, K, V) -> cache_directive_list_sep(R, [{K, V}|Acc]);
+cache_directive_quoted_string(<< $\\, C, R/bits >>, Acc, K, V) when ?IS_VCHAR_OBS(C) ->
+ cache_directive_quoted_string(R, Acc, K, << V/binary, C >>);
+cache_directive_quoted_string(<< C, R/bits >>, Acc, K, V) when ?IS_VCHAR_OBS(C) ->
+ cache_directive_quoted_string(R, Acc, K, << V/binary, C >>).
+
+cache_directive_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+cache_directive_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> cache_directive_list_sep(R, Acc);
+cache_directive_list_sep(<< $,, R/bits >>, Acc) -> cache_directive_list(R, Acc).
+
+-ifdef(TEST).
+cache_directive_unreserved_token() ->
+ ?SUCHTHAT(T,
+ token(),
+ T =/= <<"max-age">> andalso T =/= <<"max-stale">> andalso T =/= <<"min-fresh">>
+ andalso T =/= <<"s-maxage">> andalso T =/= <<"no-cache">> andalso T =/= <<"private">>
+ andalso T =/= <<"stale-while-revalidate">> andalso T =/= <<"stale-if-error">>).
+
+cache_directive() ->
+ oneof([
+ token(),
+ {cache_directive_unreserved_token(), token()},
+ {cache_directive_unreserved_token(), quoted_string()},
+ {elements([
+ <<"max-age">>, <<"max-stale">>, <<"min-fresh">>, <<"s-maxage">>,
+ <<"stale-while-revalidate">>, <<"stale-if-error">>
+ ]), non_neg_integer()},
+ {fields, elements([<<"no-cache">>, <<"private">>]), small_list(token())}
+ ]).
+
+cache_control() ->
+ ?LET(L,
+ non_empty(list(cache_directive())),
+ begin
+ << _, CacheControl/binary >> = iolist_to_binary([[$,,
+ case C of
+ {fields, K, V} -> [K, $=, $", [[F, $,] || F <- V], $"];
+ {K, V} when is_integer(V) -> [K, $=, integer_to_binary(V)];
+ {K, V} -> [K, $=, V];
+ K -> K
+ end] || C <- L]),
+ {L, CacheControl}
+ end).
+
+prop_parse_cache_control() ->
+ ?FORALL({L, CacheControl},
+ cache_control(),
+ begin
+ ResL = parse_cache_control(CacheControl),
+ CheckedL = [begin
+ ExpectedCc = case Cc of
+ {fields, K, V} -> {?LOWER(K), [?LOWER(F) || F <- V]};
+ {K, V} -> {?LOWER(K), unquote(V)};
+ K -> ?LOWER(K)
+ end,
+ ExpectedCc =:= ResCc
+ end || {Cc, ResCc} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_cache_control_test_() ->
+ Tests = [
+ {<<"no-cache">>, [<<"no-cache">>]},
+ {<<"no-store">>, [<<"no-store">>]},
+ {<<"max-age=0">>, [{<<"max-age">>, 0}]},
+ {<<"max-age=30">>, [{<<"max-age">>, 30}]},
+ {<<"private, community=\"UCI\"">>, [<<"private">>, {<<"community">>, <<"UCI">>}]},
+ {<<"private=\"Content-Type, Content-Encoding, Content-Language\"">>,
+ [{<<"private">>, [<<"content-type">>, <<"content-encoding">>, <<"content-language">>]}]},
+ %% RFC5861 3.1.
+ {<<"max-age=600, stale-while-revalidate=30">>,
+ [{<<"max-age">>, 600}, {<<"stale-while-revalidate">>, 30}]},
+ %% RFC5861 4.1.
+ {<<"max-age=600, stale-if-error=1200">>,
+ [{<<"max-age">>, 600}, {<<"stale-if-error">>, 1200}]}
+ ],
+ [{V, fun() -> R = parse_cache_control(V) end} || {V, R} <- Tests].
+
+parse_cache_control_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_cache_control(V)) end} || V <- Tests].
+
+horse_parse_cache_control_no_cache() ->
+ horse:repeat(200000,
+ parse_cache_control(<<"no-cache">>)
+ ).
+
+horse_parse_cache_control_max_age_0() ->
+ horse:repeat(200000,
+ parse_cache_control(<<"max-age=0">>)
+ ).
+
+horse_parse_cache_control_max_age_30() ->
+ horse:repeat(200000,
+ parse_cache_control(<<"max-age=30">>)
+ ).
+
+horse_parse_cache_control_custom() ->
+ horse:repeat(200000,
+ parse_cache_control(<<"private, community=\"UCI\"">>)
+ ).
+
+horse_parse_cache_control_fields() ->
+ horse:repeat(200000,
+ parse_cache_control(<<"private=\"Content-Type, Content-Encoding, Content-Language\"">>)
+ ).
+-endif.
+
+%% Connection header.
+
+-spec parse_connection(binary()) -> [binary()].
+parse_connection(<<"close">>) ->
+ [<<"close">>];
+parse_connection(<<"keep-alive">>) ->
+ [<<"keep-alive">>];
+parse_connection(Connection) ->
+ nonempty(token_ci_list(Connection, [])).
+
+-ifdef(TEST).
+prop_parse_connection() ->
+ ?FORALL(L,
+ non_empty(list(token())),
+ begin
+ << _, Connection/binary >> = iolist_to_binary([[$,, C] || C <- L]),
+ ResL = parse_connection(Connection),
+ CheckedL = [?LOWER(Co) =:= ResC || {Co, ResC} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_connection_test_() ->
+ Tests = [
+ {<<"close">>, [<<"close">>]},
+ {<<"ClOsE">>, [<<"close">>]},
+ {<<"Keep-Alive">>, [<<"keep-alive">>]},
+ {<<"keep-alive, Upgrade">>, [<<"keep-alive">>, <<"upgrade">>]}
+ ],
+ [{V, fun() -> R = parse_connection(V) end} || {V, R} <- Tests].
+
+parse_connection_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_connection(V)) end} || V <- Tests].
+
+horse_parse_connection_close() ->
+ horse:repeat(200000,
+ parse_connection(<<"close">>)
+ ).
+
+horse_parse_connection_keepalive() ->
+ horse:repeat(200000,
+ parse_connection(<<"keep-alive">>)
+ ).
+
+horse_parse_connection_keepalive_upgrade() ->
+ horse:repeat(200000,
+ parse_connection(<<"keep-alive, upgrade">>)
+ ).
+-endif.
+
+%% Content-Encoding header.
+
+-spec parse_content_encoding(binary()) -> [binary()].
+parse_content_encoding(ContentEncoding) ->
+ nonempty(token_ci_list(ContentEncoding, [])).
+
+-ifdef(TEST).
+parse_content_encoding_test_() ->
+ Tests = [
+ {<<"gzip">>, [<<"gzip">>]}
+ ],
+ [{V, fun() -> R = parse_content_encoding(V) end} || {V, R} <- Tests].
+
+parse_content_encoding_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_content_encoding(V)) end} || V <- Tests].
+
+horse_parse_content_encoding() ->
+ horse:repeat(200000,
+ parse_content_encoding(<<"gzip">>)
+ ).
+-endif.
+
+%% Content-Language header.
+%%
+%% We do not support irregular deprecated tags that do not match the ABNF.
+
+-spec parse_content_language(binary()) -> [binary()].
+parse_content_language(ContentLanguage) ->
+ nonempty(langtag_list(ContentLanguage, [])).
+
+langtag_list(<<>>, Acc) -> lists:reverse(Acc);
+langtag_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> langtag_list(R, Acc);
+langtag_list(<< A, B, C, R/bits >>, Acc) when ?IS_ALPHA(A), ?IS_ALPHA(B), ?IS_ALPHA(C) ->
+ langtag_extlang(R, Acc, << ?LC(A), ?LC(B), ?LC(C) >>, 0);
+langtag_list(<< A, B, R/bits >>, Acc) when ?IS_ALPHA(A), ?IS_ALPHA(B) ->
+ langtag_extlang(R, Acc, << ?LC(A), ?LC(B) >>, 0);
+langtag_list(<< X, R/bits >>, Acc) when X =:= $x; X =:= $X -> langtag_privateuse_sub(R, Acc, << $x >>, 0).
+
+langtag_extlang(<< $-, A, B, C, D, E, F, G, H, R/bits >>, Acc, T, _)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G), ?IS_ALPHANUM(H) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G), ?LC(H) >>);
+langtag_extlang(<< $-, A, B, C, D, E, F, G, R/bits >>, Acc, T, _)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G) >>);
+langtag_extlang(<< $-, A, B, C, D, E, F, R/bits >>, Acc, T, _)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F) >>);
+langtag_extlang(<< $-, A, B, C, D, E, R/bits >>, Acc, T, _)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D), ?IS_ALPHANUM(E) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E) >>);
+langtag_extlang(<< $-, A, B, C, D, R/bits >>, Acc, T, _)
+ when ?IS_ALPHA(A), ?IS_ALPHA(B), ?IS_ALPHA(C), ?IS_ALPHA(D) ->
+ langtag_region(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D) >>);
+langtag_extlang(<< $-, A, B, C, R/bits >>, Acc, T, N)
+ when ?IS_ALPHA(A), ?IS_ALPHA(B), ?IS_ALPHA(C) ->
+ case N of
+ 2 -> langtag_script(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C) >>);
+ _ -> langtag_extlang(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C) >>, N + 1)
+ end;
+langtag_extlang(R, Acc, T, _) -> langtag_region(R, Acc, T).
+
+langtag_script(<< $-, A, B, C, D, E, F, G, H, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G), ?IS_ALPHANUM(H) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G), ?LC(H) >>);
+langtag_script(<< $-, A, B, C, D, E, F, G, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G) >>);
+langtag_script(<< $-, A, B, C, D, E, F, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F) >>);
+langtag_script(<< $-, A, B, C, D, E, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D), ?IS_ALPHANUM(E) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E) >>);
+langtag_script(<< $-, A, B, C, D, R/bits >>, Acc, T)
+ when ?IS_ALPHA(A), ?IS_ALPHA(B), ?IS_ALPHA(C), ?IS_ALPHA(D) ->
+ langtag_region(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D) >>);
+langtag_script(R, Acc, T) ->
+ langtag_region(R, Acc, T).
+
+langtag_region(<< $-, A, B, C, D, E, F, G, H, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G), ?IS_ALPHANUM(H) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G), ?LC(H) >>);
+langtag_region(<< $-, A, B, C, D, E, F, G, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G) >>);
+langtag_region(<< $-, A, B, C, D, E, F, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F) >>);
+langtag_region(<< $-, A, B, C, D, E, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D), ?IS_ALPHANUM(E) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E) >>);
+langtag_region(<< $-, A, B, C, D, R/bits >>, Acc, T)
+ when ?IS_DIGIT(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D) ->
+ langtag_variant(R, Acc, << T/binary, $-, A, ?LC(B), ?LC(C), ?LC(D) >>);
+langtag_region(<< $-, A, B, R/bits >>, Acc, T) when ?IS_ALPHA(A), ?IS_ALPHA(B) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B) >>);
+langtag_region(<< $-, A, B, C, R/bits >>, Acc, T) when ?IS_DIGIT(A), ?IS_DIGIT(B), ?IS_DIGIT(C) ->
+ langtag_variant(R, Acc, << T/binary, $-, A, B, C >>);
+langtag_region(R, Acc, T) ->
+ langtag_variant(R, Acc, T).
+
+langtag_variant(<< $-, A, B, C, D, E, F, G, H, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G), ?IS_ALPHANUM(H) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G), ?LC(H) >>);
+langtag_variant(<< $-, A, B, C, D, E, F, G, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G) >>);
+langtag_variant(<< $-, A, B, C, D, E, F, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F) >>);
+langtag_variant(<< $-, A, B, C, D, E, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D), ?IS_ALPHANUM(E) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E) >>);
+langtag_variant(<< $-, A, B, C, D, R/bits >>, Acc, T)
+ when ?IS_DIGIT(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D) ->
+ langtag_variant(R, Acc, << T/binary, $-, A, ?LC(B), ?LC(C), ?LC(D) >>);
+langtag_variant(R, Acc, T) ->
+ langtag_extension(R, Acc, T).
+
+langtag_extension(<< $-, X, R/bits >>, Acc, T) when X =:= $x; X =:= $X -> langtag_privateuse_sub(R, Acc, << T/binary, $-, $x >>, 0);
+langtag_extension(<< $-, S, R/bits >>, Acc, T) when ?IS_ALPHANUM(S) -> langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(S) >>, 0);
+langtag_extension(R, Acc, T) -> langtag_list_sep(R, [T|Acc]).
+
+langtag_extension_sub(<< $-, A, B, C, D, E, F, G, H, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G), ?IS_ALPHANUM(H) ->
+ langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G), ?LC(H) >>, N + 1);
+langtag_extension_sub(<< $-, A, B, C, D, E, F, G, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G) ->
+ langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G) >>, N + 1);
+langtag_extension_sub(<< $-, A, B, C, D, E, F, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F) ->
+ langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F) >>, N + 1);
+langtag_extension_sub(<< $-, A, B, C, D, E, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D), ?IS_ALPHANUM(E) ->
+ langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E) >>, N + 1);
+langtag_extension_sub(<< $-, A, B, C, D, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D) ->
+ langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D) >>, N + 1);
+langtag_extension_sub(<< $-, A, B, C, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C) ->
+ langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C) >>, N + 1);
+langtag_extension_sub(<< $-, A, B, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B) ->
+ langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B) >>, N + 1);
+langtag_extension_sub(R, Acc, T, N) when N > 0 ->
+ langtag_extension(R, Acc, T).
+
+langtag_privateuse_sub(<< $-, A, B, C, D, E, F, G, H, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G), ?IS_ALPHANUM(H) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G), ?LC(H) >>, N + 1);
+langtag_privateuse_sub(<< $-, A, B, C, D, E, F, G, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G) >>, N + 1);
+langtag_privateuse_sub(<< $-, A, B, C, D, E, F, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F) >>, N + 1);
+langtag_privateuse_sub(<< $-, A, B, C, D, E, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D), ?IS_ALPHANUM(E) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E) >>, N + 1);
+langtag_privateuse_sub(<< $-, A, B, C, D, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D) >>, N + 1);
+langtag_privateuse_sub(<< $-, A, B, C, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C) >>, N + 1);
+langtag_privateuse_sub(<< $-, A, B, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B) >>, N + 1);
+langtag_privateuse_sub(<< $-, A, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A) >>, N + 1);
+langtag_privateuse_sub(R, Acc, T, N) when N > 0 -> langtag_list_sep(R, [T|Acc]).
+
+langtag_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+langtag_list_sep(<< $,, R/bits >>, Acc) -> langtag_list(R, Acc);
+langtag_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> langtag_list_sep(R, Acc).
+
+-ifdef(TEST).
+langtag_language() -> vector(2, 3, alpha()).
+langtag_extlang() -> vector(0, 3, [$-, alpha(), alpha(), alpha()]).
+langtag_script() -> oneof([[], [$-, alpha(), alpha(), alpha(), alpha()]]).
+langtag_region() -> oneof([[], [$-, alpha(), alpha()], [$-, digit(), digit(), digit()]]).
+
+langtag_variant() ->
+ small_list(frequency([
+ {4, [$-, vector(5, 8, alphanum())]},
+ {1, [$-, digit(), alphanum(), alphanum(), alphanum()]}
+ ])).
+
+langtag_extension() ->
+ small_list([$-, ?SUCHTHAT(S, alphanum(), S =/= $x andalso S =/= $X),
+ small_non_empty_list([$-, vector(2, 8, alphanum())])
+ ]).
+
+langtag_privateuse() -> oneof([[], [$-, langtag_privateuse_nodash()]]).
+langtag_privateuse_nodash() -> [elements([$x, $X]), small_non_empty_list([$-, vector(1, 8, alphanum())])].
+private_language_tag() -> ?LET(T, langtag_privateuse_nodash(), iolist_to_binary(T)).
+
+language_tag() ->
+ ?LET(IoList,
+ [langtag_language(), langtag_extlang(), langtag_script(), langtag_region(),
+ langtag_variant(), langtag_extension(), langtag_privateuse()],
+ iolist_to_binary(IoList)).
+
+content_language() ->
+ ?LET(L,
+ non_empty(list(frequency([
+ {90, language_tag()},
+ {10, private_language_tag()}
+ ]))),
+ begin
+ << _, ContentLanguage/binary >> = iolist_to_binary([[$,, T] || T <- L]),
+ {L, ContentLanguage}
+ end).
+
+prop_parse_content_language() ->
+ ?FORALL({L, ContentLanguage},
+ content_language(),
+ begin
+ ResL = parse_content_language(ContentLanguage),
+ CheckedL = [?LOWER(T) =:= ResT || {T, ResT} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_content_language_test_() ->
+ Tests = [
+ {<<"de">>, [<<"de">>]},
+ {<<"fr">>, [<<"fr">>]},
+ {<<"ja">>, [<<"ja">>]},
+ {<<"zh-Hant">>, [<<"zh-hant">>]},
+ {<<"zh-Hans">>, [<<"zh-hans">>]},
+ {<<"sr-Cyrl">>, [<<"sr-cyrl">>]},
+ {<<"sr-Latn">>, [<<"sr-latn">>]},
+ {<<"zh-cmn-Hans-CN">>, [<<"zh-cmn-hans-cn">>]},
+ {<<"cmn-Hans-CN">>, [<<"cmn-hans-cn">>]},
+ {<<"zh-yue-HK">>, [<<"zh-yue-hk">>]},
+ {<<"yue-HK">>, [<<"yue-hk">>]},
+ {<<"zh-Hans-CN">>, [<<"zh-hans-cn">>]},
+ {<<"sr-Latn-RS">>, [<<"sr-latn-rs">>]},
+ {<<"sl-rozaj">>, [<<"sl-rozaj">>]},
+ {<<"sl-rozaj-biske">>, [<<"sl-rozaj-biske">>]},
+ {<<"sl-nedis">>, [<<"sl-nedis">>]},
+ {<<"de-CH-1901">>, [<<"de-ch-1901">>]},
+ {<<"sl-IT-nedis">>, [<<"sl-it-nedis">>]},
+ {<<"hy-Latn-IT-arevela">>, [<<"hy-latn-it-arevela">>]},
+ {<<"de-DE">>, [<<"de-de">>]},
+ {<<"en-US">>, [<<"en-us">>]},
+ {<<"es-419">>, [<<"es-419">>]},
+ {<<"de-CH-x-phonebk">>, [<<"de-ch-x-phonebk">>]},
+ {<<"az-Arab-x-AZE-derbend">>, [<<"az-arab-x-aze-derbend">>]},
+ {<<"x-whatever">>, [<<"x-whatever">>]},
+ {<<"qaa-Qaaa-QM-x-southern">>, [<<"qaa-qaaa-qm-x-southern">>]},
+ {<<"de-Qaaa">>, [<<"de-qaaa">>]},
+ {<<"sr-Latn-QM">>, [<<"sr-latn-qm">>]},
+ {<<"sr-Qaaa-RS">>, [<<"sr-qaaa-rs">>]},
+ {<<"en-US-u-islamcal">>, [<<"en-us-u-islamcal">>]},
+ {<<"zh-CN-a-myext-x-private">>, [<<"zh-cn-a-myext-x-private">>]},
+ {<<"en-a-myext-b-another">>, [<<"en-a-myext-b-another">>]},
+ {<<"mn-Cyrl-MN">>, [<<"mn-cyrl-mn">>]},
+ {<<"MN-cYRL-mn">>, [<<"mn-cyrl-mn">>]},
+ {<<"mN-cYrL-Mn">>, [<<"mn-cyrl-mn">>]},
+ {<<"az-Arab-IR">>, [<<"az-arab-ir">>]},
+ {<<"zh-gan">>, [<<"zh-gan">>]},
+ {<<"zh-yue">>, [<<"zh-yue">>]},
+ {<<"zh-cmn">>, [<<"zh-cmn">>]},
+ {<<"de-AT">>, [<<"de-at">>]},
+ {<<"de-CH-1996">>, [<<"de-ch-1996">>]},
+ {<<"en-Latn-GB-boont-r-extended-sequence-x-private">>,
+ [<<"en-latn-gb-boont-r-extended-sequence-x-private">>]},
+ {<<"el-x-koine">>, [<<"el-x-koine">>]},
+ {<<"el-x-attic">>, [<<"el-x-attic">>]},
+ {<<"fr, en-US, es-419, az-Arab, x-pig-latin, man-Nkoo-GN">>,
+ [<<"fr">>, <<"en-us">>, <<"es-419">>, <<"az-arab">>, <<"x-pig-latin">>, <<"man-nkoo-gn">>]},
+ {<<"da">>, [<<"da">>]},
+ {<<"mi, en">>, [<<"mi">>, <<"en">>]}
+ ],
+ [{V, fun() -> R = parse_content_language(V) end} || {V, R} <- Tests].
+
+parse_content_language_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_content_language(V)) end} || V <- Tests].
+
+horse_parse_content_language() ->
+ horse:repeat(100000,
+ parse_content_language(<<"fr, en-US, es-419, az-Arab, x-pig-latin, man-Nkoo-GN">>)
+ ).
+-endif.
+
+%% Content-Length header.
+
+-spec parse_content_length(binary()) -> non_neg_integer().
+parse_content_length(ContentLength) ->
+ I = binary_to_integer(ContentLength),
+ true = I >= 0,
+ I.
+
+-ifdef(TEST).
+prop_parse_content_length() ->
+ ?FORALL(
+ X,
+ non_neg_integer(),
+ X =:= parse_content_length(integer_to_binary(X))
+ ).
+
+parse_content_length_test_() ->
+ Tests = [
+ {<<"0">>, 0},
+ {<<"42">>, 42},
+ {<<"69">>, 69},
+ {<<"1337">>, 1337},
+ {<<"3495">>, 3495},
+ {<<"1234567890">>, 1234567890}
+ ],
+ [{V, fun() -> R = parse_content_length(V) end} || {V, R} <- Tests].
+
+parse_content_length_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"-1">>,
+ <<"123, 123">>,
+ <<"4.17">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_content_length(V)) end} || V <- Tests].
+
+horse_parse_content_length_zero() ->
+ horse:repeat(100000,
+ parse_content_length(<<"0">>)
+ ).
+
+horse_parse_content_length_giga() ->
+ horse:repeat(100000,
+ parse_content_length(<<"1234567890">>)
+ ).
+-endif.
+
+%% Content-Range header.
+
+-spec parse_content_range(binary())
+ -> {bytes, non_neg_integer(), non_neg_integer(), non_neg_integer() | '*'}
+ | {bytes, '*', non_neg_integer()} | {binary(), binary()}.
+parse_content_range(<<"bytes */", C, R/bits >>) when ?IS_DIGIT(C) -> unsatisfied_range(R, C - $0);
+parse_content_range(<<"bytes ", C, R/bits >>) when ?IS_DIGIT(C) -> byte_range_first(R, C - $0);
+parse_content_range(<< C, R/bits >>) when ?IS_TOKEN(C) ->
+ ?LOWER(other_content_range_unit, R, <<>>).
+
+byte_range_first(<< $-, C, R/bits >>, First) when ?IS_DIGIT(C) -> byte_range_last(R, First, C - $0);
+byte_range_first(<< C, R/bits >>, First) when ?IS_DIGIT(C) -> byte_range_first(R, First * 10 + C - $0).
+
+byte_range_last(<<"/*">>, First, Last) -> {bytes, First, Last, '*'};
+byte_range_last(<< $/, C, R/bits >>, First, Last) when ?IS_DIGIT(C) -> byte_range_complete(R, First, Last, C - $0);
+byte_range_last(<< C, R/bits >>, First, Last) when ?IS_DIGIT(C) -> byte_range_last(R, First, Last * 10 + C - $0).
+
+byte_range_complete(<<>>, First, Last, Complete) -> {bytes, First, Last, Complete};
+byte_range_complete(<< C, R/bits >>, First, Last, Complete) when ?IS_DIGIT(C) ->
+ byte_range_complete(R, First, Last, Complete * 10 + C - $0).
+
+unsatisfied_range(<<>>, Complete) -> {bytes, '*', Complete};
+unsatisfied_range(<< C, R/bits >>, Complete) when ?IS_DIGIT(C) -> unsatisfied_range(R, Complete * 10 + C - $0).
+
+other_content_range_unit(<< $\s, R/bits >>, Unit) -> other_content_range_resp(R, Unit, <<>>);
+other_content_range_unit(<< C, R/bits >>, Unit) when ?IS_TOKEN(C) ->
+ ?LOWER(other_content_range_unit, R, Unit).
+
+other_content_range_resp(<<>>, Unit, Resp) -> {Unit, Resp};
+other_content_range_resp(<< C, R/bits >>, Unit, Resp) when ?IS_CHAR(C) -> other_content_range_resp(R, Unit, << Resp/binary, C >>).
+
+-ifdef(TEST).
+content_range() ->
+ ?LET(ContentRange,
+ oneof([
+ ?SUCHTHAT({bytes, First, Last, Complete},
+ {bytes, non_neg_integer(), non_neg_integer(), non_neg_integer()},
+ First =< Last andalso Last < Complete),
+ ?SUCHTHAT({bytes, First, Last, '*'},
+ {bytes, non_neg_integer(), non_neg_integer(), '*'},
+ First =< Last),
+ {bytes, '*', non_neg_integer()},
+ {token(), ?LET(L, list(abnf_char()), list_to_binary(L))}
+ ]),
+ {case ContentRange of
+ {Unit, Resp} when is_binary(Unit) -> {?LOWER(Unit), Resp};
+ _ -> ContentRange
+ end, case ContentRange of
+ {bytes, First, Last, '*'} ->
+ << "bytes ", (integer_to_binary(First))/binary, "-",
+ (integer_to_binary(Last))/binary, "/*">>;
+ {bytes, First, Last, Complete} ->
+ << "bytes ", (integer_to_binary(First))/binary, "-",
+ (integer_to_binary(Last))/binary, "/", (integer_to_binary(Complete))/binary >>;
+ {bytes, '*', Complete} ->
+ << "bytes */", (integer_to_binary(Complete))/binary >>;
+ {Unit, Resp} ->
+ << Unit/binary, $\s, Resp/binary >>
+ end}).
+
+prop_parse_content_range() ->
+ ?FORALL({Res, ContentRange},
+ content_range(),
+ Res =:= parse_content_range(ContentRange)).
+
+parse_content_range_test_() ->
+ Tests = [
+ {<<"bytes 21010-47021/47022">>, {bytes, 21010, 47021, 47022}},
+ {<<"bytes 500-999/8000">>, {bytes, 500, 999, 8000}},
+ {<<"bytes 7000-7999/8000">>, {bytes, 7000, 7999, 8000}},
+ {<<"bytes 42-1233/1234">>, {bytes, 42, 1233, 1234}},
+ {<<"bytes 42-1233/*">>, {bytes, 42, 1233, '*'}},
+ {<<"bytes */1234">>, {bytes, '*', 1234}},
+ {<<"bytes 0-499/1234">>, {bytes, 0, 499, 1234}},
+ {<<"bytes 500-999/1234">>, {bytes, 500, 999, 1234}},
+ {<<"bytes 500-1233/1234">>, {bytes, 500, 1233, 1234}},
+ {<<"bytes 734-1233/1234">>, {bytes, 734, 1233, 1234}},
+ {<<"bytes */47022">>, {bytes, '*', 47022}},
+ {<<"exampleunit 1.2-4.3/25">>, {<<"exampleunit">>, <<"1.2-4.3/25">>}},
+ {<<"exampleunit 11.2-14.3/25">>, {<<"exampleunit">>, <<"11.2-14.3/25">>}}
+ ],
+ [{V, fun() -> R = parse_content_range(V) end} || {V, R} <- Tests].
+
+parse_content_range_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_content_range(V)) end} || V <- Tests].
+
+horse_parse_content_range_bytes() ->
+ horse:repeat(200000,
+ parse_content_range(<<"bytes 21010-47021/47022">>)
+ ).
+
+horse_parse_content_range_other() ->
+ horse:repeat(200000,
+ parse_content_range(<<"exampleunit 11.2-14.3/25">>)
+ ).
+-endif.
+
+%% Content-Type header.
+
+-spec parse_content_type(binary()) -> media_type().
+parse_content_type(<< C, R/bits >>) when ?IS_TOKEN(C) ->
+ ?LOWER(media_type, R, <<>>).
+
+media_type(<< $/, C, R/bits >>, T) when ?IS_TOKEN(C) ->
+ ?LOWER(media_subtype, R, T, <<>>);
+media_type(<< C, R/bits >>, T) when ?IS_TOKEN(C) ->
+ ?LOWER(media_type, R, T).
+
+media_subtype(<< C, R/bits >>, T, S) when ?IS_TOKEN(C) ->
+ ?LOWER(media_subtype, R, T, S);
+media_subtype(R, T, S) -> media_param_sep(R, T, S, []).
+
+media_param_sep(<<>>, T, S, P) -> {T, S, lists:reverse(P)};
+media_param_sep(<< $;, R/bits >>, T, S, P) -> media_before_param(R, T, S, P);
+media_param_sep(<< C, R/bits >>, T, S, P) when ?IS_WS(C) -> media_param_sep(R, T, S, P).
+
+media_before_param(<< C, R/bits >>, T, S, P) when ?IS_WS(C)-> media_before_param(R, T, S, P);
+media_before_param(<< "charset=", $", R/bits >>, T, S, P) -> media_charset_quoted(R, T, S, P, <<>>);
+media_before_param(<< "charset=", R/bits >>, T, S, P) -> media_charset(R, T, S, P, <<>>);
+media_before_param(<< C, R/bits >>, T, S, P) when ?IS_TOKEN(C) ->
+ ?LOWER(media_param, R, T, S, P, <<>>).
+
+media_charset_quoted(<< $", R/bits >>, T, S, P, V) ->
+ media_param_sep(R, T, S, [{<<"charset">>, V}|P]);
+media_charset_quoted(<< $\\, C, R/bits >>, T, S, P, V) when ?IS_VCHAR_OBS(C) ->
+ ?LOWER(media_charset_quoted, R, T, S, P, V);
+media_charset_quoted(<< C, R/bits >>, T, S, P, V) when ?IS_VCHAR_OBS(C) ->
+ ?LOWER(media_charset_quoted, R, T, S, P, V).
+
+media_charset(<< C, R/bits >>, T, S, P, V) when ?IS_TOKEN(C) ->
+ ?LOWER(media_charset, R, T, S, P, V);
+media_charset(R, T, S, P, V) -> media_param_sep(R, T, S, [{<<"charset">>, V}|P]).
+
+media_param(<< $=, $", R/bits >>, T, S, P, K) -> media_quoted(R, T, S, P, K, <<>>);
+media_param(<< $=, C, R/bits >>, T, S, P, K) when ?IS_TOKEN(C) -> media_value(R, T, S, P, K, << C >>);
+media_param(<< C, R/bits >>, T, S, P, K) when ?IS_TOKEN(C) ->
+ ?LOWER(media_param, R, T, S, P, K).
+
+media_quoted(<< $", R/bits >>, T, S, P, K, V) -> media_param_sep(R, T, S, [{K, V}|P]);
+media_quoted(<< $\\, C, R/bits >>, T, S, P, K, V) when ?IS_VCHAR_OBS(C) -> media_quoted(R, T, S, P, K, << V/binary, C >>);
+media_quoted(<< C, R/bits >>, T, S, P, K, V) when ?IS_VCHAR_OBS(C) -> media_quoted(R, T, S, P, K, << V/binary, C >>).
+
+media_value(<< C, R/bits >>, T, S, P, K, V) when ?IS_TOKEN(C) -> media_value(R, T, S, P, K, << V/binary, C >>);
+media_value(R, T, S, P, K, V) -> media_param_sep(R, T, S, [{K, V}|P]).
+
+-ifdef(TEST).
+media_type_parameter() ->
+ frequency([
+ {90, parameter()},
+ {10, {<<"charset">>, oneof([token(), quoted_string()]), <<>>, <<>>}}
+ ]).
+
+media_type() ->
+ ?LET({T, S, P},
+ {token(), token(), small_list(media_type_parameter())},
+ {T, S, P, iolist_to_binary([T, $/, S, [[OWS1, $;, OWS2, K, $=, V] || {K, V, OWS1, OWS2} <- P]])}
+ ).
+
+prop_parse_content_type() ->
+ ?FORALL({T, S, P, MediaType},
+ media_type(),
+ begin
+ {ResT, ResS, ResP} = parse_content_type(MediaType),
+ ExpectedP = [case ?LOWER(K) of
+ <<"charset">> -> {<<"charset">>, ?LOWER(unquote(V))};
+ LowK -> {LowK, unquote(V)}
+ end || {K, V, _, _} <- P],
+ ResT =:= ?LOWER(T)
+ andalso ResS =:= ?LOWER(S)
+ andalso ResP =:= ExpectedP
+ end
+ ).
+
+parse_content_type_test_() ->
+ Tests = [
+ {<<"text/html;charset=utf-8">>,
+ {<<"text">>, <<"html">>, [{<<"charset">>, <<"utf-8">>}]}},
+ {<<"text/html;charset=UTF-8">>,
+ {<<"text">>, <<"html">>, [{<<"charset">>, <<"utf-8">>}]}},
+ {<<"Text/HTML;Charset=\"utf-8\"">>,
+ {<<"text">>, <<"html">>, [{<<"charset">>, <<"utf-8">>}]}},
+ {<<"text/html; charset=\"utf-8\"">>,
+ {<<"text">>, <<"html">>, [{<<"charset">>, <<"utf-8">>}]}},
+ {<<"text/html; charset=ISO-8859-4">>,
+ {<<"text">>, <<"html">>, [{<<"charset">>, <<"iso-8859-4">>}]}},
+ {<<"text/plain; charset=iso-8859-4">>,
+ {<<"text">>, <<"plain">>, [{<<"charset">>, <<"iso-8859-4">>}]}},
+ {<<"multipart/form-data \t;Boundary=\"MultipartIsUgly\"">>,
+ {<<"multipart">>, <<"form-data">>, [
+ {<<"boundary">>, <<"MultipartIsUgly">>}
+ ]}},
+ {<<"foo/bar; one=FirstParam; two=SecondParam">>,
+ {<<"foo">>, <<"bar">>, [
+ {<<"one">>, <<"FirstParam">>},
+ {<<"two">>, <<"SecondParam">>}
+ ]}}
+ ],
+ [{V, fun() -> R = parse_content_type(V) end} || {V, R} <- Tests].
+
+horse_parse_content_type() ->
+ horse:repeat(200000,
+ parse_content_type(<<"text/html;charset=utf-8">>)
+ ).
+-endif.
+
+%% Cookie header.
+
+-spec parse_cookie(binary()) -> [{binary(), binary()}].
+parse_cookie(Cookie) ->
+ cow_cookie:parse_cookie(Cookie).
+
+%% Date header.
+
+-spec parse_date(binary()) -> calendar:datetime().
+parse_date(Date) ->
+ cow_date:parse_date(Date).
+
+-ifdef(TEST).
+parse_date_test_() ->
+ Tests = [
+ {<<"Tue, 15 Nov 1994 08:12:31 GMT">>, {{1994, 11, 15}, {8, 12, 31}}}
+ ],
+ [{V, fun() -> R = parse_date(V) end} || {V, R} <- Tests].
+-endif.
+
+%% ETag header.
+
+-spec parse_etag(binary()) -> etag().
+parse_etag(<< $W, $/, $", R/bits >>) ->
+ etag(R, weak, <<>>);
+parse_etag(<< $", R/bits >>) ->
+ etag(R, strong, <<>>).
+
+etag(<< $" >>, Strength, Tag) ->
+ {Strength, Tag};
+etag(<< C, R/bits >>, Strength, Tag) when ?IS_ETAGC(C) ->
+ etag(R, Strength, << Tag/binary, C >>).
+
+-ifdef(TEST).
+etagc() ->
+ ?SUCHTHAT(C, integer(16#21, 16#ff), C =/= 16#22 andalso C =/= 16#7f).
+
+etag() ->
+ ?LET({Strength, Tag},
+ {elements([weak, strong]), list(etagc())},
+ begin
+ TagBin = list_to_binary(Tag),
+ {{Strength, TagBin},
+ case Strength of
+ weak -> << $W, $/, $", TagBin/binary, $" >>;
+ strong -> << $", TagBin/binary, $" >>
+ end}
+ end).
+
+prop_parse_etag() ->
+ ?FORALL({Tag, TagBin},
+ etag(),
+ Tag =:= parse_etag(TagBin)).
+
+parse_etag_test_() ->
+ Tests = [
+ {<<"\"xyzzy\"">>, {strong, <<"xyzzy">>}},
+ {<<"W/\"xyzzy\"">>, {weak, <<"xyzzy">>}},
+ {<<"\"\"">>, {strong, <<>>}}
+ ],
+ [{V, fun() -> R = parse_etag(V) end} || {V, R} <- Tests].
+
+parse_etag_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"\"">>,
+ <<"W">>,
+ <<"W/">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_etag(V)) end} || V <- Tests].
+
+horse_parse_etag() ->
+ horse:repeat(200000,
+ parse_etag(<<"W/\"xyzzy\"">>)
+ ).
+-endif.
+
+%% Expect header.
+
+-spec parse_expect(binary()) -> continue.
+parse_expect(<<"100-continue">>) ->
+ continue;
+parse_expect(<<"100-", C, O, N, T, I, M, U, E >>)
+ when (C =:= $C) or (C =:= $c), (O =:= $O) or (O =:= $o),
+ (N =:= $N) or (N =:= $n), (T =:= $T) or (T =:= $t),
+ (I =:= $I) or (I =:= $i), (M =:= $N) or (M =:= $n),
+ (U =:= $U) or (U =:= $u), (E =:= $E) or (E =:= $e) ->
+ continue.
+
+-ifdef(TEST).
+expect() ->
+ ?LET(E,
+ [$1, $0, $0, $-,
+ elements([$c, $C]), elements([$o, $O]), elements([$n, $N]),
+ elements([$t, $T]), elements([$i, $I]), elements([$n, $N]),
+ elements([$u, $U]), elements([$e, $E])],
+ list_to_binary(E)).
+
+prop_parse_expect() ->
+ ?FORALL(E, expect(), continue =:= parse_expect(E)).
+
+parse_expect_test_() ->
+ Tests = [
+ <<"100-continue">>,
+ <<"100-CONTINUE">>,
+ <<"100-Continue">>,
+ <<"100-CoNtInUe">>
+ ],
+ [{V, fun() -> continue = parse_expect(V) end} || V <- Tests].
+
+parse_expect_error_test_() ->
+ Tests = [
+ <<>>,
+ <<" ">>,
+ <<"200-OK">>,
+ <<"Cookies">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_expect(V)) end} || V <- Tests].
+
+horse_parse_expect() ->
+ horse:repeat(200000,
+ parse_expect(<<"100-continue">>)
+ ).
+-endif.
+
+%% Expires header.
+%%
+%% Recipients must interpret invalid date formats as a date
+%% in the past. The value "0" is commonly used.
+
+-spec parse_expires(binary()) -> calendar:datetime().
+parse_expires(<<"0">>) ->
+ {{1, 1, 1}, {0, 0, 0}};
+parse_expires(Expires) ->
+ try
+ cow_date:parse_date(Expires)
+ catch _:_ ->
+ {{1, 1, 1}, {0, 0, 0}}
+ end.
+
+-ifdef(TEST).
+parse_expires_test_() ->
+ Tests = [
+ {<<"0">>, {{1, 1, 1}, {0, 0, 0}}},
+ {<<"Thu, 01 Dec 1994 nope invalid">>, {{1, 1, 1}, {0, 0, 0}}},
+ {<<"Thu, 01 Dec 1994 16:00:00 GMT">>, {{1994, 12, 1}, {16, 0, 0}}}
+ ],
+ [{V, fun() -> R = parse_expires(V) end} || {V, R} <- Tests].
+
+horse_parse_expires_0() ->
+ horse:repeat(200000,
+ parse_expires(<<"0">>)
+ ).
+
+horse_parse_expires_invalid() ->
+ horse:repeat(200000,
+ parse_expires(<<"Thu, 01 Dec 1994 nope invalid">>)
+ ).
+-endif.
+
+%% Host header.
+%%
+%% We only seek to have legal characters and separate the
+%% host and port values. The number of segments in the host
+%% or the size of each segment is not checked.
+%%
+%% There is no way to distinguish IPv4 addresses from regular
+%% names until the last segment is reached therefore we do not
+%% differentiate them.
+%%
+%% The following valid hosts are currently rejected: IPv6
+%% addresses with a zone identifier; IPvFuture addresses;
+%% and percent-encoded addresses.
+
+-spec parse_host(binary()) -> {binary(), 0..65535 | undefined}.
+parse_host(<< $[, R/bits >>) ->
+ ipv6_address(R, << $[ >>);
+parse_host(Host) ->
+ reg_name(Host, <<>>).
+
+ipv6_address(<< $] >>, IP) -> {<< IP/binary, $] >>, undefined};
+ipv6_address(<< $], $:, Port/bits >>, IP) -> {<< IP/binary, $] >>, binary_to_integer(Port)};
+ipv6_address(<< C, R/bits >>, IP) when ?IS_HEX(C) or (C =:= $:) or (C =:= $.) ->
+ ?LOWER(ipv6_address, R, IP).
+
+reg_name(<<>>, Name) -> {Name, undefined};
+reg_name(<< $:, Port/bits >>, Name) -> {Name, binary_to_integer(Port)};
+reg_name(<< C, R/bits >>, Name) when ?IS_URI_UNRESERVED(C) or ?IS_URI_SUB_DELIMS(C) ->
+ ?LOWER(reg_name, R, Name).
+
+-ifdef(TEST).
+host_chars() -> "!$&'()*+,-.0123456789;=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~".
+host() -> vector(1, 255, elements(host_chars())).
+
+host_port() ->
+ ?LET({Host, Port},
+ {host(), oneof([undefined, integer(1, 65535)])},
+ begin
+ HostBin = list_to_binary(Host),
+ {{?LOWER(HostBin), Port},
+ case Port of
+ undefined -> HostBin;
+ _ -> << HostBin/binary, $:, (integer_to_binary(Port))/binary >>
+ end}
+ end).
+
+prop_parse_host() ->
+ ?FORALL({Res, Host}, host_port(), Res =:= parse_host(Host)).
+
+parse_host_test_() ->
+ Tests = [
+ {<<>>, {<<>>, undefined}},
+ {<<"www.example.org:8080">>, {<<"www.example.org">>, 8080}},
+ {<<"www.example.org">>, {<<"www.example.org">>, undefined}},
+ {<<"192.0.2.1:8080">>, {<<"192.0.2.1">>, 8080}},
+ {<<"192.0.2.1">>, {<<"192.0.2.1">>, undefined}},
+ {<<"[2001:db8::1]:8080">>, {<<"[2001:db8::1]">>, 8080}},
+ {<<"[2001:db8::1]">>, {<<"[2001:db8::1]">>, undefined}},
+ {<<"[::ffff:192.0.2.1]:8080">>, {<<"[::ffff:192.0.2.1]">>, 8080}},
+ {<<"[::ffff:192.0.2.1]">>, {<<"[::ffff:192.0.2.1]">>, undefined}}
+ ],
+ [{V, fun() -> R = parse_host(V) end} || {V, R} <- Tests].
+
+horse_parse_host_blue_example_org() ->
+ horse:repeat(200000,
+ parse_host(<<"blue.example.org:8080">>)
+ ).
+
+horse_parse_host_ipv4() ->
+ horse:repeat(200000,
+ parse_host(<<"192.0.2.1:8080">>)
+ ).
+
+horse_parse_host_ipv6() ->
+ horse:repeat(200000,
+ parse_host(<<"[2001:db8::1]:8080">>)
+ ).
+
+horse_parse_host_ipv6_v4() ->
+ horse:repeat(200000,
+ parse_host(<<"[::ffff:192.0.2.1]:8080">>)
+ ).
+-endif.
+
+%% HTTP2-Settings header.
+
+-spec parse_http2_settings(binary()) -> map().
+parse_http2_settings(HTTP2Settings) ->
+ cow_http2:parse_settings_payload(base64:decode(HTTP2Settings)).
+
+%% If-Match header.
+
+-spec parse_if_match(binary()) -> '*' | [etag()].
+parse_if_match(<<"*">>) ->
+ '*';
+parse_if_match(IfMatch) ->
+ nonempty(etag_list(IfMatch, [])).
+
+etag_list(<<>>, Acc) -> lists:reverse(Acc);
+etag_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> etag_list(R, Acc);
+etag_list(<< $W, $/, $", R/bits >>, Acc) -> etag(R, Acc, weak, <<>>);
+etag_list(<< $", R/bits >>, Acc) -> etag(R, Acc, strong, <<>>).
+
+etag(<< $", R/bits >>, Acc, Strength, Tag) -> etag_list_sep(R, [{Strength, Tag}|Acc]);
+etag(<< C, R/bits >>, Acc, Strength, Tag) when ?IS_ETAGC(C) -> etag(R, Acc, Strength, << Tag/binary, C >>).
+
+etag_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+etag_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> etag_list_sep(R, Acc);
+etag_list_sep(<< $,, R/bits >>, Acc) -> etag_list(R, Acc).
+
+-ifdef(TEST).
+prop_parse_if_match() ->
+ ?FORALL(L,
+ non_empty(list(etag())),
+ begin
+ << _, IfMatch/binary >> = iolist_to_binary([[$,, T] || {_, T} <- L]),
+ ResL = parse_if_match(IfMatch),
+ CheckedL = [T =:= ResT || {{T, _}, ResT} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_if_match_test_() ->
+ Tests = [
+ {<<"\"xyzzy\"">>, [{strong, <<"xyzzy">>}]},
+ {<<"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"">>,
+ [{strong, <<"xyzzy">>}, {strong, <<"r2d2xxxx">>}, {strong, <<"c3piozzzz">>}]},
+ {<<"*">>, '*'}
+ ],
+ [{V, fun() -> R = parse_if_match(V) end} || {V, R} <- Tests].
+
+parse_if_match_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_if_match(V)) end} || V <- Tests].
+
+horse_parse_if_match() ->
+ horse:repeat(200000,
+ parse_if_match(<<"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"">>)
+ ).
+-endif.
+
+%% If-Modified-Since header.
+
+-spec parse_if_modified_since(binary()) -> calendar:datetime().
+parse_if_modified_since(IfModifiedSince) ->
+ cow_date:parse_date(IfModifiedSince).
+
+-ifdef(TEST).
+parse_if_modified_since_test_() ->
+ Tests = [
+ {<<"Sat, 29 Oct 1994 19:43:31 GMT">>, {{1994, 10, 29}, {19, 43, 31}}}
+ ],
+ [{V, fun() -> R = parse_if_modified_since(V) end} || {V, R} <- Tests].
+-endif.
+
+%% If-None-Match header.
+
+-spec parse_if_none_match(binary()) -> '*' | [etag()].
+parse_if_none_match(<<"*">>) ->
+ '*';
+parse_if_none_match(IfNoneMatch) ->
+ nonempty(etag_list(IfNoneMatch, [])).
+
+-ifdef(TEST).
+parse_if_none_match_test_() ->
+ Tests = [
+ {<<"\"xyzzy\"">>, [{strong, <<"xyzzy">>}]},
+ {<<"W/\"xyzzy\"">>, [{weak, <<"xyzzy">>}]},
+ {<<"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"">>,
+ [{strong, <<"xyzzy">>}, {strong, <<"r2d2xxxx">>}, {strong, <<"c3piozzzz">>}]},
+ {<<"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\"">>,
+ [{weak, <<"xyzzy">>}, {weak, <<"r2d2xxxx">>}, {weak, <<"c3piozzzz">>}]},
+ {<<"*">>, '*'}
+ ],
+ [{V, fun() -> R = parse_if_none_match(V) end} || {V, R} <- Tests].
+
+parse_if_none_match_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_if_none_match(V)) end} || V <- Tests].
+
+horse_parse_if_none_match() ->
+ horse:repeat(200000,
+ parse_if_none_match(<<"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\"">>)
+ ).
+-endif.
+
+%% If-Range header.
+
+-spec parse_if_range(binary()) -> etag() | calendar:datetime().
+parse_if_range(<< $W, $/, $", R/bits >>) ->
+ etag(R, weak, <<>>);
+parse_if_range(<< $", R/bits >>) ->
+ etag(R, strong, <<>>);
+parse_if_range(IfRange) ->
+ cow_date:parse_date(IfRange).
+
+-ifdef(TEST).
+parse_if_range_test_() ->
+ Tests = [
+ {<<"W/\"xyzzy\"">>, {weak, <<"xyzzy">>}},
+ {<<"\"xyzzy\"">>, {strong, <<"xyzzy">>}},
+ {<<"Sat, 29 Oct 1994 19:43:31 GMT">>, {{1994, 10, 29}, {19, 43, 31}}}
+ ],
+ [{V, fun() -> R = parse_if_range(V) end} || {V, R} <- Tests].
+
+parse_if_range_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_if_range(V)) end} || V <- Tests].
+
+horse_parse_if_range_etag() ->
+ horse:repeat(200000,
+ parse_if_range(<<"\"xyzzy\"">>)
+ ).
+
+horse_parse_if_range_date() ->
+ horse:repeat(200000,
+ parse_if_range(<<"Sat, 29 Oct 1994 19:43:31 GMT">>)
+ ).
+-endif.
+
+%% If-Unmodified-Since header.
+
+-spec parse_if_unmodified_since(binary()) -> calendar:datetime().
+parse_if_unmodified_since(IfModifiedSince) ->
+ cow_date:parse_date(IfModifiedSince).
+
+-ifdef(TEST).
+parse_if_unmodified_since_test_() ->
+ Tests = [
+ {<<"Sat, 29 Oct 1994 19:43:31 GMT">>, {{1994, 10, 29}, {19, 43, 31}}}
+ ],
+ [{V, fun() -> R = parse_if_unmodified_since(V) end} || {V, R} <- Tests].
+-endif.
+
+%% Last-Modified header.
+
+-spec parse_last_modified(binary()) -> calendar:datetime().
+parse_last_modified(LastModified) ->
+ cow_date:parse_date(LastModified).
+
+-ifdef(TEST).
+parse_last_modified_test_() ->
+ Tests = [
+ {<<"Tue, 15 Nov 1994 12:45:26 GMT">>, {{1994, 11, 15}, {12, 45, 26}}}
+ ],
+ [{V, fun() -> R = parse_last_modified(V) end} || {V, R} <- Tests].
+-endif.
+
+%% Link header.
+
+-spec parse_link(binary()) -> [cow_link:link()].
+parse_link(Link) ->
+ cow_link:parse_link(Link).
+
+%% Max-Forwards header.
+
+-spec parse_max_forwards(binary()) -> non_neg_integer().
+parse_max_forwards(MaxForwards) ->
+ I = binary_to_integer(MaxForwards),
+ true = I >= 0,
+ I.
+
+-ifdef(TEST).
+prop_parse_max_forwards() ->
+ ?FORALL(
+ X,
+ non_neg_integer(),
+ X =:= parse_max_forwards(integer_to_binary(X))
+ ).
+
+parse_max_forwards_test_() ->
+ Tests = [
+ {<<"0">>, 0},
+ {<<"42">>, 42},
+ {<<"69">>, 69},
+ {<<"1337">>, 1337},
+ {<<"1234567890">>, 1234567890}
+ ],
+ [{V, fun() -> R = parse_max_forwards(V) end} || {V, R} <- Tests].
+
+parse_max_forwards_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"123, 123">>,
+ <<"4.17">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_max_forwards(V)) end} || V <- Tests].
+-endif.
+
+%% Origin header.
+
+%% According to the RFC6454 we should generate
+%% a fresh globally unique identifier and return that value if:
+%% - URI does not use a hierarchical element as a naming authority
+%% or the URI is not an absolute URI
+%% - the implementation doesn't support the protocol given by uri-scheme
+%% Thus, erlang reference represents a GUID here.
+%%
+%% We only seek to have legal characters and separate the
+%% host and port values. The number of segments in the host
+%% or the size of each segment is not checked.
+%%
+%% There is no way to distinguish IPv4 addresses from regular
+%% names until the last segment is reached therefore we do not
+%% differentiate them.
+%%
+%% @todo The following valid hosts are currently rejected: IPv6
+%% addresses with a zone identifier; IPvFuture addresses;
+%% and percent-encoded addresses.
+
+-spec parse_origin(binary()) -> [{binary(), binary(), 0..65535} | reference()].
+parse_origin(Origins) ->
+ nonempty(origin_scheme(Origins, [])).
+
+origin_scheme(<<>>, Acc) -> Acc;
+origin_scheme(<< "http://", R/bits >>, Acc) -> origin_host(R, Acc, <<"http">>);
+origin_scheme(<< "https://", R/bits >>, Acc) -> origin_host(R, Acc, <<"https">>);
+origin_scheme(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) -> origin_scheme(next_origin(R), [make_ref()|Acc]).
+
+origin_host(<< $[, R/bits >>, Acc, Scheme) -> origin_ipv6_address(R, Acc, Scheme, << $[ >>);
+origin_host(Host, Acc, Scheme) -> origin_reg_name(Host, Acc, Scheme, <<>>).
+
+origin_ipv6_address(<< $] >>, Acc, Scheme, IP) ->
+ lists:reverse([{Scheme, << IP/binary, $] >>, default_port(Scheme)}|Acc]);
+origin_ipv6_address(<< $], $\s, R/bits >>, Acc, Scheme, IP) ->
+ origin_scheme(R, [{Scheme, << IP/binary, $] >>, default_port(Scheme)}|Acc]);
+origin_ipv6_address(<< $], $:, Port/bits >>, Acc, Scheme, IP) ->
+ origin_port(Port, Acc, Scheme, << IP/binary, $] >>, <<>>);
+origin_ipv6_address(<< C, R/bits >>, Acc, Scheme, IP) when ?IS_HEX(C) or (C =:= $:) or (C =:= $.) ->
+ ?LOWER(origin_ipv6_address, R, Acc, Scheme, IP).
+
+origin_reg_name(<<>>, Acc, Scheme, Name) ->
+ lists:reverse([{Scheme, Name, default_port(Scheme)}|Acc]);
+origin_reg_name(<< $\s, R/bits >>, Acc, Scheme, Name) ->
+ origin_scheme(R, [{Scheme, Name, default_port(Scheme)}|Acc]);
+origin_reg_name(<< $:, Port/bits >>, Acc, Scheme, Name) ->
+ origin_port(Port, Acc, Scheme, Name, <<>>);
+origin_reg_name(<< C, R/bits >>, Acc, Scheme, Name) when ?IS_URI_UNRESERVED(C) or ?IS_URI_SUB_DELIMS(C) ->
+ ?LOWER(origin_reg_name, R, Acc, Scheme, Name).
+
+origin_port(<<>>, Acc, Scheme, Host, Port) ->
+ lists:reverse([{Scheme, Host, binary_to_integer(Port)}|Acc]);
+origin_port(<< $\s, R/bits >>, Acc, Scheme, Host, Port) ->
+ origin_scheme(R, [{Scheme, Host, binary_to_integer(Port)}|Acc]);
+origin_port(<< C, R/bits >>, Acc, Scheme, Host, Port) when ?IS_DIGIT(C) ->
+ origin_port(R, Acc, Scheme, Host, << Port/binary, C >>).
+
+next_origin(<<>>) -> <<>>;
+next_origin(<< $\s, C, R/bits >>) when ?IS_TOKEN(C) -> << C, R/bits >>;
+next_origin(<< C, R/bits >>) when ?IS_TOKEN(C) or (C =:= $:) or (C =:= $/) -> next_origin(R).
+
+default_port(<< "http" >>) -> 80;
+default_port(<< "https" >>) -> 443.
+
+-ifdef(TEST).
+scheme() -> oneof([<<"http">>, <<"https">>]).
+
+scheme_host_port() ->
+ ?LET({Scheme, Host, Port},
+ {scheme(), host(), integer(1, 65535)},
+ begin
+ HostBin = list_to_binary(Host),
+ {[{Scheme, ?LOWER(HostBin), Port}],
+ case default_port(Scheme) of
+ Port -> << Scheme/binary, "://", HostBin/binary>>;
+ _ -> << Scheme/binary, "://", HostBin/binary, $:, (integer_to_binary(Port))/binary >>
+ end}
+ end).
+
+prop_parse_origin() ->
+ ?FORALL({Res, Origin}, scheme_host_port(), Res =:= parse_origin(Origin)).
+
+parse_origin_test_() ->
+ Tests = [
+ {<<"http://www.example.org:8080">>, [{<<"http">>, <<"www.example.org">>, 8080}]},
+ {<<"http://www.example.org">>, [{<<"http">>, <<"www.example.org">>, 80}]},
+ {<<"http://192.0.2.1:8080">>, [{<<"http">>, <<"192.0.2.1">>, 8080}]},
+ {<<"http://192.0.2.1">>, [{<<"http">>, <<"192.0.2.1">>, 80}]},
+ {<<"http://[2001:db8::1]:8080">>, [{<<"http">>, <<"[2001:db8::1]">>, 8080}]},
+ {<<"http://[2001:db8::1]">>, [{<<"http">>, <<"[2001:db8::1]">>, 80}]},
+ {<<"http://[::ffff:192.0.2.1]:8080">>, [{<<"http">>, <<"[::ffff:192.0.2.1]">>, 8080}]},
+ {<<"http://[::ffff:192.0.2.1]">>, [{<<"http">>, <<"[::ffff:192.0.2.1]">>, 80}]},
+ {<<"http://example.org https://blue.example.com:8080">>,
+ [{<<"http">>, <<"example.org">>, 80},
+ {<<"https">>, <<"blue.example.com">>, 8080}]}
+ ],
+ [{V, fun() -> R = parse_origin(V) end} || {V, R} <- Tests].
+
+parse_origin_reference_test_() ->
+ Tests = [
+ <<"null">>,
+ <<"httpx://example.org:80">>,
+ <<"httpx://example.org:80 null">>,
+ <<"null null">>
+ ],
+ [{V, fun() -> [true = is_reference(Ref) || Ref <- parse_origin(V)] end} || V <- Tests].
+
+parse_origin_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"null", $\t, "null">>,
+ <<"null", $\s, $\s, "null">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_origin(V)) end} || V <- Tests].
+
+horse_parse_origin_blue_example_org() ->
+ horse:repeat(200000,
+ parse_origin(<<"http://blue.example.org:8080">>)
+ ).
+
+horse_parse_origin_ipv4() ->
+ horse:repeat(200000,
+ parse_origin(<<"http://192.0.2.1:8080">>)
+ ).
+
+horse_parse_origin_ipv6() ->
+ horse:repeat(200000,
+ parse_origin(<<"http://[2001:db8::1]:8080">>)
+ ).
+
+horse_parse_origin_ipv6_v4() ->
+ horse:repeat(200000,
+ parse_origin(<<"http://[::ffff:192.0.2.1]:8080">>)
+ ).
+
+horse_parse_origin_null() ->
+ horse:repeat(200000,
+ parse_origin(<<"null">>)
+ ).
+-endif.
+
+%% Pragma header.
+%%
+%% Legacy header kept for backward compatibility with HTTP/1.0 caches.
+%% Only the "no-cache" directive was ever specified, and only for
+%% request messages.
+%%
+%% We take a large shortcut in the parsing of this header, expecting
+%% an exact match of "no-cache".
+
+-spec parse_pragma(binary()) -> cache | no_cache.
+parse_pragma(<<"no-cache">>) -> no_cache;
+parse_pragma(_) -> cache.
+
+%% Proxy-Authenticate header.
+%%
+%% Alias of parse_www_authenticate/1 due to identical syntax.
+
+-spec parse_proxy_authenticate(binary()) -> [{basic, binary()}
+ | {bearer | digest | binary(), [{binary(), binary()}]}].
+parse_proxy_authenticate(ProxyAuthenticate) ->
+ parse_www_authenticate(ProxyAuthenticate).
+
+%% Proxy-Authorization header.
+%%
+%% Alias of parse_authorization/1 due to identical syntax.
+
+-spec parse_proxy_authorization(binary())
+ -> {basic, binary(), binary()}
+ | {bearer, binary()}
+ | {digest, [{binary(), binary()}]}.
+parse_proxy_authorization(ProxyAuthorization) ->
+ parse_authorization(ProxyAuthorization).
+
+%% Range header.
+
+-spec parse_range(binary())
+ -> {bytes, [{non_neg_integer(), non_neg_integer() | infinity} | neg_integer()]}
+ | {binary(), binary()}.
+parse_range(<<"bytes=", R/bits >>) ->
+ bytes_range_set(R, []);
+parse_range(<< C, R/bits >>) when ?IS_TOKEN(C) ->
+ ?LOWER(other_range_unit, R, <<>>).
+
+bytes_range_set(<<>>, Acc) -> {bytes, lists:reverse(Acc)};
+bytes_range_set(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> bytes_range_set(R, Acc);
+bytes_range_set(<< $-, C, R/bits >>, Acc) when ?IS_DIGIT(C) -> bytes_range_suffix_spec(R, Acc, C - $0);
+bytes_range_set(<< C, R/bits >>, Acc) when ?IS_DIGIT(C) -> bytes_range_spec(R, Acc, C - $0).
+
+bytes_range_spec(<< $-, C, R/bits >>, Acc, First) when ?IS_DIGIT(C) -> bytes_range_spec_last(R, Acc, First, C - $0);
+bytes_range_spec(<< $-, R/bits >>, Acc, First) -> bytes_range_set_sep(R, [{First, infinity}|Acc]);
+bytes_range_spec(<< C, R/bits >>, Acc, First) when ?IS_DIGIT(C) -> bytes_range_spec(R, Acc, First * 10 + C - $0).
+
+bytes_range_spec_last(<< C, R/bits >>, Acc, First, Last) when ?IS_DIGIT(C) -> bytes_range_spec_last(R, Acc, First, Last * 10 + C - $0);
+bytes_range_spec_last(R, Acc, First, Last) -> bytes_range_set_sep(R, [{First, Last}|Acc]).
+
+bytes_range_suffix_spec(<< C, R/bits >>, Acc, Suffix) when ?IS_DIGIT(C) -> bytes_range_suffix_spec(R, Acc, Suffix * 10 + C - $0);
+bytes_range_suffix_spec(R, Acc, Suffix) -> bytes_range_set_sep(R, [-Suffix|Acc]).
+
+bytes_range_set_sep(<<>>, Acc) -> {bytes, lists:reverse(Acc)};
+bytes_range_set_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> bytes_range_set_sep(R, Acc);
+bytes_range_set_sep(<< $,, R/bits >>, Acc) -> bytes_range_set(R, Acc).
+
+other_range_unit(<< $=, C, R/bits >>, U) when ?IS_VCHAR(C) ->
+ other_range_set(R, U, << C >>);
+other_range_unit(<< C, R/bits >>, U) when ?IS_TOKEN(C) ->
+ ?LOWER(other_range_unit, R, U).
+
+other_range_set(<<>>, U, S) ->
+ {U, S};
+other_range_set(<< C, R/bits >>, U, S) when ?IS_VCHAR(C) ->
+ other_range_set(R, U, << S/binary, C >>).
+
+-ifdef(TEST).
+bytes_range() ->
+ ?LET(BytesSet,
+ non_empty(list(oneof([
+ ?SUCHTHAT({First, Last}, {pos_integer(), pos_integer()}, First =< Last),
+ {pos_integer(), infinity},
+ ?LET(I, pos_integer(), -I)
+ ]))),
+ {{bytes, BytesSet}, begin
+ << _, Set/bits >> = iolist_to_binary([
+ case Spec of
+ {First, infinity} -> [$,, integer_to_binary(First), $-];
+ {First, Last} -> [$,, integer_to_binary(First), $-, integer_to_binary(Last)];
+ Suffix -> [$,, integer_to_binary(Suffix)]
+ end || Spec <- BytesSet]),
+ <<"bytes=", Set/binary >>
+ end}).
+
+other_range() ->
+ ?LET(Range = {Unit, Set},
+ {token(), ?LET(L, non_empty(list(vchar())), list_to_binary(L))},
+ {Range, << Unit/binary, $=, Set/binary >>}).
+
+range() ->
+ oneof([
+ bytes_range(),
+ other_range()
+ ]).
+
+prop_parse_range() ->
+ ?FORALL({Range, RangeBin},
+ range(),
+ begin
+ Range2 = case Range of
+ {bytes, _} -> Range;
+ {Unit, Set} -> {?LOWER(Unit), Set}
+ end,
+ Range2 =:= parse_range(RangeBin)
+ end).
+
+parse_range_test_() ->
+ Tests = [
+ {<<"bytes=0-499">>, {bytes, [{0, 499}]}},
+ {<<"bytes=500-999">>, {bytes, [{500, 999}]}},
+ {<<"bytes=-500">>, {bytes, [-500]}},
+ {<<"bytes=9500-">>, {bytes, [{9500, infinity}]}},
+ {<<"bytes=0-0,-1">>, {bytes, [{0, 0}, -1]}},
+ {<<"bytes=500-600,601-999">>, {bytes, [{500, 600}, {601, 999}]}},
+ {<<"bytes=500-700,601-999">>, {bytes, [{500, 700}, {601, 999}]}},
+ {<<"books=I-III,V-IX">>, {<<"books">>, <<"I-III,V-IX">>}}
+ ],
+ [{V, fun() -> R = parse_range(V) end} || {V, R} <- Tests].
+
+parse_range_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_range(V)) end} || V <- Tests].
+
+horse_parse_range_first_last() ->
+ horse:repeat(200000,
+ parse_range(<<"bytes=500-999">>)
+ ).
+
+horse_parse_range_infinity() ->
+ horse:repeat(200000,
+ parse_range(<<"bytes=9500-">>)
+ ).
+
+horse_parse_range_suffix() ->
+ horse:repeat(200000,
+ parse_range(<<"bytes=-500">>)
+ ).
+
+horse_parse_range_two() ->
+ horse:repeat(200000,
+ parse_range(<<"bytes=500-700,601-999">>)
+ ).
+
+horse_parse_range_other() ->
+ horse:repeat(200000,
+ parse_range(<<"books=I-III,V-IX">>)
+ ).
+-endif.
+
+%% Retry-After header.
+
+-spec parse_retry_after(binary()) -> non_neg_integer() | calendar:datetime().
+parse_retry_after(RetryAfter = << D, _/bits >>) when ?IS_DIGIT(D) ->
+ I = binary_to_integer(RetryAfter),
+ true = I >= 0,
+ I;
+parse_retry_after(RetryAfter) ->
+ cow_date:parse_date(RetryAfter).
+
+-ifdef(TEST).
+parse_retry_after_test_() ->
+ Tests = [
+ {<<"Fri, 31 Dec 1999 23:59:59 GMT">>, {{1999, 12, 31}, {23, 59, 59}}},
+ {<<"120">>, 120}
+ ],
+ [{V, fun() -> R = parse_retry_after(V) end} || {V, R} <- Tests].
+
+parse_retry_after_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_retry_after(V)) end} || V <- Tests].
+
+horse_parse_retry_after_date() ->
+ horse:repeat(200000,
+ parse_retry_after(<<"Fri, 31 Dec 1999 23:59:59 GMT">>)
+ ).
+
+horse_parse_retry_after_delay_seconds() ->
+ horse:repeat(200000,
+ parse_retry_after(<<"120">>)
+ ).
+-endif.
+
+%% Sec-WebSocket-Accept header.
+%%
+%% The argument is returned without any processing. This value is
+%% expected to be matched directly by the client so no parsing is
+%% needed.
+
+-spec parse_sec_websocket_accept(binary()) -> binary().
+parse_sec_websocket_accept(SecWebSocketAccept) ->
+ SecWebSocketAccept.
+
+%% Sec-WebSocket-Extensions header.
+
+-spec parse_sec_websocket_extensions(binary()) -> [{binary(), [binary() | {binary(), binary()}]}].
+parse_sec_websocket_extensions(SecWebSocketExtensions) ->
+ nonempty(ws_extension_list(SecWebSocketExtensions, [])).
+
+ws_extension_list(<<>>, Acc) -> lists:reverse(Acc);
+ws_extension_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> ws_extension_list(R, Acc);
+ws_extension_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) -> ws_extension(R, Acc, << C >>).
+
+ws_extension(<< C, R/bits >>, Acc, E) when ?IS_TOKEN(C) -> ws_extension(R, Acc, << E/binary, C >>);
+ws_extension(R, Acc, E) -> ws_extension_param_sep(R, Acc, E, []).
+
+ws_extension_param_sep(<<>>, Acc, E, P) -> lists:reverse([{E, lists:reverse(P)}|Acc]);
+ws_extension_param_sep(<< $,, R/bits >>, Acc, E, P) -> ws_extension_list(R, [{E, lists:reverse(P)}|Acc]);
+ws_extension_param_sep(<< $;, R/bits >>, Acc, E, P) -> ws_extension_before_param(R, Acc, E, P);
+ws_extension_param_sep(<< C, R/bits >>, Acc, E, P) when ?IS_WS(C) -> ws_extension_param_sep(R, Acc, E, P).
+
+ws_extension_before_param(<< C, R/bits >>, Acc, E, P) when ?IS_WS(C) -> ws_extension_before_param(R, Acc, E, P);
+ws_extension_before_param(<< C, R/bits >>, Acc, E, P) when ?IS_TOKEN(C) -> ws_extension_param(R, Acc, E, P, << C >>).
+
+ws_extension_param(<< $=, $", R/bits >>, Acc, E, P, K) -> ws_extension_quoted(R, Acc, E, P, K, <<>>);
+ws_extension_param(<< $=, C, R/bits >>, Acc, E, P, K) when ?IS_TOKEN(C) -> ws_extension_value(R, Acc, E, P, K, << C >>);
+ws_extension_param(<< C, R/bits >>, Acc, E, P, K) when ?IS_TOKEN(C) -> ws_extension_param(R, Acc, E, P, << K/binary, C >>);
+ws_extension_param(R, Acc, E, P, K) -> ws_extension_param_sep(R, Acc, E, [K|P]).
+
+ws_extension_quoted(<< $", R/bits >>, Acc, E, P, K, V) -> ws_extension_param_sep(R, Acc, E, [{K, V}|P]);
+ws_extension_quoted(<< $\\, C, R/bits >>, Acc, E, P, K, V) when ?IS_TOKEN(C) -> ws_extension_quoted(R, Acc, E, P, K, << V/binary, C >>);
+ws_extension_quoted(<< C, R/bits >>, Acc, E, P, K, V) when ?IS_TOKEN(C) -> ws_extension_quoted(R, Acc, E, P, K, << V/binary, C >>).
+
+ws_extension_value(<< C, R/bits >>, Acc, E, P, K, V) when ?IS_TOKEN(C) -> ws_extension_value(R, Acc, E, P, K, << V/binary, C >>);
+ws_extension_value(R, Acc, E, P, K, V) -> ws_extension_param_sep(R, Acc, E, [{K, V}|P]).
+
+-ifdef(TEST).
+quoted_token() ->
+ ?LET(T,
+ non_empty(list(frequency([
+ {99, tchar()},
+ {1, [$\\, tchar()]}
+ ]))),
+ [$", T, $"]).
+
+ws_extension() ->
+ ?LET({E, PL},
+ {token(), small_list({ows(), ows(), oneof([token(), {token(), oneof([token(), quoted_token()])}])})},
+ {E, PL, iolist_to_binary([E,
+ [case P of
+ {OWS1, OWS2, {K, V}} -> [OWS1, $;, OWS2, K, $=, V];
+ {OWS1, OWS2, K} -> [OWS1, $;, OWS2, K]
+ end || P <- PL]
+ ])}).
+
+prop_parse_sec_websocket_extensions() ->
+ ?FORALL(L,
+ vector(1, 50, ws_extension()),
+ begin
+ << _, SecWebsocketExtensions/binary >> = iolist_to_binary([[$,, E] || {_, _, E} <- L]),
+ ResL = parse_sec_websocket_extensions(SecWebsocketExtensions),
+ CheckedL = [begin
+ ExpectedPL = [case P of
+ {_, _, {K, V}} -> {K, unquote(V)};
+ {_, _, K} -> K
+ end || P <- PL],
+ E =:= ResE andalso ExpectedPL =:= ResPL
+ end || {{E, PL, _}, {ResE, ResPL}} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_sec_websocket_extensions_test_() ->
+ Tests = [
+ {<<"foo">>, [{<<"foo">>, []}]},
+ {<<"bar; baz=2">>, [{<<"bar">>, [{<<"baz">>, <<"2">>}]}]},
+ {<<"foo, bar; baz=2">>, [{<<"foo">>, []}, {<<"bar">>, [{<<"baz">>, <<"2">>}]}]},
+ {<<"deflate-stream">>, [{<<"deflate-stream">>, []}]},
+ {<<"mux; max-channels=4; flow-control, deflate-stream">>,
+ [{<<"mux">>, [{<<"max-channels">>, <<"4">>}, <<"flow-control">>]}, {<<"deflate-stream">>, []}]},
+ {<<"private-extension">>, [{<<"private-extension">>, []}]}
+ ],
+ [{V, fun() -> R = parse_sec_websocket_extensions(V) end} || {V, R} <- Tests].
+
+parse_sec_websocket_extensions_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_extensions(V)) end}
+ || V <- Tests].
+
+horse_parse_sec_websocket_extensions() ->
+ horse:repeat(200000,
+ parse_sec_websocket_extensions(<<"mux; max-channels=4; flow-control, deflate-stream">>)
+ ).
+-endif.
+
+%% Sec-WebSocket-Key header.
+%%
+%% The argument is returned without any processing. This value is
+%% expected to be prepended to a static value, the result of which
+%% hashed to form a new base64 value returned in Sec-WebSocket-Accept,
+%% therefore no parsing is needed.
+
+-spec parse_sec_websocket_key(binary()) -> binary().
+parse_sec_websocket_key(SecWebSocketKey) ->
+ SecWebSocketKey.
+
+%% Sec-WebSocket-Protocol request header.
+
+-spec parse_sec_websocket_protocol_req(binary()) -> [binary()].
+parse_sec_websocket_protocol_req(SecWebSocketProtocol) ->
+ nonempty(token_list(SecWebSocketProtocol, [])).
+
+-ifdef(TEST).
+parse_sec_websocket_protocol_req_test_() ->
+ Tests = [
+ {<<"chat, superchat">>, [<<"chat">>, <<"superchat">>]},
+ {<<"Chat, SuperChat">>, [<<"Chat">>, <<"SuperChat">>]}
+ ],
+ [{V, fun() -> R = parse_sec_websocket_protocol_req(V) end} || {V, R} <- Tests].
+
+parse_sec_websocket_protocol_req_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_protocol_req(V)) end}
+ || V <- Tests].
+
+horse_parse_sec_websocket_protocol_req() ->
+ horse:repeat(200000,
+ parse_sec_websocket_protocol_req(<<"chat, superchat">>)
+ ).
+-endif.
+
+%% Sec-Websocket-Protocol response header.
+
+-spec parse_sec_websocket_protocol_resp(binary()) -> binary().
+parse_sec_websocket_protocol_resp(Protocol) ->
+ true = <<>> =/= Protocol,
+ ok = validate_token(Protocol),
+ Protocol.
+
+-ifdef(TEST).
+prop_parse_sec_websocket_protocol_resp() ->
+ ?FORALL(T,
+ token(),
+ T =:= parse_sec_websocket_protocol_resp(T)).
+
+parse_sec_websocket_protocol_resp_test_() ->
+ Tests = [
+ {<<"chat">>, <<"chat">>},
+ {<<"CHAT">>, <<"CHAT">>}
+ ],
+ [{V, fun() -> R = parse_sec_websocket_protocol_resp(V) end} || {V, R} <- Tests].
+
+parse_sec_websocket_protocol_resp_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_protocol_resp(V)) end}
+ || V <- Tests].
+
+horse_parse_sec_websocket_protocol_resp() ->
+ horse:repeat(200000,
+ parse_sec_websocket_protocol_resp(<<"chat">>)
+ ).
+-endif.
+
+%% Sec-WebSocket-Version request header.
+
+-spec parse_sec_websocket_version_req(binary()) -> websocket_version().
+parse_sec_websocket_version_req(SecWebSocketVersion) when byte_size(SecWebSocketVersion) < 4 ->
+ Version = binary_to_integer(SecWebSocketVersion),
+ true = Version >= 0 andalso Version =< 255,
+ Version.
+
+-ifdef(TEST).
+prop_parse_sec_websocket_version_req() ->
+ ?FORALL(Version,
+ integer(0, 255),
+ Version =:= parse_sec_websocket_version_req(integer_to_binary(Version))).
+
+parse_sec_websocket_version_req_test_() ->
+ Tests = [
+ {<<"13">>, 13},
+ {<<"25">>, 25}
+ ],
+ [{V, fun() -> R = parse_sec_websocket_version_req(V) end} || {V, R} <- Tests].
+
+parse_sec_websocket_version_req_error_test_() ->
+ Tests = [
+ <<>>,
+ <<" ">>,
+ <<"7, 8, 13">>,
+ <<"invalid">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_version_req(V)) end}
+ || V <- Tests].
+
+horse_parse_sec_websocket_version_req_13() ->
+ horse:repeat(200000,
+ parse_sec_websocket_version_req(<<"13">>)
+ ).
+
+horse_parse_sec_websocket_version_req_255() ->
+ horse:repeat(200000,
+ parse_sec_websocket_version_req(<<"255">>)
+ ).
+-endif.
+
+%% Sec-WebSocket-Version response header.
+
+-spec parse_sec_websocket_version_resp(binary()) -> [websocket_version()].
+parse_sec_websocket_version_resp(SecWebSocketVersion) ->
+ nonempty(ws_version_list(SecWebSocketVersion, [])).
+
+ws_version_list(<<>>, Acc) -> lists:reverse(Acc);
+ws_version_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> ws_version_list(R, Acc);
+ws_version_list(<< C, R/bits >>, Acc) when ?IS_DIGIT(C) -> ws_version(R, Acc, C - $0).
+
+ws_version(<< C, R/bits >>, Acc, V) when ?IS_DIGIT(C) -> ws_version(R, Acc, V * 10 + C - $0);
+ws_version(R, Acc, V) -> ws_version_list_sep(R, [V|Acc]).
+
+ws_version_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+ws_version_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> ws_version_list_sep(R, Acc);
+ws_version_list_sep(<< $,, R/bits >>, Acc) -> ws_version_list(R, Acc).
+
+-ifdef(TEST).
+sec_websocket_version_resp() ->
+ ?LET(L,
+ non_empty(list({ows(), ows(), integer(0, 255)})),
+ begin
+ << _, SecWebSocketVersion/binary >> = iolist_to_binary(
+ [[OWS1, $,, OWS2, integer_to_binary(V)] || {OWS1, OWS2, V} <- L]),
+ {[V || {_, _, V} <- L], SecWebSocketVersion}
+ end).
+
+prop_parse_sec_websocket_version_resp() ->
+ ?FORALL({L, SecWebSocketVersion},
+ sec_websocket_version_resp(),
+ L =:= parse_sec_websocket_version_resp(SecWebSocketVersion)).
+
+parse_sec_websocket_version_resp_test_() ->
+ Tests = [
+ {<<"13, 8, 7">>, [13, 8, 7]}
+ ],
+ [{V, fun() -> R = parse_sec_websocket_version_resp(V) end} || {V, R} <- Tests].
+
+parse_sec_websocket_version_resp_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_version_resp(V)) end}
+ || V <- Tests].
+
+horse_parse_sec_websocket_version_resp() ->
+ horse:repeat(200000,
+ parse_sec_websocket_version_resp(<<"13, 8, 7">>)
+ ).
+-endif.
+
+%% Set-Cookie header.
+
+-spec parse_set_cookie(binary())
+ -> {ok, binary(), binary(), cow_cookie:cookie_attrs()}
+ | ignore.
+parse_set_cookie(SetCookie) ->
+ cow_cookie:parse_set_cookie(SetCookie).
+
+%% TE header.
+%%
+%% This function does not support parsing of transfer-parameter.
+
+-spec parse_te(binary()) -> {trailers | no_trailers, [{binary(), qvalue()}]}.
+parse_te(TE) ->
+ te_list(TE, no_trailers, []).
+
+te_list(<<>>, Trail, Acc) -> {Trail, lists:reverse(Acc)};
+te_list(<< C, R/bits >>, Trail, Acc) when ?IS_WS_COMMA(C) -> te_list(R, Trail, Acc);
+te_list(<< "trailers", R/bits >>, Trail, Acc) -> te(R, Trail, Acc, <<"trailers">>);
+te_list(<< "compress", R/bits >>, Trail, Acc) -> te(R, Trail, Acc, <<"compress">>);
+te_list(<< "deflate", R/bits >>, Trail, Acc) -> te(R, Trail, Acc, <<"deflate">>);
+te_list(<< "gzip", R/bits >>, Trail, Acc) -> te(R, Trail, Acc, <<"gzip">>);
+te_list(<< C, R/bits >>, Trail, Acc) when ?IS_TOKEN(C) ->
+ ?LOWER(te, R, Trail, Acc, <<>>).
+
+te(<<>>, _, Acc, <<"trailers">>) -> {trailers, lists:reverse(Acc)};
+te(<< $,, R/bits >>, _, Acc, <<"trailers">>) -> te_list(R, trailers, Acc);
+te(<< $;, R/bits >>, Trail, Acc, T) when T =/= <<"trailers">> -> te_before_weight(R, Trail, Acc, T);
+te(<< C, R/bits >>, _, Acc, <<"trailers">>) when ?IS_WS(C) -> te_list_sep(R, trailers, Acc);
+te(<< C, R/bits >>, Trail, Acc, T) when ?IS_TOKEN(C) ->
+ ?LOWER(te, R, Trail, Acc, T);
+te(R, Trail, Acc, T) -> te_param_sep(R, Trail, Acc, T).
+
+te_param_sep(<<>>, Trail, Acc, T) -> {Trail, lists:reverse([{T, 1000}|Acc])};
+te_param_sep(<< $,, R/bits >>, Trail, Acc, T) -> te_list(R, Trail, [{T, 1000}|Acc]);
+te_param_sep(<< C, R/bits >>, Trail, Acc, T) when ?IS_WS(C) -> te_param_sep(R, Trail, Acc, T).
+
+te_before_weight(<< C, R/bits >>, Trail, Acc, T) when ?IS_WS(C) -> te_before_weight(R, Trail, Acc, T);
+te_before_weight(<< $q, $=, R/bits >>, Trail, Acc, T) -> te_weight(R, Trail, Acc, T).
+
+te_weight(<< "1.000", R/bits >>, Trail, Acc, T) -> te_list_sep(R, Trail, [{T, 1000}|Acc]);
+te_weight(<< "1.00", R/bits >>, Trail, Acc, T) -> te_list_sep(R, Trail, [{T, 1000}|Acc]);
+te_weight(<< "1.0", R/bits >>, Trail, Acc, T) -> te_list_sep(R, Trail, [{T, 1000}|Acc]);
+te_weight(<< "1.", R/bits >>, Trail, Acc, T) -> te_list_sep(R, Trail, [{T, 1000}|Acc]);
+te_weight(<< "1", R/bits >>, Trail, Acc, T) -> te_list_sep(R, Trail, [{T, 1000}|Acc]);
+te_weight(<< "0.", A, B, C, R/bits >>, Trail, Acc, T) when ?IS_DIGIT(A), ?IS_DIGIT(B), ?IS_DIGIT(C) ->
+ te_list_sep(R, Trail, [{T, (A - $0) * 100 + (B - $0) * 10 + (C - $0)}|Acc]);
+te_weight(<< "0.", A, B, R/bits >>, Trail, Acc, T) when ?IS_DIGIT(A), ?IS_DIGIT(B) ->
+ te_list_sep(R, Trail, [{T, (A - $0) * 100 + (B - $0) * 10}|Acc]);
+te_weight(<< "0.", A, R/bits >>, Trail, Acc, T) when ?IS_DIGIT(A) ->
+ te_list_sep(R, Trail, [{T, (A - $0) * 100}|Acc]);
+te_weight(<< "0.", R/bits >>, Trail, Acc, T) -> te_list_sep(R, Trail, [{T, 0}|Acc]);
+te_weight(<< "0", R/bits >>, Trail, Acc, T) -> te_list_sep(R, Trail, [{T, 0}|Acc]).
+
+te_list_sep(<<>>, Trail, Acc) -> {Trail, lists:reverse(Acc)};
+te_list_sep(<< C, R/bits >>, Trail, Acc) when ?IS_WS(C) -> te_list_sep(R, Trail, Acc);
+te_list_sep(<< $,, R/bits >>, Trail, Acc) -> te_list(R, Trail, Acc).
+
+-ifdef(TEST).
+te() ->
+ ?LET({Trail, L},
+ {elements([trailers, no_trailers]),
+ small_non_empty_list({?SUCHTHAT(T, token(), T =/= <<"trailers">>), weight()})},
+ {Trail, L, begin
+ L2 = case Trail of
+ no_trailers -> L;
+ trailers ->
+ Rand = rand:uniform(length(L) + 1) - 1,
+ {Before, After} = lists:split(Rand, L),
+ Before ++ [{<<"trailers">>, undefined}|After]
+ end,
+ << _, TE/binary >> = iolist_to_binary([case W of
+ undefined -> [$,, T];
+ _ -> [$,, T, <<";q=">>, qvalue_to_iodata(W)]
+ end || {T, W} <- L2]),
+ TE
+ end}
+ ).
+
+prop_parse_te() ->
+ ?FORALL({Trail, L, TE},
+ te(),
+ begin
+ {ResTrail, ResL} = parse_te(TE),
+ CheckedL = [begin
+ ResT =:= ?LOWER(T)
+ andalso (ResW =:= W orelse (W =:= undefined andalso ResW =:= 1000))
+ end || {{T, W}, {ResT, ResW}} <- lists:zip(L, ResL)],
+ ResTrail =:= Trail andalso [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_te_test_() ->
+ Tests = [
+ {<<"deflate">>, {no_trailers, [{<<"deflate">>, 1000}]}},
+ {<<>>, {no_trailers, []}},
+ {<<"trailers, deflate;q=0.5">>, {trailers, [{<<"deflate">>, 500}]}}
+ ],
+ [{V, fun() -> R = parse_te(V) end} || {V, R} <- Tests].
+
+horse_parse_te() ->
+ horse:repeat(200000,
+ parse_te(<<"trailers, deflate;q=0.5">>)
+ ).
+-endif.
+
+%% Trailer header.
+
+-spec parse_trailer(binary()) -> [binary()].
+parse_trailer(Trailer) ->
+ nonempty(token_ci_list(Trailer, [])).
+
+-ifdef(TEST).
+parse_trailer_test_() ->
+ Tests = [
+ {<<"Date, Content-MD5">>, [<<"date">>, <<"content-md5">>]}
+ ],
+ [{V, fun() -> R = parse_trailer(V) end} || {V, R} <- Tests].
+
+parse_trailer_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_trailer(V)) end} || V <- Tests].
+
+horse_parse_trailer() ->
+ horse:repeat(200000,
+ parse_trailer(<<"Date, Content-MD5">>)
+ ).
+-endif.
+
+%% Transfer-Encoding header.
+%%
+%% This function does not support parsing of transfer-parameter.
+
+-spec parse_transfer_encoding(binary()) -> [binary()].
+parse_transfer_encoding(<<"chunked">>) ->
+ [<<"chunked">>];
+parse_transfer_encoding(TransferEncoding) ->
+ nonempty(token_ci_list(TransferEncoding, [])).
+
+-ifdef(TEST).
+prop_parse_transfer_encoding() ->
+ ?FORALL(L,
+ non_empty(list(token())),
+ begin
+ << _, TransferEncoding/binary >> = iolist_to_binary([[$,, C] || C <- L]),
+ ResL = parse_transfer_encoding(TransferEncoding),
+ CheckedL = [?LOWER(Co) =:= ResC || {Co, ResC} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_transfer_encoding_test_() ->
+ Tests = [
+ {<<"a , , , ">>, [<<"a">>]},
+ {<<" , , , a">>, [<<"a">>]},
+ {<<"a , , b">>, [<<"a">>, <<"b">>]},
+ {<<"chunked">>, [<<"chunked">>]},
+ {<<"chunked, something">>, [<<"chunked">>, <<"something">>]},
+ {<<"gzip, chunked">>, [<<"gzip">>, <<"chunked">>]}
+ ],
+ [{V, fun() -> R = parse_transfer_encoding(V) end} || {V, R} <- Tests].
+
+parse_transfer_encoding_error_test_() ->
+ Tests = [
+ <<>>,
+ <<" ">>,
+ <<" , ">>,
+ <<",,,">>,
+ <<"a b">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_transfer_encoding(V)) end}
+ || V <- Tests].
+
+horse_parse_transfer_encoding_chunked() ->
+ horse:repeat(200000,
+ parse_transfer_encoding(<<"chunked">>)
+ ).
+
+horse_parse_transfer_encoding_custom() ->
+ horse:repeat(200000,
+ parse_transfer_encoding(<<"chunked, something">>)
+ ).
+-endif.
+
+%% Upgrade header.
+%%
+%% It is unclear from the RFC whether the values here are
+%% case sensitive.
+%%
+%% We handle them in a case insensitive manner because they
+%% are described as case insensitive in the Websocket RFC.
+
+-spec parse_upgrade(binary()) -> [binary()].
+parse_upgrade(Upgrade) ->
+ nonempty(protocol_list(Upgrade, [])).
+
+protocol_list(<<>>, Acc) -> lists:reverse(Acc);
+protocol_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> protocol_list(R, Acc);
+protocol_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) ->
+ ?LOWER(protocol_name, R, Acc, <<>>).
+
+protocol_name(<< $/, C, R/bits >>, Acc, P) ->
+ ?LOWER(protocol_version, R, Acc, << P/binary, $/ >>);
+protocol_name(<< C, R/bits >>, Acc, P) when ?IS_TOKEN(C) ->
+ ?LOWER(protocol_name, R, Acc, P);
+protocol_name(R, Acc, P) -> protocol_list_sep(R, [P|Acc]).
+
+protocol_version(<< C, R/bits >>, Acc, P) when ?IS_TOKEN(C) ->
+ ?LOWER(protocol_version, R, Acc, P);
+protocol_version(R, Acc, P) -> protocol_list_sep(R, [P|Acc]).
+
+protocol_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+protocol_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> protocol_list_sep(R, Acc);
+protocol_list_sep(<< $,, R/bits >>, Acc) -> protocol_list(R, Acc).
+
+-ifdef(TEST).
+protocols() ->
+ ?LET(P,
+ oneof([token(), [token(), $/, token()]]),
+ iolist_to_binary(P)).
+
+prop_parse_upgrade() ->
+ ?FORALL(L,
+ non_empty(list(protocols())),
+ begin
+ << _, Upgrade/binary >> = iolist_to_binary([[$,, P] || P <- L]),
+ ResL = parse_upgrade(Upgrade),
+ CheckedL = [?LOWER(P) =:= ResP || {P, ResP} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_upgrade_test_() ->
+ Tests = [
+ {<<"HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11">>,
+ [<<"http/2.0">>, <<"shttp/1.3">>, <<"irc/6.9">>, <<"rta/x11">>]},
+ {<<"HTTP/2.0">>, [<<"http/2.0">>]}
+ ],
+ [{V, fun() -> R = parse_upgrade(V) end} || {V, R} <- Tests].
+
+parse_upgrade_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_upgrade(V)) end}
+ || V <- Tests].
+-endif.
+
+%% Variant-Key-06 (draft) header.
+%%
+%% The Variants header must be parsed first in order to know
+%% the NumMembers argument as it is the number of members in
+%% the Variants dictionary.
+
+-spec parse_variant_key(binary(), pos_integer()) -> [[binary()]].
+parse_variant_key(VariantKey, NumMembers) ->
+ List = cow_http_struct_hd:parse_list(VariantKey),
+ [case Inner of
+ {list, InnerList, []} ->
+ NumMembers = length(InnerList),
+ [case Item of
+ {item, {token, Value}, []} -> Value;
+ {item, {string, Value}, []} -> Value
+ end || Item <- InnerList]
+ end || Inner <- List].
+
+-ifdef(TEST).
+parse_variant_key_test_() ->
+ Tests = [
+ {<<"(en)">>, 1, [[<<"en">>]]},
+ {<<"(gzip fr)">>, 2, [[<<"gzip">>, <<"fr">>]]},
+ {<<"(gzip fr), (\"identity\" fr)">>, 2, [[<<"gzip">>, <<"fr">>], [<<"identity">>, <<"fr">>]]},
+ {<<"(\"gzip \" fr)">>, 2, [[<<"gzip ">>, <<"fr">>]]},
+ {<<"(en br)">>, 2, [[<<"en">>, <<"br">>]]},
+ {<<"(\"0\")">>, 1, [[<<"0">>]]},
+ {<<"(silver), (\"bronze\")">>, 1, [[<<"silver">>], [<<"bronze">>]]},
+ {<<"(some_person)">>, 1, [[<<"some_person">>]]},
+ {<<"(gold europe)">>, 2, [[<<"gold">>, <<"europe">>]]}
+ ],
+ [{V, fun() -> R = parse_variant_key(V, N) end} || {V, N, R} <- Tests].
+
+parse_variant_key_error_test_() ->
+ Tests = [
+ {<<"(gzip fr), (identity fr), (br fr oops)">>, 2}
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_variant_key(V, N)) end} || {V, N} <- Tests].
+-endif.
+
+-spec variant_key([[binary()]]) -> iolist().
+%% We assume that the lists are of correct length.
+variant_key(VariantKeys) ->
+ cow_http_struct_hd:list([
+ {list, [
+ {item, {string, Value}, []}
+ || Value <- InnerList], []}
+ || InnerList <- VariantKeys]).
+
+-ifdef(TEST).
+variant_key_identity_test_() ->
+ Tests = [
+ {1, [[<<"en">>]]},
+ {2, [[<<"gzip">>, <<"fr">>]]},
+ {2, [[<<"gzip">>, <<"fr">>], [<<"identity">>, <<"fr">>]]},
+ {2, [[<<"gzip ">>, <<"fr">>]]},
+ {2, [[<<"en">>, <<"br">>]]},
+ {1, [[<<"0">>]]},
+ {1, [[<<"silver">>], [<<"bronze">>]]},
+ {1, [[<<"some_person">>]]},
+ {2, [[<<"gold">>, <<"europe">>]]}
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> V = parse_variant_key(iolist_to_binary(variant_key(V)), N) end} || {N, V} <- Tests].
+-endif.
+
+%% Variants-06 (draft) header.
+
+-spec parse_variants(binary()) -> [{binary(), [binary()]}].
+parse_variants(Variants) ->
+ Dict = cow_http_struct_hd:parse_dictionary(Variants),
+ [case DictItem of
+ {Key, {list, List, []}} ->
+ {Key, [case Item of
+ {item, {token, Value}, []} -> Value;
+ {item, {string, Value}, []} -> Value
+ end || Item <- List]}
+ end || DictItem <- Dict].
+
+-ifdef(TEST).
+parse_variants_test_() ->
+ Tests = [
+ {<<"accept-language=(de en jp)">>, [{<<"accept-language">>, [<<"de">>, <<"en">>, <<"jp">>]}]},
+ {<<"accept-encoding=(gzip)">>, [{<<"accept-encoding">>, [<<"gzip">>]}]},
+ {<<"accept-encoding=()">>, [{<<"accept-encoding">>, []}]},
+ {<<"accept-encoding=(gzip br), accept-language=(en fr)">>, [
+ {<<"accept-encoding">>, [<<"gzip">>, <<"br">>]},
+ {<<"accept-language">>, [<<"en">>, <<"fr">>]}
+ ]},
+ {<<"accept-language=(en fr de), accept-encoding=(gzip br)">>, [
+ {<<"accept-language">>, [<<"en">>, <<"fr">>, <<"de">>]},
+ {<<"accept-encoding">>, [<<"gzip">>, <<"br">>]}
+ ]}
+ ],
+ [{V, fun() -> R = parse_variants(V) end} || {V, R} <- Tests].
+-endif.
+
+-spec variants([{binary(), [binary()]}]) -> iolist().
+variants(Variants) ->
+ cow_http_struct_hd:dictionary([
+ {Key, {list, [
+ {item, {string, Value}, []}
+ || Value <- List], []}}
+ || {Key, List} <- Variants]).
+
+-ifdef(TEST).
+variants_identity_test_() ->
+ Tests = [
+ [{<<"accept-language">>, [<<"de">>, <<"en">>, <<"jp">>]}],
+ [{<<"accept-encoding">>, [<<"gzip">>]}],
+ [{<<"accept-encoding">>, []}],
+ [
+ {<<"accept-encoding">>, [<<"gzip">>, <<"br">>]},
+ {<<"accept-language">>, [<<"en">>, <<"fr">>]}
+ ],
+ [
+ {<<"accept-language">>, [<<"en">>, <<"fr">>, <<"de">>]},
+ {<<"accept-encoding">>, [<<"gzip">>, <<"br">>]}
+ ]
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> V = parse_variants(iolist_to_binary(variants(V))) end} || V <- Tests].
+-endif.
+
+%% Vary header.
+
+-spec parse_vary(binary()) -> '*' | [binary()].
+parse_vary(<<"*">>) ->
+ '*';
+parse_vary(Vary) ->
+ nonempty(token_ci_list(Vary, [])).
+
+-ifdef(TEST).
+parse_vary_test_() ->
+ Tests = [
+ {<<"*">>, '*'},
+ {<<"Accept-Encoding">>, [<<"accept-encoding">>]},
+ {<<"accept-encoding, accept-language">>, [<<"accept-encoding">>, <<"accept-language">>]}
+ ],
+ [{V, fun() -> R = parse_vary(V) end} || {V, R} <- Tests].
+
+parse_vary_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_vary(V)) end} || V <- Tests].
+-endif.
+
+%% WWW-Authenticate header.
+%%
+%% Unknown schemes are represented as the lowercase binary
+%% instead of an atom. Unlike with parse_authorization/1,
+%% we do not crash on unknown schemes.
+%%
+%% When parsing auth-params, we do not accept BWS characters around the "=".
+
+-spec parse_www_authenticate(binary()) -> [{basic, binary()}
+ | {bearer | digest | binary(), [{binary(), binary()}]}].
+parse_www_authenticate(Authenticate) ->
+ nonempty(www_auth_list(Authenticate, [])).
+
+www_auth_list(<<>>, Acc) -> lists:reverse(Acc);
+www_auth_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> www_auth_list(R, Acc);
+www_auth_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) ->
+ ?LOWER(www_auth_scheme, R, Acc, <<>>).
+
+www_auth_scheme(<< C, R/bits >>, Acc, Scheme0) when ?IS_WS(C) ->
+ Scheme = case Scheme0 of
+ <<"basic">> -> basic;
+ <<"bearer">> -> bearer;
+ <<"digest">> -> digest;
+ _ -> Scheme0
+ end,
+ www_auth_params_list(R, Acc, Scheme, []);
+www_auth_scheme(<< C, R/bits >>, Acc, Scheme) when ?IS_TOKEN(C) ->
+ ?LOWER(www_auth_scheme, R, Acc, Scheme).
+
+www_auth_params_list(<<>>, Acc, Scheme, Params) ->
+ lists:reverse([www_auth_tuple(Scheme, nonempty(Params))|Acc]);
+www_auth_params_list(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_WS_COMMA(C) ->
+ www_auth_params_list(R, Acc, Scheme, Params);
+www_auth_params_list(<< "algorithm=", C, R/bits >>, Acc, Scheme, Params) when ?IS_TOKEN(C) ->
+ www_auth_token(R, Acc, Scheme, Params, <<"algorithm">>, << C >>);
+www_auth_params_list(<< "domain=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"domain">>, <<>>);
+www_auth_params_list(<< "error=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"error">>, <<>>);
+www_auth_params_list(<< "error_description=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"error_description">>, <<>>);
+www_auth_params_list(<< "error_uri=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"error_uri">>, <<>>);
+www_auth_params_list(<< "nonce=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"nonce">>, <<>>);
+www_auth_params_list(<< "opaque=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"opaque">>, <<>>);
+www_auth_params_list(<< "qop=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"qop">>, <<>>);
+www_auth_params_list(<< "realm=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"realm">>, <<>>);
+www_auth_params_list(<< "scope=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"scope">>, <<>>);
+www_auth_params_list(<< "stale=false", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_params_list_sep(R, Acc, Scheme, [{<<"stale">>, <<"false">>}|Params]);
+www_auth_params_list(<< "stale=true", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_params_list_sep(R, Acc, Scheme, [{<<"stale">>, <<"true">>}|Params]);
+www_auth_params_list(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_TOKEN(C) ->
+ ?LOWER(www_auth_param, R, Acc, Scheme, Params, <<>>).
+
+www_auth_param(<< $=, $", R/bits >>, Acc, Scheme, Params, K) ->
+ www_auth_quoted(R, Acc, Scheme, Params, K, <<>>);
+www_auth_param(<< $=, C, R/bits >>, Acc, Scheme, Params, K) when ?IS_TOKEN(C) ->
+ www_auth_token(R, Acc, Scheme, Params, K, << C >>);
+www_auth_param(<< C, R/bits >>, Acc, Scheme, Params, K) when ?IS_TOKEN(C) ->
+ ?LOWER(www_auth_param, R, Acc, Scheme, Params, K);
+www_auth_param(R, Acc, Scheme, Params, NewScheme) ->
+ www_auth_scheme(R, [www_auth_tuple(Scheme, Params)|Acc], NewScheme).
+
+www_auth_token(<< C, R/bits >>, Acc, Scheme, Params, K, V) when ?IS_TOKEN(C) ->
+ www_auth_token(R, Acc, Scheme, Params, K, << V/binary, C >>);
+www_auth_token(R, Acc, Scheme, Params, K, V) ->
+ www_auth_params_list_sep(R, Acc, Scheme, [{K, V}|Params]).
+
+www_auth_quoted(<< $", R/bits >>, Acc, Scheme, Params, K, V) ->
+ www_auth_params_list_sep(R, Acc, Scheme, [{K, V}|Params]);
+www_auth_quoted(<< $\\, C, R/bits >>, Acc, Scheme, Params, K, V) when ?IS_VCHAR_OBS(C) ->
+ www_auth_quoted(R, Acc, Scheme, Params, K, << V/binary, C >>);
+www_auth_quoted(<< C, R/bits >>, Acc, Scheme, Params, K, V) when ?IS_VCHAR_OBS(C) ->
+ www_auth_quoted(R, Acc, Scheme, Params, K, << V/binary, C >>).
+
+www_auth_params_list_sep(<<>>, Acc, Scheme, Params) ->
+ lists:reverse([www_auth_tuple(Scheme, Params)|Acc]);
+www_auth_params_list_sep(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_WS(C) ->
+ www_auth_params_list_sep(R, Acc, Scheme, Params);
+www_auth_params_list_sep(<< $,, R/bits >>, Acc, Scheme, Params) ->
+ www_auth_params_list_after_sep(R, Acc, Scheme, Params).
+
+www_auth_params_list_after_sep(<<>>, Acc, Scheme, Params) ->
+ lists:reverse([www_auth_tuple(Scheme, Params)|Acc]);
+www_auth_params_list_after_sep(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_WS_COMMA(C) ->
+ www_auth_params_list_after_sep(R, Acc, Scheme, Params);
+www_auth_params_list_after_sep(R, Acc, Scheme, Params) ->
+ www_auth_params_list(R, Acc, Scheme, Params).
+
+www_auth_tuple(basic, Params) ->
+ %% Unknown parameters MUST be ignored. (RFC7617 2)
+ {<<"realm">>, Realm} = lists:keyfind(<<"realm">>, 1, Params),
+ {basic, Realm};
+www_auth_tuple(Scheme, Params) ->
+ {Scheme, lists:reverse(Params)}.
+
+-ifdef(TEST).
+parse_www_authenticate_test_() ->
+ Tests = [
+ {<<"Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"">>,
+ [{<<"newauth">>, [
+ {<<"realm">>, <<"apps">>},
+ {<<"type">>, <<"1">>},
+ {<<"title">>, <<"Login to \"apps\"">>}]},
+ {basic, <<"simple">>}]},
+ %% Same test, different order.
+ {<<"Basic realm=\"simple\", Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\"">>,
+ [{basic, <<"simple">>},
+ {<<"newauth">>, [
+ {<<"realm">>, <<"apps">>},
+ {<<"type">>, <<"1">>},
+ {<<"title">>, <<"Login to \"apps\"">>}]}]},
+ {<<"Bearer realm=\"example\"">>,
+ [{bearer, [{<<"realm">>, <<"example">>}]}]},
+ {<<"Bearer realm=\"example\", error=\"invalid_token\", error_description=\"The access token expired\"">>,
+ [{bearer, [
+ {<<"realm">>, <<"example">>},
+ {<<"error">>, <<"invalid_token">>},
+ {<<"error_description">>, <<"The access token expired">>}
+ ]}]},
+ {<<"Basic realm=\"WallyWorld\"">>,
+ [{basic, <<"WallyWorld">>}]},
+ %% RFC7617 2.1.
+ {<<"Basic realm=\"foo\", charset=\"UTF-8\"">>,
+ [{basic, <<"foo">>}]},
+ %% A real-world example.
+ {<<"Basic realm=\"https://123456789012.dkr.ecr.eu-north-1.amazonaws.com/\",service=\"ecr.amazonaws.com\"">>,
+ [{basic, <<"https://123456789012.dkr.ecr.eu-north-1.amazonaws.com/">>}]},
+ {<<"Bearer realm=\"example\", Basic realm=\"foo\", charset=\"UTF-8\"">>,
+ [{bearer, [{<<"realm">>, <<"example">>}]},
+ {basic, <<"foo">>}]},
+ {<<"Basic realm=\"foo\", foo=\"bar\", charset=\"UTF-8\", Bearer realm=\"example\",foo=\"bar\"">>,
+ [{basic, <<"foo">>},
+ {bearer, [{<<"realm">>, <<"example">>}, {<<"foo">>,<<"bar">>}]}]},
+ {<<"Digest realm=\"testrealm@host.com\", qop=\"auth,auth-int\", "
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
+ "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"">>,
+ [{digest, [
+ {<<"realm">>, <<"testrealm@host.com">>},
+ {<<"qop">>, <<"auth,auth-int">>},
+ {<<"nonce">>, <<"dcd98b7102dd2f0e8b11d0f600bfb0c093">>},
+ {<<"opaque">>, <<"5ccc069c403ebaf9f0171e9517f40e41">>}
+ ]}]}
+ ],
+ [{V, fun() -> R = parse_www_authenticate(V) end} || {V, R} <- Tests].
+
+parse_www_authenticate_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_www_authenticate(V)) end} || V <- Tests].
+
+horse_parse_www_authenticate() ->
+ horse:repeat(200000,
+ parse_www_authenticate(<<"Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"">>)
+ ).
+-endif.
+
+%% X-Forwarded-For header.
+%%
+%% This header has no specification but *looks like* it is
+%% a list of tokens.
+%%
+%% This header is deprecated in favor of the Forwarded header.
+
+-spec parse_x_forwarded_for(binary()) -> [binary()].
+parse_x_forwarded_for(XForwardedFor) ->
+ nonempty(nodeid_list(XForwardedFor, [])).
+
+-define(IS_NODEID_TOKEN(C),
+ ?IS_ALPHA(C) or ?IS_DIGIT(C)
+ or (C =:= $:) or (C =:= $.) or (C =:= $_)
+ or (C =:= $-) or (C =:= $[) or (C =:= $])).
+
+nodeid_list(<<>>, Acc) -> lists:reverse(Acc);
+nodeid_list(<<C, R/bits>>, Acc) when ?IS_WS_COMMA(C) -> nodeid_list(R, Acc);
+nodeid_list(<<C, R/bits>>, Acc) when ?IS_NODEID_TOKEN(C) -> nodeid(R, Acc, <<C>>).
+
+nodeid(<<C, R/bits>>, Acc, T) when ?IS_NODEID_TOKEN(C) -> nodeid(R, Acc, <<T/binary, C>>);
+nodeid(R, Acc, T) -> nodeid_list_sep(R, [T|Acc]).
+
+nodeid_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+nodeid_list_sep(<<C, R/bits>>, Acc) when ?IS_WS(C) -> nodeid_list_sep(R, Acc);
+nodeid_list_sep(<<$,, R/bits>>, Acc) -> nodeid_list(R, Acc).
+
+-ifdef(TEST).
+parse_x_forwarded_for_test_() ->
+ Tests = [
+ {<<"client, proxy1, proxy2">>,
+ [<<"client">>, <<"proxy1">>, <<"proxy2">>]},
+ {<<"128.138.243.150, unknown, 192.52.106.30">>,
+ [<<"128.138.243.150">>, <<"unknown">>, <<"192.52.106.30">>]},
+ %% Examples from Mozilla DN.
+ {<<"2001:db8:85a3:8d3:1319:8a2e:370:7348">>,
+ [<<"2001:db8:85a3:8d3:1319:8a2e:370:7348">>]},
+ {<<"203.0.113.195">>,
+ [<<"203.0.113.195">>]},
+ {<<"203.0.113.195, 70.41.3.18, 150.172.238.178">>,
+ [<<"203.0.113.195">>, <<"70.41.3.18">>, <<"150.172.238.178">>]},
+ %% Examples from RFC7239 modified for x-forwarded-for.
+ {<<"[2001:db8:cafe::17]:4711">>,
+ [<<"[2001:db8:cafe::17]:4711">>]},
+ {<<"192.0.2.43, 198.51.100.17">>,
+ [<<"192.0.2.43">>, <<"198.51.100.17">>]},
+ {<<"_hidden">>,
+ [<<"_hidden">>]},
+ {<<"192.0.2.43,[2001:db8:cafe::17],unknown">>,
+ [<<"192.0.2.43">>, <<"[2001:db8:cafe::17]">>, <<"unknown">>]},
+ {<<"192.0.2.43, [2001:db8:cafe::17], unknown">>,
+ [<<"192.0.2.43">>, <<"[2001:db8:cafe::17]">>, <<"unknown">>]},
+ {<<"192.0.2.43, 2001:db8:cafe::17">>,
+ [<<"192.0.2.43">>, <<"2001:db8:cafe::17">>]},
+ {<<"192.0.2.43, [2001:db8:cafe::17]">>,
+ [<<"192.0.2.43">>, <<"[2001:db8:cafe::17]">>]}
+ ],
+ [{V, fun() -> R = parse_x_forwarded_for(V) end} || {V, R} <- Tests].
+
+parse_x_forwarded_for_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_x_forwarded_for(V)) end} || V <- Tests].
+-endif.
+
+%% Internal.
+
+%% Only return if the list is not empty.
+nonempty(L) when L =/= [] -> L.
+
+%% Parse a list of case sensitive tokens.
+token_list(<<>>, Acc) -> lists:reverse(Acc);
+token_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> token_list(R, Acc);
+token_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) -> token(R, Acc, << C >>).
+
+token(<< C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> token(R, Acc, << T/binary, C >>);
+token(R, Acc, T) -> token_list_sep(R, [T|Acc]).
+
+token_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+token_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> token_list_sep(R, Acc);
+token_list_sep(<< $,, R/bits >>, Acc) -> token_list(R, Acc).
+
+%% Parse a list of case insensitive tokens.
+token_ci_list(<<>>, Acc) -> lists:reverse(Acc);
+token_ci_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> token_ci_list(R, Acc);
+token_ci_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) -> ?LOWER(token_ci, R, Acc, <<>>).
+
+token_ci(<< C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> ?LOWER(token_ci, R, Acc, T);
+token_ci(R, Acc, T) -> token_ci_list_sep(R, [T|Acc]).
+
+token_ci_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+token_ci_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> token_ci_list_sep(R, Acc);
+token_ci_list_sep(<< $,, R/bits >>, Acc) -> token_ci_list(R, Acc).
+
+join_token_list([]) -> [];
+join_token_list([H|T]) -> join_token_list(T, [H]).
+
+join_token_list([], Acc) -> lists:reverse(Acc);
+join_token_list([H|T], Acc) -> join_token_list(T, [H,<<", ">>|Acc]).
diff --git a/server/_build/default/lib/cowlib/src/cow_http_struct_hd.erl b/server/_build/default/lib/cowlib/src/cow_http_struct_hd.erl
new file mode 100644
index 0000000..a79c691
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_http_struct_hd.erl
@@ -0,0 +1,522 @@
+%% Copyright (c) 2019-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% The mapping between Erlang and structured headers types is as follow:
+%%
+%% List: list()
+%% Inner list: {list, [item()], params()}
+%% Dictionary: [{binary(), item()}]
+%% There is no distinction between empty list and empty dictionary.
+%% Item with parameters: {item, bare_item(), params()}
+%% Parameters: [{binary(), bare_item()}]
+%% Bare item: one bare_item() that can be of type:
+%% Integer: integer()
+%% Decimal: {decimal, {integer(), integer()}}
+%% String: {string, binary()}
+%% Token: {token, binary()}
+%% Byte sequence: {binary, binary()}
+%% Boolean: boolean()
+
+-module(cow_http_struct_hd).
+
+-export([parse_dictionary/1]).
+-export([parse_item/1]).
+-export([parse_list/1]).
+-export([dictionary/1]).
+-export([item/1]).
+-export([list/1]).
+
+-include("cow_parse.hrl").
+
+-type sh_list() :: [sh_item() | sh_inner_list()].
+-type sh_inner_list() :: {list, [sh_item()], sh_params()}.
+-type sh_params() :: [{binary(), sh_bare_item()}].
+-type sh_dictionary() :: [{binary(), sh_item() | sh_inner_list()}].
+-type sh_item() :: {item, sh_bare_item(), sh_params()}.
+-type sh_bare_item() :: integer() | sh_decimal() | boolean()
+ | {string | token | binary, binary()}.
+-type sh_decimal() :: {decimal, {integer(), integer()}}.
+
+-define(IS_LC_ALPHA(C),
+ (C =:= $a) or (C =:= $b) or (C =:= $c) or (C =:= $d) or (C =:= $e) or
+ (C =:= $f) or (C =:= $g) or (C =:= $h) or (C =:= $i) or (C =:= $j) or
+ (C =:= $k) or (C =:= $l) or (C =:= $m) or (C =:= $n) or (C =:= $o) or
+ (C =:= $p) or (C =:= $q) or (C =:= $r) or (C =:= $s) or (C =:= $t) or
+ (C =:= $u) or (C =:= $v) or (C =:= $w) or (C =:= $x) or (C =:= $y) or
+ (C =:= $z)
+).
+
+%% Parsing.
+
+-spec parse_dictionary(binary()) -> sh_dictionary().
+parse_dictionary(<<>>) ->
+ [];
+parse_dictionary(<<C,R/bits>>) when ?IS_LC_ALPHA(C) or (C =:= $*) ->
+ parse_dict_key(R, [], <<C>>).
+
+parse_dict_key(<<$=,$(,R0/bits>>, Acc, K) ->
+ {Item, R} = parse_inner_list(R0, []),
+ parse_dict_before_sep(R, lists:keystore(K, 1, Acc, {K, Item}));
+parse_dict_key(<<$=,R0/bits>>, Acc, K) ->
+ {Item, R} = parse_item1(R0),
+ parse_dict_before_sep(R, lists:keystore(K, 1, Acc, {K, Item}));
+parse_dict_key(<<C,R/bits>>, Acc, K)
+ when ?IS_LC_ALPHA(C) or ?IS_DIGIT(C)
+ or (C =:= $_) or (C =:= $-) or (C =:= $.) or (C =:= $*) ->
+ parse_dict_key(R, Acc, <<K/binary,C>>);
+parse_dict_key(<<$;,R0/bits>>, Acc, K) ->
+ {Params, R} = parse_before_param(R0, []),
+ parse_dict_before_sep(R, lists:keystore(K, 1, Acc, {K, {item, true, Params}}));
+parse_dict_key(R, Acc, K) ->
+ parse_dict_before_sep(R, lists:keystore(K, 1, Acc, {K, {item, true, []}})).
+
+parse_dict_before_sep(<<$\s,R/bits>>, Acc) ->
+ parse_dict_before_sep(R, Acc);
+parse_dict_before_sep(<<$\t,R/bits>>, Acc) ->
+ parse_dict_before_sep(R, Acc);
+parse_dict_before_sep(<<C,R/bits>>, Acc) when C =:= $, ->
+ parse_dict_before_member(R, Acc);
+parse_dict_before_sep(<<>>, Acc) ->
+ Acc.
+
+parse_dict_before_member(<<$\s,R/bits>>, Acc) ->
+ parse_dict_before_member(R, Acc);
+parse_dict_before_member(<<$\t,R/bits>>, Acc) ->
+ parse_dict_before_member(R, Acc);
+parse_dict_before_member(<<C,R/bits>>, Acc) when ?IS_LC_ALPHA(C) or (C =:= $*) ->
+ parse_dict_key(R, Acc, <<C>>).
+
+-spec parse_item(binary()) -> sh_item().
+parse_item(Bin) ->
+ {Item, <<>>} = parse_item1(Bin),
+ Item.
+
+parse_item1(Bin) ->
+ case parse_bare_item(Bin) of
+ {Item, <<$;,R/bits>>} ->
+ {Params, Rest} = parse_before_param(R, []),
+ {{item, Item, Params}, Rest};
+ {Item, Rest} ->
+ {{item, Item, []}, Rest}
+ end.
+
+-spec parse_list(binary()) -> sh_list().
+parse_list(<<>>) ->
+ [];
+parse_list(Bin) ->
+ parse_list_before_member(Bin, []).
+
+parse_list_member(<<$(,R0/bits>>, Acc) ->
+ {Item, R} = parse_inner_list(R0, []),
+ parse_list_before_sep(R, [Item|Acc]);
+parse_list_member(R0, Acc) ->
+ {Item, R} = parse_item1(R0),
+ parse_list_before_sep(R, [Item|Acc]).
+
+parse_list_before_sep(<<$\s,R/bits>>, Acc) ->
+ parse_list_before_sep(R, Acc);
+parse_list_before_sep(<<$\t,R/bits>>, Acc) ->
+ parse_list_before_sep(R, Acc);
+parse_list_before_sep(<<$,,R/bits>>, Acc) ->
+ parse_list_before_member(R, Acc);
+parse_list_before_sep(<<>>, Acc) ->
+ lists:reverse(Acc).
+
+parse_list_before_member(<<$\s,R/bits>>, Acc) ->
+ parse_list_before_member(R, Acc);
+parse_list_before_member(<<$\t,R/bits>>, Acc) ->
+ parse_list_before_member(R, Acc);
+parse_list_before_member(R, Acc) ->
+ parse_list_member(R, Acc).
+
+%% Internal.
+
+parse_inner_list(<<$\s,R/bits>>, Acc) ->
+ parse_inner_list(R, Acc);
+parse_inner_list(<<$),$;,R0/bits>>, Acc) ->
+ {Params, R} = parse_before_param(R0, []),
+ {{list, lists:reverse(Acc), Params}, R};
+parse_inner_list(<<$),R/bits>>, Acc) ->
+ {{list, lists:reverse(Acc), []}, R};
+parse_inner_list(R0, Acc) ->
+ {Item, R = <<C,_/bits>>} = parse_item1(R0),
+ true = (C =:= $\s) orelse (C =:= $)),
+ parse_inner_list(R, [Item|Acc]).
+
+parse_before_param(<<$\s,R/bits>>, Acc) ->
+ parse_before_param(R, Acc);
+parse_before_param(<<C,R/bits>>, Acc) when ?IS_LC_ALPHA(C) or (C =:= $*) ->
+ parse_param(R, Acc, <<C>>).
+
+parse_param(<<$;,R/bits>>, Acc, K) ->
+ parse_before_param(R, lists:keystore(K, 1, Acc, {K, true}));
+parse_param(<<$=,R0/bits>>, Acc, K) ->
+ case parse_bare_item(R0) of
+ {Item, <<$;,R/bits>>} ->
+ parse_before_param(R, lists:keystore(K, 1, Acc, {K, Item}));
+ {Item, R} ->
+ {lists:keystore(K, 1, Acc, {K, Item}), R}
+ end;
+parse_param(<<C,R/bits>>, Acc, K)
+ when ?IS_LC_ALPHA(C) or ?IS_DIGIT(C)
+ or (C =:= $_) or (C =:= $-) or (C =:= $.) or (C =:= $*) ->
+ parse_param(R, Acc, <<K/binary,C>>);
+parse_param(R, Acc, K) ->
+ {lists:keystore(K, 1, Acc, {K, true}), R}.
+
+%% Integer or decimal.
+parse_bare_item(<<$-,R/bits>>) -> parse_number(R, 0, <<$->>);
+parse_bare_item(<<C,R/bits>>) when ?IS_DIGIT(C) -> parse_number(R, 1, <<C>>);
+%% String.
+parse_bare_item(<<$",R/bits>>) -> parse_string(R, <<>>);
+%% Token.
+parse_bare_item(<<C,R/bits>>) when ?IS_ALPHA(C) or (C =:= $*) -> parse_token(R, <<C>>);
+%% Byte sequence.
+parse_bare_item(<<$:,R/bits>>) -> parse_binary(R, <<>>);
+%% Boolean.
+parse_bare_item(<<"?0",R/bits>>) -> {false, R};
+parse_bare_item(<<"?1",R/bits>>) -> {true, R}.
+
+parse_number(<<C,R/bits>>, L, Acc) when ?IS_DIGIT(C) ->
+ parse_number(R, L+1, <<Acc/binary,C>>);
+parse_number(<<$.,R/bits>>, L, Acc) ->
+ parse_decimal(R, L, 0, Acc, <<>>);
+parse_number(R, L, Acc) when L =< 15 ->
+ {binary_to_integer(Acc), R}.
+
+parse_decimal(<<C,R/bits>>, L1, L2, IntAcc, FracAcc) when ?IS_DIGIT(C) ->
+ parse_decimal(R, L1, L2+1, IntAcc, <<FracAcc/binary,C>>);
+parse_decimal(R, L1, L2, IntAcc, FracAcc0) when L1 =< 12, L2 >= 1, L2 =< 3 ->
+ %% While not strictly required this gives a more consistent representation.
+ FracAcc = case FracAcc0 of
+ <<$0>> -> <<>>;
+ <<$0,$0>> -> <<>>;
+ <<$0,$0,$0>> -> <<>>;
+ <<A,B,$0>> -> <<A,B>>;
+ <<A,$0,$0>> -> <<A>>;
+ <<A,$0>> -> <<A>>;
+ _ -> FracAcc0
+ end,
+ Mul = case byte_size(FracAcc) of
+ 3 -> 1000;
+ 2 -> 100;
+ 1 -> 10;
+ 0 -> 1
+ end,
+ Int = binary_to_integer(IntAcc),
+ Frac = case FracAcc of
+ <<>> -> 0;
+ %% Mind the sign.
+ _ when Int < 0 -> -binary_to_integer(FracAcc);
+ _ -> binary_to_integer(FracAcc)
+ end,
+ {{decimal, {Int * Mul + Frac, -byte_size(FracAcc)}}, R}.
+
+parse_string(<<$\\,$",R/bits>>, Acc) ->
+ parse_string(R, <<Acc/binary,$">>);
+parse_string(<<$\\,$\\,R/bits>>, Acc) ->
+ parse_string(R, <<Acc/binary,$\\>>);
+parse_string(<<$",R/bits>>, Acc) ->
+ {{string, Acc}, R};
+parse_string(<<C,R/bits>>, Acc) when
+ C >= 16#20, C =< 16#21;
+ C >= 16#23, C =< 16#5b;
+ C >= 16#5d, C =< 16#7e ->
+ parse_string(R, <<Acc/binary,C>>).
+
+parse_token(<<C,R/bits>>, Acc) when ?IS_TOKEN(C) or (C =:= $:) or (C =:= $/) ->
+ parse_token(R, <<Acc/binary,C>>);
+parse_token(R, Acc) ->
+ {{token, Acc}, R}.
+
+parse_binary(<<$:,R/bits>>, Acc) ->
+ {{binary, base64:decode(Acc)}, R};
+parse_binary(<<C,R/bits>>, Acc) when ?IS_ALPHANUM(C) or (C =:= $+) or (C =:= $/) or (C =:= $=) ->
+ parse_binary(R, <<Acc/binary,C>>).
+
+-ifdef(TEST).
+parse_struct_hd_test_() ->
+ Files = filelib:wildcard("deps/structured-header-tests/*.json"),
+ lists:flatten([begin
+ {ok, JSON} = file:read_file(File),
+ Tests = jsx:decode(JSON, [return_maps]),
+ [
+ {iolist_to_binary(io_lib:format("~s: ~s", [filename:basename(File), Name])), fun() ->
+ %% The implementation is strict. We fail whenever we can.
+ CanFail = maps:get(<<"can_fail">>, Test, false),
+ MustFail = maps:get(<<"must_fail">>, Test, false),
+ io:format("must fail ~p~nexpected json ~0p~n",
+ [MustFail, maps:get(<<"expected">>, Test, undefined)]),
+ Expected = case MustFail of
+ true -> undefined;
+ false -> expected_to_term(maps:get(<<"expected">>, Test))
+ end,
+ io:format("expected term: ~0p", [Expected]),
+ Raw = raw_to_binary(Raw0),
+ case HeaderType of
+ <<"dictionary">> when MustFail; CanFail ->
+ {'EXIT', _} = (catch parse_dictionary(Raw));
+ %% The test "binary.json: non-zero pad bits" does not fail
+ %% due to our reliance on Erlang/OTP's base64 module.
+ <<"item">> when CanFail ->
+ case (catch parse_item(Raw)) of
+ {'EXIT', _} -> ok;
+ Expected -> ok
+ end;
+ <<"item">> when MustFail ->
+ {'EXIT', _} = (catch parse_item(Raw));
+ <<"list">> when MustFail; CanFail ->
+ {'EXIT', _} = (catch parse_list(Raw));
+ <<"dictionary">> ->
+ Expected = (catch parse_dictionary(Raw));
+ <<"item">> ->
+ Expected = (catch parse_item(Raw));
+ <<"list">> ->
+ Expected = (catch parse_list(Raw))
+ end
+ end}
+ || Test=#{
+ <<"name">> := Name,
+ <<"header_type">> := HeaderType,
+ <<"raw">> := Raw0
+ } <- Tests]
+ end || File <- Files]).
+
+%% The tests JSON use arrays for almost everything. Identifying
+%% what is what requires looking deeper in the values:
+%%
+%% dict: [["k", v], ["k2", v2]] (values may have params)
+%% params: [["k", v], ["k2", v2]] (no params for values)
+%% list: [e1, e2, e3]
+%% inner-list: [[ [items...], params]]
+%% item: [bare, params]
+
+%% Item.
+expected_to_term([Bare, []])
+ when is_boolean(Bare); is_number(Bare); is_binary(Bare); is_map(Bare) ->
+ {item, e2tb(Bare), []};
+expected_to_term([Bare, Params = [[<<_/bits>>, _]|_]])
+ when is_boolean(Bare); is_number(Bare); is_binary(Bare); is_map(Bare) ->
+ {item, e2tb(Bare), e2tp(Params)};
+%% Empty list or dictionary.
+expected_to_term([]) ->
+ [];
+%% Dictionary.
+%%
+%% We exclude empty list from values because that could
+%% be confused with an outer list of strings. There is
+%% currently no conflicts in the tests thankfully.
+expected_to_term(Dict = [[<<_/bits>>, V]|_]) when V =/= [] ->
+ e2t(Dict);
+%% Outer list.
+expected_to_term(List) when is_list(List) ->
+ [e2t(E) || E <- List].
+
+%% Dictionary.
+e2t(Dict = [[<<_/bits>>, _]|_]) ->
+ [{K, e2t(V)} || [K, V] <- Dict];
+%% Inner list.
+e2t([List, Params]) when is_list(List) ->
+ {list, [e2t(E) || E <- List], e2tp(Params)};
+%% Item.
+e2t([Bare, Params]) ->
+ {item, e2tb(Bare), e2tp(Params)}.
+
+%% Bare item.
+e2tb(#{<<"__type">> := <<"token">>, <<"value">> := V}) ->
+ {token, V};
+e2tb(#{<<"__type">> := <<"binary">>, <<"value">> := V}) ->
+ {binary, base32:decode(V)};
+e2tb(V) when is_binary(V) ->
+ {string, V};
+e2tb(V) when is_float(V) ->
+ %% There should be no rounding needed for the test cases.
+ {decimal, decimal:to_decimal(V, #{precision => 3, rounding => round_down})};
+e2tb(V) ->
+ V.
+
+%% Params.
+e2tp([]) ->
+ [];
+e2tp(Params) ->
+ [{K, e2tb(V)} || [K, V] <- Params].
+
+%% The Cowlib parsers currently do not support resuming parsing
+%% in the case of multiple headers. To make tests work we modify
+%% the raw value the same way Cowboy does when encountering
+%% multiple headers: by adding a comma and space in between.
+%%
+%% Similarly, the Cowlib parsers expect the leading and trailing
+%% whitespace to be removed before calling the parser.
+raw_to_binary(RawList) ->
+ trim_ws(iolist_to_binary(lists:join(<<", ">>, RawList))).
+
+trim_ws(<<$\s,R/bits>>) -> trim_ws(R);
+trim_ws(R) -> trim_ws_end(R, byte_size(R) - 1).
+
+trim_ws_end(_, -1) ->
+ <<>>;
+trim_ws_end(Value, N) ->
+ case binary:at(Value, N) of
+ $\s -> trim_ws_end(Value, N - 1);
+ _ ->
+ S = N + 1,
+ << Value2:S/binary, _/bits >> = Value,
+ Value2
+ end.
+-endif.
+
+%% Building.
+
+-spec dictionary(#{binary() => sh_item() | sh_inner_list()} | sh_dictionary())
+ -> iolist().
+dictionary(Map) when is_map(Map) ->
+ dictionary(maps:to_list(Map));
+dictionary(KVList) when is_list(KVList) ->
+ lists:join(<<", ">>, [
+ case Value of
+ true -> Key;
+ _ -> [Key, $=, item_or_inner_list(Value)]
+ end
+ || {Key, Value} <- KVList]).
+
+-spec item(sh_item()) -> iolist().
+item({item, BareItem, Params}) ->
+ [bare_item(BareItem), params(Params)].
+
+-spec list(sh_list()) -> iolist().
+list(List) ->
+ lists:join(<<", ">>, [item_or_inner_list(Value) || Value <- List]).
+
+item_or_inner_list(Value = {list, _, _}) ->
+ inner_list(Value);
+item_or_inner_list(Value) ->
+ item(Value).
+
+inner_list({list, List, Params}) ->
+ [$(, lists:join($\s, [item(Value) || Value <- List]), $), params(Params)].
+
+bare_item({string, String}) ->
+ [$", escape_string(String, <<>>), $"];
+%% @todo Must fail if Token has invalid characters.
+bare_item({token, Token}) ->
+ Token;
+bare_item({binary, Binary}) ->
+ [$:, base64:encode(Binary), $:];
+bare_item({decimal, {Base, Exp}}) when Exp >= 0 ->
+ Mul = case Exp of
+ 0 -> 1;
+ 1 -> 10;
+ 2 -> 100;
+ 3 -> 1000;
+ 4 -> 10000;
+ 5 -> 100000;
+ 6 -> 1000000;
+ 7 -> 10000000;
+ 8 -> 100000000;
+ 9 -> 1000000000;
+ 10 -> 10000000000;
+ 11 -> 100000000000;
+ 12 -> 1000000000000
+ end,
+ MaxLenWithSign = if
+ Base < 0 -> 13;
+ true -> 12
+ end,
+ Bin = integer_to_binary(Base * Mul),
+ true = byte_size(Bin) =< MaxLenWithSign,
+ [Bin, <<".0">>];
+bare_item({decimal, {Base, -1}}) ->
+ Int = Base div 10,
+ Frac = abs(Base) rem 10,
+ [integer_to_binary(Int), $., integer_to_binary(Frac)];
+bare_item({decimal, {Base, -2}}) ->
+ Int = Base div 100,
+ Frac = abs(Base) rem 100,
+ [integer_to_binary(Int), $., integer_to_binary(Frac)];
+bare_item({decimal, {Base, -3}}) ->
+ Int = Base div 1000,
+ Frac = abs(Base) rem 1000,
+ [integer_to_binary(Int), $., integer_to_binary(Frac)];
+bare_item({decimal, {Base, Exp}}) ->
+ Div = exp_div(Exp),
+ Int0 = Base div Div,
+ true = abs(Int0) < 1000000000000,
+ Frac0 = abs(Base) rem Div,
+ DivFrac = Div div 1000,
+ Frac1 = Frac0 div DivFrac,
+ {Int, Frac} = if
+ (Frac0 rem DivFrac) > (DivFrac div 2) ->
+ case Frac1 of
+ 999 when Int0 < 0 -> {Int0 - 1, 0};
+ 999 -> {Int0 + 1, 0};
+ _ -> {Int0, Frac1 + 1}
+ end;
+ true ->
+ {Int0, Frac1}
+ end,
+ [integer_to_binary(Int), $., if
+ Frac < 10 -> [$0, $0, integer_to_binary(Frac)];
+ Frac < 100 -> [$0, integer_to_binary(Frac)];
+ true -> integer_to_binary(Frac)
+ end];
+bare_item(Integer) when is_integer(Integer) ->
+ integer_to_binary(Integer);
+bare_item(true) ->
+ <<"?1">>;
+bare_item(false) ->
+ <<"?0">>.
+
+exp_div(0) -> 1;
+exp_div(N) -> 10 * exp_div(N + 1).
+
+escape_string(<<>>, Acc) -> Acc;
+escape_string(<<$\\,R/bits>>, Acc) -> escape_string(R, <<Acc/binary,$\\,$\\>>);
+escape_string(<<$",R/bits>>, Acc) -> escape_string(R, <<Acc/binary,$\\,$">>);
+escape_string(<<C,R/bits>>, Acc) -> escape_string(R, <<Acc/binary,C>>).
+
+params(Params) ->
+ [case Param of
+ {Key, true} -> [$;, Key];
+ {Key, Value} -> [$;, Key, $=, bare_item(Value)]
+ end || Param <- Params].
+
+-ifdef(TEST).
+struct_hd_identity_test_() ->
+ Files = filelib:wildcard("deps/structured-header-tests/*.json"),
+ lists:flatten([begin
+ {ok, JSON} = file:read_file(File),
+ Tests = jsx:decode(JSON, [return_maps]),
+ [
+ {iolist_to_binary(io_lib:format("~s: ~s", [filename:basename(File), Name])), fun() ->
+ io:format("expected json ~0p~n", [Expected0]),
+ Expected = expected_to_term(Expected0),
+ io:format("expected term: ~0p", [Expected]),
+ case HeaderType of
+ <<"dictionary">> ->
+ Expected = parse_dictionary(iolist_to_binary(dictionary(Expected)));
+ <<"item">> ->
+ Expected = parse_item(iolist_to_binary(item(Expected)));
+ <<"list">> ->
+ Expected = parse_list(iolist_to_binary(list(Expected)))
+ end
+ end}
+ || #{
+ <<"name">> := Name,
+ <<"header_type">> := HeaderType,
+ %% We only run tests that must not fail.
+ <<"expected">> := Expected0
+ } <- Tests]
+ end || File <- Files]).
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_http_te.erl b/server/_build/default/lib/cowlib/src/cow_http_te.erl
new file mode 100644
index 0000000..e3473cf
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_http_te.erl
@@ -0,0 +1,373 @@
+%% Copyright (c) 2014-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_http_te).
+
+%% Identity.
+-export([stream_identity/2]).
+-export([identity/1]).
+
+%% Chunked.
+-export([stream_chunked/2]).
+-export([chunk/1]).
+-export([last_chunk/0]).
+
+%% The state type is the same for both identity and chunked.
+-type state() :: {non_neg_integer(), non_neg_integer()}.
+-export_type([state/0]).
+
+-type decode_ret() :: more
+ | {more, Data::binary(), state()}
+ | {more, Data::binary(), RemLen::non_neg_integer(), state()}
+ | {more, Data::binary(), Rest::binary(), state()}
+ | {done, HasTrailers::trailers | no_trailers, Rest::binary()}
+ | {done, Data::binary(), HasTrailers::trailers | no_trailers, Rest::binary()}.
+-export_type([decode_ret/0]).
+
+-include("cow_parse.hrl").
+
+-ifdef(TEST).
+dripfeed(<< C, Rest/bits >>, Acc, State, F) ->
+ case F(<< Acc/binary, C >>, State) of
+ more ->
+ dripfeed(Rest, << Acc/binary, C >>, State, F);
+ {more, _, State2} ->
+ dripfeed(Rest, <<>>, State2, F);
+ {more, _, Length, State2} when is_integer(Length) ->
+ dripfeed(Rest, <<>>, State2, F);
+ {more, _, Acc2, State2} ->
+ dripfeed(Rest, Acc2, State2, F);
+ {done, _, <<>>} ->
+ ok;
+ {done, _, _, <<>>} ->
+ ok
+ end.
+-endif.
+
+%% Identity.
+
+%% @doc Decode an identity stream.
+
+-spec stream_identity(Data, State)
+ -> {more, Data, Len, State} | {done, Data, Len, Data}
+ when Data::binary(), State::state(), Len::non_neg_integer().
+stream_identity(Data, {Streamed, Total}) ->
+ Streamed2 = Streamed + byte_size(Data),
+ if
+ Streamed2 < Total ->
+ {more, Data, Total - Streamed2, {Streamed2, Total}};
+ true ->
+ Size = Total - Streamed,
+ << Data2:Size/binary, Rest/bits >> = Data,
+ {done, Data2, Total, Rest}
+ end.
+
+-spec identity(Data) -> Data when Data::iodata().
+identity(Data) ->
+ Data.
+
+-ifdef(TEST).
+stream_identity_test() ->
+ {done, <<>>, 0, <<>>}
+ = stream_identity(identity(<<>>), {0, 0}),
+ {done, <<"\r\n">>, 2, <<>>}
+ = stream_identity(identity(<<"\r\n">>), {0, 2}),
+ {done, << 0:80000 >>, 10000, <<>>}
+ = stream_identity(identity(<< 0:80000 >>), {0, 10000}),
+ ok.
+
+stream_identity_parts_test() ->
+ {more, << 0:8000 >>, 1999, S1}
+ = stream_identity(<< 0:8000 >>, {0, 2999}),
+ {more, << 0:8000 >>, 999, S2}
+ = stream_identity(<< 0:8000 >>, S1),
+ {done, << 0:7992 >>, 2999, <<>>}
+ = stream_identity(<< 0:7992 >>, S2),
+ ok.
+
+%% Using the same data as the chunked one for comparison.
+horse_stream_identity() ->
+ horse:repeat(10000,
+ stream_identity(<<
+ "4\r\n"
+ "Wiki\r\n"
+ "5\r\n"
+ "pedia\r\n"
+ "e\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0\r\n"
+ "\r\n">>, {0, 43})
+ ).
+
+horse_stream_identity_dripfeed() ->
+ horse:repeat(10000,
+ dripfeed(<<
+ "4\r\n"
+ "Wiki\r\n"
+ "5\r\n"
+ "pedia\r\n"
+ "e\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0\r\n"
+ "\r\n">>, <<>>, {0, 43}, fun stream_identity/2)
+ ).
+-endif.
+
+%% Chunked.
+
+%% @doc Decode a chunked stream.
+
+-spec stream_chunked(Data, State)
+ -> more | {more, Data, State} | {more, Data, non_neg_integer(), State}
+ | {more, Data, Data, State}
+ | {done, HasTrailers, Data} | {done, Data, HasTrailers, Data}
+ when Data::binary(), State::state(), HasTrailers::trailers | no_trailers.
+stream_chunked(Data, State) ->
+ stream_chunked(Data, State, <<>>).
+
+%% New chunk.
+stream_chunked(Data = << C, _/bits >>, {0, Streamed}, Acc) when C =/= $\r ->
+ case chunked_len(Data, Streamed, Acc, 0) of
+ {next, Rest, State, Acc2} ->
+ stream_chunked(Rest, State, Acc2);
+ {more, State, Acc2} ->
+ {more, Acc2, Data, State};
+ Ret ->
+ Ret
+ end;
+%% Trailing \r\n before next chunk.
+stream_chunked(<< "\r\n", Rest/bits >>, {2, Streamed}, Acc) ->
+ stream_chunked(Rest, {0, Streamed}, Acc);
+%% Trailing \r before next chunk.
+stream_chunked(<< "\r" >>, {2, Streamed}, Acc) ->
+ {more, Acc, {1, Streamed}};
+%% Trailing \n before next chunk.
+stream_chunked(<< "\n", Rest/bits >>, {1, Streamed}, Acc) ->
+ stream_chunked(Rest, {0, Streamed}, Acc);
+%% More data needed.
+stream_chunked(<<>>, State = {Rem, _}, Acc) ->
+ {more, Acc, Rem, State};
+%% Chunk data.
+stream_chunked(Data, {Rem, Streamed}, Acc) when Rem > 2 ->
+ DataSize = byte_size(Data),
+ RemSize = Rem - 2,
+ case Data of
+ << Chunk:RemSize/binary, "\r\n", Rest/bits >> ->
+ stream_chunked(Rest, {0, Streamed + RemSize}, << Acc/binary, Chunk/binary >>);
+ << Chunk:RemSize/binary, "\r" >> ->
+ {more, << Acc/binary, Chunk/binary >>, {1, Streamed + RemSize}};
+ %% Everything in Data is part of the chunk. If we have more
+ %% data than the chunk accepts, then this is an error and we crash.
+ _ when DataSize =< RemSize ->
+ Rem2 = Rem - DataSize,
+ {more, << Acc/binary, Data/binary >>, Rem2, {Rem2, Streamed + DataSize}}
+ end.
+
+chunked_len(<< $0, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16);
+chunked_len(<< $1, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 1);
+chunked_len(<< $2, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 2);
+chunked_len(<< $3, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 3);
+chunked_len(<< $4, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 4);
+chunked_len(<< $5, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 5);
+chunked_len(<< $6, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 6);
+chunked_len(<< $7, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 7);
+chunked_len(<< $8, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 8);
+chunked_len(<< $9, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 9);
+chunked_len(<< $A, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 10);
+chunked_len(<< $B, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 11);
+chunked_len(<< $C, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 12);
+chunked_len(<< $D, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 13);
+chunked_len(<< $E, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 14);
+chunked_len(<< $F, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 15);
+chunked_len(<< $a, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 10);
+chunked_len(<< $b, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 11);
+chunked_len(<< $c, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 12);
+chunked_len(<< $d, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 13);
+chunked_len(<< $e, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 14);
+chunked_len(<< $f, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 15);
+%% Chunk extensions.
+%%
+%% Note that we currently skip the first character we encounter here,
+%% and not in the skip_chunk_ext function. If we latter implement
+%% chunk extensions (unlikely) we will need to change this clause too.
+chunked_len(<< C, R/bits >>, S, A, Len) when ?IS_WS(C); C =:= $; -> skip_chunk_ext(R, S, A, Len, 0);
+%% Final chunk.
+%%
+%% When trailers are following we simply return them as the Rest.
+%% Then the user code can decide to call the stream_trailers function
+%% to parse them. The user can therefore ignore trailers as necessary
+%% if they do not wish to handle them.
+chunked_len(<< "\r\n\r\n", R/bits >>, _, <<>>, 0) -> {done, no_trailers, R};
+chunked_len(<< "\r\n\r\n", R/bits >>, _, A, 0) -> {done, A, no_trailers, R};
+chunked_len(<< "\r\n", R/bits >>, _, <<>>, 0) when byte_size(R) > 2 -> {done, trailers, R};
+chunked_len(<< "\r\n", R/bits >>, _, A, 0) when byte_size(R) > 2 -> {done, A, trailers, R};
+chunked_len(_, _, _, 0) -> more;
+%% Normal chunk. Add 2 to Len for the trailing \r\n.
+chunked_len(<< "\r\n", R/bits >>, S, A, Len) -> {next, R, {Len + 2, S}, A};
+chunked_len(<<"\r">>, _, <<>>, _) -> more;
+chunked_len(<<"\r">>, S, A, _) -> {more, {0, S}, A};
+chunked_len(<<>>, _, <<>>, _) -> more;
+chunked_len(<<>>, S, A, _) -> {more, {0, S}, A}.
+
+skip_chunk_ext(R = << "\r", _/bits >>, S, A, Len, _) -> chunked_len(R, S, A, Len);
+skip_chunk_ext(R = <<>>, S, A, Len, _) -> chunked_len(R, S, A, Len);
+%% We skip up to 128 characters of chunk extensions. The value
+%% is hardcoded: chunk extensions are very rarely seen in the
+%% wild and Cowboy doesn't do anything with them anyway.
+%%
+%% Line breaks are not allowed in the middle of chunk extensions.
+skip_chunk_ext(<< C, R/bits >>, S, A, Len, Skipped) when C =/= $\n, Skipped < 128 ->
+ skip_chunk_ext(R, S, A, Len, Skipped + 1).
+
+%% @doc Encode a chunk.
+
+-spec chunk(D) -> D when D::iodata().
+chunk(Data) ->
+ [integer_to_list(iolist_size(Data), 16), <<"\r\n">>,
+ Data, <<"\r\n">>].
+
+%% @doc Encode the last chunk of a chunked stream.
+
+-spec last_chunk() -> << _:40 >>.
+last_chunk() ->
+ <<"0\r\n\r\n">>.
+
+-ifdef(TEST).
+stream_chunked_identity_test() ->
+ {done, <<"Wikipedia in\r\n\r\nchunks.">>, no_trailers, <<>>}
+ = stream_chunked(iolist_to_binary([
+ chunk("Wiki"),
+ chunk("pedia"),
+ chunk(" in\r\n\r\nchunks."),
+ last_chunk()
+ ]), {0, 0}),
+ ok.
+
+stream_chunked_one_pass_test() ->
+ {done, no_trailers, <<>>} = stream_chunked(<<"0\r\n\r\n">>, {0, 0}),
+ {done, <<"Wikipedia in\r\n\r\nchunks.">>, no_trailers, <<>>}
+ = stream_chunked(<<
+ "4\r\n"
+ "Wiki\r\n"
+ "5\r\n"
+ "pedia\r\n"
+ "e\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0\r\n"
+ "\r\n">>, {0, 0}),
+ %% Same but with extra spaces or chunk extensions.
+ {done, <<"Wikipedia in\r\n\r\nchunks.">>, no_trailers, <<>>}
+ = stream_chunked(<<
+ "4 \r\n"
+ "Wiki\r\n"
+ "5 ; ext = abc\r\n"
+ "pedia\r\n"
+ "e;ext=abc\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0;ext\r\n"
+ "\r\n">>, {0, 0}),
+ %% Same but with trailers.
+ {done, <<"Wikipedia in\r\n\r\nchunks.">>, trailers, Rest}
+ = stream_chunked(<<
+ "4\r\n"
+ "Wiki\r\n"
+ "5\r\n"
+ "pedia\r\n"
+ "e\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0\r\n"
+ "x-foo-bar: bar foo\r\n"
+ "\r\n">>, {0, 0}),
+ {[{<<"x-foo-bar">>, <<"bar foo">>}], <<>>} = cow_http:parse_headers(Rest),
+ ok.
+
+stream_chunked_n_passes_test() ->
+ S0 = {0, 0},
+ more = stream_chunked(<<"4\r">>, S0),
+ {more, <<>>, 6, S1} = stream_chunked(<<"4\r\n">>, S0),
+ {more, <<"Wiki">>, 0, S2} = stream_chunked(<<"Wiki\r\n">>, S1),
+ {more, <<"pedia">>, <<"e\r">>, S3} = stream_chunked(<<"5\r\npedia\r\ne\r">>, S2),
+ {more, <<" in\r\n\r\nchunks.">>, 2, S4} = stream_chunked(<<"e\r\n in\r\n\r\nchunks.">>, S3),
+ {done, no_trailers, <<>>} = stream_chunked(<<"\r\n0\r\n\r\n">>, S4),
+ %% A few extra for coverage purposes.
+ more = stream_chunked(<<"\n3">>, {1, 0}),
+ {more, <<"abc">>, 2, {2, 3}} = stream_chunked(<<"\n3\r\nabc">>, {1, 0}),
+ {more, <<"abc">>, {1, 3}} = stream_chunked(<<"3\r\nabc\r">>, {0, 0}),
+ {more, <<"abc">>, <<"123">>, {0, 3}} = stream_chunked(<<"3\r\nabc\r\n123">>, {0, 0}),
+ ok.
+
+stream_chunked_dripfeed_test() ->
+ dripfeed(<<
+ "4\r\n"
+ "Wiki\r\n"
+ "5\r\n"
+ "pedia\r\n"
+ "e\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0\r\n"
+ "\r\n">>, <<>>, {0, 0}, fun stream_chunked/2).
+
+do_body_to_chunks(_, <<>>, Acc) ->
+ lists:reverse([<<"0\r\n\r\n">>|Acc]);
+do_body_to_chunks(ChunkSize, Body, Acc) ->
+ BodySize = byte_size(Body),
+ ChunkSize2 = case BodySize < ChunkSize of
+ true -> BodySize;
+ false -> ChunkSize
+ end,
+ << Chunk:ChunkSize2/binary, Rest/binary >> = Body,
+ ChunkSizeBin = list_to_binary(integer_to_list(ChunkSize2, 16)),
+ do_body_to_chunks(ChunkSize, Rest,
+ [<< ChunkSizeBin/binary, "\r\n", Chunk/binary, "\r\n" >>|Acc]).
+
+stream_chunked_dripfeed2_test() ->
+ Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])),
+ Body2 = iolist_to_binary(do_body_to_chunks(50, Body, [])),
+ dripfeed(Body2, <<>>, {0, 0}, fun stream_chunked/2).
+
+stream_chunked_error_test_() ->
+ Tests = [
+ {<<>>, undefined},
+ {<<"\n\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">>, {2, 0}}
+ ],
+ [{lists:flatten(io_lib:format("value ~p state ~p", [V, S])),
+ fun() -> {'EXIT', _} = (catch stream_chunked(V, S)) end}
+ || {V, S} <- Tests].
+
+horse_stream_chunked() ->
+ horse:repeat(10000,
+ stream_chunked(<<
+ "4\r\n"
+ "Wiki\r\n"
+ "5\r\n"
+ "pedia\r\n"
+ "e\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0\r\n"
+ "\r\n">>, {0, 0})
+ ).
+
+horse_stream_chunked_dripfeed() ->
+ horse:repeat(10000,
+ dripfeed(<<
+ "4\r\n"
+ "Wiki\r\n"
+ "5\r\n"
+ "pedia\r\n"
+ "e\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0\r\n"
+ "\r\n">>, <<>>, {0, 43}, fun stream_chunked/2)
+ ).
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_iolists.erl b/server/_build/default/lib/cowlib/src/cow_iolists.erl
new file mode 100644
index 0000000..a5e75df
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_iolists.erl
@@ -0,0 +1,95 @@
+%% Copyright (c) 2017-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_iolists).
+
+-export([split/2]).
+
+-ifdef(TEST).
+-include_lib("proper/include/proper.hrl").
+-endif.
+
+-spec split(non_neg_integer(), iodata()) -> {iodata(), iodata()}.
+split(N, Iolist) ->
+ case split(N, Iolist, []) of
+ {ok, Before, After} ->
+ {Before, After};
+ {more, _, Before} ->
+ {lists:reverse(Before), <<>>}
+ end.
+
+split(0, Rest, Acc) ->
+ {ok, lists:reverse(Acc), Rest};
+split(N, [], Acc) ->
+ {more, N, Acc};
+split(N, Binary, Acc) when byte_size(Binary) =< N ->
+ {more, N - byte_size(Binary), [Binary|Acc]};
+split(N, Binary, Acc) when is_binary(Binary) ->
+ << Before:N/binary, After/bits >> = Binary,
+ {ok, lists:reverse([Before|Acc]), After};
+split(N, [Binary|Tail], Acc) when byte_size(Binary) =< N ->
+ split(N - byte_size(Binary), Tail, [Binary|Acc]);
+split(N, [Binary|Tail], Acc) when is_binary(Binary) ->
+ << Before:N/binary, After/bits >> = Binary,
+ {ok, lists:reverse([Before|Acc]), [After|Tail]};
+split(N, [Char|Tail], Acc) when is_integer(Char) ->
+ split(N - 1, Tail, [Char|Acc]);
+split(N, [List|Tail], Acc0) ->
+ case split(N, List, Acc0) of
+ {ok, Before, After} ->
+ {ok, Before, [After|Tail]};
+ {more, More, Acc} ->
+ split(More, Tail, Acc)
+ end.
+
+-ifdef(TEST).
+
+split_test_() ->
+ Tests = [
+ {10, "Hello world!", "Hello worl", "d!"},
+ {10, <<"Hello world!">>, "Hello worl", "d!"},
+ {10, ["He", [<<"llo">>], $\s, [["world"], <<"!">>]], "Hello worl", "d!"},
+ {10, ["Hello "|<<"world!">>], "Hello worl", "d!"},
+ {10, "Hello!", "Hello!", ""},
+ {10, <<"Hello!">>, "Hello!", ""},
+ {10, ["He", [<<"ll">>], $o, [["!"]]], "Hello!", ""},
+ {10, ["Hel"|<<"lo!">>], "Hello!", ""},
+ {10, [[<<>>|<<>>], [], <<"Hello world!">>], "Hello worl", "d!"},
+ {10, [[<<"He">>|<<"llo">>], [$\s], <<"world!">>], "Hello worl", "d!"},
+ {10, [[[]|<<"He">>], [[]|<<"llo wor">>]|<<"ld!">>], "Hello worl", "d!"}
+ ],
+ [{iolist_to_binary(V), fun() ->
+ {B, A} = split(N, V),
+ true = iolist_to_binary(RB) =:= iolist_to_binary(B),
+ true = iolist_to_binary(RA) =:= iolist_to_binary(A)
+ end} || {N, V, RB, RA} <- Tests].
+
+prop_split_test() ->
+ ?FORALL({N, Input},
+ {non_neg_integer(), iolist()},
+ begin
+ Size = iolist_size(Input),
+ {Before, After} = split(N, Input),
+ if
+ N >= Size ->
+ ((iolist_size(After) =:= 0)
+ andalso iolist_to_binary(Before) =:= iolist_to_binary(Input));
+ true ->
+ <<ExpectBefore:N/binary, ExpectAfter/bits>> = iolist_to_binary(Input),
+ (ExpectBefore =:= iolist_to_binary(Before))
+ andalso (ExpectAfter =:= iolist_to_binary(After))
+ end
+ end).
+
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_link.erl b/server/_build/default/lib/cowlib/src/cow_link.erl
new file mode 100644
index 0000000..b649786
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_link.erl
@@ -0,0 +1,445 @@
+%% Copyright (c) 2019-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_link).
+-compile({no_auto_import, [link/1]}).
+
+-export([parse_link/1]).
+-export([resolve_link/2]).
+-export([resolve_link/3]).
+-export([link/1]).
+
+-include("cow_inline.hrl").
+-include("cow_parse.hrl").
+
+-type link() :: #{
+ target := binary(),
+ rel := binary(),
+ attributes := [{binary(), binary()}]
+}.
+-export_type([link/0]).
+
+-type resolve_opts() :: #{
+ allow_anchor => boolean()
+}.
+
+-type uri() :: uri_string:uri_map() | uri_string:uri_string() | undefined.
+
+%% Parse a link header.
+
+%% This function returns the URI target from the header directly.
+%% Relative URIs must then be resolved as per RFC3986 5. In some
+%% cases it might not be possible to resolve URIs, for example when
+%% the link header is returned with a 404 status code.
+-spec parse_link(binary()) -> [link()].
+parse_link(Link) ->
+ before_target(Link, []).
+
+before_target(<<>>, Acc) -> lists:reverse(Acc);
+before_target(<<$<,R/bits>>, Acc) -> target(R, Acc, <<>>);
+before_target(<<C,R/bits>>, Acc) when ?IS_WS(C) -> before_target(R, Acc).
+
+target(<<$>,R/bits>>, Acc, T) -> param_sep(R, Acc, T, []);
+target(<<C,R/bits>>, Acc, T) -> target(R, Acc, <<T/binary, C>>).
+
+param_sep(<<>>, Acc, T, P) -> lists:reverse(acc_link(Acc, T, P));
+param_sep(<<$,,R/bits>>, Acc, T, P) -> before_target(R, acc_link(Acc, T, P));
+param_sep(<<$;,R/bits>>, Acc, T, P) -> before_param(R, Acc, T, P);
+param_sep(<<C,R/bits>>, Acc, T, P) when ?IS_WS(C) -> param_sep(R, Acc, T, P).
+
+before_param(<<C,R/bits>>, Acc, T, P) when ?IS_WS(C) -> before_param(R, Acc, T, P);
+before_param(<<C,R/bits>>, Acc, T, P) when ?IS_TOKEN(C) -> ?LOWER(param, R, Acc, T, P, <<>>).
+
+param(<<$=,$",R/bits>>, Acc, T, P, K) -> quoted(R, Acc, T, P, K, <<>>);
+param(<<$=,C,R/bits>>, Acc, T, P, K) when ?IS_TOKEN(C) -> value(R, Acc, T, P, K, <<C>>);
+param(<<C,R/bits>>, Acc, T, P, K) when ?IS_TOKEN(C) -> ?LOWER(param, R, Acc, T, P, K).
+
+quoted(<<$",R/bits>>, Acc, T, P, K, V) -> param_sep(R, Acc, T, [{K, V}|P]);
+quoted(<<$\\,C,R/bits>>, Acc, T, P, K, V) when ?IS_VCHAR_OBS(C) -> quoted(R, Acc, T, P, K, <<V/binary,C>>);
+quoted(<<C,R/bits>>, Acc, T, P, K, V) when ?IS_VCHAR_OBS(C) -> quoted(R, Acc, T, P, K, <<V/binary,C>>).
+
+value(<<C,R/bits>>, Acc, T, P, K, V) when ?IS_TOKEN(C) -> value(R, Acc, T, P, K, <<V/binary,C>>);
+value(R, Acc, T, P, K, V) -> param_sep(R, Acc, T, [{K, V}|P]).
+
+acc_link(Acc, Target, Params0) ->
+ Params1 = lists:reverse(Params0),
+ %% The rel parameter MUST be present. (RFC8288 3.3)
+ {value, {_, Rel}, Params2} = lists:keytake(<<"rel">>, 1, Params1),
+ %% Occurrences after the first MUST be ignored by parsers.
+ Params = filter_out_duplicates(Params2, #{}),
+ [#{
+ target => Target,
+ rel => ?LOWER(Rel),
+ attributes => Params
+ }|Acc].
+
+%% This function removes duplicates for attributes that don't allow them.
+filter_out_duplicates([], _) ->
+ [];
+%% The "rel" is mandatory and was already removed from params.
+filter_out_duplicates([{<<"rel">>, _}|Tail], State) ->
+ filter_out_duplicates(Tail, State);
+filter_out_duplicates([{<<"anchor">>, _}|Tail], State=#{anchor := true}) ->
+ filter_out_duplicates(Tail, State);
+filter_out_duplicates([{<<"media">>, _}|Tail], State=#{media := true}) ->
+ filter_out_duplicates(Tail, State);
+filter_out_duplicates([{<<"title">>, _}|Tail], State=#{title := true}) ->
+ filter_out_duplicates(Tail, State);
+filter_out_duplicates([{<<"title*">>, _}|Tail], State=#{title_star := true}) ->
+ filter_out_duplicates(Tail, State);
+filter_out_duplicates([{<<"type">>, _}|Tail], State=#{type := true}) ->
+ filter_out_duplicates(Tail, State);
+filter_out_duplicates([Tuple={<<"anchor">>, _}|Tail], State) ->
+ [Tuple|filter_out_duplicates(Tail, State#{anchor => true})];
+filter_out_duplicates([Tuple={<<"media">>, _}|Tail], State) ->
+ [Tuple|filter_out_duplicates(Tail, State#{media => true})];
+filter_out_duplicates([Tuple={<<"title">>, _}|Tail], State) ->
+ [Tuple|filter_out_duplicates(Tail, State#{title => true})];
+filter_out_duplicates([Tuple={<<"title*">>, _}|Tail], State) ->
+ [Tuple|filter_out_duplicates(Tail, State#{title_star => true})];
+filter_out_duplicates([Tuple={<<"type">>, _}|Tail], State) ->
+ [Tuple|filter_out_duplicates(Tail, State#{type => true})];
+filter_out_duplicates([Tuple|Tail], State) ->
+ [Tuple|filter_out_duplicates(Tail, State)].
+
+-ifdef(TEST).
+parse_link_test_() ->
+ Tests = [
+ {<<>>, []},
+ {<<" ">>, []},
+ %% Examples from the RFC.
+ {<<"<http://example.com/TheBook/chapter2>; rel=\"previous\"; title=\"previous chapter\"">>, [
+ #{
+ target => <<"http://example.com/TheBook/chapter2">>,
+ rel => <<"previous">>,
+ attributes => [
+ {<<"title">>, <<"previous chapter">>}
+ ]
+ }
+ ]},
+ {<<"</>; rel=\"http://example.net/foo\"">>, [
+ #{
+ target => <<"/">>,
+ rel => <<"http://example.net/foo">>,
+ attributes => []
+ }
+ ]},
+ {<<"</terms>; rel=\"copyright\"; anchor=\"#foo\"">>, [
+ #{
+ target => <<"/terms">>,
+ rel => <<"copyright">>,
+ attributes => [
+ {<<"anchor">>, <<"#foo">>}
+ ]
+ }
+ ]},
+% {<<"</TheBook/chapter2>; rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, "
+% "</TheBook/chapter4>; rel=\"next\"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel">>, [
+% %% @todo
+% ]}
+ {<<"<http://example.org/>; rel=\"start http://example.net/relation/other\"">>, [
+ #{
+ target => <<"http://example.org/">>,
+ rel => <<"start http://example.net/relation/other">>,
+ attributes => []
+ }
+ ]},
+ {<<"<https://example.org/>; rel=\"start\", "
+ "<https://example.org/index>; rel=\"index\"">>, [
+ #{
+ target => <<"https://example.org/">>,
+ rel => <<"start">>,
+ attributes => []
+ },
+ #{
+ target => <<"https://example.org/index">>,
+ rel => <<"index">>,
+ attributes => []
+ }
+ ]},
+ %% Relation types are case insensitive.
+ {<<"</>; rel=\"SELF\"">>, [
+ #{
+ target => <<"/">>,
+ rel => <<"self">>,
+ attributes => []
+ }
+ ]},
+ {<<"</>; rel=\"HTTP://EXAMPLE.NET/FOO\"">>, [
+ #{
+ target => <<"/">>,
+ rel => <<"http://example.net/foo">>,
+ attributes => []
+ }
+ ]},
+ %% Attribute names are case insensitive.
+ {<<"</terms>; REL=\"copyright\"; ANCHOR=\"#foo\"">>, [
+ #{
+ target => <<"/terms">>,
+ rel => <<"copyright">>,
+ attributes => [
+ {<<"anchor">>, <<"#foo">>}
+ ]
+ }
+ ]}
+ ],
+ [{V, fun() -> R = parse_link(V) end} || {V, R} <- Tests].
+-endif.
+
+%% Resolve a link based on the context URI and options.
+
+-spec resolve_link(Link, uri()) -> Link | false when Link::link().
+resolve_link(Link, ContextURI) ->
+ resolve_link(Link, ContextURI, #{}).
+
+-spec resolve_link(Link, uri(), resolve_opts()) -> Link | false when Link::link().
+%% When we do not have a context URI we only succeed when the target URI is absolute.
+%% The target URI will only be normalized in that case.
+resolve_link(Link=#{target := TargetURI}, undefined, _) ->
+ case uri_string:parse(TargetURI) of
+ URIMap = #{scheme := _} ->
+ Link#{target => uri_string:normalize(URIMap)};
+ _ ->
+ false
+ end;
+resolve_link(Link=#{attributes := Params}, ContextURI, Opts) ->
+ AllowAnchor = maps:get(allow_anchor, Opts, true),
+ case lists:keyfind(<<"anchor">>, 1, Params) of
+ false ->
+ do_resolve_link(Link, ContextURI);
+ {_, Anchor} when AllowAnchor ->
+ do_resolve_link(Link, resolve(Anchor, ContextURI));
+ _ ->
+ false
+ end.
+
+do_resolve_link(Link=#{target := TargetURI}, ContextURI) ->
+ Link#{target => uri_string:recompose(resolve(TargetURI, ContextURI))}.
+
+-ifdef(TEST).
+resolve_link_test_() ->
+ Tests = [
+ %% No context URI available.
+ {#{target => <<"http://a/b/./c">>}, undefined, #{},
+ #{target => <<"http://a/b/c">>}},
+ {#{target => <<"a/b/./c">>}, undefined, #{},
+ false},
+ %% Context URI available, allow_anchor => true.
+ {#{target => <<"http://a/b">>, attributes => []}, <<"http://a/c">>, #{},
+ #{target => <<"http://a/b">>, attributes => []}},
+ {#{target => <<"b">>, attributes => []}, <<"http://a/c">>, #{},
+ #{target => <<"http://a/b">>, attributes => []}},
+ {#{target => <<"b">>, attributes => [{<<"anchor">>, <<"#frag">>}]}, <<"http://a/c">>, #{},
+ #{target => <<"http://a/b">>, attributes => [{<<"anchor">>, <<"#frag">>}]}},
+ {#{target => <<"b">>, attributes => [{<<"anchor">>, <<"d/e">>}]}, <<"http://a/c">>, #{},
+ #{target => <<"http://a/d/b">>, attributes => [{<<"anchor">>, <<"d/e">>}]}},
+ %% Context URI available, allow_anchor => false.
+ {#{target => <<"http://a/b">>, attributes => []}, <<"http://a/c">>, #{allow_anchor => false},
+ #{target => <<"http://a/b">>, attributes => []}},
+ {#{target => <<"b">>, attributes => []}, <<"http://a/c">>, #{allow_anchor => false},
+ #{target => <<"http://a/b">>, attributes => []}},
+ {#{target => <<"b">>, attributes => [{<<"anchor">>, <<"#frag">>}]},
+ <<"http://a/c">>, #{allow_anchor => false}, false},
+ {#{target => <<"b">>, attributes => [{<<"anchor">>, <<"d/e">>}]},
+ <<"http://a/c">>, #{allow_anchor => false}, false}
+ ],
+ [{iolist_to_binary(io_lib:format("~0p", [L])),
+ fun() -> R = resolve_link(L, C, O) end} || {L, C, O, R} <- Tests].
+-endif.
+
+%% @todo This function has been added to Erlang/OTP 22.3 as uri_string:resolve/2,3.
+resolve(URI, BaseURI) ->
+ case resolve1(ensure_map_uri(URI), BaseURI) of
+ TargetURI = #{path := Path0} ->
+ %% We remove dot segments. Normalizing the entire URI
+ %% will sometimes add an extra slash we don't want.
+ #{path := Path} = uri_string:normalize(#{path => Path0}, [return_map]),
+ TargetURI#{path => Path};
+ TargetURI ->
+ TargetURI
+ end.
+
+resolve1(URI=#{scheme := _}, _) ->
+ URI;
+resolve1(URI=#{host := _}, BaseURI) ->
+ #{scheme := Scheme} = ensure_map_uri(BaseURI),
+ URI#{scheme => Scheme};
+resolve1(URI=#{path := <<>>}, BaseURI0) ->
+ BaseURI = ensure_map_uri(BaseURI0),
+ Keys = case maps:is_key(query, URI) of
+ true -> [scheme, host, port, path];
+ false -> [scheme, host, port, path, query]
+ end,
+ maps:merge(URI, maps:with(Keys, BaseURI));
+resolve1(URI=#{path := <<"/",_/bits>>}, BaseURI0) ->
+ BaseURI = ensure_map_uri(BaseURI0),
+ maps:merge(URI, maps:with([scheme, host, port], BaseURI));
+resolve1(URI=#{path := Path}, BaseURI0) ->
+ BaseURI = ensure_map_uri(BaseURI0),
+ maps:merge(
+ URI#{path := merge_paths(Path, BaseURI)},
+ maps:with([scheme, host, port], BaseURI)).
+
+merge_paths(Path, #{host := _, path := <<>>}) ->
+ <<$/, Path/binary>>;
+merge_paths(Path, #{path := BasePath0}) ->
+ case string:split(BasePath0, <<$/>>, trailing) of
+ [BasePath, _] -> <<BasePath/binary, $/, Path/binary>>;
+ [_] -> <<$/, Path/binary>>
+ end.
+
+ensure_map_uri(URI) when is_map(URI) -> URI;
+ensure_map_uri(URI) -> uri_string:parse(iolist_to_binary(URI)).
+
+-ifdef(TEST).
+resolve_test_() ->
+ Tests = [
+ %% 5.4.1. Normal Examples
+ {<<"g:h">>, <<"g:h">>},
+ {<<"g">>, <<"http://a/b/c/g">>},
+ {<<"./g">>, <<"http://a/b/c/g">>},
+ {<<"g/">>, <<"http://a/b/c/g/">>},
+ {<<"/g">>, <<"http://a/g">>},
+ {<<"//g">>, <<"http://g">>},
+ {<<"?y">>, <<"http://a/b/c/d;p?y">>},
+ {<<"g?y">>, <<"http://a/b/c/g?y">>},
+ {<<"#s">>, <<"http://a/b/c/d;p?q#s">>},
+ {<<"g#s">>, <<"http://a/b/c/g#s">>},
+ {<<"g?y#s">>, <<"http://a/b/c/g?y#s">>},
+ {<<";x">>, <<"http://a/b/c/;x">>},
+ {<<"g;x">>, <<"http://a/b/c/g;x">>},
+ {<<"g;x?y#s">>, <<"http://a/b/c/g;x?y#s">>},
+ {<<"">>, <<"http://a/b/c/d;p?q">>},
+ {<<".">>, <<"http://a/b/c/">>},
+ {<<"./">>, <<"http://a/b/c/">>},
+ {<<"..">>, <<"http://a/b/">>},
+ {<<"../">>, <<"http://a/b/">>},
+ {<<"../g">>, <<"http://a/b/g">>},
+ {<<"../..">>, <<"http://a/">>},
+ {<<"../../">>, <<"http://a/">>},
+ {<<"../../g">>, <<"http://a/g">>},
+ %% 5.4.2. Abnormal Examples
+ {<<"../../../g">>, <<"http://a/g">>},
+ {<<"../../../../g">>, <<"http://a/g">>},
+ {<<"/./g">>, <<"http://a/g">>},
+ {<<"/../g">>, <<"http://a/g">>},
+ {<<"g.">>, <<"http://a/b/c/g.">>},
+ {<<".g">>, <<"http://a/b/c/.g">>},
+ {<<"g..">>, <<"http://a/b/c/g..">>},
+ {<<"..g">>, <<"http://a/b/c/..g">>},
+ {<<"./../g">>, <<"http://a/b/g">>},
+ {<<"./g/.">>, <<"http://a/b/c/g/">>},
+ {<<"g/./h">>, <<"http://a/b/c/g/h">>},
+ {<<"g/../h">>, <<"http://a/b/c/h">>},
+ {<<"g;x=1/./y">>, <<"http://a/b/c/g;x=1/y">>},
+ {<<"g;x=1/../y">>, <<"http://a/b/c/y">>},
+ {<<"g?y/./x">>, <<"http://a/b/c/g?y/./x">>},
+ {<<"g?y/../x">>, <<"http://a/b/c/g?y/../x">>},
+ {<<"g#s/./x">>, <<"http://a/b/c/g#s/./x">>},
+ {<<"g#s/../x">>, <<"http://a/b/c/g#s/../x">>},
+ {<<"http:g">>, <<"http:g">>} %% for strict parsers
+ ],
+ [{V, fun() -> R = uri_string:recompose(resolve(V, <<"http://a/b/c/d;p?q">>)) end} || {V, R} <- Tests].
+-endif.
+
+%% Build a link header.
+
+-spec link([#{
+ target := binary(),
+ rel := binary(),
+ attributes := [{binary(), binary()}]
+}]) -> iodata().
+link(Links) ->
+ lists:join(<<", ">>, [do_link(Link) || Link <- Links]).
+
+do_link(#{target := TargetURI, rel := Rel, attributes := Params}) ->
+ [
+ $<, TargetURI, <<">"
+ "; rel=\"">>, Rel, $",
+ [[<<"; ">>, Key, <<"=\"">>, escape(iolist_to_binary(Value), <<>>), $"]
+ || {Key, Value} <- Params]
+ ].
+
+escape(<<>>, Acc) -> Acc;
+escape(<<$\\,R/bits>>, Acc) -> escape(R, <<Acc/binary,$\\,$\\>>);
+escape(<<$\",R/bits>>, Acc) -> escape(R, <<Acc/binary,$\\,$\">>);
+escape(<<C,R/bits>>, Acc) -> escape(R, <<Acc/binary,C>>).
+
+-ifdef(TEST).
+link_test_() ->
+ Tests = [
+ {<<>>, []},
+ %% Examples from the RFC.
+ {<<"<http://example.com/TheBook/chapter2>; rel=\"previous\"; title=\"previous chapter\"">>, [
+ #{
+ target => <<"http://example.com/TheBook/chapter2">>,
+ rel => <<"previous">>,
+ attributes => [
+ {<<"title">>, <<"previous chapter">>}
+ ]
+ }
+ ]},
+ {<<"</>; rel=\"http://example.net/foo\"">>, [
+ #{
+ target => <<"/">>,
+ rel => <<"http://example.net/foo">>,
+ attributes => []
+ }
+ ]},
+ {<<"</terms>; rel=\"copyright\"; anchor=\"#foo\"">>, [
+ #{
+ target => <<"/terms">>,
+ rel => <<"copyright">>,
+ attributes => [
+ {<<"anchor">>, <<"#foo">>}
+ ]
+ }
+ ]},
+% {<<"</TheBook/chapter2>; rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, "
+% "</TheBook/chapter4>; rel=\"next\"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel">>, [
+% %% @todo
+% ]}
+ {<<"<http://example.org/>; rel=\"start http://example.net/relation/other\"">>, [
+ #{
+ target => <<"http://example.org/">>,
+ rel => <<"start http://example.net/relation/other">>,
+ attributes => []
+ }
+ ]},
+ {<<"<https://example.org/>; rel=\"start\", "
+ "<https://example.org/index>; rel=\"index\"">>, [
+ #{
+ target => <<"https://example.org/">>,
+ rel => <<"start">>,
+ attributes => []
+ },
+ #{
+ target => <<"https://example.org/index">>,
+ rel => <<"index">>,
+ attributes => []
+ }
+ ]},
+ {<<"</>; rel=\"previous\"; quoted=\"name=\\\"value\\\"\"">>, [
+ #{
+ target => <<"/">>,
+ rel => <<"previous">>,
+ attributes => [
+ {<<"quoted">>, <<"name=\"value\"">>}
+ ]
+ }
+ ]}
+ ],
+ [{iolist_to_binary(io_lib:format("~0p", [V])),
+ fun() -> R = iolist_to_binary(link(V)) end} || {R, V} <- Tests].
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_mimetypes.erl b/server/_build/default/lib/cowlib/src/cow_mimetypes.erl
new file mode 100644
index 0000000..756e609
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_mimetypes.erl
@@ -0,0 +1,1045 @@
+%% Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_mimetypes).
+
+-export([all/1]).
+-export([web/1]).
+
+%% @doc Return the mimetype for any file by looking at its extension.
+
+-spec all(binary()) -> {binary(), binary(), []}.
+all(Path) ->
+ case filename:extension(Path) of
+ <<>> -> {<<"application">>, <<"octet-stream">>, []};
+ %% @todo Convert to string:lowercase on OTP-20+.
+ << $., Ext/binary >> -> all_ext(list_to_binary(string:to_lower(binary_to_list(Ext))))
+ end.
+
+%% @doc Return the mimetype for a Web related file by looking at its extension.
+
+-spec web(binary()) -> {binary(), binary(), []}.
+web(Path) ->
+ case filename:extension(Path) of
+ <<>> -> {<<"application">>, <<"octet-stream">>, []};
+ %% @todo Convert to string:lowercase on OTP-20+.
+ << $., Ext/binary >> -> web_ext(list_to_binary(string:to_lower(binary_to_list(Ext))))
+ end.
+
+%% Internal.
+
+%% GENERATED
+all_ext(<<"123">>) -> {<<"application">>, <<"vnd.lotus-1-2-3">>, []};
+all_ext(<<"3dml">>) -> {<<"text">>, <<"vnd.in3d.3dml">>, []};
+all_ext(<<"3ds">>) -> {<<"image">>, <<"x-3ds">>, []};
+all_ext(<<"3g2">>) -> {<<"video">>, <<"3gpp2">>, []};
+all_ext(<<"3gp">>) -> {<<"video">>, <<"3gpp">>, []};
+all_ext(<<"7z">>) -> {<<"application">>, <<"x-7z-compressed">>, []};
+all_ext(<<"aab">>) -> {<<"application">>, <<"x-authorware-bin">>, []};
+all_ext(<<"aac">>) -> {<<"audio">>, <<"x-aac">>, []};
+all_ext(<<"aam">>) -> {<<"application">>, <<"x-authorware-map">>, []};
+all_ext(<<"aas">>) -> {<<"application">>, <<"x-authorware-seg">>, []};
+all_ext(<<"abw">>) -> {<<"application">>, <<"x-abiword">>, []};
+all_ext(<<"ac">>) -> {<<"application">>, <<"pkix-attr-cert">>, []};
+all_ext(<<"acc">>) -> {<<"application">>, <<"vnd.americandynamics.acc">>, []};
+all_ext(<<"ace">>) -> {<<"application">>, <<"x-ace-compressed">>, []};
+all_ext(<<"acu">>) -> {<<"application">>, <<"vnd.acucobol">>, []};
+all_ext(<<"acutc">>) -> {<<"application">>, <<"vnd.acucorp">>, []};
+all_ext(<<"adp">>) -> {<<"audio">>, <<"adpcm">>, []};
+all_ext(<<"aep">>) -> {<<"application">>, <<"vnd.audiograph">>, []};
+all_ext(<<"afm">>) -> {<<"application">>, <<"x-font-type1">>, []};
+all_ext(<<"afp">>) -> {<<"application">>, <<"vnd.ibm.modcap">>, []};
+all_ext(<<"ahead">>) -> {<<"application">>, <<"vnd.ahead.space">>, []};
+all_ext(<<"ai">>) -> {<<"application">>, <<"postscript">>, []};
+all_ext(<<"aif">>) -> {<<"audio">>, <<"x-aiff">>, []};
+all_ext(<<"aifc">>) -> {<<"audio">>, <<"x-aiff">>, []};
+all_ext(<<"aiff">>) -> {<<"audio">>, <<"x-aiff">>, []};
+all_ext(<<"air">>) -> {<<"application">>, <<"vnd.adobe.air-application-installer-package+zip">>, []};
+all_ext(<<"ait">>) -> {<<"application">>, <<"vnd.dvb.ait">>, []};
+all_ext(<<"ami">>) -> {<<"application">>, <<"vnd.amiga.ami">>, []};
+all_ext(<<"apk">>) -> {<<"application">>, <<"vnd.android.package-archive">>, []};
+all_ext(<<"appcache">>) -> {<<"text">>, <<"cache-manifest">>, []};
+all_ext(<<"application">>) -> {<<"application">>, <<"x-ms-application">>, []};
+all_ext(<<"apr">>) -> {<<"application">>, <<"vnd.lotus-approach">>, []};
+all_ext(<<"arc">>) -> {<<"application">>, <<"x-freearc">>, []};
+all_ext(<<"asc">>) -> {<<"application">>, <<"pgp-signature">>, []};
+all_ext(<<"asf">>) -> {<<"video">>, <<"x-ms-asf">>, []};
+all_ext(<<"asm">>) -> {<<"text">>, <<"x-asm">>, []};
+all_ext(<<"aso">>) -> {<<"application">>, <<"vnd.accpac.simply.aso">>, []};
+all_ext(<<"asx">>) -> {<<"video">>, <<"x-ms-asf">>, []};
+all_ext(<<"atc">>) -> {<<"application">>, <<"vnd.acucorp">>, []};
+all_ext(<<"atom">>) -> {<<"application">>, <<"atom+xml">>, []};
+all_ext(<<"atomcat">>) -> {<<"application">>, <<"atomcat+xml">>, []};
+all_ext(<<"atomsvc">>) -> {<<"application">>, <<"atomsvc+xml">>, []};
+all_ext(<<"atx">>) -> {<<"application">>, <<"vnd.antix.game-component">>, []};
+all_ext(<<"au">>) -> {<<"audio">>, <<"basic">>, []};
+all_ext(<<"avi">>) -> {<<"video">>, <<"x-msvideo">>, []};
+all_ext(<<"aw">>) -> {<<"application">>, <<"applixware">>, []};
+all_ext(<<"azf">>) -> {<<"application">>, <<"vnd.airzip.filesecure.azf">>, []};
+all_ext(<<"azs">>) -> {<<"application">>, <<"vnd.airzip.filesecure.azs">>, []};
+all_ext(<<"azw">>) -> {<<"application">>, <<"vnd.amazon.ebook">>, []};
+all_ext(<<"bat">>) -> {<<"application">>, <<"x-msdownload">>, []};
+all_ext(<<"bcpio">>) -> {<<"application">>, <<"x-bcpio">>, []};
+all_ext(<<"bdf">>) -> {<<"application">>, <<"x-font-bdf">>, []};
+all_ext(<<"bdm">>) -> {<<"application">>, <<"vnd.syncml.dm+wbxml">>, []};
+all_ext(<<"bed">>) -> {<<"application">>, <<"vnd.realvnc.bed">>, []};
+all_ext(<<"bh2">>) -> {<<"application">>, <<"vnd.fujitsu.oasysprs">>, []};
+all_ext(<<"bin">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"blb">>) -> {<<"application">>, <<"x-blorb">>, []};
+all_ext(<<"blorb">>) -> {<<"application">>, <<"x-blorb">>, []};
+all_ext(<<"bmi">>) -> {<<"application">>, <<"vnd.bmi">>, []};
+all_ext(<<"bmp">>) -> {<<"image">>, <<"bmp">>, []};
+all_ext(<<"book">>) -> {<<"application">>, <<"vnd.framemaker">>, []};
+all_ext(<<"box">>) -> {<<"application">>, <<"vnd.previewsystems.box">>, []};
+all_ext(<<"boz">>) -> {<<"application">>, <<"x-bzip2">>, []};
+all_ext(<<"bpk">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"btif">>) -> {<<"image">>, <<"prs.btif">>, []};
+all_ext(<<"bz2">>) -> {<<"application">>, <<"x-bzip2">>, []};
+all_ext(<<"bz">>) -> {<<"application">>, <<"x-bzip">>, []};
+all_ext(<<"c11amc">>) -> {<<"application">>, <<"vnd.cluetrust.cartomobile-config">>, []};
+all_ext(<<"c11amz">>) -> {<<"application">>, <<"vnd.cluetrust.cartomobile-config-pkg">>, []};
+all_ext(<<"c4d">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
+all_ext(<<"c4f">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
+all_ext(<<"c4g">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
+all_ext(<<"c4p">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
+all_ext(<<"c4u">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
+all_ext(<<"cab">>) -> {<<"application">>, <<"vnd.ms-cab-compressed">>, []};
+all_ext(<<"caf">>) -> {<<"audio">>, <<"x-caf">>, []};
+all_ext(<<"cap">>) -> {<<"application">>, <<"vnd.tcpdump.pcap">>, []};
+all_ext(<<"car">>) -> {<<"application">>, <<"vnd.curl.car">>, []};
+all_ext(<<"cat">>) -> {<<"application">>, <<"vnd.ms-pki.seccat">>, []};
+all_ext(<<"cb7">>) -> {<<"application">>, <<"x-cbr">>, []};
+all_ext(<<"cba">>) -> {<<"application">>, <<"x-cbr">>, []};
+all_ext(<<"cbr">>) -> {<<"application">>, <<"x-cbr">>, []};
+all_ext(<<"cbt">>) -> {<<"application">>, <<"x-cbr">>, []};
+all_ext(<<"cbz">>) -> {<<"application">>, <<"x-cbr">>, []};
+all_ext(<<"cct">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"cc">>) -> {<<"text">>, <<"x-c">>, []};
+all_ext(<<"ccxml">>) -> {<<"application">>, <<"ccxml+xml">>, []};
+all_ext(<<"cdbcmsg">>) -> {<<"application">>, <<"vnd.contact.cmsg">>, []};
+all_ext(<<"cdf">>) -> {<<"application">>, <<"x-netcdf">>, []};
+all_ext(<<"cdkey">>) -> {<<"application">>, <<"vnd.mediastation.cdkey">>, []};
+all_ext(<<"cdmia">>) -> {<<"application">>, <<"cdmi-capability">>, []};
+all_ext(<<"cdmic">>) -> {<<"application">>, <<"cdmi-container">>, []};
+all_ext(<<"cdmid">>) -> {<<"application">>, <<"cdmi-domain">>, []};
+all_ext(<<"cdmio">>) -> {<<"application">>, <<"cdmi-object">>, []};
+all_ext(<<"cdmiq">>) -> {<<"application">>, <<"cdmi-queue">>, []};
+all_ext(<<"cdx">>) -> {<<"chemical">>, <<"x-cdx">>, []};
+all_ext(<<"cdxml">>) -> {<<"application">>, <<"vnd.chemdraw+xml">>, []};
+all_ext(<<"cdy">>) -> {<<"application">>, <<"vnd.cinderella">>, []};
+all_ext(<<"cer">>) -> {<<"application">>, <<"pkix-cert">>, []};
+all_ext(<<"cfs">>) -> {<<"application">>, <<"x-cfs-compressed">>, []};
+all_ext(<<"cgm">>) -> {<<"image">>, <<"cgm">>, []};
+all_ext(<<"chat">>) -> {<<"application">>, <<"x-chat">>, []};
+all_ext(<<"chm">>) -> {<<"application">>, <<"vnd.ms-htmlhelp">>, []};
+all_ext(<<"chrt">>) -> {<<"application">>, <<"vnd.kde.kchart">>, []};
+all_ext(<<"cif">>) -> {<<"chemical">>, <<"x-cif">>, []};
+all_ext(<<"cii">>) -> {<<"application">>, <<"vnd.anser-web-certificate-issue-initiation">>, []};
+all_ext(<<"cil">>) -> {<<"application">>, <<"vnd.ms-artgalry">>, []};
+all_ext(<<"cla">>) -> {<<"application">>, <<"vnd.claymore">>, []};
+all_ext(<<"class">>) -> {<<"application">>, <<"java-vm">>, []};
+all_ext(<<"clkk">>) -> {<<"application">>, <<"vnd.crick.clicker.keyboard">>, []};
+all_ext(<<"clkp">>) -> {<<"application">>, <<"vnd.crick.clicker.palette">>, []};
+all_ext(<<"clkt">>) -> {<<"application">>, <<"vnd.crick.clicker.template">>, []};
+all_ext(<<"clkw">>) -> {<<"application">>, <<"vnd.crick.clicker.wordbank">>, []};
+all_ext(<<"clkx">>) -> {<<"application">>, <<"vnd.crick.clicker">>, []};
+all_ext(<<"clp">>) -> {<<"application">>, <<"x-msclip">>, []};
+all_ext(<<"cmc">>) -> {<<"application">>, <<"vnd.cosmocaller">>, []};
+all_ext(<<"cmdf">>) -> {<<"chemical">>, <<"x-cmdf">>, []};
+all_ext(<<"cml">>) -> {<<"chemical">>, <<"x-cml">>, []};
+all_ext(<<"cmp">>) -> {<<"application">>, <<"vnd.yellowriver-custom-menu">>, []};
+all_ext(<<"cmx">>) -> {<<"image">>, <<"x-cmx">>, []};
+all_ext(<<"cod">>) -> {<<"application">>, <<"vnd.rim.cod">>, []};
+all_ext(<<"com">>) -> {<<"application">>, <<"x-msdownload">>, []};
+all_ext(<<"conf">>) -> {<<"text">>, <<"plain">>, []};
+all_ext(<<"cpio">>) -> {<<"application">>, <<"x-cpio">>, []};
+all_ext(<<"cpp">>) -> {<<"text">>, <<"x-c">>, []};
+all_ext(<<"cpt">>) -> {<<"application">>, <<"mac-compactpro">>, []};
+all_ext(<<"crd">>) -> {<<"application">>, <<"x-mscardfile">>, []};
+all_ext(<<"crl">>) -> {<<"application">>, <<"pkix-crl">>, []};
+all_ext(<<"crt">>) -> {<<"application">>, <<"x-x509-ca-cert">>, []};
+all_ext(<<"cryptonote">>) -> {<<"application">>, <<"vnd.rig.cryptonote">>, []};
+all_ext(<<"csh">>) -> {<<"application">>, <<"x-csh">>, []};
+all_ext(<<"csml">>) -> {<<"chemical">>, <<"x-csml">>, []};
+all_ext(<<"csp">>) -> {<<"application">>, <<"vnd.commonspace">>, []};
+all_ext(<<"css">>) -> {<<"text">>, <<"css">>, []};
+all_ext(<<"cst">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"csv">>) -> {<<"text">>, <<"csv">>, []};
+all_ext(<<"c">>) -> {<<"text">>, <<"x-c">>, []};
+all_ext(<<"cu">>) -> {<<"application">>, <<"cu-seeme">>, []};
+all_ext(<<"curl">>) -> {<<"text">>, <<"vnd.curl">>, []};
+all_ext(<<"cww">>) -> {<<"application">>, <<"prs.cww">>, []};
+all_ext(<<"cxt">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"cxx">>) -> {<<"text">>, <<"x-c">>, []};
+all_ext(<<"dae">>) -> {<<"model">>, <<"vnd.collada+xml">>, []};
+all_ext(<<"daf">>) -> {<<"application">>, <<"vnd.mobius.daf">>, []};
+all_ext(<<"dart">>) -> {<<"application">>, <<"vnd.dart">>, []};
+all_ext(<<"dataless">>) -> {<<"application">>, <<"vnd.fdsn.seed">>, []};
+all_ext(<<"davmount">>) -> {<<"application">>, <<"davmount+xml">>, []};
+all_ext(<<"dbk">>) -> {<<"application">>, <<"docbook+xml">>, []};
+all_ext(<<"dcr">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"dcurl">>) -> {<<"text">>, <<"vnd.curl.dcurl">>, []};
+all_ext(<<"dd2">>) -> {<<"application">>, <<"vnd.oma.dd2+xml">>, []};
+all_ext(<<"ddd">>) -> {<<"application">>, <<"vnd.fujixerox.ddd">>, []};
+all_ext(<<"deb">>) -> {<<"application">>, <<"x-debian-package">>, []};
+all_ext(<<"def">>) -> {<<"text">>, <<"plain">>, []};
+all_ext(<<"deploy">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"der">>) -> {<<"application">>, <<"x-x509-ca-cert">>, []};
+all_ext(<<"dfac">>) -> {<<"application">>, <<"vnd.dreamfactory">>, []};
+all_ext(<<"dgc">>) -> {<<"application">>, <<"x-dgc-compressed">>, []};
+all_ext(<<"dic">>) -> {<<"text">>, <<"x-c">>, []};
+all_ext(<<"dir">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"dis">>) -> {<<"application">>, <<"vnd.mobius.dis">>, []};
+all_ext(<<"dist">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"distz">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"djv">>) -> {<<"image">>, <<"vnd.djvu">>, []};
+all_ext(<<"djvu">>) -> {<<"image">>, <<"vnd.djvu">>, []};
+all_ext(<<"dll">>) -> {<<"application">>, <<"x-msdownload">>, []};
+all_ext(<<"dmg">>) -> {<<"application">>, <<"x-apple-diskimage">>, []};
+all_ext(<<"dmp">>) -> {<<"application">>, <<"vnd.tcpdump.pcap">>, []};
+all_ext(<<"dms">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"dna">>) -> {<<"application">>, <<"vnd.dna">>, []};
+all_ext(<<"doc">>) -> {<<"application">>, <<"msword">>, []};
+all_ext(<<"docm">>) -> {<<"application">>, <<"vnd.ms-word.document.macroenabled.12">>, []};
+all_ext(<<"docx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.wordprocessingml.document">>, []};
+all_ext(<<"dot">>) -> {<<"application">>, <<"msword">>, []};
+all_ext(<<"dotm">>) -> {<<"application">>, <<"vnd.ms-word.template.macroenabled.12">>, []};
+all_ext(<<"dotx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.wordprocessingml.template">>, []};
+all_ext(<<"dp">>) -> {<<"application">>, <<"vnd.osgi.dp">>, []};
+all_ext(<<"dpg">>) -> {<<"application">>, <<"vnd.dpgraph">>, []};
+all_ext(<<"dra">>) -> {<<"audio">>, <<"vnd.dra">>, []};
+all_ext(<<"dsc">>) -> {<<"text">>, <<"prs.lines.tag">>, []};
+all_ext(<<"dssc">>) -> {<<"application">>, <<"dssc+der">>, []};
+all_ext(<<"dtb">>) -> {<<"application">>, <<"x-dtbook+xml">>, []};
+all_ext(<<"dtd">>) -> {<<"application">>, <<"xml-dtd">>, []};
+all_ext(<<"dts">>) -> {<<"audio">>, <<"vnd.dts">>, []};
+all_ext(<<"dtshd">>) -> {<<"audio">>, <<"vnd.dts.hd">>, []};
+all_ext(<<"dump">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"dvb">>) -> {<<"video">>, <<"vnd.dvb.file">>, []};
+all_ext(<<"dvi">>) -> {<<"application">>, <<"x-dvi">>, []};
+all_ext(<<"dwf">>) -> {<<"model">>, <<"vnd.dwf">>, []};
+all_ext(<<"dwg">>) -> {<<"image">>, <<"vnd.dwg">>, []};
+all_ext(<<"dxf">>) -> {<<"image">>, <<"vnd.dxf">>, []};
+all_ext(<<"dxp">>) -> {<<"application">>, <<"vnd.spotfire.dxp">>, []};
+all_ext(<<"dxr">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"ecelp4800">>) -> {<<"audio">>, <<"vnd.nuera.ecelp4800">>, []};
+all_ext(<<"ecelp7470">>) -> {<<"audio">>, <<"vnd.nuera.ecelp7470">>, []};
+all_ext(<<"ecelp9600">>) -> {<<"audio">>, <<"vnd.nuera.ecelp9600">>, []};
+all_ext(<<"ecma">>) -> {<<"application">>, <<"ecmascript">>, []};
+all_ext(<<"edm">>) -> {<<"application">>, <<"vnd.novadigm.edm">>, []};
+all_ext(<<"edx">>) -> {<<"application">>, <<"vnd.novadigm.edx">>, []};
+all_ext(<<"efif">>) -> {<<"application">>, <<"vnd.picsel">>, []};
+all_ext(<<"ei6">>) -> {<<"application">>, <<"vnd.pg.osasli">>, []};
+all_ext(<<"elc">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"emf">>) -> {<<"application">>, <<"x-msmetafile">>, []};
+all_ext(<<"eml">>) -> {<<"message">>, <<"rfc822">>, []};
+all_ext(<<"emma">>) -> {<<"application">>, <<"emma+xml">>, []};
+all_ext(<<"emz">>) -> {<<"application">>, <<"x-msmetafile">>, []};
+all_ext(<<"eol">>) -> {<<"audio">>, <<"vnd.digital-winds">>, []};
+all_ext(<<"eot">>) -> {<<"application">>, <<"vnd.ms-fontobject">>, []};
+all_ext(<<"eps">>) -> {<<"application">>, <<"postscript">>, []};
+all_ext(<<"epub">>) -> {<<"application">>, <<"epub+zip">>, []};
+all_ext(<<"es3">>) -> {<<"application">>, <<"vnd.eszigno3+xml">>, []};
+all_ext(<<"esa">>) -> {<<"application">>, <<"vnd.osgi.subsystem">>, []};
+all_ext(<<"esf">>) -> {<<"application">>, <<"vnd.epson.esf">>, []};
+all_ext(<<"et3">>) -> {<<"application">>, <<"vnd.eszigno3+xml">>, []};
+all_ext(<<"etx">>) -> {<<"text">>, <<"x-setext">>, []};
+all_ext(<<"eva">>) -> {<<"application">>, <<"x-eva">>, []};
+all_ext(<<"evy">>) -> {<<"application">>, <<"x-envoy">>, []};
+all_ext(<<"exe">>) -> {<<"application">>, <<"x-msdownload">>, []};
+all_ext(<<"exi">>) -> {<<"application">>, <<"exi">>, []};
+all_ext(<<"ext">>) -> {<<"application">>, <<"vnd.novadigm.ext">>, []};
+all_ext(<<"ez2">>) -> {<<"application">>, <<"vnd.ezpix-album">>, []};
+all_ext(<<"ez3">>) -> {<<"application">>, <<"vnd.ezpix-package">>, []};
+all_ext(<<"ez">>) -> {<<"application">>, <<"andrew-inset">>, []};
+all_ext(<<"f4v">>) -> {<<"video">>, <<"x-f4v">>, []};
+all_ext(<<"f77">>) -> {<<"text">>, <<"x-fortran">>, []};
+all_ext(<<"f90">>) -> {<<"text">>, <<"x-fortran">>, []};
+all_ext(<<"fbs">>) -> {<<"image">>, <<"vnd.fastbidsheet">>, []};
+all_ext(<<"fcdt">>) -> {<<"application">>, <<"vnd.adobe.formscentral.fcdt">>, []};
+all_ext(<<"fcs">>) -> {<<"application">>, <<"vnd.isac.fcs">>, []};
+all_ext(<<"fdf">>) -> {<<"application">>, <<"vnd.fdf">>, []};
+all_ext(<<"fe_launch">>) -> {<<"application">>, <<"vnd.denovo.fcselayout-link">>, []};
+all_ext(<<"fg5">>) -> {<<"application">>, <<"vnd.fujitsu.oasysgp">>, []};
+all_ext(<<"fgd">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"fh4">>) -> {<<"image">>, <<"x-freehand">>, []};
+all_ext(<<"fh5">>) -> {<<"image">>, <<"x-freehand">>, []};
+all_ext(<<"fh7">>) -> {<<"image">>, <<"x-freehand">>, []};
+all_ext(<<"fhc">>) -> {<<"image">>, <<"x-freehand">>, []};
+all_ext(<<"fh">>) -> {<<"image">>, <<"x-freehand">>, []};
+all_ext(<<"fig">>) -> {<<"application">>, <<"x-xfig">>, []};
+all_ext(<<"flac">>) -> {<<"audio">>, <<"x-flac">>, []};
+all_ext(<<"fli">>) -> {<<"video">>, <<"x-fli">>, []};
+all_ext(<<"flo">>) -> {<<"application">>, <<"vnd.micrografx.flo">>, []};
+all_ext(<<"flv">>) -> {<<"video">>, <<"x-flv">>, []};
+all_ext(<<"flw">>) -> {<<"application">>, <<"vnd.kde.kivio">>, []};
+all_ext(<<"flx">>) -> {<<"text">>, <<"vnd.fmi.flexstor">>, []};
+all_ext(<<"fly">>) -> {<<"text">>, <<"vnd.fly">>, []};
+all_ext(<<"fm">>) -> {<<"application">>, <<"vnd.framemaker">>, []};
+all_ext(<<"fnc">>) -> {<<"application">>, <<"vnd.frogans.fnc">>, []};
+all_ext(<<"for">>) -> {<<"text">>, <<"x-fortran">>, []};
+all_ext(<<"fpx">>) -> {<<"image">>, <<"vnd.fpx">>, []};
+all_ext(<<"frame">>) -> {<<"application">>, <<"vnd.framemaker">>, []};
+all_ext(<<"fsc">>) -> {<<"application">>, <<"vnd.fsc.weblaunch">>, []};
+all_ext(<<"fst">>) -> {<<"image">>, <<"vnd.fst">>, []};
+all_ext(<<"ftc">>) -> {<<"application">>, <<"vnd.fluxtime.clip">>, []};
+all_ext(<<"f">>) -> {<<"text">>, <<"x-fortran">>, []};
+all_ext(<<"fti">>) -> {<<"application">>, <<"vnd.anser-web-funds-transfer-initiation">>, []};
+all_ext(<<"fvt">>) -> {<<"video">>, <<"vnd.fvt">>, []};
+all_ext(<<"fxp">>) -> {<<"application">>, <<"vnd.adobe.fxp">>, []};
+all_ext(<<"fxpl">>) -> {<<"application">>, <<"vnd.adobe.fxp">>, []};
+all_ext(<<"fzs">>) -> {<<"application">>, <<"vnd.fuzzysheet">>, []};
+all_ext(<<"g2w">>) -> {<<"application">>, <<"vnd.geoplan">>, []};
+all_ext(<<"g3">>) -> {<<"image">>, <<"g3fax">>, []};
+all_ext(<<"g3w">>) -> {<<"application">>, <<"vnd.geospace">>, []};
+all_ext(<<"gac">>) -> {<<"application">>, <<"vnd.groove-account">>, []};
+all_ext(<<"gam">>) -> {<<"application">>, <<"x-tads">>, []};
+all_ext(<<"gbr">>) -> {<<"application">>, <<"rpki-ghostbusters">>, []};
+all_ext(<<"gca">>) -> {<<"application">>, <<"x-gca-compressed">>, []};
+all_ext(<<"gdl">>) -> {<<"model">>, <<"vnd.gdl">>, []};
+all_ext(<<"geo">>) -> {<<"application">>, <<"vnd.dynageo">>, []};
+all_ext(<<"gex">>) -> {<<"application">>, <<"vnd.geometry-explorer">>, []};
+all_ext(<<"ggb">>) -> {<<"application">>, <<"vnd.geogebra.file">>, []};
+all_ext(<<"ggt">>) -> {<<"application">>, <<"vnd.geogebra.tool">>, []};
+all_ext(<<"ghf">>) -> {<<"application">>, <<"vnd.groove-help">>, []};
+all_ext(<<"gif">>) -> {<<"image">>, <<"gif">>, []};
+all_ext(<<"gim">>) -> {<<"application">>, <<"vnd.groove-identity-message">>, []};
+all_ext(<<"gml">>) -> {<<"application">>, <<"gml+xml">>, []};
+all_ext(<<"gmx">>) -> {<<"application">>, <<"vnd.gmx">>, []};
+all_ext(<<"gnumeric">>) -> {<<"application">>, <<"x-gnumeric">>, []};
+all_ext(<<"gph">>) -> {<<"application">>, <<"vnd.flographit">>, []};
+all_ext(<<"gpx">>) -> {<<"application">>, <<"gpx+xml">>, []};
+all_ext(<<"gqf">>) -> {<<"application">>, <<"vnd.grafeq">>, []};
+all_ext(<<"gqs">>) -> {<<"application">>, <<"vnd.grafeq">>, []};
+all_ext(<<"gram">>) -> {<<"application">>, <<"srgs">>, []};
+all_ext(<<"gramps">>) -> {<<"application">>, <<"x-gramps-xml">>, []};
+all_ext(<<"gre">>) -> {<<"application">>, <<"vnd.geometry-explorer">>, []};
+all_ext(<<"grv">>) -> {<<"application">>, <<"vnd.groove-injector">>, []};
+all_ext(<<"grxml">>) -> {<<"application">>, <<"srgs+xml">>, []};
+all_ext(<<"gsf">>) -> {<<"application">>, <<"x-font-ghostscript">>, []};
+all_ext(<<"gtar">>) -> {<<"application">>, <<"x-gtar">>, []};
+all_ext(<<"gtm">>) -> {<<"application">>, <<"vnd.groove-tool-message">>, []};
+all_ext(<<"gtw">>) -> {<<"model">>, <<"vnd.gtw">>, []};
+all_ext(<<"gv">>) -> {<<"text">>, <<"vnd.graphviz">>, []};
+all_ext(<<"gxf">>) -> {<<"application">>, <<"gxf">>, []};
+all_ext(<<"gxt">>) -> {<<"application">>, <<"vnd.geonext">>, []};
+all_ext(<<"h261">>) -> {<<"video">>, <<"h261">>, []};
+all_ext(<<"h263">>) -> {<<"video">>, <<"h263">>, []};
+all_ext(<<"h264">>) -> {<<"video">>, <<"h264">>, []};
+all_ext(<<"hal">>) -> {<<"application">>, <<"vnd.hal+xml">>, []};
+all_ext(<<"hbci">>) -> {<<"application">>, <<"vnd.hbci">>, []};
+all_ext(<<"hdf">>) -> {<<"application">>, <<"x-hdf">>, []};
+all_ext(<<"hh">>) -> {<<"text">>, <<"x-c">>, []};
+all_ext(<<"hlp">>) -> {<<"application">>, <<"winhlp">>, []};
+all_ext(<<"hpgl">>) -> {<<"application">>, <<"vnd.hp-hpgl">>, []};
+all_ext(<<"hpid">>) -> {<<"application">>, <<"vnd.hp-hpid">>, []};
+all_ext(<<"hps">>) -> {<<"application">>, <<"vnd.hp-hps">>, []};
+all_ext(<<"hqx">>) -> {<<"application">>, <<"mac-binhex40">>, []};
+all_ext(<<"h">>) -> {<<"text">>, <<"x-c">>, []};
+all_ext(<<"htke">>) -> {<<"application">>, <<"vnd.kenameaapp">>, []};
+all_ext(<<"html">>) -> {<<"text">>, <<"html">>, []};
+all_ext(<<"htm">>) -> {<<"text">>, <<"html">>, []};
+all_ext(<<"hvd">>) -> {<<"application">>, <<"vnd.yamaha.hv-dic">>, []};
+all_ext(<<"hvp">>) -> {<<"application">>, <<"vnd.yamaha.hv-voice">>, []};
+all_ext(<<"hvs">>) -> {<<"application">>, <<"vnd.yamaha.hv-script">>, []};
+all_ext(<<"i2g">>) -> {<<"application">>, <<"vnd.intergeo">>, []};
+all_ext(<<"icc">>) -> {<<"application">>, <<"vnd.iccprofile">>, []};
+all_ext(<<"ice">>) -> {<<"x-conference">>, <<"x-cooltalk">>, []};
+all_ext(<<"icm">>) -> {<<"application">>, <<"vnd.iccprofile">>, []};
+all_ext(<<"ico">>) -> {<<"image">>, <<"x-icon">>, []};
+all_ext(<<"ics">>) -> {<<"text">>, <<"calendar">>, []};
+all_ext(<<"ief">>) -> {<<"image">>, <<"ief">>, []};
+all_ext(<<"ifb">>) -> {<<"text">>, <<"calendar">>, []};
+all_ext(<<"ifm">>) -> {<<"application">>, <<"vnd.shana.informed.formdata">>, []};
+all_ext(<<"iges">>) -> {<<"model">>, <<"iges">>, []};
+all_ext(<<"igl">>) -> {<<"application">>, <<"vnd.igloader">>, []};
+all_ext(<<"igm">>) -> {<<"application">>, <<"vnd.insors.igm">>, []};
+all_ext(<<"igs">>) -> {<<"model">>, <<"iges">>, []};
+all_ext(<<"igx">>) -> {<<"application">>, <<"vnd.micrografx.igx">>, []};
+all_ext(<<"iif">>) -> {<<"application">>, <<"vnd.shana.informed.interchange">>, []};
+all_ext(<<"imp">>) -> {<<"application">>, <<"vnd.accpac.simply.imp">>, []};
+all_ext(<<"ims">>) -> {<<"application">>, <<"vnd.ms-ims">>, []};
+all_ext(<<"ink">>) -> {<<"application">>, <<"inkml+xml">>, []};
+all_ext(<<"inkml">>) -> {<<"application">>, <<"inkml+xml">>, []};
+all_ext(<<"install">>) -> {<<"application">>, <<"x-install-instructions">>, []};
+all_ext(<<"in">>) -> {<<"text">>, <<"plain">>, []};
+all_ext(<<"iota">>) -> {<<"application">>, <<"vnd.astraea-software.iota">>, []};
+all_ext(<<"ipfix">>) -> {<<"application">>, <<"ipfix">>, []};
+all_ext(<<"ipk">>) -> {<<"application">>, <<"vnd.shana.informed.package">>, []};
+all_ext(<<"irm">>) -> {<<"application">>, <<"vnd.ibm.rights-management">>, []};
+all_ext(<<"irp">>) -> {<<"application">>, <<"vnd.irepository.package+xml">>, []};
+all_ext(<<"iso">>) -> {<<"application">>, <<"x-iso9660-image">>, []};
+all_ext(<<"itp">>) -> {<<"application">>, <<"vnd.shana.informed.formtemplate">>, []};
+all_ext(<<"ivp">>) -> {<<"application">>, <<"vnd.immervision-ivp">>, []};
+all_ext(<<"ivu">>) -> {<<"application">>, <<"vnd.immervision-ivu">>, []};
+all_ext(<<"jad">>) -> {<<"text">>, <<"vnd.sun.j2me.app-descriptor">>, []};
+all_ext(<<"jam">>) -> {<<"application">>, <<"vnd.jam">>, []};
+all_ext(<<"jar">>) -> {<<"application">>, <<"java-archive">>, []};
+all_ext(<<"java">>) -> {<<"text">>, <<"x-java-source">>, []};
+all_ext(<<"jisp">>) -> {<<"application">>, <<"vnd.jisp">>, []};
+all_ext(<<"jlt">>) -> {<<"application">>, <<"vnd.hp-jlyt">>, []};
+all_ext(<<"jnlp">>) -> {<<"application">>, <<"x-java-jnlp-file">>, []};
+all_ext(<<"joda">>) -> {<<"application">>, <<"vnd.joost.joda-archive">>, []};
+all_ext(<<"jpeg">>) -> {<<"image">>, <<"jpeg">>, []};
+all_ext(<<"jpe">>) -> {<<"image">>, <<"jpeg">>, []};
+all_ext(<<"jpg">>) -> {<<"image">>, <<"jpeg">>, []};
+all_ext(<<"jpgm">>) -> {<<"video">>, <<"jpm">>, []};
+all_ext(<<"jpgv">>) -> {<<"video">>, <<"jpeg">>, []};
+all_ext(<<"jpm">>) -> {<<"video">>, <<"jpm">>, []};
+all_ext(<<"js">>) -> {<<"application">>, <<"javascript">>, []};
+all_ext(<<"json">>) -> {<<"application">>, <<"json">>, []};
+all_ext(<<"jsonml">>) -> {<<"application">>, <<"jsonml+json">>, []};
+all_ext(<<"kar">>) -> {<<"audio">>, <<"midi">>, []};
+all_ext(<<"karbon">>) -> {<<"application">>, <<"vnd.kde.karbon">>, []};
+all_ext(<<"kfo">>) -> {<<"application">>, <<"vnd.kde.kformula">>, []};
+all_ext(<<"kia">>) -> {<<"application">>, <<"vnd.kidspiration">>, []};
+all_ext(<<"kml">>) -> {<<"application">>, <<"vnd.google-earth.kml+xml">>, []};
+all_ext(<<"kmz">>) -> {<<"application">>, <<"vnd.google-earth.kmz">>, []};
+all_ext(<<"kne">>) -> {<<"application">>, <<"vnd.kinar">>, []};
+all_ext(<<"knp">>) -> {<<"application">>, <<"vnd.kinar">>, []};
+all_ext(<<"kon">>) -> {<<"application">>, <<"vnd.kde.kontour">>, []};
+all_ext(<<"kpr">>) -> {<<"application">>, <<"vnd.kde.kpresenter">>, []};
+all_ext(<<"kpt">>) -> {<<"application">>, <<"vnd.kde.kpresenter">>, []};
+all_ext(<<"kpxx">>) -> {<<"application">>, <<"vnd.ds-keypoint">>, []};
+all_ext(<<"ksp">>) -> {<<"application">>, <<"vnd.kde.kspread">>, []};
+all_ext(<<"ktr">>) -> {<<"application">>, <<"vnd.kahootz">>, []};
+all_ext(<<"ktx">>) -> {<<"image">>, <<"ktx">>, []};
+all_ext(<<"ktz">>) -> {<<"application">>, <<"vnd.kahootz">>, []};
+all_ext(<<"kwd">>) -> {<<"application">>, <<"vnd.kde.kword">>, []};
+all_ext(<<"kwt">>) -> {<<"application">>, <<"vnd.kde.kword">>, []};
+all_ext(<<"lasxml">>) -> {<<"application">>, <<"vnd.las.las+xml">>, []};
+all_ext(<<"latex">>) -> {<<"application">>, <<"x-latex">>, []};
+all_ext(<<"lbd">>) -> {<<"application">>, <<"vnd.llamagraphics.life-balance.desktop">>, []};
+all_ext(<<"lbe">>) -> {<<"application">>, <<"vnd.llamagraphics.life-balance.exchange+xml">>, []};
+all_ext(<<"les">>) -> {<<"application">>, <<"vnd.hhe.lesson-player">>, []};
+all_ext(<<"lha">>) -> {<<"application">>, <<"x-lzh-compressed">>, []};
+all_ext(<<"link66">>) -> {<<"application">>, <<"vnd.route66.link66+xml">>, []};
+all_ext(<<"list3820">>) -> {<<"application">>, <<"vnd.ibm.modcap">>, []};
+all_ext(<<"listafp">>) -> {<<"application">>, <<"vnd.ibm.modcap">>, []};
+all_ext(<<"list">>) -> {<<"text">>, <<"plain">>, []};
+all_ext(<<"lnk">>) -> {<<"application">>, <<"x-ms-shortcut">>, []};
+all_ext(<<"log">>) -> {<<"text">>, <<"plain">>, []};
+all_ext(<<"lostxml">>) -> {<<"application">>, <<"lost+xml">>, []};
+all_ext(<<"lrf">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"lrm">>) -> {<<"application">>, <<"vnd.ms-lrm">>, []};
+all_ext(<<"ltf">>) -> {<<"application">>, <<"vnd.frogans.ltf">>, []};
+all_ext(<<"lvp">>) -> {<<"audio">>, <<"vnd.lucent.voice">>, []};
+all_ext(<<"lwp">>) -> {<<"application">>, <<"vnd.lotus-wordpro">>, []};
+all_ext(<<"lzh">>) -> {<<"application">>, <<"x-lzh-compressed">>, []};
+all_ext(<<"m13">>) -> {<<"application">>, <<"x-msmediaview">>, []};
+all_ext(<<"m14">>) -> {<<"application">>, <<"x-msmediaview">>, []};
+all_ext(<<"m1v">>) -> {<<"video">>, <<"mpeg">>, []};
+all_ext(<<"m21">>) -> {<<"application">>, <<"mp21">>, []};
+all_ext(<<"m2a">>) -> {<<"audio">>, <<"mpeg">>, []};
+all_ext(<<"m2v">>) -> {<<"video">>, <<"mpeg">>, []};
+all_ext(<<"m3a">>) -> {<<"audio">>, <<"mpeg">>, []};
+all_ext(<<"m3u8">>) -> {<<"application">>, <<"vnd.apple.mpegurl">>, []};
+all_ext(<<"m3u">>) -> {<<"audio">>, <<"x-mpegurl">>, []};
+all_ext(<<"m4a">>) -> {<<"audio">>, <<"mp4">>, []};
+all_ext(<<"m4u">>) -> {<<"video">>, <<"vnd.mpegurl">>, []};
+all_ext(<<"m4v">>) -> {<<"video">>, <<"x-m4v">>, []};
+all_ext(<<"ma">>) -> {<<"application">>, <<"mathematica">>, []};
+all_ext(<<"mads">>) -> {<<"application">>, <<"mads+xml">>, []};
+all_ext(<<"mag">>) -> {<<"application">>, <<"vnd.ecowin.chart">>, []};
+all_ext(<<"maker">>) -> {<<"application">>, <<"vnd.framemaker">>, []};
+all_ext(<<"man">>) -> {<<"text">>, <<"troff">>, []};
+all_ext(<<"mar">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"mathml">>) -> {<<"application">>, <<"mathml+xml">>, []};
+all_ext(<<"mb">>) -> {<<"application">>, <<"mathematica">>, []};
+all_ext(<<"mbk">>) -> {<<"application">>, <<"vnd.mobius.mbk">>, []};
+all_ext(<<"mbox">>) -> {<<"application">>, <<"mbox">>, []};
+all_ext(<<"mc1">>) -> {<<"application">>, <<"vnd.medcalcdata">>, []};
+all_ext(<<"mcd">>) -> {<<"application">>, <<"vnd.mcd">>, []};
+all_ext(<<"mcurl">>) -> {<<"text">>, <<"vnd.curl.mcurl">>, []};
+all_ext(<<"mdb">>) -> {<<"application">>, <<"x-msaccess">>, []};
+all_ext(<<"mdi">>) -> {<<"image">>, <<"vnd.ms-modi">>, []};
+all_ext(<<"mesh">>) -> {<<"model">>, <<"mesh">>, []};
+all_ext(<<"meta4">>) -> {<<"application">>, <<"metalink4+xml">>, []};
+all_ext(<<"metalink">>) -> {<<"application">>, <<"metalink+xml">>, []};
+all_ext(<<"me">>) -> {<<"text">>, <<"troff">>, []};
+all_ext(<<"mets">>) -> {<<"application">>, <<"mets+xml">>, []};
+all_ext(<<"mfm">>) -> {<<"application">>, <<"vnd.mfmp">>, []};
+all_ext(<<"mft">>) -> {<<"application">>, <<"rpki-manifest">>, []};
+all_ext(<<"mgp">>) -> {<<"application">>, <<"vnd.osgeo.mapguide.package">>, []};
+all_ext(<<"mgz">>) -> {<<"application">>, <<"vnd.proteus.magazine">>, []};
+all_ext(<<"mid">>) -> {<<"audio">>, <<"midi">>, []};
+all_ext(<<"midi">>) -> {<<"audio">>, <<"midi">>, []};
+all_ext(<<"mie">>) -> {<<"application">>, <<"x-mie">>, []};
+all_ext(<<"mif">>) -> {<<"application">>, <<"vnd.mif">>, []};
+all_ext(<<"mime">>) -> {<<"message">>, <<"rfc822">>, []};
+all_ext(<<"mj2">>) -> {<<"video">>, <<"mj2">>, []};
+all_ext(<<"mjp2">>) -> {<<"video">>, <<"mj2">>, []};
+all_ext(<<"mk3d">>) -> {<<"video">>, <<"x-matroska">>, []};
+all_ext(<<"mka">>) -> {<<"audio">>, <<"x-matroska">>, []};
+all_ext(<<"mks">>) -> {<<"video">>, <<"x-matroska">>, []};
+all_ext(<<"mkv">>) -> {<<"video">>, <<"x-matroska">>, []};
+all_ext(<<"mlp">>) -> {<<"application">>, <<"vnd.dolby.mlp">>, []};
+all_ext(<<"mmd">>) -> {<<"application">>, <<"vnd.chipnuts.karaoke-mmd">>, []};
+all_ext(<<"mmf">>) -> {<<"application">>, <<"vnd.smaf">>, []};
+all_ext(<<"mmr">>) -> {<<"image">>, <<"vnd.fujixerox.edmics-mmr">>, []};
+all_ext(<<"mng">>) -> {<<"video">>, <<"x-mng">>, []};
+all_ext(<<"mny">>) -> {<<"application">>, <<"x-msmoney">>, []};
+all_ext(<<"mobi">>) -> {<<"application">>, <<"x-mobipocket-ebook">>, []};
+all_ext(<<"mods">>) -> {<<"application">>, <<"mods+xml">>, []};
+all_ext(<<"movie">>) -> {<<"video">>, <<"x-sgi-movie">>, []};
+all_ext(<<"mov">>) -> {<<"video">>, <<"quicktime">>, []};
+all_ext(<<"mp21">>) -> {<<"application">>, <<"mp21">>, []};
+all_ext(<<"mp2a">>) -> {<<"audio">>, <<"mpeg">>, []};
+all_ext(<<"mp2">>) -> {<<"audio">>, <<"mpeg">>, []};
+all_ext(<<"mp3">>) -> {<<"audio">>, <<"mpeg">>, []};
+all_ext(<<"mp4a">>) -> {<<"audio">>, <<"mp4">>, []};
+all_ext(<<"mp4s">>) -> {<<"application">>, <<"mp4">>, []};
+all_ext(<<"mp4">>) -> {<<"video">>, <<"mp4">>, []};
+all_ext(<<"mp4v">>) -> {<<"video">>, <<"mp4">>, []};
+all_ext(<<"mpc">>) -> {<<"application">>, <<"vnd.mophun.certificate">>, []};
+all_ext(<<"mpeg">>) -> {<<"video">>, <<"mpeg">>, []};
+all_ext(<<"mpe">>) -> {<<"video">>, <<"mpeg">>, []};
+all_ext(<<"mpg4">>) -> {<<"video">>, <<"mp4">>, []};
+all_ext(<<"mpga">>) -> {<<"audio">>, <<"mpeg">>, []};
+all_ext(<<"mpg">>) -> {<<"video">>, <<"mpeg">>, []};
+all_ext(<<"mpkg">>) -> {<<"application">>, <<"vnd.apple.installer+xml">>, []};
+all_ext(<<"mpm">>) -> {<<"application">>, <<"vnd.blueice.multipass">>, []};
+all_ext(<<"mpn">>) -> {<<"application">>, <<"vnd.mophun.application">>, []};
+all_ext(<<"mpp">>) -> {<<"application">>, <<"vnd.ms-project">>, []};
+all_ext(<<"mpt">>) -> {<<"application">>, <<"vnd.ms-project">>, []};
+all_ext(<<"mpy">>) -> {<<"application">>, <<"vnd.ibm.minipay">>, []};
+all_ext(<<"mqy">>) -> {<<"application">>, <<"vnd.mobius.mqy">>, []};
+all_ext(<<"mrc">>) -> {<<"application">>, <<"marc">>, []};
+all_ext(<<"mrcx">>) -> {<<"application">>, <<"marcxml+xml">>, []};
+all_ext(<<"mscml">>) -> {<<"application">>, <<"mediaservercontrol+xml">>, []};
+all_ext(<<"mseed">>) -> {<<"application">>, <<"vnd.fdsn.mseed">>, []};
+all_ext(<<"mseq">>) -> {<<"application">>, <<"vnd.mseq">>, []};
+all_ext(<<"msf">>) -> {<<"application">>, <<"vnd.epson.msf">>, []};
+all_ext(<<"msh">>) -> {<<"model">>, <<"mesh">>, []};
+all_ext(<<"msi">>) -> {<<"application">>, <<"x-msdownload">>, []};
+all_ext(<<"msl">>) -> {<<"application">>, <<"vnd.mobius.msl">>, []};
+all_ext(<<"ms">>) -> {<<"text">>, <<"troff">>, []};
+all_ext(<<"msty">>) -> {<<"application">>, <<"vnd.muvee.style">>, []};
+all_ext(<<"mts">>) -> {<<"model">>, <<"vnd.mts">>, []};
+all_ext(<<"mus">>) -> {<<"application">>, <<"vnd.musician">>, []};
+all_ext(<<"musicxml">>) -> {<<"application">>, <<"vnd.recordare.musicxml+xml">>, []};
+all_ext(<<"mvb">>) -> {<<"application">>, <<"x-msmediaview">>, []};
+all_ext(<<"mwf">>) -> {<<"application">>, <<"vnd.mfer">>, []};
+all_ext(<<"mxf">>) -> {<<"application">>, <<"mxf">>, []};
+all_ext(<<"mxl">>) -> {<<"application">>, <<"vnd.recordare.musicxml">>, []};
+all_ext(<<"mxml">>) -> {<<"application">>, <<"xv+xml">>, []};
+all_ext(<<"mxs">>) -> {<<"application">>, <<"vnd.triscape.mxs">>, []};
+all_ext(<<"mxu">>) -> {<<"video">>, <<"vnd.mpegurl">>, []};
+all_ext(<<"n3">>) -> {<<"text">>, <<"n3">>, []};
+all_ext(<<"nb">>) -> {<<"application">>, <<"mathematica">>, []};
+all_ext(<<"nbp">>) -> {<<"application">>, <<"vnd.wolfram.player">>, []};
+all_ext(<<"nc">>) -> {<<"application">>, <<"x-netcdf">>, []};
+all_ext(<<"ncx">>) -> {<<"application">>, <<"x-dtbncx+xml">>, []};
+all_ext(<<"nfo">>) -> {<<"text">>, <<"x-nfo">>, []};
+all_ext(<<"n-gage">>) -> {<<"application">>, <<"vnd.nokia.n-gage.symbian.install">>, []};
+all_ext(<<"ngdat">>) -> {<<"application">>, <<"vnd.nokia.n-gage.data">>, []};
+all_ext(<<"nitf">>) -> {<<"application">>, <<"vnd.nitf">>, []};
+all_ext(<<"nlu">>) -> {<<"application">>, <<"vnd.neurolanguage.nlu">>, []};
+all_ext(<<"nml">>) -> {<<"application">>, <<"vnd.enliven">>, []};
+all_ext(<<"nnd">>) -> {<<"application">>, <<"vnd.noblenet-directory">>, []};
+all_ext(<<"nns">>) -> {<<"application">>, <<"vnd.noblenet-sealer">>, []};
+all_ext(<<"nnw">>) -> {<<"application">>, <<"vnd.noblenet-web">>, []};
+all_ext(<<"npx">>) -> {<<"image">>, <<"vnd.net-fpx">>, []};
+all_ext(<<"nsc">>) -> {<<"application">>, <<"x-conference">>, []};
+all_ext(<<"nsf">>) -> {<<"application">>, <<"vnd.lotus-notes">>, []};
+all_ext(<<"ntf">>) -> {<<"application">>, <<"vnd.nitf">>, []};
+all_ext(<<"nzb">>) -> {<<"application">>, <<"x-nzb">>, []};
+all_ext(<<"oa2">>) -> {<<"application">>, <<"vnd.fujitsu.oasys2">>, []};
+all_ext(<<"oa3">>) -> {<<"application">>, <<"vnd.fujitsu.oasys3">>, []};
+all_ext(<<"oas">>) -> {<<"application">>, <<"vnd.fujitsu.oasys">>, []};
+all_ext(<<"obd">>) -> {<<"application">>, <<"x-msbinder">>, []};
+all_ext(<<"obj">>) -> {<<"application">>, <<"x-tgif">>, []};
+all_ext(<<"oda">>) -> {<<"application">>, <<"oda">>, []};
+all_ext(<<"odb">>) -> {<<"application">>, <<"vnd.oasis.opendocument.database">>, []};
+all_ext(<<"odc">>) -> {<<"application">>, <<"vnd.oasis.opendocument.chart">>, []};
+all_ext(<<"odf">>) -> {<<"application">>, <<"vnd.oasis.opendocument.formula">>, []};
+all_ext(<<"odft">>) -> {<<"application">>, <<"vnd.oasis.opendocument.formula-template">>, []};
+all_ext(<<"odg">>) -> {<<"application">>, <<"vnd.oasis.opendocument.graphics">>, []};
+all_ext(<<"odi">>) -> {<<"application">>, <<"vnd.oasis.opendocument.image">>, []};
+all_ext(<<"odm">>) -> {<<"application">>, <<"vnd.oasis.opendocument.text-master">>, []};
+all_ext(<<"odp">>) -> {<<"application">>, <<"vnd.oasis.opendocument.presentation">>, []};
+all_ext(<<"ods">>) -> {<<"application">>, <<"vnd.oasis.opendocument.spreadsheet">>, []};
+all_ext(<<"odt">>) -> {<<"application">>, <<"vnd.oasis.opendocument.text">>, []};
+all_ext(<<"oga">>) -> {<<"audio">>, <<"ogg">>, []};
+all_ext(<<"ogg">>) -> {<<"audio">>, <<"ogg">>, []};
+all_ext(<<"ogv">>) -> {<<"video">>, <<"ogg">>, []};
+all_ext(<<"ogx">>) -> {<<"application">>, <<"ogg">>, []};
+all_ext(<<"omdoc">>) -> {<<"application">>, <<"omdoc+xml">>, []};
+all_ext(<<"onepkg">>) -> {<<"application">>, <<"onenote">>, []};
+all_ext(<<"onetmp">>) -> {<<"application">>, <<"onenote">>, []};
+all_ext(<<"onetoc2">>) -> {<<"application">>, <<"onenote">>, []};
+all_ext(<<"onetoc">>) -> {<<"application">>, <<"onenote">>, []};
+all_ext(<<"opf">>) -> {<<"application">>, <<"oebps-package+xml">>, []};
+all_ext(<<"opml">>) -> {<<"text">>, <<"x-opml">>, []};
+all_ext(<<"oprc">>) -> {<<"application">>, <<"vnd.palm">>, []};
+all_ext(<<"org">>) -> {<<"application">>, <<"vnd.lotus-organizer">>, []};
+all_ext(<<"osf">>) -> {<<"application">>, <<"vnd.yamaha.openscoreformat">>, []};
+all_ext(<<"osfpvg">>) -> {<<"application">>, <<"vnd.yamaha.openscoreformat.osfpvg+xml">>, []};
+all_ext(<<"otc">>) -> {<<"application">>, <<"vnd.oasis.opendocument.chart-template">>, []};
+all_ext(<<"otf">>) -> {<<"font">>, <<"otf">>, []};
+all_ext(<<"otg">>) -> {<<"application">>, <<"vnd.oasis.opendocument.graphics-template">>, []};
+all_ext(<<"oth">>) -> {<<"application">>, <<"vnd.oasis.opendocument.text-web">>, []};
+all_ext(<<"oti">>) -> {<<"application">>, <<"vnd.oasis.opendocument.image-template">>, []};
+all_ext(<<"otp">>) -> {<<"application">>, <<"vnd.oasis.opendocument.presentation-template">>, []};
+all_ext(<<"ots">>) -> {<<"application">>, <<"vnd.oasis.opendocument.spreadsheet-template">>, []};
+all_ext(<<"ott">>) -> {<<"application">>, <<"vnd.oasis.opendocument.text-template">>, []};
+all_ext(<<"oxps">>) -> {<<"application">>, <<"oxps">>, []};
+all_ext(<<"oxt">>) -> {<<"application">>, <<"vnd.openofficeorg.extension">>, []};
+all_ext(<<"p10">>) -> {<<"application">>, <<"pkcs10">>, []};
+all_ext(<<"p12">>) -> {<<"application">>, <<"x-pkcs12">>, []};
+all_ext(<<"p7b">>) -> {<<"application">>, <<"x-pkcs7-certificates">>, []};
+all_ext(<<"p7c">>) -> {<<"application">>, <<"pkcs7-mime">>, []};
+all_ext(<<"p7m">>) -> {<<"application">>, <<"pkcs7-mime">>, []};
+all_ext(<<"p7r">>) -> {<<"application">>, <<"x-pkcs7-certreqresp">>, []};
+all_ext(<<"p7s">>) -> {<<"application">>, <<"pkcs7-signature">>, []};
+all_ext(<<"p8">>) -> {<<"application">>, <<"pkcs8">>, []};
+all_ext(<<"pas">>) -> {<<"text">>, <<"x-pascal">>, []};
+all_ext(<<"paw">>) -> {<<"application">>, <<"vnd.pawaafile">>, []};
+all_ext(<<"pbd">>) -> {<<"application">>, <<"vnd.powerbuilder6">>, []};
+all_ext(<<"pbm">>) -> {<<"image">>, <<"x-portable-bitmap">>, []};
+all_ext(<<"pcap">>) -> {<<"application">>, <<"vnd.tcpdump.pcap">>, []};
+all_ext(<<"pcf">>) -> {<<"application">>, <<"x-font-pcf">>, []};
+all_ext(<<"pcl">>) -> {<<"application">>, <<"vnd.hp-pcl">>, []};
+all_ext(<<"pclxl">>) -> {<<"application">>, <<"vnd.hp-pclxl">>, []};
+all_ext(<<"pct">>) -> {<<"image">>, <<"x-pict">>, []};
+all_ext(<<"pcurl">>) -> {<<"application">>, <<"vnd.curl.pcurl">>, []};
+all_ext(<<"pcx">>) -> {<<"image">>, <<"x-pcx">>, []};
+all_ext(<<"pdb">>) -> {<<"application">>, <<"vnd.palm">>, []};
+all_ext(<<"pdf">>) -> {<<"application">>, <<"pdf">>, []};
+all_ext(<<"pfa">>) -> {<<"application">>, <<"x-font-type1">>, []};
+all_ext(<<"pfb">>) -> {<<"application">>, <<"x-font-type1">>, []};
+all_ext(<<"pfm">>) -> {<<"application">>, <<"x-font-type1">>, []};
+all_ext(<<"pfr">>) -> {<<"application">>, <<"font-tdpfr">>, []};
+all_ext(<<"pfx">>) -> {<<"application">>, <<"x-pkcs12">>, []};
+all_ext(<<"pgm">>) -> {<<"image">>, <<"x-portable-graymap">>, []};
+all_ext(<<"pgn">>) -> {<<"application">>, <<"x-chess-pgn">>, []};
+all_ext(<<"pgp">>) -> {<<"application">>, <<"pgp-encrypted">>, []};
+all_ext(<<"pic">>) -> {<<"image">>, <<"x-pict">>, []};
+all_ext(<<"pkg">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"pki">>) -> {<<"application">>, <<"pkixcmp">>, []};
+all_ext(<<"pkipath">>) -> {<<"application">>, <<"pkix-pkipath">>, []};
+all_ext(<<"plb">>) -> {<<"application">>, <<"vnd.3gpp.pic-bw-large">>, []};
+all_ext(<<"plc">>) -> {<<"application">>, <<"vnd.mobius.plc">>, []};
+all_ext(<<"plf">>) -> {<<"application">>, <<"vnd.pocketlearn">>, []};
+all_ext(<<"pls">>) -> {<<"application">>, <<"pls+xml">>, []};
+all_ext(<<"pml">>) -> {<<"application">>, <<"vnd.ctc-posml">>, []};
+all_ext(<<"png">>) -> {<<"image">>, <<"png">>, []};
+all_ext(<<"pnm">>) -> {<<"image">>, <<"x-portable-anymap">>, []};
+all_ext(<<"portpkg">>) -> {<<"application">>, <<"vnd.macports.portpkg">>, []};
+all_ext(<<"pot">>) -> {<<"application">>, <<"vnd.ms-powerpoint">>, []};
+all_ext(<<"potm">>) -> {<<"application">>, <<"vnd.ms-powerpoint.template.macroenabled.12">>, []};
+all_ext(<<"potx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.presentationml.template">>, []};
+all_ext(<<"ppam">>) -> {<<"application">>, <<"vnd.ms-powerpoint.addin.macroenabled.12">>, []};
+all_ext(<<"ppd">>) -> {<<"application">>, <<"vnd.cups-ppd">>, []};
+all_ext(<<"ppm">>) -> {<<"image">>, <<"x-portable-pixmap">>, []};
+all_ext(<<"pps">>) -> {<<"application">>, <<"vnd.ms-powerpoint">>, []};
+all_ext(<<"ppsm">>) -> {<<"application">>, <<"vnd.ms-powerpoint.slideshow.macroenabled.12">>, []};
+all_ext(<<"ppsx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.presentationml.slideshow">>, []};
+all_ext(<<"ppt">>) -> {<<"application">>, <<"vnd.ms-powerpoint">>, []};
+all_ext(<<"pptm">>) -> {<<"application">>, <<"vnd.ms-powerpoint.presentation.macroenabled.12">>, []};
+all_ext(<<"pptx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.presentationml.presentation">>, []};
+all_ext(<<"pqa">>) -> {<<"application">>, <<"vnd.palm">>, []};
+all_ext(<<"prc">>) -> {<<"application">>, <<"x-mobipocket-ebook">>, []};
+all_ext(<<"pre">>) -> {<<"application">>, <<"vnd.lotus-freelance">>, []};
+all_ext(<<"prf">>) -> {<<"application">>, <<"pics-rules">>, []};
+all_ext(<<"ps">>) -> {<<"application">>, <<"postscript">>, []};
+all_ext(<<"psb">>) -> {<<"application">>, <<"vnd.3gpp.pic-bw-small">>, []};
+all_ext(<<"psd">>) -> {<<"image">>, <<"vnd.adobe.photoshop">>, []};
+all_ext(<<"psf">>) -> {<<"application">>, <<"x-font-linux-psf">>, []};
+all_ext(<<"pskcxml">>) -> {<<"application">>, <<"pskc+xml">>, []};
+all_ext(<<"p">>) -> {<<"text">>, <<"x-pascal">>, []};
+all_ext(<<"ptid">>) -> {<<"application">>, <<"vnd.pvi.ptid1">>, []};
+all_ext(<<"pub">>) -> {<<"application">>, <<"x-mspublisher">>, []};
+all_ext(<<"pvb">>) -> {<<"application">>, <<"vnd.3gpp.pic-bw-var">>, []};
+all_ext(<<"pwn">>) -> {<<"application">>, <<"vnd.3m.post-it-notes">>, []};
+all_ext(<<"pya">>) -> {<<"audio">>, <<"vnd.ms-playready.media.pya">>, []};
+all_ext(<<"pyv">>) -> {<<"video">>, <<"vnd.ms-playready.media.pyv">>, []};
+all_ext(<<"qam">>) -> {<<"application">>, <<"vnd.epson.quickanime">>, []};
+all_ext(<<"qbo">>) -> {<<"application">>, <<"vnd.intu.qbo">>, []};
+all_ext(<<"qfx">>) -> {<<"application">>, <<"vnd.intu.qfx">>, []};
+all_ext(<<"qps">>) -> {<<"application">>, <<"vnd.publishare-delta-tree">>, []};
+all_ext(<<"qt">>) -> {<<"video">>, <<"quicktime">>, []};
+all_ext(<<"qwd">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
+all_ext(<<"qwt">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
+all_ext(<<"qxb">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
+all_ext(<<"qxd">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
+all_ext(<<"qxl">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
+all_ext(<<"qxt">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
+all_ext(<<"ra">>) -> {<<"audio">>, <<"x-pn-realaudio">>, []};
+all_ext(<<"ram">>) -> {<<"audio">>, <<"x-pn-realaudio">>, []};
+all_ext(<<"rar">>) -> {<<"application">>, <<"x-rar-compressed">>, []};
+all_ext(<<"ras">>) -> {<<"image">>, <<"x-cmu-raster">>, []};
+all_ext(<<"rcprofile">>) -> {<<"application">>, <<"vnd.ipunplugged.rcprofile">>, []};
+all_ext(<<"rdf">>) -> {<<"application">>, <<"rdf+xml">>, []};
+all_ext(<<"rdz">>) -> {<<"application">>, <<"vnd.data-vision.rdz">>, []};
+all_ext(<<"rep">>) -> {<<"application">>, <<"vnd.businessobjects">>, []};
+all_ext(<<"res">>) -> {<<"application">>, <<"x-dtbresource+xml">>, []};
+all_ext(<<"rgb">>) -> {<<"image">>, <<"x-rgb">>, []};
+all_ext(<<"rif">>) -> {<<"application">>, <<"reginfo+xml">>, []};
+all_ext(<<"rip">>) -> {<<"audio">>, <<"vnd.rip">>, []};
+all_ext(<<"ris">>) -> {<<"application">>, <<"x-research-info-systems">>, []};
+all_ext(<<"rl">>) -> {<<"application">>, <<"resource-lists+xml">>, []};
+all_ext(<<"rlc">>) -> {<<"image">>, <<"vnd.fujixerox.edmics-rlc">>, []};
+all_ext(<<"rld">>) -> {<<"application">>, <<"resource-lists-diff+xml">>, []};
+all_ext(<<"rm">>) -> {<<"application">>, <<"vnd.rn-realmedia">>, []};
+all_ext(<<"rmi">>) -> {<<"audio">>, <<"midi">>, []};
+all_ext(<<"rmp">>) -> {<<"audio">>, <<"x-pn-realaudio-plugin">>, []};
+all_ext(<<"rms">>) -> {<<"application">>, <<"vnd.jcp.javame.midlet-rms">>, []};
+all_ext(<<"rmvb">>) -> {<<"application">>, <<"vnd.rn-realmedia-vbr">>, []};
+all_ext(<<"rnc">>) -> {<<"application">>, <<"relax-ng-compact-syntax">>, []};
+all_ext(<<"roa">>) -> {<<"application">>, <<"rpki-roa">>, []};
+all_ext(<<"roff">>) -> {<<"text">>, <<"troff">>, []};
+all_ext(<<"rp9">>) -> {<<"application">>, <<"vnd.cloanto.rp9">>, []};
+all_ext(<<"rpss">>) -> {<<"application">>, <<"vnd.nokia.radio-presets">>, []};
+all_ext(<<"rpst">>) -> {<<"application">>, <<"vnd.nokia.radio-preset">>, []};
+all_ext(<<"rq">>) -> {<<"application">>, <<"sparql-query">>, []};
+all_ext(<<"rs">>) -> {<<"application">>, <<"rls-services+xml">>, []};
+all_ext(<<"rsd">>) -> {<<"application">>, <<"rsd+xml">>, []};
+all_ext(<<"rss">>) -> {<<"application">>, <<"rss+xml">>, []};
+all_ext(<<"rtf">>) -> {<<"application">>, <<"rtf">>, []};
+all_ext(<<"rtx">>) -> {<<"text">>, <<"richtext">>, []};
+all_ext(<<"s3m">>) -> {<<"audio">>, <<"s3m">>, []};
+all_ext(<<"saf">>) -> {<<"application">>, <<"vnd.yamaha.smaf-audio">>, []};
+all_ext(<<"sbml">>) -> {<<"application">>, <<"sbml+xml">>, []};
+all_ext(<<"sc">>) -> {<<"application">>, <<"vnd.ibm.secure-container">>, []};
+all_ext(<<"scd">>) -> {<<"application">>, <<"x-msschedule">>, []};
+all_ext(<<"scm">>) -> {<<"application">>, <<"vnd.lotus-screencam">>, []};
+all_ext(<<"scq">>) -> {<<"application">>, <<"scvp-cv-request">>, []};
+all_ext(<<"scs">>) -> {<<"application">>, <<"scvp-cv-response">>, []};
+all_ext(<<"scurl">>) -> {<<"text">>, <<"vnd.curl.scurl">>, []};
+all_ext(<<"sda">>) -> {<<"application">>, <<"vnd.stardivision.draw">>, []};
+all_ext(<<"sdc">>) -> {<<"application">>, <<"vnd.stardivision.calc">>, []};
+all_ext(<<"sdd">>) -> {<<"application">>, <<"vnd.stardivision.impress">>, []};
+all_ext(<<"sdkd">>) -> {<<"application">>, <<"vnd.solent.sdkm+xml">>, []};
+all_ext(<<"sdkm">>) -> {<<"application">>, <<"vnd.solent.sdkm+xml">>, []};
+all_ext(<<"sdp">>) -> {<<"application">>, <<"sdp">>, []};
+all_ext(<<"sdw">>) -> {<<"application">>, <<"vnd.stardivision.writer">>, []};
+all_ext(<<"see">>) -> {<<"application">>, <<"vnd.seemail">>, []};
+all_ext(<<"seed">>) -> {<<"application">>, <<"vnd.fdsn.seed">>, []};
+all_ext(<<"sema">>) -> {<<"application">>, <<"vnd.sema">>, []};
+all_ext(<<"semd">>) -> {<<"application">>, <<"vnd.semd">>, []};
+all_ext(<<"semf">>) -> {<<"application">>, <<"vnd.semf">>, []};
+all_ext(<<"ser">>) -> {<<"application">>, <<"java-serialized-object">>, []};
+all_ext(<<"setpay">>) -> {<<"application">>, <<"set-payment-initiation">>, []};
+all_ext(<<"setreg">>) -> {<<"application">>, <<"set-registration-initiation">>, []};
+all_ext(<<"sfd-hdstx">>) -> {<<"application">>, <<"vnd.hydrostatix.sof-data">>, []};
+all_ext(<<"sfs">>) -> {<<"application">>, <<"vnd.spotfire.sfs">>, []};
+all_ext(<<"sfv">>) -> {<<"text">>, <<"x-sfv">>, []};
+all_ext(<<"sgi">>) -> {<<"image">>, <<"sgi">>, []};
+all_ext(<<"sgl">>) -> {<<"application">>, <<"vnd.stardivision.writer-global">>, []};
+all_ext(<<"sgml">>) -> {<<"text">>, <<"sgml">>, []};
+all_ext(<<"sgm">>) -> {<<"text">>, <<"sgml">>, []};
+all_ext(<<"sh">>) -> {<<"application">>, <<"x-sh">>, []};
+all_ext(<<"shar">>) -> {<<"application">>, <<"x-shar">>, []};
+all_ext(<<"shf">>) -> {<<"application">>, <<"shf+xml">>, []};
+all_ext(<<"sid">>) -> {<<"image">>, <<"x-mrsid-image">>, []};
+all_ext(<<"sig">>) -> {<<"application">>, <<"pgp-signature">>, []};
+all_ext(<<"sil">>) -> {<<"audio">>, <<"silk">>, []};
+all_ext(<<"silo">>) -> {<<"model">>, <<"mesh">>, []};
+all_ext(<<"sis">>) -> {<<"application">>, <<"vnd.symbian.install">>, []};
+all_ext(<<"sisx">>) -> {<<"application">>, <<"vnd.symbian.install">>, []};
+all_ext(<<"sit">>) -> {<<"application">>, <<"x-stuffit">>, []};
+all_ext(<<"sitx">>) -> {<<"application">>, <<"x-stuffitx">>, []};
+all_ext(<<"skd">>) -> {<<"application">>, <<"vnd.koan">>, []};
+all_ext(<<"skm">>) -> {<<"application">>, <<"vnd.koan">>, []};
+all_ext(<<"skp">>) -> {<<"application">>, <<"vnd.koan">>, []};
+all_ext(<<"skt">>) -> {<<"application">>, <<"vnd.koan">>, []};
+all_ext(<<"sldm">>) -> {<<"application">>, <<"vnd.ms-powerpoint.slide.macroenabled.12">>, []};
+all_ext(<<"sldx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.presentationml.slide">>, []};
+all_ext(<<"slt">>) -> {<<"application">>, <<"vnd.epson.salt">>, []};
+all_ext(<<"sm">>) -> {<<"application">>, <<"vnd.stepmania.stepchart">>, []};
+all_ext(<<"smf">>) -> {<<"application">>, <<"vnd.stardivision.math">>, []};
+all_ext(<<"smi">>) -> {<<"application">>, <<"smil+xml">>, []};
+all_ext(<<"smil">>) -> {<<"application">>, <<"smil+xml">>, []};
+all_ext(<<"smv">>) -> {<<"video">>, <<"x-smv">>, []};
+all_ext(<<"smzip">>) -> {<<"application">>, <<"vnd.stepmania.package">>, []};
+all_ext(<<"snd">>) -> {<<"audio">>, <<"basic">>, []};
+all_ext(<<"snf">>) -> {<<"application">>, <<"x-font-snf">>, []};
+all_ext(<<"so">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"spc">>) -> {<<"application">>, <<"x-pkcs7-certificates">>, []};
+all_ext(<<"spf">>) -> {<<"application">>, <<"vnd.yamaha.smaf-phrase">>, []};
+all_ext(<<"spl">>) -> {<<"application">>, <<"x-futuresplash">>, []};
+all_ext(<<"spot">>) -> {<<"text">>, <<"vnd.in3d.spot">>, []};
+all_ext(<<"spp">>) -> {<<"application">>, <<"scvp-vp-response">>, []};
+all_ext(<<"spq">>) -> {<<"application">>, <<"scvp-vp-request">>, []};
+all_ext(<<"spx">>) -> {<<"audio">>, <<"ogg">>, []};
+all_ext(<<"sql">>) -> {<<"application">>, <<"x-sql">>, []};
+all_ext(<<"src">>) -> {<<"application">>, <<"x-wais-source">>, []};
+all_ext(<<"srt">>) -> {<<"application">>, <<"x-subrip">>, []};
+all_ext(<<"sru">>) -> {<<"application">>, <<"sru+xml">>, []};
+all_ext(<<"srx">>) -> {<<"application">>, <<"sparql-results+xml">>, []};
+all_ext(<<"ssdl">>) -> {<<"application">>, <<"ssdl+xml">>, []};
+all_ext(<<"sse">>) -> {<<"application">>, <<"vnd.kodak-descriptor">>, []};
+all_ext(<<"ssf">>) -> {<<"application">>, <<"vnd.epson.ssf">>, []};
+all_ext(<<"ssml">>) -> {<<"application">>, <<"ssml+xml">>, []};
+all_ext(<<"st">>) -> {<<"application">>, <<"vnd.sailingtracker.track">>, []};
+all_ext(<<"stc">>) -> {<<"application">>, <<"vnd.sun.xml.calc.template">>, []};
+all_ext(<<"std">>) -> {<<"application">>, <<"vnd.sun.xml.draw.template">>, []};
+all_ext(<<"s">>) -> {<<"text">>, <<"x-asm">>, []};
+all_ext(<<"stf">>) -> {<<"application">>, <<"vnd.wt.stf">>, []};
+all_ext(<<"sti">>) -> {<<"application">>, <<"vnd.sun.xml.impress.template">>, []};
+all_ext(<<"stk">>) -> {<<"application">>, <<"hyperstudio">>, []};
+all_ext(<<"stl">>) -> {<<"application">>, <<"vnd.ms-pki.stl">>, []};
+all_ext(<<"str">>) -> {<<"application">>, <<"vnd.pg.format">>, []};
+all_ext(<<"stw">>) -> {<<"application">>, <<"vnd.sun.xml.writer.template">>, []};
+all_ext(<<"sub">>) -> {<<"image">>, <<"vnd.dvb.subtitle">>, []};
+all_ext(<<"sus">>) -> {<<"application">>, <<"vnd.sus-calendar">>, []};
+all_ext(<<"susp">>) -> {<<"application">>, <<"vnd.sus-calendar">>, []};
+all_ext(<<"sv4cpio">>) -> {<<"application">>, <<"x-sv4cpio">>, []};
+all_ext(<<"sv4crc">>) -> {<<"application">>, <<"x-sv4crc">>, []};
+all_ext(<<"svc">>) -> {<<"application">>, <<"vnd.dvb.service">>, []};
+all_ext(<<"svd">>) -> {<<"application">>, <<"vnd.svd">>, []};
+all_ext(<<"svg">>) -> {<<"image">>, <<"svg+xml">>, []};
+all_ext(<<"svgz">>) -> {<<"image">>, <<"svg+xml">>, []};
+all_ext(<<"swa">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"swf">>) -> {<<"application">>, <<"x-shockwave-flash">>, []};
+all_ext(<<"swi">>) -> {<<"application">>, <<"vnd.aristanetworks.swi">>, []};
+all_ext(<<"sxc">>) -> {<<"application">>, <<"vnd.sun.xml.calc">>, []};
+all_ext(<<"sxd">>) -> {<<"application">>, <<"vnd.sun.xml.draw">>, []};
+all_ext(<<"sxg">>) -> {<<"application">>, <<"vnd.sun.xml.writer.global">>, []};
+all_ext(<<"sxi">>) -> {<<"application">>, <<"vnd.sun.xml.impress">>, []};
+all_ext(<<"sxm">>) -> {<<"application">>, <<"vnd.sun.xml.math">>, []};
+all_ext(<<"sxw">>) -> {<<"application">>, <<"vnd.sun.xml.writer">>, []};
+all_ext(<<"t3">>) -> {<<"application">>, <<"x-t3vm-image">>, []};
+all_ext(<<"taglet">>) -> {<<"application">>, <<"vnd.mynfc">>, []};
+all_ext(<<"tao">>) -> {<<"application">>, <<"vnd.tao.intent-module-archive">>, []};
+all_ext(<<"tar">>) -> {<<"application">>, <<"x-tar">>, []};
+all_ext(<<"tcap">>) -> {<<"application">>, <<"vnd.3gpp2.tcap">>, []};
+all_ext(<<"tcl">>) -> {<<"application">>, <<"x-tcl">>, []};
+all_ext(<<"teacher">>) -> {<<"application">>, <<"vnd.smart.teacher">>, []};
+all_ext(<<"tei">>) -> {<<"application">>, <<"tei+xml">>, []};
+all_ext(<<"teicorpus">>) -> {<<"application">>, <<"tei+xml">>, []};
+all_ext(<<"tex">>) -> {<<"application">>, <<"x-tex">>, []};
+all_ext(<<"texi">>) -> {<<"application">>, <<"x-texinfo">>, []};
+all_ext(<<"texinfo">>) -> {<<"application">>, <<"x-texinfo">>, []};
+all_ext(<<"text">>) -> {<<"text">>, <<"plain">>, []};
+all_ext(<<"tfi">>) -> {<<"application">>, <<"thraud+xml">>, []};
+all_ext(<<"tfm">>) -> {<<"application">>, <<"x-tex-tfm">>, []};
+all_ext(<<"tga">>) -> {<<"image">>, <<"x-tga">>, []};
+all_ext(<<"thmx">>) -> {<<"application">>, <<"vnd.ms-officetheme">>, []};
+all_ext(<<"tiff">>) -> {<<"image">>, <<"tiff">>, []};
+all_ext(<<"tif">>) -> {<<"image">>, <<"tiff">>, []};
+all_ext(<<"tmo">>) -> {<<"application">>, <<"vnd.tmobile-livetv">>, []};
+all_ext(<<"torrent">>) -> {<<"application">>, <<"x-bittorrent">>, []};
+all_ext(<<"tpl">>) -> {<<"application">>, <<"vnd.groove-tool-template">>, []};
+all_ext(<<"tpt">>) -> {<<"application">>, <<"vnd.trid.tpt">>, []};
+all_ext(<<"tra">>) -> {<<"application">>, <<"vnd.trueapp">>, []};
+all_ext(<<"trm">>) -> {<<"application">>, <<"x-msterminal">>, []};
+all_ext(<<"tr">>) -> {<<"text">>, <<"troff">>, []};
+all_ext(<<"tsd">>) -> {<<"application">>, <<"timestamped-data">>, []};
+all_ext(<<"tsv">>) -> {<<"text">>, <<"tab-separated-values">>, []};
+all_ext(<<"ttc">>) -> {<<"font">>, <<"collection">>, []};
+all_ext(<<"t">>) -> {<<"text">>, <<"troff">>, []};
+all_ext(<<"ttf">>) -> {<<"font">>, <<"ttf">>, []};
+all_ext(<<"ttl">>) -> {<<"text">>, <<"turtle">>, []};
+all_ext(<<"twd">>) -> {<<"application">>, <<"vnd.simtech-mindmapper">>, []};
+all_ext(<<"twds">>) -> {<<"application">>, <<"vnd.simtech-mindmapper">>, []};
+all_ext(<<"txd">>) -> {<<"application">>, <<"vnd.genomatix.tuxedo">>, []};
+all_ext(<<"txf">>) -> {<<"application">>, <<"vnd.mobius.txf">>, []};
+all_ext(<<"txt">>) -> {<<"text">>, <<"plain">>, []};
+all_ext(<<"u32">>) -> {<<"application">>, <<"x-authorware-bin">>, []};
+all_ext(<<"udeb">>) -> {<<"application">>, <<"x-debian-package">>, []};
+all_ext(<<"ufd">>) -> {<<"application">>, <<"vnd.ufdl">>, []};
+all_ext(<<"ufdl">>) -> {<<"application">>, <<"vnd.ufdl">>, []};
+all_ext(<<"ulx">>) -> {<<"application">>, <<"x-glulx">>, []};
+all_ext(<<"umj">>) -> {<<"application">>, <<"vnd.umajin">>, []};
+all_ext(<<"unityweb">>) -> {<<"application">>, <<"vnd.unity">>, []};
+all_ext(<<"uoml">>) -> {<<"application">>, <<"vnd.uoml+xml">>, []};
+all_ext(<<"uris">>) -> {<<"text">>, <<"uri-list">>, []};
+all_ext(<<"uri">>) -> {<<"text">>, <<"uri-list">>, []};
+all_ext(<<"urls">>) -> {<<"text">>, <<"uri-list">>, []};
+all_ext(<<"ustar">>) -> {<<"application">>, <<"x-ustar">>, []};
+all_ext(<<"utz">>) -> {<<"application">>, <<"vnd.uiq.theme">>, []};
+all_ext(<<"uu">>) -> {<<"text">>, <<"x-uuencode">>, []};
+all_ext(<<"uva">>) -> {<<"audio">>, <<"vnd.dece.audio">>, []};
+all_ext(<<"uvd">>) -> {<<"application">>, <<"vnd.dece.data">>, []};
+all_ext(<<"uvf">>) -> {<<"application">>, <<"vnd.dece.data">>, []};
+all_ext(<<"uvg">>) -> {<<"image">>, <<"vnd.dece.graphic">>, []};
+all_ext(<<"uvh">>) -> {<<"video">>, <<"vnd.dece.hd">>, []};
+all_ext(<<"uvi">>) -> {<<"image">>, <<"vnd.dece.graphic">>, []};
+all_ext(<<"uvm">>) -> {<<"video">>, <<"vnd.dece.mobile">>, []};
+all_ext(<<"uvp">>) -> {<<"video">>, <<"vnd.dece.pd">>, []};
+all_ext(<<"uvs">>) -> {<<"video">>, <<"vnd.dece.sd">>, []};
+all_ext(<<"uvt">>) -> {<<"application">>, <<"vnd.dece.ttml+xml">>, []};
+all_ext(<<"uvu">>) -> {<<"video">>, <<"vnd.uvvu.mp4">>, []};
+all_ext(<<"uvva">>) -> {<<"audio">>, <<"vnd.dece.audio">>, []};
+all_ext(<<"uvvd">>) -> {<<"application">>, <<"vnd.dece.data">>, []};
+all_ext(<<"uvvf">>) -> {<<"application">>, <<"vnd.dece.data">>, []};
+all_ext(<<"uvvg">>) -> {<<"image">>, <<"vnd.dece.graphic">>, []};
+all_ext(<<"uvvh">>) -> {<<"video">>, <<"vnd.dece.hd">>, []};
+all_ext(<<"uvvi">>) -> {<<"image">>, <<"vnd.dece.graphic">>, []};
+all_ext(<<"uvvm">>) -> {<<"video">>, <<"vnd.dece.mobile">>, []};
+all_ext(<<"uvvp">>) -> {<<"video">>, <<"vnd.dece.pd">>, []};
+all_ext(<<"uvvs">>) -> {<<"video">>, <<"vnd.dece.sd">>, []};
+all_ext(<<"uvvt">>) -> {<<"application">>, <<"vnd.dece.ttml+xml">>, []};
+all_ext(<<"uvvu">>) -> {<<"video">>, <<"vnd.uvvu.mp4">>, []};
+all_ext(<<"uvv">>) -> {<<"video">>, <<"vnd.dece.video">>, []};
+all_ext(<<"uvvv">>) -> {<<"video">>, <<"vnd.dece.video">>, []};
+all_ext(<<"uvvx">>) -> {<<"application">>, <<"vnd.dece.unspecified">>, []};
+all_ext(<<"uvvz">>) -> {<<"application">>, <<"vnd.dece.zip">>, []};
+all_ext(<<"uvx">>) -> {<<"application">>, <<"vnd.dece.unspecified">>, []};
+all_ext(<<"uvz">>) -> {<<"application">>, <<"vnd.dece.zip">>, []};
+all_ext(<<"vcard">>) -> {<<"text">>, <<"vcard">>, []};
+all_ext(<<"vcd">>) -> {<<"application">>, <<"x-cdlink">>, []};
+all_ext(<<"vcf">>) -> {<<"text">>, <<"x-vcard">>, []};
+all_ext(<<"vcg">>) -> {<<"application">>, <<"vnd.groove-vcard">>, []};
+all_ext(<<"vcs">>) -> {<<"text">>, <<"x-vcalendar">>, []};
+all_ext(<<"vcx">>) -> {<<"application">>, <<"vnd.vcx">>, []};
+all_ext(<<"vis">>) -> {<<"application">>, <<"vnd.visionary">>, []};
+all_ext(<<"viv">>) -> {<<"video">>, <<"vnd.vivo">>, []};
+all_ext(<<"vob">>) -> {<<"video">>, <<"x-ms-vob">>, []};
+all_ext(<<"vor">>) -> {<<"application">>, <<"vnd.stardivision.writer">>, []};
+all_ext(<<"vox">>) -> {<<"application">>, <<"x-authorware-bin">>, []};
+all_ext(<<"vrml">>) -> {<<"model">>, <<"vrml">>, []};
+all_ext(<<"vsd">>) -> {<<"application">>, <<"vnd.visio">>, []};
+all_ext(<<"vsf">>) -> {<<"application">>, <<"vnd.vsf">>, []};
+all_ext(<<"vss">>) -> {<<"application">>, <<"vnd.visio">>, []};
+all_ext(<<"vst">>) -> {<<"application">>, <<"vnd.visio">>, []};
+all_ext(<<"vsw">>) -> {<<"application">>, <<"vnd.visio">>, []};
+all_ext(<<"vtu">>) -> {<<"model">>, <<"vnd.vtu">>, []};
+all_ext(<<"vxml">>) -> {<<"application">>, <<"voicexml+xml">>, []};
+all_ext(<<"w3d">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"wad">>) -> {<<"application">>, <<"x-doom">>, []};
+all_ext(<<"wav">>) -> {<<"audio">>, <<"x-wav">>, []};
+all_ext(<<"wax">>) -> {<<"audio">>, <<"x-ms-wax">>, []};
+all_ext(<<"wbmp">>) -> {<<"image">>, <<"vnd.wap.wbmp">>, []};
+all_ext(<<"wbs">>) -> {<<"application">>, <<"vnd.criticaltools.wbs+xml">>, []};
+all_ext(<<"wbxml">>) -> {<<"application">>, <<"vnd.wap.wbxml">>, []};
+all_ext(<<"wcm">>) -> {<<"application">>, <<"vnd.ms-works">>, []};
+all_ext(<<"wdb">>) -> {<<"application">>, <<"vnd.ms-works">>, []};
+all_ext(<<"wdp">>) -> {<<"image">>, <<"vnd.ms-photo">>, []};
+all_ext(<<"weba">>) -> {<<"audio">>, <<"webm">>, []};
+all_ext(<<"webm">>) -> {<<"video">>, <<"webm">>, []};
+all_ext(<<"webp">>) -> {<<"image">>, <<"webp">>, []};
+all_ext(<<"wg">>) -> {<<"application">>, <<"vnd.pmi.widget">>, []};
+all_ext(<<"wgt">>) -> {<<"application">>, <<"widget">>, []};
+all_ext(<<"wks">>) -> {<<"application">>, <<"vnd.ms-works">>, []};
+all_ext(<<"wma">>) -> {<<"audio">>, <<"x-ms-wma">>, []};
+all_ext(<<"wmd">>) -> {<<"application">>, <<"x-ms-wmd">>, []};
+all_ext(<<"wmf">>) -> {<<"application">>, <<"x-msmetafile">>, []};
+all_ext(<<"wmlc">>) -> {<<"application">>, <<"vnd.wap.wmlc">>, []};
+all_ext(<<"wmlsc">>) -> {<<"application">>, <<"vnd.wap.wmlscriptc">>, []};
+all_ext(<<"wmls">>) -> {<<"text">>, <<"vnd.wap.wmlscript">>, []};
+all_ext(<<"wml">>) -> {<<"text">>, <<"vnd.wap.wml">>, []};
+all_ext(<<"wm">>) -> {<<"video">>, <<"x-ms-wm">>, []};
+all_ext(<<"wmv">>) -> {<<"video">>, <<"x-ms-wmv">>, []};
+all_ext(<<"wmx">>) -> {<<"video">>, <<"x-ms-wmx">>, []};
+all_ext(<<"wmz">>) -> {<<"application">>, <<"x-msmetafile">>, []};
+all_ext(<<"woff2">>) -> {<<"font">>, <<"woff2">>, []};
+all_ext(<<"woff">>) -> {<<"font">>, <<"woff">>, []};
+all_ext(<<"wpd">>) -> {<<"application">>, <<"vnd.wordperfect">>, []};
+all_ext(<<"wpl">>) -> {<<"application">>, <<"vnd.ms-wpl">>, []};
+all_ext(<<"wps">>) -> {<<"application">>, <<"vnd.ms-works">>, []};
+all_ext(<<"wqd">>) -> {<<"application">>, <<"vnd.wqd">>, []};
+all_ext(<<"wri">>) -> {<<"application">>, <<"x-mswrite">>, []};
+all_ext(<<"wrl">>) -> {<<"model">>, <<"vrml">>, []};
+all_ext(<<"wsdl">>) -> {<<"application">>, <<"wsdl+xml">>, []};
+all_ext(<<"wspolicy">>) -> {<<"application">>, <<"wspolicy+xml">>, []};
+all_ext(<<"wtb">>) -> {<<"application">>, <<"vnd.webturbo">>, []};
+all_ext(<<"wvx">>) -> {<<"video">>, <<"x-ms-wvx">>, []};
+all_ext(<<"x32">>) -> {<<"application">>, <<"x-authorware-bin">>, []};
+all_ext(<<"x3db">>) -> {<<"model">>, <<"x3d+binary">>, []};
+all_ext(<<"x3dbz">>) -> {<<"model">>, <<"x3d+binary">>, []};
+all_ext(<<"x3d">>) -> {<<"model">>, <<"x3d+xml">>, []};
+all_ext(<<"x3dv">>) -> {<<"model">>, <<"x3d+vrml">>, []};
+all_ext(<<"x3dvz">>) -> {<<"model">>, <<"x3d+vrml">>, []};
+all_ext(<<"x3dz">>) -> {<<"model">>, <<"x3d+xml">>, []};
+all_ext(<<"xaml">>) -> {<<"application">>, <<"xaml+xml">>, []};
+all_ext(<<"xap">>) -> {<<"application">>, <<"x-silverlight-app">>, []};
+all_ext(<<"xar">>) -> {<<"application">>, <<"vnd.xara">>, []};
+all_ext(<<"xbap">>) -> {<<"application">>, <<"x-ms-xbap">>, []};
+all_ext(<<"xbd">>) -> {<<"application">>, <<"vnd.fujixerox.docuworks.binder">>, []};
+all_ext(<<"xbm">>) -> {<<"image">>, <<"x-xbitmap">>, []};
+all_ext(<<"xdf">>) -> {<<"application">>, <<"xcap-diff+xml">>, []};
+all_ext(<<"xdm">>) -> {<<"application">>, <<"vnd.syncml.dm+xml">>, []};
+all_ext(<<"xdp">>) -> {<<"application">>, <<"vnd.adobe.xdp+xml">>, []};
+all_ext(<<"xdssc">>) -> {<<"application">>, <<"dssc+xml">>, []};
+all_ext(<<"xdw">>) -> {<<"application">>, <<"vnd.fujixerox.docuworks">>, []};
+all_ext(<<"xenc">>) -> {<<"application">>, <<"xenc+xml">>, []};
+all_ext(<<"xer">>) -> {<<"application">>, <<"patch-ops-error+xml">>, []};
+all_ext(<<"xfdf">>) -> {<<"application">>, <<"vnd.adobe.xfdf">>, []};
+all_ext(<<"xfdl">>) -> {<<"application">>, <<"vnd.xfdl">>, []};
+all_ext(<<"xht">>) -> {<<"application">>, <<"xhtml+xml">>, []};
+all_ext(<<"xhtml">>) -> {<<"application">>, <<"xhtml+xml">>, []};
+all_ext(<<"xhvml">>) -> {<<"application">>, <<"xv+xml">>, []};
+all_ext(<<"xif">>) -> {<<"image">>, <<"vnd.xiff">>, []};
+all_ext(<<"xla">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
+all_ext(<<"xlam">>) -> {<<"application">>, <<"vnd.ms-excel.addin.macroenabled.12">>, []};
+all_ext(<<"xlc">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
+all_ext(<<"xlf">>) -> {<<"application">>, <<"x-xliff+xml">>, []};
+all_ext(<<"xlm">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
+all_ext(<<"xls">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
+all_ext(<<"xlsb">>) -> {<<"application">>, <<"vnd.ms-excel.sheet.binary.macroenabled.12">>, []};
+all_ext(<<"xlsm">>) -> {<<"application">>, <<"vnd.ms-excel.sheet.macroenabled.12">>, []};
+all_ext(<<"xlsx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.spreadsheetml.sheet">>, []};
+all_ext(<<"xlt">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
+all_ext(<<"xltm">>) -> {<<"application">>, <<"vnd.ms-excel.template.macroenabled.12">>, []};
+all_ext(<<"xltx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.spreadsheetml.template">>, []};
+all_ext(<<"xlw">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
+all_ext(<<"xm">>) -> {<<"audio">>, <<"xm">>, []};
+all_ext(<<"xml">>) -> {<<"application">>, <<"xml">>, []};
+all_ext(<<"xo">>) -> {<<"application">>, <<"vnd.olpc-sugar">>, []};
+all_ext(<<"xop">>) -> {<<"application">>, <<"xop+xml">>, []};
+all_ext(<<"xpi">>) -> {<<"application">>, <<"x-xpinstall">>, []};
+all_ext(<<"xpl">>) -> {<<"application">>, <<"xproc+xml">>, []};
+all_ext(<<"xpm">>) -> {<<"image">>, <<"x-xpixmap">>, []};
+all_ext(<<"xpr">>) -> {<<"application">>, <<"vnd.is-xpr">>, []};
+all_ext(<<"xps">>) -> {<<"application">>, <<"vnd.ms-xpsdocument">>, []};
+all_ext(<<"xpw">>) -> {<<"application">>, <<"vnd.intercon.formnet">>, []};
+all_ext(<<"xpx">>) -> {<<"application">>, <<"vnd.intercon.formnet">>, []};
+all_ext(<<"xsl">>) -> {<<"application">>, <<"xml">>, []};
+all_ext(<<"xslt">>) -> {<<"application">>, <<"xslt+xml">>, []};
+all_ext(<<"xsm">>) -> {<<"application">>, <<"vnd.syncml+xml">>, []};
+all_ext(<<"xspf">>) -> {<<"application">>, <<"xspf+xml">>, []};
+all_ext(<<"xul">>) -> {<<"application">>, <<"vnd.mozilla.xul+xml">>, []};
+all_ext(<<"xvm">>) -> {<<"application">>, <<"xv+xml">>, []};
+all_ext(<<"xvml">>) -> {<<"application">>, <<"xv+xml">>, []};
+all_ext(<<"xwd">>) -> {<<"image">>, <<"x-xwindowdump">>, []};
+all_ext(<<"xyz">>) -> {<<"chemical">>, <<"x-xyz">>, []};
+all_ext(<<"xz">>) -> {<<"application">>, <<"x-xz">>, []};
+all_ext(<<"yang">>) -> {<<"application">>, <<"yang">>, []};
+all_ext(<<"yin">>) -> {<<"application">>, <<"yin+xml">>, []};
+all_ext(<<"z1">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"z2">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"z3">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"z4">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"z5">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"z6">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"z7">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"z8">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"zaz">>) -> {<<"application">>, <<"vnd.zzazz.deck+xml">>, []};
+all_ext(<<"zip">>) -> {<<"application">>, <<"zip">>, []};
+all_ext(<<"zir">>) -> {<<"application">>, <<"vnd.zul">>, []};
+all_ext(<<"zirz">>) -> {<<"application">>, <<"vnd.zul">>, []};
+all_ext(<<"zmm">>) -> {<<"application">>, <<"vnd.handheld-entertainment+xml">>, []};
+%% GENERATED
+all_ext(_) -> {<<"application">>, <<"octet-stream">>, []}.
+
+web_ext(<<"css">>) -> {<<"text">>, <<"css">>, []};
+web_ext(<<"gif">>) -> {<<"image">>, <<"gif">>, []};
+web_ext(<<"html">>) -> {<<"text">>, <<"html">>, []};
+web_ext(<<"htm">>) -> {<<"text">>, <<"html">>, []};
+web_ext(<<"ico">>) -> {<<"image">>, <<"x-icon">>, []};
+web_ext(<<"jpeg">>) -> {<<"image">>, <<"jpeg">>, []};
+web_ext(<<"jpg">>) -> {<<"image">>, <<"jpeg">>, []};
+web_ext(<<"js">>) -> {<<"application">>, <<"javascript">>, []};
+web_ext(<<"mp3">>) -> {<<"audio">>, <<"mpeg">>, []};
+web_ext(<<"mp4">>) -> {<<"video">>, <<"mp4">>, []};
+web_ext(<<"ogg">>) -> {<<"audio">>, <<"ogg">>, []};
+web_ext(<<"ogv">>) -> {<<"video">>, <<"ogg">>, []};
+web_ext(<<"png">>) -> {<<"image">>, <<"png">>, []};
+web_ext(<<"svg">>) -> {<<"image">>, <<"svg+xml">>, []};
+web_ext(<<"wav">>) -> {<<"audio">>, <<"x-wav">>, []};
+web_ext(<<"webm">>) -> {<<"video">>, <<"webm">>, []};
+web_ext(_) -> {<<"application">>, <<"octet-stream">>, []}.
diff --git a/server/_build/default/lib/cowlib/src/cow_mimetypes.erl.src b/server/_build/default/lib/cowlib/src/cow_mimetypes.erl.src
new file mode 100644
index 0000000..7cccdd3
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_mimetypes.erl.src
@@ -0,0 +1,61 @@
+%% Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_mimetypes).
+
+-export([all/1]).
+-export([web/1]).
+
+%% @doc Return the mimetype for any file by looking at its extension.
+
+-spec all(binary()) -> {binary(), binary(), []}.
+all(Path) ->
+ case filename:extension(Path) of
+ <<>> -> {<<"application">>, <<"octet-stream">>, []};
+ %% @todo Convert to string:lowercase on OTP-20+.
+ << $., Ext/binary >> -> all_ext(list_to_binary(string:to_lower(binary_to_list(Ext))))
+ end.
+
+%% @doc Return the mimetype for a Web related file by looking at its extension.
+
+-spec web(binary()) -> {binary(), binary(), []}.
+web(Path) ->
+ case filename:extension(Path) of
+ <<>> -> {<<"application">>, <<"octet-stream">>, []};
+ %% @todo Convert to string:lowercase on OTP-20+.
+ << $., Ext/binary >> -> web_ext(list_to_binary(string:to_lower(binary_to_list(Ext))))
+ end.
+
+%% Internal.
+
+%% GENERATED
+all_ext(_) -> {<<"application">>, <<"octet-stream">>, []}.
+
+web_ext(<<"css">>) -> {<<"text">>, <<"css">>, []};
+web_ext(<<"gif">>) -> {<<"image">>, <<"gif">>, []};
+web_ext(<<"html">>) -> {<<"text">>, <<"html">>, []};
+web_ext(<<"htm">>) -> {<<"text">>, <<"html">>, []};
+web_ext(<<"ico">>) -> {<<"image">>, <<"x-icon">>, []};
+web_ext(<<"jpeg">>) -> {<<"image">>, <<"jpeg">>, []};
+web_ext(<<"jpg">>) -> {<<"image">>, <<"jpeg">>, []};
+web_ext(<<"js">>) -> {<<"application">>, <<"javascript">>, []};
+web_ext(<<"mp3">>) -> {<<"audio">>, <<"mpeg">>, []};
+web_ext(<<"mp4">>) -> {<<"video">>, <<"mp4">>, []};
+web_ext(<<"ogg">>) -> {<<"audio">>, <<"ogg">>, []};
+web_ext(<<"ogv">>) -> {<<"video">>, <<"ogg">>, []};
+web_ext(<<"png">>) -> {<<"image">>, <<"png">>, []};
+web_ext(<<"svg">>) -> {<<"image">>, <<"svg+xml">>, []};
+web_ext(<<"wav">>) -> {<<"audio">>, <<"x-wav">>, []};
+web_ext(<<"webm">>) -> {<<"video">>, <<"webm">>, []};
+web_ext(_) -> {<<"application">>, <<"octet-stream">>, []}.
diff --git a/server/_build/default/lib/cowlib/src/cow_multipart.erl b/server/_build/default/lib/cowlib/src/cow_multipart.erl
new file mode 100644
index 0000000..4d6d574
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_multipart.erl
@@ -0,0 +1,775 @@
+%% Copyright (c) 2014-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_multipart).
+
+%% Parsing.
+-export([parse_headers/2]).
+-export([parse_body/2]).
+
+%% Building.
+-export([boundary/0]).
+-export([first_part/2]).
+-export([part/2]).
+-export([close/1]).
+
+%% Headers.
+-export([form_data/1]).
+-export([parse_content_disposition/1]).
+-export([parse_content_transfer_encoding/1]).
+-export([parse_content_type/1]).
+
+-type headers() :: [{iodata(), iodata()}].
+-export_type([headers/0]).
+
+-include("cow_inline.hrl").
+
+-define(TEST1_MIME, <<
+ "This is a message with multiple parts in MIME format.\r\n"
+ "--frontier\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "This is the body of the message.\r\n"
+ "--frontier\r\n"
+ "Content-Type: application/octet-stream\r\n"
+ "Content-Transfer-Encoding: base64\r\n"
+ "\r\n"
+ "PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
+ "Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==\r\n"
+ "--frontier--"
+>>).
+-define(TEST1_BOUNDARY, <<"frontier">>).
+
+-define(TEST2_MIME, <<
+ "--AaB03x\r\n"
+ "Content-Disposition: form-data; name=\"submit-name\"\r\n"
+ "\r\n"
+ "Larry\r\n"
+ "--AaB03x\r\n"
+ "Content-Disposition: form-data; name=\"files\"\r\n"
+ "Content-Type: multipart/mixed; boundary=BbC04y\r\n"
+ "\r\n"
+ "--BbC04y\r\n"
+ "Content-Disposition: file; filename=\"file1.txt\"\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "... contents of file1.txt ...\r\n"
+ "--BbC04y\r\n"
+ "Content-Disposition: file; filename=\"file2.gif\"\r\n"
+ "Content-Type: image/gif\r\n"
+ "Content-Transfer-Encoding: binary\r\n"
+ "\r\n"
+ "...contents of file2.gif...\r\n"
+ "--BbC04y--\r\n"
+ "--AaB03x--"
+>>).
+-define(TEST2_BOUNDARY, <<"AaB03x">>).
+
+-define(TEST3_MIME, <<
+ "This is the preamble.\r\n"
+ "--boundary\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "This is the body of the message.\r\n"
+ "--boundary--"
+ "\r\nThis is the epilogue. Here it includes leading CRLF"
+>>).
+-define(TEST3_BOUNDARY, <<"boundary">>).
+
+-define(TEST4_MIME, <<
+ "This is the preamble.\r\n"
+ "--boundary\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "This is the body of the message.\r\n"
+ "--boundary--"
+ "\r\n"
+>>).
+-define(TEST4_BOUNDARY, <<"boundary">>).
+
+%% RFC 2046, Section 5.1.1
+-define(TEST5_MIME, <<
+ "This is the preamble. It is to be ignored, though it\r\n"
+ "is a handy place for composition agents to include an\r\n"
+ "explanatory note to non-MIME conformant readers.\r\n"
+ "\r\n"
+ "--simple boundary\r\n",
+ "\r\n"
+ "This is implicitly typed plain US-ASCII text.\r\n"
+ "It does NOT end with a linebreak."
+ "\r\n"
+ "--simple boundary\r\n",
+ "Content-type: text/plain; charset=us-ascii\r\n"
+ "\r\n"
+ "This is explicitly typed plain US-ASCII text.\r\n"
+ "It DOES end with a linebreak.\r\n"
+ "\r\n"
+ "--simple boundary--\r\n"
+ "\r\n"
+ "This is the epilogue. It is also to be ignored."
+>>).
+-define(TEST5_BOUNDARY, <<"simple boundary">>).
+
+%% Parsing.
+%%
+%% The multipart format is defined in RFC 2045.
+
+%% @doc Parse the headers for the next multipart part.
+%%
+%% This function skips any preamble before the boundary.
+%% The preamble may be retrieved using parse_body/2.
+%%
+%% This function will accept input of any size, it is
+%% up to the caller to limit it if needed.
+
+-spec parse_headers(binary(), binary())
+ -> more | {more, binary()}
+ | {ok, headers(), binary()}
+ | {done, binary()}.
+%% If the stream starts with the boundary we can make a few assumptions
+%% and quickly figure out if we got the complete list of headers.
+parse_headers(<< "--", Stream/bits >>, Boundary) ->
+ BoundarySize = byte_size(Boundary),
+ case Stream of
+ %% Last boundary. Return the epilogue.
+ << Boundary:BoundarySize/binary, "--", Stream2/bits >> ->
+ {done, Stream2};
+ << Boundary:BoundarySize/binary, Stream2/bits >> ->
+ %% We have all the headers only if there is a \r\n\r\n
+ %% somewhere in the data after the boundary.
+ case binary:match(Stream2, <<"\r\n\r\n">>) of
+ nomatch ->
+ more;
+ _ ->
+ before_parse_headers(Stream2)
+ end;
+ %% If there isn't enough to represent Boundary \r\n\r\n
+ %% then we definitely don't have all the headers.
+ _ when byte_size(Stream) < byte_size(Boundary) + 4 ->
+ more;
+ %% Otherwise we have preamble data to skip.
+ %% We still got rid of the first two misleading bytes.
+ _ ->
+ skip_preamble(Stream, Boundary)
+ end;
+%% Otherwise we have preamble data to skip.
+parse_headers(Stream, Boundary) ->
+ skip_preamble(Stream, Boundary).
+
+%% We need to find the boundary and a \r\n\r\n after that.
+%% Since the boundary isn't at the start, it must be right
+%% after a \r\n too.
+skip_preamble(Stream, Boundary) ->
+ case binary:match(Stream, <<"\r\n--", Boundary/bits >>) of
+ %% No boundary, need more data.
+ nomatch ->
+ %% We can safely skip the size of the stream
+ %% minus the last 3 bytes which may be a partial boundary.
+ SkipSize = byte_size(Stream) - 3,
+ case SkipSize > 0 of
+ false ->
+ more;
+ true ->
+ << _:SkipSize/binary, Stream2/bits >> = Stream,
+ {more, Stream2}
+ end;
+ {Start, Length} ->
+ Start2 = Start + Length,
+ << _:Start2/binary, Stream2/bits >> = Stream,
+ case Stream2 of
+ %% Last boundary. Return the epilogue.
+ << "--", Stream3/bits >> ->
+ {done, Stream3};
+ _ ->
+ case binary:match(Stream, <<"\r\n\r\n">>) of
+ %% We don't have the full headers.
+ nomatch ->
+ {more, Stream2};
+ _ ->
+ before_parse_headers(Stream2)
+ end
+ end
+ end.
+
+before_parse_headers(<< "\r\n\r\n", Stream/bits >>) ->
+ %% This indicates that there are no headers, so we can abort immediately.
+ {ok, [], Stream};
+before_parse_headers(<< "\r\n", Stream/bits >>) ->
+ %% There is a line break right after the boundary, skip it.
+ parse_hd_name(Stream, [], <<>>).
+
+parse_hd_name(<< C, Rest/bits >>, H, SoFar) ->
+ case C of
+ $: -> parse_hd_before_value(Rest, H, SoFar);
+ $\s -> parse_hd_name_ws(Rest, H, SoFar);
+ $\t -> parse_hd_name_ws(Rest, H, SoFar);
+ _ -> ?LOWER(parse_hd_name, Rest, H, SoFar)
+ end.
+
+parse_hd_name_ws(<< C, Rest/bits >>, H, Name) ->
+ case C of
+ $\s -> parse_hd_name_ws(Rest, H, Name);
+ $\t -> parse_hd_name_ws(Rest, H, Name);
+ $: -> parse_hd_before_value(Rest, H, Name)
+ end.
+
+parse_hd_before_value(<< $\s, Rest/bits >>, H, N) ->
+ parse_hd_before_value(Rest, H, N);
+parse_hd_before_value(<< $\t, Rest/bits >>, H, N) ->
+ parse_hd_before_value(Rest, H, N);
+parse_hd_before_value(Buffer, H, N) ->
+ parse_hd_value(Buffer, H, N, <<>>).
+
+parse_hd_value(<< $\r, Rest/bits >>, Headers, Name, SoFar) ->
+ case Rest of
+ << "\n\r\n", Rest2/bits >> ->
+ {ok, [{Name, SoFar}|Headers], Rest2};
+ << $\n, C, Rest2/bits >> when C =:= $\s; C =:= $\t ->
+ parse_hd_value(Rest2, Headers, Name, SoFar);
+ << $\n, Rest2/bits >> ->
+ parse_hd_name(Rest2, [{Name, SoFar}|Headers], <<>>)
+ end;
+parse_hd_value(<< C, Rest/bits >>, H, N, SoFar) ->
+ parse_hd_value(Rest, H, N, << SoFar/binary, C >>).
+
+%% @doc Parse the body of the current multipart part.
+%%
+%% The body is everything until the next boundary.
+
+-spec parse_body(binary(), binary())
+ -> {ok, binary()} | {ok, binary(), binary()}
+ | done | {done, binary()} | {done, binary(), binary()}.
+parse_body(Stream, Boundary) ->
+ BoundarySize = byte_size(Boundary),
+ case Stream of
+ << "--", Boundary:BoundarySize/binary, _/bits >> ->
+ done;
+ _ ->
+ case binary:match(Stream, << "\r\n--", Boundary/bits >>) of
+ %% No boundary, check for a possible partial at the end.
+ %% Return more or less of the body depending on the result.
+ nomatch ->
+ StreamSize = byte_size(Stream),
+ From = StreamSize - BoundarySize - 3,
+ MatchOpts = if
+ %% Binary too small to contain boundary, check it fully.
+ From < 0 -> [];
+ %% Optimize, only check the end of the binary.
+ true -> [{scope, {From, StreamSize - From}}]
+ end,
+ case binary:match(Stream, <<"\r">>, MatchOpts) of
+ nomatch ->
+ {ok, Stream};
+ {Pos, _} ->
+ case Stream of
+ << Body:Pos/binary >> ->
+ {ok, Body};
+ << Body:Pos/binary, Rest/bits >> ->
+ {ok, Body, Rest}
+ end
+ end;
+ %% Boundary found, this is the last chunk of the body.
+ {Pos, _} ->
+ case Stream of
+ << Body:Pos/binary, "\r\n" >> ->
+ {done, Body};
+ << Body:Pos/binary, "\r\n", Rest/bits >> ->
+ {done, Body, Rest};
+ << Body:Pos/binary, Rest/bits >> ->
+ {done, Body, Rest}
+ end
+ end
+ end.
+
+-ifdef(TEST).
+parse_test() ->
+ H1 = [{<<"content-type">>, <<"text/plain">>}],
+ Body1 = <<"This is the body of the message.">>,
+ H2 = lists:sort([{<<"content-type">>, <<"application/octet-stream">>},
+ {<<"content-transfer-encoding">>, <<"base64">>}]),
+ Body2 = <<"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
+ "Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==">>,
+ {ok, H1, Rest} = parse_headers(?TEST1_MIME, ?TEST1_BOUNDARY),
+ {done, Body1, Rest2} = parse_body(Rest, ?TEST1_BOUNDARY),
+ done = parse_body(Rest2, ?TEST1_BOUNDARY),
+ {ok, H2Unsorted, Rest3} = parse_headers(Rest2, ?TEST1_BOUNDARY),
+ H2 = lists:sort(H2Unsorted),
+ {done, Body2, Rest4} = parse_body(Rest3, ?TEST1_BOUNDARY),
+ done = parse_body(Rest4, ?TEST1_BOUNDARY),
+ {done, <<>>} = parse_headers(Rest4, ?TEST1_BOUNDARY),
+ ok.
+
+parse_interleaved_test() ->
+ H1 = [{<<"content-disposition">>, <<"form-data; name=\"submit-name\"">>}],
+ Body1 = <<"Larry">>,
+ H2 = lists:sort([{<<"content-disposition">>, <<"form-data; name=\"files\"">>},
+ {<<"content-type">>, <<"multipart/mixed; boundary=BbC04y">>}]),
+ InH1 = lists:sort([{<<"content-disposition">>, <<"file; filename=\"file1.txt\"">>},
+ {<<"content-type">>, <<"text/plain">>}]),
+ InBody1 = <<"... contents of file1.txt ...">>,
+ InH2 = lists:sort([{<<"content-disposition">>, <<"file; filename=\"file2.gif\"">>},
+ {<<"content-type">>, <<"image/gif">>},
+ {<<"content-transfer-encoding">>, <<"binary">>}]),
+ InBody2 = <<"...contents of file2.gif...">>,
+ {ok, H1, Rest} = parse_headers(?TEST2_MIME, ?TEST2_BOUNDARY),
+ {done, Body1, Rest2} = parse_body(Rest, ?TEST2_BOUNDARY),
+ done = parse_body(Rest2, ?TEST2_BOUNDARY),
+ {ok, H2Unsorted, Rest3} = parse_headers(Rest2, ?TEST2_BOUNDARY),
+ H2 = lists:sort(H2Unsorted),
+ {_, ContentType} = lists:keyfind(<<"content-type">>, 1, H2),
+ {<<"multipart">>, <<"mixed">>, [{<<"boundary">>, InBoundary}]}
+ = parse_content_type(ContentType),
+ {ok, InH1Unsorted, InRest} = parse_headers(Rest3, InBoundary),
+ InH1 = lists:sort(InH1Unsorted),
+ {done, InBody1, InRest2} = parse_body(InRest, InBoundary),
+ done = parse_body(InRest2, InBoundary),
+ {ok, InH2Unsorted, InRest3} = parse_headers(InRest2, InBoundary),
+ InH2 = lists:sort(InH2Unsorted),
+ {done, InBody2, InRest4} = parse_body(InRest3, InBoundary),
+ done = parse_body(InRest4, InBoundary),
+ {done, Rest4} = parse_headers(InRest4, InBoundary),
+ {done, <<>>} = parse_headers(Rest4, ?TEST2_BOUNDARY),
+ ok.
+
+parse_epilogue_test() ->
+ H1 = [{<<"content-type">>, <<"text/plain">>}],
+ Body1 = <<"This is the body of the message.">>,
+ Epilogue = <<"\r\nThis is the epilogue. Here it includes leading CRLF">>,
+ {ok, H1, Rest} = parse_headers(?TEST3_MIME, ?TEST3_BOUNDARY),
+ {done, Body1, Rest2} = parse_body(Rest, ?TEST3_BOUNDARY),
+ done = parse_body(Rest2, ?TEST3_BOUNDARY),
+ {done, Epilogue} = parse_headers(Rest2, ?TEST3_BOUNDARY),
+ ok.
+
+parse_epilogue_crlf_test() ->
+ H1 = [{<<"content-type">>, <<"text/plain">>}],
+ Body1 = <<"This is the body of the message.">>,
+ Epilogue = <<"\r\n">>,
+ {ok, H1, Rest} = parse_headers(?TEST4_MIME, ?TEST4_BOUNDARY),
+ {done, Body1, Rest2} = parse_body(Rest, ?TEST4_BOUNDARY),
+ done = parse_body(Rest2, ?TEST4_BOUNDARY),
+ {done, Epilogue} = parse_headers(Rest2, ?TEST4_BOUNDARY),
+ ok.
+
+parse_rfc2046_test() ->
+ %% The following is an example included in RFC 2046, Section 5.1.1.
+ Body1 = <<"This is implicitly typed plain US-ASCII text.\r\n"
+ "It does NOT end with a linebreak.">>,
+ Body2 = <<"This is explicitly typed plain US-ASCII text.\r\n"
+ "It DOES end with a linebreak.\r\n">>,
+ H2 = [{<<"content-type">>, <<"text/plain; charset=us-ascii">>}],
+ Epilogue = <<"\r\n\r\nThis is the epilogue. It is also to be ignored.">>,
+ {ok, [], Rest} = parse_headers(?TEST5_MIME, ?TEST5_BOUNDARY),
+ {done, Body1, Rest2} = parse_body(Rest, ?TEST5_BOUNDARY),
+ {ok, H2, Rest3} = parse_headers(Rest2, ?TEST5_BOUNDARY),
+ {done, Body2, Rest4} = parse_body(Rest3, ?TEST5_BOUNDARY),
+ {done, Epilogue} = parse_headers(Rest4, ?TEST5_BOUNDARY),
+ ok.
+
+parse_partial_test() ->
+ {ok, <<0:8000, "abcdef">>, <<"\rghij">>}
+ = parse_body(<<0:8000, "abcdef\rghij">>, <<"boundary">>),
+ {ok, <<"abcdef">>, <<"\rghij">>}
+ = parse_body(<<"abcdef\rghij">>, <<"boundary">>),
+ {ok, <<"abc">>, <<"\rdef">>}
+ = parse_body(<<"abc\rdef">>, <<"boundaryboundary">>),
+ {ok, <<0:8000, "abcdef">>, <<"\r\nghij">>}
+ = parse_body(<<0:8000, "abcdef\r\nghij">>, <<"boundary">>),
+ {ok, <<"abcdef">>, <<"\r\nghij">>}
+ = parse_body(<<"abcdef\r\nghij">>, <<"boundary">>),
+ {ok, <<"abc">>, <<"\r\ndef">>}
+ = parse_body(<<"abc\r\ndef">>, <<"boundaryboundary">>),
+ {ok, <<"boundary">>, <<"\r">>}
+ = parse_body(<<"boundary\r">>, <<"boundary">>),
+ {ok, <<"boundary">>, <<"\r\n">>}
+ = parse_body(<<"boundary\r\n">>, <<"boundary">>),
+ {ok, <<"boundary">>, <<"\r\n-">>}
+ = parse_body(<<"boundary\r\n-">>, <<"boundary">>),
+ {ok, <<"boundary">>, <<"\r\n--">>}
+ = parse_body(<<"boundary\r\n--">>, <<"boundary">>),
+ ok.
+
+perf_parse_multipart(Stream, Boundary) ->
+ case parse_headers(Stream, Boundary) of
+ {ok, _, Rest} ->
+ {_, _, Rest2} = parse_body(Rest, Boundary),
+ perf_parse_multipart(Rest2, Boundary);
+ {done, _} ->
+ ok
+ end.
+
+horse_parse() ->
+ horse:repeat(50000,
+ perf_parse_multipart(?TEST1_MIME, ?TEST1_BOUNDARY)
+ ).
+-endif.
+
+%% Building.
+
+%% @doc Generate a new random boundary.
+%%
+%% The boundary generated has a low probability of ever appearing
+%% in the data.
+
+-spec boundary() -> binary().
+boundary() ->
+ cow_base64url:encode(crypto:strong_rand_bytes(48), #{padding => false}).
+
+%% @doc Return the first part's head.
+%%
+%% This works exactly like the part/2 function except there is
+%% no leading \r\n. It's not required to use this function,
+%% just makes the output a little smaller and prettier.
+
+-spec first_part(binary(), headers()) -> iodata().
+first_part(Boundary, Headers) ->
+ [<<"--">>, Boundary, <<"\r\n">>, headers_to_iolist(Headers, [])].
+
+%% @doc Return a part's head.
+
+-spec part(binary(), headers()) -> iodata().
+part(Boundary, Headers) ->
+ [<<"\r\n--">>, Boundary, <<"\r\n">>, headers_to_iolist(Headers, [])].
+
+headers_to_iolist([], Acc) ->
+ lists:reverse([<<"\r\n">>|Acc]);
+headers_to_iolist([{N, V}|Tail], Acc) ->
+ %% We don't want to create a sublist so we list the
+ %% values in reverse order so that it gets reversed properly.
+ headers_to_iolist(Tail, [<<"\r\n">>, V, <<": ">>, N|Acc]).
+
+%% @doc Return the closing delimiter of the multipart message.
+
+-spec close(binary()) -> iodata().
+close(Boundary) ->
+ [<<"\r\n--">>, Boundary, <<"--">>].
+
+-ifdef(TEST).
+build_test() ->
+ Result = string:to_lower(binary_to_list(?TEST1_MIME)),
+ Result = string:to_lower(binary_to_list(iolist_to_binary([
+ <<"This is a message with multiple parts in MIME format.\r\n">>,
+ first_part(?TEST1_BOUNDARY, [{<<"content-type">>, <<"text/plain">>}]),
+ <<"This is the body of the message.">>,
+ part(?TEST1_BOUNDARY, [
+ {<<"content-type">>, <<"application/octet-stream">>},
+ {<<"content-transfer-encoding">>, <<"base64">>}]),
+ <<"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
+ "Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==">>,
+ close(?TEST1_BOUNDARY)
+ ]))),
+ ok.
+
+identity_test() ->
+ B = boundary(),
+ Preamble = <<"This is a message with multiple parts in MIME format.">>,
+ H1 = [{<<"content-type">>, <<"text/plain">>}],
+ Body1 = <<"This is the body of the message.">>,
+ H2 = lists:sort([{<<"content-type">>, <<"application/octet-stream">>},
+ {<<"content-transfer-encoding">>, <<"base64">>}]),
+ Body2 = <<"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
+ "Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==">>,
+ Epilogue = <<"Gotta go fast!">>,
+ M = iolist_to_binary([
+ Preamble,
+ part(B, H1), Body1,
+ part(B, H2), Body2,
+ close(B),
+ Epilogue
+ ]),
+ {done, Preamble, M2} = parse_body(M, B),
+ {ok, H1, M3} = parse_headers(M2, B),
+ {done, Body1, M4} = parse_body(M3, B),
+ {ok, H2Unsorted, M5} = parse_headers(M4, B),
+ H2 = lists:sort(H2Unsorted),
+ {done, Body2, M6} = parse_body(M5, B),
+ {done, Epilogue} = parse_headers(M6, B),
+ ok.
+
+perf_build_multipart() ->
+ B = boundary(),
+ [
+ <<"preamble\r\n">>,
+ first_part(B, [{<<"content-type">>, <<"text/plain">>}]),
+ <<"This is the body of the message.">>,
+ part(B, [
+ {<<"content-type">>, <<"application/octet-stream">>},
+ {<<"content-transfer-encoding">>, <<"base64">>}]),
+ <<"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
+ "Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==">>,
+ close(B),
+ <<"epilogue">>
+ ].
+
+horse_build() ->
+ horse:repeat(50000,
+ perf_build_multipart()
+ ).
+-endif.
+
+%% Headers.
+
+%% @doc Convenience function for extracting information from headers
+%% when parsing a multipart/form-data stream.
+
+-spec form_data(headers() | #{binary() => binary()})
+ -> {data, binary()}
+ | {file, binary(), binary(), binary()}.
+form_data(Headers) when is_map(Headers) ->
+ form_data(maps:to_list(Headers));
+form_data(Headers) ->
+ {_, DispositionBin} = lists:keyfind(<<"content-disposition">>, 1, Headers),
+ {<<"form-data">>, Params} = parse_content_disposition(DispositionBin),
+ {_, FieldName} = lists:keyfind(<<"name">>, 1, Params),
+ case lists:keyfind(<<"filename">>, 1, Params) of
+ false ->
+ {data, FieldName};
+ {_, Filename} ->
+ Type = case lists:keyfind(<<"content-type">>, 1, Headers) of
+ false -> <<"text/plain">>;
+ {_, T} -> T
+ end,
+ {file, FieldName, Filename, Type}
+ end.
+
+-ifdef(TEST).
+form_data_test_() ->
+ Tests = [
+ {[{<<"content-disposition">>, <<"form-data; name=\"submit-name\"">>}],
+ {data, <<"submit-name">>}},
+ {[{<<"content-disposition">>,
+ <<"form-data; name=\"files\"; filename=\"file1.txt\"">>},
+ {<<"content-type">>, <<"text/x-plain">>}],
+ {file, <<"files">>, <<"file1.txt">>, <<"text/x-plain">>}}
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> R = form_data(V) end} || {V, R} <- Tests].
+-endif.
+
+%% @todo parse_content_description
+%% @todo parse_content_id
+
+%% @doc Parse an RFC 2183 content-disposition value.
+%% @todo Support RFC 2231.
+
+-spec parse_content_disposition(binary())
+ -> {binary(), [{binary(), binary()}]}.
+parse_content_disposition(Bin) ->
+ parse_cd_type(Bin, <<>>).
+
+parse_cd_type(<<>>, Acc) ->
+ {Acc, []};
+parse_cd_type(<< C, Rest/bits >>, Acc) ->
+ case C of
+ $; -> {Acc, parse_before_param(Rest, [])};
+ $\s -> {Acc, parse_before_param(Rest, [])};
+ $\t -> {Acc, parse_before_param(Rest, [])};
+ _ -> ?LOWER(parse_cd_type, Rest, Acc)
+ end.
+
+-ifdef(TEST).
+parse_content_disposition_test_() ->
+ Tests = [
+ {<<"inline">>, {<<"inline">>, []}},
+ {<<"attachment">>, {<<"attachment">>, []}},
+ {<<"attachment; filename=genome.jpeg;"
+ " modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";">>,
+ {<<"attachment">>, [
+ {<<"filename">>, <<"genome.jpeg">>},
+ {<<"modification-date">>, <<"Wed, 12 Feb 1997 16:29:51 -0500">>}
+ ]}},
+ {<<"form-data; name=\"user\"">>,
+ {<<"form-data">>, [{<<"name">>, <<"user">>}]}},
+ {<<"form-data; NAME=\"submit-name\"">>,
+ {<<"form-data">>, [{<<"name">>, <<"submit-name">>}]}},
+ {<<"form-data; name=\"files\"; filename=\"file1.txt\"">>,
+ {<<"form-data">>, [
+ {<<"name">>, <<"files">>},
+ {<<"filename">>, <<"file1.txt">>}
+ ]}},
+ {<<"file; filename=\"file1.txt\"">>,
+ {<<"file">>, [{<<"filename">>, <<"file1.txt">>}]}},
+ {<<"file; filename=\"file2.gif\"">>,
+ {<<"file">>, [{<<"filename">>, <<"file2.gif">>}]}}
+ ],
+ [{V, fun() -> R = parse_content_disposition(V) end} || {V, R} <- Tests].
+
+horse_parse_content_disposition_attachment() ->
+ horse:repeat(100000,
+ parse_content_disposition(<<"attachment; filename=genome.jpeg;"
+ " modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";">>)
+ ).
+
+horse_parse_content_disposition_form_data() ->
+ horse:repeat(100000,
+ parse_content_disposition(
+ <<"form-data; name=\"files\"; filename=\"file1.txt\"">>)
+ ).
+
+horse_parse_content_disposition_inline() ->
+ horse:repeat(100000,
+ parse_content_disposition(<<"inline">>)
+ ).
+-endif.
+
+%% @doc Parse an RFC 2045 content-transfer-encoding header.
+
+-spec parse_content_transfer_encoding(binary()) -> binary().
+parse_content_transfer_encoding(Bin) ->
+ ?LOWER(Bin).
+
+-ifdef(TEST).
+parse_content_transfer_encoding_test_() ->
+ Tests = [
+ {<<"7bit">>, <<"7bit">>},
+ {<<"7BIT">>, <<"7bit">>},
+ {<<"8bit">>, <<"8bit">>},
+ {<<"binary">>, <<"binary">>},
+ {<<"quoted-printable">>, <<"quoted-printable">>},
+ {<<"base64">>, <<"base64">>},
+ {<<"Base64">>, <<"base64">>},
+ {<<"BASE64">>, <<"base64">>},
+ {<<"bAsE64">>, <<"base64">>}
+ ],
+ [{V, fun() -> R = parse_content_transfer_encoding(V) end}
+ || {V, R} <- Tests].
+
+horse_parse_content_transfer_encoding() ->
+ horse:repeat(100000,
+ parse_content_transfer_encoding(<<"QUOTED-PRINTABLE">>)
+ ).
+-endif.
+
+%% @doc Parse an RFC 2045 content-type header.
+
+-spec parse_content_type(binary())
+ -> {binary(), binary(), [{binary(), binary()}]}.
+parse_content_type(Bin) ->
+ parse_ct_type(Bin, <<>>).
+
+parse_ct_type(<< C, Rest/bits >>, Acc) ->
+ case C of
+ $/ -> parse_ct_subtype(Rest, Acc, <<>>);
+ _ -> ?LOWER(parse_ct_type, Rest, Acc)
+ end.
+
+parse_ct_subtype(<<>>, Type, Subtype) when Subtype =/= <<>> ->
+ {Type, Subtype, []};
+parse_ct_subtype(<< C, Rest/bits >>, Type, Acc) ->
+ case C of
+ $; -> {Type, Acc, parse_before_param(Rest, [])};
+ $\s -> {Type, Acc, parse_before_param(Rest, [])};
+ $\t -> {Type, Acc, parse_before_param(Rest, [])};
+ _ -> ?LOWER(parse_ct_subtype, Rest, Type, Acc)
+ end.
+
+-ifdef(TEST).
+parse_content_type_test_() ->
+ Tests = [
+ {<<"image/gif">>,
+ {<<"image">>, <<"gif">>, []}},
+ {<<"text/plain">>,
+ {<<"text">>, <<"plain">>, []}},
+ {<<"text/plain; charset=us-ascii">>,
+ {<<"text">>, <<"plain">>, [{<<"charset">>, <<"us-ascii">>}]}},
+ {<<"text/plain; charset=\"us-ascii\"">>,
+ {<<"text">>, <<"plain">>, [{<<"charset">>, <<"us-ascii">>}]}},
+ {<<"multipart/form-data; boundary=AaB03x">>,
+ {<<"multipart">>, <<"form-data">>,
+ [{<<"boundary">>, <<"AaB03x">>}]}},
+ {<<"multipart/mixed; boundary=BbC04y">>,
+ {<<"multipart">>, <<"mixed">>, [{<<"boundary">>, <<"BbC04y">>}]}},
+ {<<"multipart/mixed; boundary=--------">>,
+ {<<"multipart">>, <<"mixed">>, [{<<"boundary">>, <<"--------">>}]}},
+ {<<"application/x-horse; filename=genome.jpeg;"
+ " some-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";"
+ " charset=us-ascii; empty=; number=12345">>,
+ {<<"application">>, <<"x-horse">>, [
+ {<<"filename">>, <<"genome.jpeg">>},
+ {<<"some-date">>, <<"Wed, 12 Feb 1997 16:29:51 -0500">>},
+ {<<"charset">>, <<"us-ascii">>},
+ {<<"empty">>, <<>>},
+ {<<"number">>, <<"12345">>}
+ ]}}
+ ],
+ [{V, fun() -> R = parse_content_type(V) end}
+ || {V, R} <- Tests].
+
+horse_parse_content_type_zero() ->
+ horse:repeat(100000,
+ parse_content_type(<<"text/plain">>)
+ ).
+
+horse_parse_content_type_one() ->
+ horse:repeat(100000,
+ parse_content_type(<<"text/plain; charset=\"us-ascii\"">>)
+ ).
+
+horse_parse_content_type_five() ->
+ horse:repeat(100000,
+ parse_content_type(<<"application/x-horse; filename=genome.jpeg;"
+ " some-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";"
+ " charset=us-ascii; empty=; number=12345">>)
+ ).
+-endif.
+
+%% @doc Parse RFC 2045 parameters.
+
+parse_before_param(<<>>, Params) ->
+ lists:reverse(Params);
+parse_before_param(<< C, Rest/bits >>, Params) ->
+ case C of
+ $; -> parse_before_param(Rest, Params);
+ $\s -> parse_before_param(Rest, Params);
+ $\t -> parse_before_param(Rest, Params);
+ _ -> ?LOWER(parse_param_name, Rest, Params, <<>>)
+ end.
+
+parse_param_name(<<>>, Params, Acc) ->
+ lists:reverse([{Acc, <<>>}|Params]);
+parse_param_name(<< C, Rest/bits >>, Params, Acc) ->
+ case C of
+ $= -> parse_param_value(Rest, Params, Acc);
+ _ -> ?LOWER(parse_param_name, Rest, Params, Acc)
+ end.
+
+parse_param_value(<<>>, Params, Name) ->
+ lists:reverse([{Name, <<>>}|Params]);
+parse_param_value(<< C, Rest/bits >>, Params, Name) ->
+ case C of
+ $" -> parse_param_quoted_value(Rest, Params, Name, <<>>);
+ $; -> parse_before_param(Rest, [{Name, <<>>}|Params]);
+ $\s -> parse_before_param(Rest, [{Name, <<>>}|Params]);
+ $\t -> parse_before_param(Rest, [{Name, <<>>}|Params]);
+ C -> parse_param_value(Rest, Params, Name, << C >>)
+ end.
+
+parse_param_value(<<>>, Params, Name, Acc) ->
+ lists:reverse([{Name, Acc}|Params]);
+parse_param_value(<< C, Rest/bits >>, Params, Name, Acc) ->
+ case C of
+ $; -> parse_before_param(Rest, [{Name, Acc}|Params]);
+ $\s -> parse_before_param(Rest, [{Name, Acc}|Params]);
+ $\t -> parse_before_param(Rest, [{Name, Acc}|Params]);
+ C -> parse_param_value(Rest, Params, Name, << Acc/binary, C >>)
+ end.
+
+%% We expect a final $" so no need to test for <<>>.
+parse_param_quoted_value(<< $\\, C, Rest/bits >>, Params, Name, Acc) ->
+ parse_param_quoted_value(Rest, Params, Name, << Acc/binary, C >>);
+parse_param_quoted_value(<< $", Rest/bits >>, Params, Name, Acc) ->
+ parse_before_param(Rest, [{Name, Acc}|Params]);
+parse_param_quoted_value(<< C, Rest/bits >>, Params, Name, Acc)
+ when C =/= $\r ->
+ parse_param_quoted_value(Rest, Params, Name, << Acc/binary, C >>).
diff --git a/server/_build/default/lib/cowlib/src/cow_qs.erl b/server/_build/default/lib/cowlib/src/cow_qs.erl
new file mode 100644
index 0000000..442ecc8
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_qs.erl
@@ -0,0 +1,563 @@
+%% Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_qs).
+
+-export([parse_qs/1]).
+-export([qs/1]).
+-export([urldecode/1]).
+-export([urlencode/1]).
+
+-type qs_vals() :: [{binary(), binary() | true}].
+
+%% @doc Parse an application/x-www-form-urlencoded string.
+%%
+%% The percent decoding is inlined to greatly improve the performance
+%% by avoiding copying binaries twice (once for extracting, once for
+%% decoding) instead of just extracting the proper representation.
+
+-spec parse_qs(binary()) -> qs_vals().
+parse_qs(B) ->
+ parse_qs_name(B, [], <<>>).
+
+parse_qs_name(<< $%, H, L, Rest/bits >>, Acc, Name) ->
+ C = (unhex(H) bsl 4 bor unhex(L)),
+ parse_qs_name(Rest, Acc, << Name/bits, C >>);
+parse_qs_name(<< $+, Rest/bits >>, Acc, Name) ->
+ parse_qs_name(Rest, Acc, << Name/bits, " " >>);
+parse_qs_name(<< $=, Rest/bits >>, Acc, Name) when Name =/= <<>> ->
+ parse_qs_value(Rest, Acc, Name, <<>>);
+parse_qs_name(<< $&, Rest/bits >>, Acc, Name) ->
+ case Name of
+ <<>> -> parse_qs_name(Rest, Acc, <<>>);
+ _ -> parse_qs_name(Rest, [{Name, true}|Acc], <<>>)
+ end;
+parse_qs_name(<< C, Rest/bits >>, Acc, Name) when C =/= $%, C =/= $= ->
+ parse_qs_name(Rest, Acc, << Name/bits, C >>);
+parse_qs_name(<<>>, Acc, Name) ->
+ case Name of
+ <<>> -> lists:reverse(Acc);
+ _ -> lists:reverse([{Name, true}|Acc])
+ end.
+
+parse_qs_value(<< $%, H, L, Rest/bits >>, Acc, Name, Value) ->
+ C = (unhex(H) bsl 4 bor unhex(L)),
+ parse_qs_value(Rest, Acc, Name, << Value/bits, C >>);
+parse_qs_value(<< $+, Rest/bits >>, Acc, Name, Value) ->
+ parse_qs_value(Rest, Acc, Name, << Value/bits, " " >>);
+parse_qs_value(<< $&, Rest/bits >>, Acc, Name, Value) ->
+ parse_qs_name(Rest, [{Name, Value}|Acc], <<>>);
+parse_qs_value(<< C, Rest/bits >>, Acc, Name, Value) when C =/= $% ->
+ parse_qs_value(Rest, Acc, Name, << Value/bits, C >>);
+parse_qs_value(<<>>, Acc, Name, Value) ->
+ lists:reverse([{Name, Value}|Acc]).
+
+-ifdef(TEST).
+parse_qs_test_() ->
+ Tests = [
+ {<<>>, []},
+ {<<"&">>, []},
+ {<<"a">>, [{<<"a">>, true}]},
+ {<<"a&">>, [{<<"a">>, true}]},
+ {<<"&a">>, [{<<"a">>, true}]},
+ {<<"a&b">>, [{<<"a">>, true}, {<<"b">>, true}]},
+ {<<"a&&b">>, [{<<"a">>, true}, {<<"b">>, true}]},
+ {<<"a&b&">>, [{<<"a">>, true}, {<<"b">>, true}]},
+ {<<"=">>, error},
+ {<<"=b">>, error},
+ {<<"a=">>, [{<<"a">>, <<>>}]},
+ {<<"a=b">>, [{<<"a">>, <<"b">>}]},
+ {<<"a=&b=">>, [{<<"a">>, <<>>}, {<<"b">>, <<>>}]},
+ {<<"a=b&c&d=e">>, [{<<"a">>, <<"b">>},
+ {<<"c">>, true}, {<<"d">>, <<"e">>}]},
+ {<<"a=b=c&d=e=f&g=h=i">>, [{<<"a">>, <<"b=c">>},
+ {<<"d">>, <<"e=f">>}, {<<"g">>, <<"h=i">>}]},
+ {<<"+">>, [{<<" ">>, true}]},
+ {<<"+=+">>, [{<<" ">>, <<" ">>}]},
+ {<<"a+b=c+d">>, [{<<"a b">>, <<"c d">>}]},
+ {<<"+a+=+b+&+c+=+d+">>, [{<<" a ">>, <<" b ">>},
+ {<<" c ">>, <<" d ">>}]},
+ {<<"a%20b=c%20d">>, [{<<"a b">>, <<"c d">>}]},
+ {<<"%25%26%3D=%25%26%3D&_-.=.-_">>, [{<<"%&=">>, <<"%&=">>},
+ {<<"_-.">>, <<".-_">>}]},
+ {<<"for=extend%2Franch">>, [{<<"for">>, <<"extend/ranch">>}]}
+ ],
+ [{Qs, fun() ->
+ E = try parse_qs(Qs) of
+ R -> R
+ catch _:_ ->
+ error
+ end
+ end} || {Qs, E} <- Tests].
+
+parse_qs_identity_test_() ->
+ Tests = [
+ <<"+">>,
+ <<"hl=en&q=erlang+cowboy">>,
+ <<"direction=desc&for=extend%2Franch&sort=updated&state=open">>,
+ <<"i=EWiIXmPj5gl6&v=QowBp0oDLQXdd4x_GwiywA&ip=98.20.31.81&"
+ "la=en&pg=New8.undertonebrandsafe.com%2F698a2525065ee2"
+ "60c0b2f2aaad89ab82&re=&sz=1&fc=1&fr=140&br=3&bv=11.0."
+ "696.16&os=3&ov=&rs=vpl&k=cookies%7Csale%7Cbrowser%7Cm"
+ "ore%7Cprivacy%7Cstatistics%7Cactivities%7Cauction%7Ce"
+ "mail%7Cfree%7Cin...&t=112373&xt=5%7C61%7C0&tz=-1&ev=x"
+ "&tk=&za=1&ortb-za=1&zu=&zl=&ax=U&ay=U&ortb-pid=536454"
+ ".55&ortb-sid=112373.8&seats=999&ortb-xt=IAB24&ortb-ugc=">>,
+ <<"i=9pQNskA&v=0ySQQd1F&ev=12345678&t=12345&sz=3&ip=67.58."
+ "236.89&la=en&pg=http%3A%2F%2Fwww.yahoo.com%2Fpage1.ht"
+ "m&re=http%3A%2F%2Fsearch.google.com&fc=1&fr=1&br=2&bv"
+ "=3.0.14&os=1&ov=XP&k=cars%2Cford&rs=js&xt=5%7C22%7C23"
+ "4&tz=%2B180&tk=key1%3Dvalue1%7Ckey2%3Dvalue2&zl=4%2C5"
+ "%2C6&za=4&zu=competitor.com&ua=Mozilla%2F5.0+%28Windo"
+ "ws%3B+U%3B+Windows+NT+6.1%3B+en-US%29+AppleWebKit%2F5"
+ "34.13+%28KHTML%2C+like+Gecko%29+Chrome%2F9.0.597.98+S"
+ "afari%2F534.13&ortb-za=1%2C6%2C13&ortb-pid=521732&ort"
+ "b-sid=521732&ortb-xt=IAB3&ortb-ugc=">>
+ ],
+ [{V, fun() -> V = qs(parse_qs(V)) end} || V <- Tests].
+
+horse_parse_qs_shorter() ->
+ horse:repeat(20000,
+ parse_qs(<<"hl=en&q=erlang%20cowboy">>)
+ ).
+
+horse_parse_qs_short() ->
+ horse:repeat(20000,
+ parse_qs(
+ <<"direction=desc&for=extend%2Franch&sort=updated&state=open">>)
+ ).
+
+horse_parse_qs_long() ->
+ horse:repeat(20000,
+ parse_qs(<<"i=EWiIXmPj5gl6&v=QowBp0oDLQXdd4x_GwiywA&ip=98.20.31.81&"
+ "la=en&pg=New8.undertonebrandsafe.com%2F698a2525065ee260c0b2f2a"
+ "aad89ab82&re=&sz=1&fc=1&fr=140&br=3&bv=11.0.696.16&os=3&ov=&rs"
+ "=vpl&k=cookies%7Csale%7Cbrowser%7Cmore%7Cprivacy%7Cstatistics%"
+ "7Cactivities%7Cauction%7Cemail%7Cfree%7Cin...&t=112373&xt=5%7C"
+ "61%7C0&tz=-1&ev=x&tk=&za=1&ortb-za=1&zu=&zl=&ax=U&ay=U&ortb-pi"
+ "d=536454.55&ortb-sid=112373.8&seats=999&ortb-xt=IAB24&ortb-ugc"
+ "=">>)
+ ).
+
+horse_parse_qs_longer() ->
+ horse:repeat(20000,
+ parse_qs(<<"i=9pQNskA&v=0ySQQd1F&ev=12345678&t=12345&sz=3&ip=67.58."
+ "236.89&la=en&pg=http%3A%2F%2Fwww.yahoo.com%2Fpage1.htm&re=http"
+ "%3A%2F%2Fsearch.google.com&fc=1&fr=1&br=2&bv=3.0.14&os=1&ov=XP"
+ "&k=cars%2cford&rs=js&xt=5%7c22%7c234&tz=%2b180&tk=key1%3Dvalue"
+ "1%7Ckey2%3Dvalue2&zl=4,5,6&za=4&zu=competitor.com&ua=Mozilla%2"
+ "F5.0%20(Windows%3B%20U%3B%20Windows%20NT%206.1%3B%20en-US)%20A"
+ "ppleWebKit%2F534.13%20(KHTML%2C%20like%20Gecko)%20Chrome%2F9.0"
+ ".597.98%20Safari%2F534.13&ortb-za=1%2C6%2C13&ortb-pid=521732&o"
+ "rtb-sid=521732&ortb-xt=IAB3&ortb-ugc=">>)
+ ).
+-endif.
+
+%% @doc Build an application/x-www-form-urlencoded string.
+
+-spec qs(qs_vals()) -> binary().
+qs([]) ->
+ <<>>;
+qs(L) ->
+ qs(L, <<>>).
+
+qs([], Acc) ->
+ << $&, Qs/bits >> = Acc,
+ Qs;
+qs([{Name, true}|Tail], Acc) ->
+ Acc2 = urlencode(Name, << Acc/bits, $& >>),
+ qs(Tail, Acc2);
+qs([{Name, Value}|Tail], Acc) ->
+ Acc2 = urlencode(Name, << Acc/bits, $& >>),
+ Acc3 = urlencode(Value, << Acc2/bits, $= >>),
+ qs(Tail, Acc3).
+
+-define(QS_SHORTER, [
+ {<<"hl">>, <<"en">>},
+ {<<"q">>, <<"erlang cowboy">>}
+]).
+
+-define(QS_SHORT, [
+ {<<"direction">>, <<"desc">>},
+ {<<"for">>, <<"extend/ranch">>},
+ {<<"sort">>, <<"updated">>},
+ {<<"state">>, <<"open">>}
+]).
+
+-define(QS_LONG, [
+ {<<"i">>, <<"EWiIXmPj5gl6">>},
+ {<<"v">>, <<"QowBp0oDLQXdd4x_GwiywA">>},
+ {<<"ip">>, <<"98.20.31.81">>},
+ {<<"la">>, <<"en">>},
+ {<<"pg">>, <<"New8.undertonebrandsafe.com/"
+ "698a2525065ee260c0b2f2aaad89ab82">>},
+ {<<"re">>, <<>>},
+ {<<"sz">>, <<"1">>},
+ {<<"fc">>, <<"1">>},
+ {<<"fr">>, <<"140">>},
+ {<<"br">>, <<"3">>},
+ {<<"bv">>, <<"11.0.696.16">>},
+ {<<"os">>, <<"3">>},
+ {<<"ov">>, <<>>},
+ {<<"rs">>, <<"vpl">>},
+ {<<"k">>, <<"cookies|sale|browser|more|privacy|statistics|"
+ "activities|auction|email|free|in...">>},
+ {<<"t">>, <<"112373">>},
+ {<<"xt">>, <<"5|61|0">>},
+ {<<"tz">>, <<"-1">>},
+ {<<"ev">>, <<"x">>},
+ {<<"tk">>, <<>>},
+ {<<"za">>, <<"1">>},
+ {<<"ortb-za">>, <<"1">>},
+ {<<"zu">>, <<>>},
+ {<<"zl">>, <<>>},
+ {<<"ax">>, <<"U">>},
+ {<<"ay">>, <<"U">>},
+ {<<"ortb-pid">>, <<"536454.55">>},
+ {<<"ortb-sid">>, <<"112373.8">>},
+ {<<"seats">>, <<"999">>},
+ {<<"ortb-xt">>, <<"IAB24">>},
+ {<<"ortb-ugc">>, <<>>}
+]).
+
+-define(QS_LONGER, [
+ {<<"i">>, <<"9pQNskA">>},
+ {<<"v">>, <<"0ySQQd1F">>},
+ {<<"ev">>, <<"12345678">>},
+ {<<"t">>, <<"12345">>},
+ {<<"sz">>, <<"3">>},
+ {<<"ip">>, <<"67.58.236.89">>},
+ {<<"la">>, <<"en">>},
+ {<<"pg">>, <<"http://www.yahoo.com/page1.htm">>},
+ {<<"re">>, <<"http://search.google.com">>},
+ {<<"fc">>, <<"1">>},
+ {<<"fr">>, <<"1">>},
+ {<<"br">>, <<"2">>},
+ {<<"bv">>, <<"3.0.14">>},
+ {<<"os">>, <<"1">>},
+ {<<"ov">>, <<"XP">>},
+ {<<"k">>, <<"cars,ford">>},
+ {<<"rs">>, <<"js">>},
+ {<<"xt">>, <<"5|22|234">>},
+ {<<"tz">>, <<"+180">>},
+ {<<"tk">>, <<"key1=value1|key2=value2">>},
+ {<<"zl">>, <<"4,5,6">>},
+ {<<"za">>, <<"4">>},
+ {<<"zu">>, <<"competitor.com">>},
+ {<<"ua">>, <<"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) "
+ "AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.98 "
+ "Safari/534.13">>},
+ {<<"ortb-za">>, <<"1,6,13">>},
+ {<<"ortb-pid">>, <<"521732">>},
+ {<<"ortb-sid">>, <<"521732">>},
+ {<<"ortb-xt">>, <<"IAB3">>},
+ {<<"ortb-ugc">>, <<>>}
+]).
+
+-ifdef(TEST).
+qs_test_() ->
+ Tests = [
+ {[<<"a">>], error},
+ {[{<<"a">>, <<"b">>, <<"c">>}], error},
+ {[], <<>>},
+ {[{<<"a">>, true}], <<"a">>},
+ {[{<<"a">>, true}, {<<"b">>, true}], <<"a&b">>},
+ {[{<<"a">>, <<>>}], <<"a=">>},
+ {[{<<"a">>, <<"b">>}], <<"a=b">>},
+ {[{<<"a">>, <<>>}, {<<"b">>, <<>>}], <<"a=&b=">>},
+ {[{<<"a">>, <<"b">>}, {<<"c">>, true}, {<<"d">>, <<"e">>}],
+ <<"a=b&c&d=e">>},
+ {[{<<"a">>, <<"b=c">>}, {<<"d">>, <<"e=f">>}, {<<"g">>, <<"h=i">>}],
+ <<"a=b%3Dc&d=e%3Df&g=h%3Di">>},
+ {[{<<" ">>, true}], <<"+">>},
+ {[{<<" ">>, <<" ">>}], <<"+=+">>},
+ {[{<<"a b">>, <<"c d">>}], <<"a+b=c+d">>},
+ {[{<<" a ">>, <<" b ">>}, {<<" c ">>, <<" d ">>}],
+ <<"+a+=+b+&+c+=+d+">>},
+ {[{<<"%&=">>, <<"%&=">>}, {<<"_-.">>, <<".-_">>}],
+ <<"%25%26%3D=%25%26%3D&_-.=.-_">>},
+ {[{<<"for">>, <<"extend/ranch">>}], <<"for=extend%2Franch">>}
+ ],
+ [{lists:flatten(io_lib:format("~p", [Vals])), fun() ->
+ E = try qs(Vals) of
+ R -> R
+ catch _:_ ->
+ error
+ end
+ end} || {Vals, E} <- Tests].
+
+qs_identity_test_() ->
+ Tests = [
+ [{<<"+">>, true}],
+ ?QS_SHORTER,
+ ?QS_SHORT,
+ ?QS_LONG,
+ ?QS_LONGER
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])), fun() ->
+ V = parse_qs(qs(V))
+ end} || V <- Tests].
+
+horse_qs_shorter() ->
+ horse:repeat(20000, qs(?QS_SHORTER)).
+
+horse_qs_short() ->
+ horse:repeat(20000, qs(?QS_SHORT)).
+
+horse_qs_long() ->
+ horse:repeat(20000, qs(?QS_LONG)).
+
+horse_qs_longer() ->
+ horse:repeat(20000, qs(?QS_LONGER)).
+-endif.
+
+%% @doc Decode a percent encoded string (x-www-form-urlencoded rules).
+
+-spec urldecode(B) -> B when B::binary().
+urldecode(B) ->
+ urldecode(B, <<>>).
+
+urldecode(<< $%, H, L, Rest/bits >>, Acc) ->
+ C = (unhex(H) bsl 4 bor unhex(L)),
+ urldecode(Rest, << Acc/bits, C >>);
+urldecode(<< $+, Rest/bits >>, Acc) ->
+ urldecode(Rest, << Acc/bits, " " >>);
+urldecode(<< C, Rest/bits >>, Acc) when C =/= $% ->
+ urldecode(Rest, << Acc/bits, C >>);
+urldecode(<<>>, Acc) ->
+ Acc.
+
+unhex($0) -> 0;
+unhex($1) -> 1;
+unhex($2) -> 2;
+unhex($3) -> 3;
+unhex($4) -> 4;
+unhex($5) -> 5;
+unhex($6) -> 6;
+unhex($7) -> 7;
+unhex($8) -> 8;
+unhex($9) -> 9;
+unhex($A) -> 10;
+unhex($B) -> 11;
+unhex($C) -> 12;
+unhex($D) -> 13;
+unhex($E) -> 14;
+unhex($F) -> 15;
+unhex($a) -> 10;
+unhex($b) -> 11;
+unhex($c) -> 12;
+unhex($d) -> 13;
+unhex($e) -> 14;
+unhex($f) -> 15.
+
+-ifdef(TEST).
+urldecode_test_() ->
+ Tests = [
+ {<<"%20">>, <<" ">>},
+ {<<"+">>, <<" ">>},
+ {<<"%00">>, <<0>>},
+ {<<"%fF">>, <<255>>},
+ {<<"123">>, <<"123">>},
+ {<<"%i5">>, error},
+ {<<"%5">>, error}
+ ],
+ [{Qs, fun() ->
+ E = try urldecode(Qs) of
+ R -> R
+ catch _:_ ->
+ error
+ end
+ end} || {Qs, E} <- Tests].
+
+urldecode_identity_test_() ->
+ Tests = [
+ <<"+">>,
+ <<"nothingnothingnothingnothing">>,
+ <<"Small+fast+modular+HTTP+server">>,
+ <<"Small%2C+fast%2C+modular+HTTP+server.">>,
+ <<"%E3%83%84%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%A6%E3%83"
+ "%AB%E3%80%9C%E8%BC%AA%E5%BB%BB%E3%81%99%E3%82%8B%E6%97%8B%E5"
+ "%BE%8B%E3%80%9C">>
+ ],
+ [{V, fun() -> V = urlencode(urldecode(V)) end} || V <- Tests].
+
+horse_urldecode() ->
+ horse:repeat(100000,
+ urldecode(<<"nothingnothingnothingnothing">>)
+ ).
+
+horse_urldecode_plus() ->
+ horse:repeat(100000,
+ urldecode(<<"Small+fast+modular+HTTP+server">>)
+ ).
+
+horse_urldecode_hex() ->
+ horse:repeat(100000,
+ urldecode(<<"Small%2C%20fast%2C%20modular%20HTTP%20server.">>)
+ ).
+
+horse_urldecode_jp_hex() ->
+ horse:repeat(100000,
+ urldecode(<<"%E3%83%84%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%A6%E3%83"
+ "%AB%E3%80%9C%E8%BC%AA%E5%BB%BB%E3%81%99%E3%82%8B%E6%97%8B%E5"
+ "%BE%8B%E3%80%9C">>)
+ ).
+
+horse_urldecode_mix() ->
+ horse:repeat(100000,
+ urldecode(<<"Small%2C+fast%2C+modular+HTTP+server.">>)
+ ).
+-endif.
+
+%% @doc Percent encode a string (x-www-form-urlencoded rules).
+
+-spec urlencode(B) -> B when B::binary().
+urlencode(B) ->
+ urlencode(B, <<>>).
+
+urlencode(<< $\s, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $+ >>);
+urlencode(<< $-, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $- >>);
+urlencode(<< $., Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $. >>);
+urlencode(<< $0, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $0 >>);
+urlencode(<< $1, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $1 >>);
+urlencode(<< $2, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $2 >>);
+urlencode(<< $3, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $3 >>);
+urlencode(<< $4, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $4 >>);
+urlencode(<< $5, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $5 >>);
+urlencode(<< $6, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $6 >>);
+urlencode(<< $7, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $7 >>);
+urlencode(<< $8, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $8 >>);
+urlencode(<< $9, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $9 >>);
+urlencode(<< $A, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $A >>);
+urlencode(<< $B, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $B >>);
+urlencode(<< $C, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $C >>);
+urlencode(<< $D, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $D >>);
+urlencode(<< $E, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $E >>);
+urlencode(<< $F, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $F >>);
+urlencode(<< $G, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $G >>);
+urlencode(<< $H, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $H >>);
+urlencode(<< $I, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $I >>);
+urlencode(<< $J, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $J >>);
+urlencode(<< $K, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $K >>);
+urlencode(<< $L, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $L >>);
+urlencode(<< $M, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $M >>);
+urlencode(<< $N, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $N >>);
+urlencode(<< $O, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $O >>);
+urlencode(<< $P, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $P >>);
+urlencode(<< $Q, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Q >>);
+urlencode(<< $R, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $R >>);
+urlencode(<< $S, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $S >>);
+urlencode(<< $T, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $T >>);
+urlencode(<< $U, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $U >>);
+urlencode(<< $V, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $V >>);
+urlencode(<< $W, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $W >>);
+urlencode(<< $X, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $X >>);
+urlencode(<< $Y, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Y >>);
+urlencode(<< $Z, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Z >>);
+urlencode(<< $_, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $_ >>);
+urlencode(<< $a, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $a >>);
+urlencode(<< $b, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $b >>);
+urlencode(<< $c, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $c >>);
+urlencode(<< $d, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $d >>);
+urlencode(<< $e, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $e >>);
+urlencode(<< $f, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $f >>);
+urlencode(<< $g, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $g >>);
+urlencode(<< $h, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $h >>);
+urlencode(<< $i, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $i >>);
+urlencode(<< $j, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $j >>);
+urlencode(<< $k, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $k >>);
+urlencode(<< $l, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $l >>);
+urlencode(<< $m, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $m >>);
+urlencode(<< $n, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $n >>);
+urlencode(<< $o, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $o >>);
+urlencode(<< $p, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $p >>);
+urlencode(<< $q, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $q >>);
+urlencode(<< $r, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $r >>);
+urlencode(<< $s, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $s >>);
+urlencode(<< $t, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $t >>);
+urlencode(<< $u, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $u >>);
+urlencode(<< $v, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $v >>);
+urlencode(<< $w, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $w >>);
+urlencode(<< $x, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $x >>);
+urlencode(<< $y, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $y >>);
+urlencode(<< $z, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $z >>);
+urlencode(<< C, Rest/bits >>, Acc) ->
+ H = hex(C bsr 4),
+ L = hex(C band 16#0f),
+ urlencode(Rest, << Acc/bits, $%, H, L >>);
+urlencode(<<>>, Acc) ->
+ Acc.
+
+hex( 0) -> $0;
+hex( 1) -> $1;
+hex( 2) -> $2;
+hex( 3) -> $3;
+hex( 4) -> $4;
+hex( 5) -> $5;
+hex( 6) -> $6;
+hex( 7) -> $7;
+hex( 8) -> $8;
+hex( 9) -> $9;
+hex(10) -> $A;
+hex(11) -> $B;
+hex(12) -> $C;
+hex(13) -> $D;
+hex(14) -> $E;
+hex(15) -> $F.
+
+-ifdef(TEST).
+urlencode_test_() ->
+ Tests = [
+ {<<255, 0>>, <<"%FF%00">>},
+ {<<255, " ">>, <<"%FF+">>},
+ {<<" ">>, <<"+">>},
+ {<<"aBc123">>, <<"aBc123">>},
+ {<<".-_">>, <<".-_">>}
+ ],
+ [{V, fun() -> E = urlencode(V) end} || {V, E} <- Tests].
+
+urlencode_identity_test_() ->
+ Tests = [
+ <<"+">>,
+ <<"nothingnothingnothingnothing">>,
+ <<"Small fast modular HTTP server">>,
+ <<"Small, fast, modular HTTP server.">>,
+ <<227,131,132,227,130,164,227,131,179,227,130,189,227,
+ 130,166,227,131,171,227,128,156,232,188,170,229,187,187,227,
+ 129,153,227,130,139,230,151,139,229,190,139,227,128,156>>
+ ],
+ [{V, fun() -> V = urldecode(urlencode(V)) end} || V <- Tests].
+
+horse_urlencode() ->
+ horse:repeat(100000,
+ urlencode(<<"nothingnothingnothingnothing">>)
+ ).
+
+horse_urlencode_plus() ->
+ horse:repeat(100000,
+ urlencode(<<"Small fast modular HTTP server">>)
+ ).
+
+horse_urlencode_jp() ->
+ horse:repeat(100000,
+ urlencode(<<227,131,132,227,130,164,227,131,179,227,130,189,227,
+ 130,166,227,131,171,227,128,156,232,188,170,229,187,187,227,
+ 129,153,227,130,139,230,151,139,229,190,139,227,128,156>>)
+ ).
+
+horse_urlencode_mix() ->
+ horse:repeat(100000,
+ urlencode(<<"Small, fast, modular HTTP server.">>)
+ ).
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_spdy.erl b/server/_build/default/lib/cowlib/src/cow_spdy.erl
new file mode 100644
index 0000000..e7b4043
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_spdy.erl
@@ -0,0 +1,313 @@
+%% Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_spdy).
+
+%% Zstream.
+-export([deflate_init/0]).
+-export([inflate_init/0]).
+
+%% Parse.
+-export([split/1]).
+-export([parse/2]).
+
+%% Build.
+-export([data/3]).
+-export([syn_stream/12]).
+-export([syn_reply/6]).
+-export([rst_stream/2]).
+-export([settings/2]).
+-export([ping/1]).
+-export([goaway/2]).
+%% @todo headers
+%% @todo window_update
+
+-include("cow_spdy.hrl").
+
+%% Zstream.
+
+deflate_init() ->
+ Zdef = zlib:open(),
+ ok = zlib:deflateInit(Zdef),
+ _ = zlib:deflateSetDictionary(Zdef, ?ZDICT),
+ Zdef.
+
+inflate_init() ->
+ Zinf = zlib:open(),
+ ok = zlib:inflateInit(Zinf),
+ Zinf.
+
+%% Parse.
+
+split(Data = << _:40, Length:24, _/bits >>)
+ when byte_size(Data) >= Length + 8 ->
+ Length2 = Length + 8,
+ << Frame:Length2/binary, Rest/bits >> = Data,
+ {true, Frame, Rest};
+split(_) ->
+ false.
+
+parse(<< 0:1, StreamID:31, 0:7, IsFinFlag:1, _:24, Data/bits >>, _) ->
+ {data, StreamID, from_flag(IsFinFlag), Data};
+parse(<< 1:1, 3:15, 1:16, 0:6, IsUnidirectionalFlag:1, IsFinFlag:1,
+ _:25, StreamID:31, _:1, AssocToStreamID:31, Priority:3, _:5,
+ 0:8, Rest/bits >>, Zinf) ->
+ case parse_headers(Rest, Zinf) of
+ {ok, Headers, [{<<":host">>, Host}, {<<":method">>, Method},
+ {<<":path">>, Path}, {<<":scheme">>, Scheme},
+ {<<":version">>, Version}]} ->
+ {syn_stream, StreamID, AssocToStreamID, from_flag(IsFinFlag),
+ from_flag(IsUnidirectionalFlag), Priority, Method,
+ Scheme, Host, Path, Version, Headers};
+ _ ->
+ {error, badprotocol}
+ end;
+parse(<< 1:1, 3:15, 2:16, 0:7, IsFinFlag:1, _:25,
+ StreamID:31, Rest/bits >>, Zinf) ->
+ case parse_headers(Rest, Zinf) of
+ {ok, Headers, [{<<":status">>, Status}, {<<":version">>, Version}]} ->
+ {syn_reply, StreamID, from_flag(IsFinFlag),
+ Status, Version, Headers};
+ _ ->
+ {error, badprotocol}
+ end;
+parse(<< 1:1, 3:15, 3:16, 0:8, _:56, StatusCode:32 >>, _)
+ when StatusCode =:= 0; StatusCode > 11 ->
+ {error, badprotocol};
+parse(<< 1:1, 3:15, 3:16, 0:8, _:25, StreamID:31, StatusCode:32 >>, _) ->
+ Status = case StatusCode of
+ 1 -> protocol_error;
+ 2 -> invalid_stream;
+ 3 -> refused_stream;
+ 4 -> unsupported_version;
+ 5 -> cancel;
+ 6 -> internal_error;
+ 7 -> flow_control_error;
+ 8 -> stream_in_use;
+ 9 -> stream_already_closed;
+ 10 -> invalid_credentials;
+ 11 -> frame_too_large
+ end,
+ {rst_stream, StreamID, Status};
+parse(<< 1:1, 3:15, 4:16, 0:7, ClearSettingsFlag:1, _:24,
+ NbEntries:32, Rest/bits >>, _) ->
+ try
+ Settings = [begin
+ Is0 = 0,
+ Key = case ID of
+ 1 -> upload_bandwidth;
+ 2 -> download_bandwidth;
+ 3 -> round_trip_time;
+ 4 -> max_concurrent_streams;
+ 5 -> current_cwnd;
+ 6 -> download_retrans_rate;
+ 7 -> initial_window_size;
+ 8 -> client_certificate_vector_size
+ end,
+ {Key, Value, from_flag(PersistFlag), from_flag(WasPersistedFlag)}
+ end || << Is0:6, WasPersistedFlag:1, PersistFlag:1,
+ ID:24, Value:32 >> <= Rest],
+ NbEntries = length(Settings),
+ {settings, from_flag(ClearSettingsFlag), Settings}
+ catch _:_ ->
+ {error, badprotocol}
+ end;
+parse(<< 1:1, 3:15, 6:16, 0:8, _:24, PingID:32 >>, _) ->
+ {ping, PingID};
+parse(<< 1:1, 3:15, 7:16, 0:8, _:56, StatusCode:32 >>, _)
+ when StatusCode > 2 ->
+ {error, badprotocol};
+parse(<< 1:1, 3:15, 7:16, 0:8, _:25, LastGoodStreamID:31,
+ StatusCode:32 >>, _) ->
+ Status = case StatusCode of
+ 0 -> ok;
+ 1 -> protocol_error;
+ 2 -> internal_error
+ end,
+ {goaway, LastGoodStreamID, Status};
+parse(<< 1:1, 3:15, 8:16, 0:7, IsFinFlag:1, _:25, StreamID:31,
+ Rest/bits >>, Zinf) ->
+ case parse_headers(Rest, Zinf) of
+ {ok, Headers, []} ->
+ {headers, StreamID, from_flag(IsFinFlag), Headers};
+ _ ->
+ {error, badprotocol}
+ end;
+parse(<< 1:1, 3:15, 9:16, 0:8, _:57, 0:31 >>, _) ->
+ {error, badprotocol};
+parse(<< 1:1, 3:15, 9:16, 0:8, _:25, StreamID:31,
+ _:1, DeltaWindowSize:31 >>, _) ->
+ {window_update, StreamID, DeltaWindowSize};
+parse(_, _) ->
+ {error, badprotocol}.
+
+parse_headers(Data, Zinf) ->
+ [<< NbHeaders:32, Rest/bits >>] = inflate(Zinf, Data),
+ parse_headers(Rest, NbHeaders, [], []).
+
+parse_headers(<<>>, 0, Headers, SpHeaders) ->
+ {ok, lists:reverse(Headers), lists:sort(SpHeaders)};
+parse_headers(<<>>, _, _, _) ->
+ error;
+parse_headers(_, 0, _, _) ->
+ error;
+parse_headers(<< 0:32, _/bits >>, _, _, _) ->
+ error;
+parse_headers(<< L1:32, Key:L1/binary, L2:32, Value:L2/binary, Rest/bits >>,
+ NbHeaders, Acc, SpAcc) ->
+ case Key of
+ << $:, _/bits >> ->
+ parse_headers(Rest, NbHeaders - 1, Acc,
+ lists:keystore(Key, 1, SpAcc, {Key, Value}));
+ _ ->
+ parse_headers(Rest, NbHeaders - 1, [{Key, Value}|Acc], SpAcc)
+ end.
+
+inflate(Zinf, Data) ->
+ try
+ zlib:inflate(Zinf, Data)
+ catch _:_ ->
+ ok = zlib:inflateSetDictionary(Zinf, ?ZDICT),
+ zlib:inflate(Zinf, <<>>)
+ end.
+
+from_flag(0) -> false;
+from_flag(1) -> true.
+
+%% Build.
+
+data(StreamID, IsFin, Data) ->
+ IsFinFlag = to_flag(IsFin),
+ Length = iolist_size(Data),
+ [<< 0:1, StreamID:31, 0:7, IsFinFlag:1, Length:24 >>, Data].
+
+syn_stream(Zdef, StreamID, AssocToStreamID, IsFin, IsUnidirectional,
+ Priority, Method, Scheme, Host, Path, Version, Headers) ->
+ IsFinFlag = to_flag(IsFin),
+ IsUnidirectionalFlag = to_flag(IsUnidirectional),
+ HeaderBlock = build_headers(Zdef, [
+ {<<":method">>, Method},
+ {<<":scheme">>, Scheme},
+ {<<":host">>, Host},
+ {<<":path">>, Path},
+ {<<":version">>, Version}
+ |Headers]),
+ Length = 10 + iolist_size(HeaderBlock),
+ [<< 1:1, 3:15, 1:16, 0:6, IsUnidirectionalFlag:1, IsFinFlag:1,
+ Length:24, 0:1, StreamID:31, 0:1, AssocToStreamID:31,
+ Priority:3, 0:5, 0:8 >>, HeaderBlock].
+
+syn_reply(Zdef, StreamID, IsFin, Status, Version, Headers) ->
+ IsFinFlag = to_flag(IsFin),
+ HeaderBlock = build_headers(Zdef, [
+ {<<":status">>, Status},
+ {<<":version">>, Version}
+ |Headers]),
+ Length = 4 + iolist_size(HeaderBlock),
+ [<< 1:1, 3:15, 2:16, 0:7, IsFinFlag:1, Length:24,
+ 0:1, StreamID:31 >>, HeaderBlock].
+
+rst_stream(StreamID, Status) ->
+ StatusCode = case Status of
+ protocol_error -> 1;
+ invalid_stream -> 2;
+ refused_stream -> 3;
+ unsupported_version -> 4;
+ cancel -> 5;
+ internal_error -> 6;
+ flow_control_error -> 7;
+ stream_in_use -> 8;
+ stream_already_closed -> 9;
+ invalid_credentials -> 10;
+ frame_too_large -> 11
+ end,
+ << 1:1, 3:15, 3:16, 0:8, 8:24,
+ 0:1, StreamID:31, StatusCode:32 >>.
+
+settings(ClearSettingsFlag, Settings) ->
+ IsClearSettingsFlag = to_flag(ClearSettingsFlag),
+ NbEntries = length(Settings),
+ Entries = [begin
+ IsWasPersistedFlag = to_flag(WasPersistedFlag),
+ IsPersistFlag = to_flag(PersistFlag),
+ ID = case Key of
+ upload_bandwidth -> 1;
+ download_bandwidth -> 2;
+ round_trip_time -> 3;
+ max_concurrent_streams -> 4;
+ current_cwnd -> 5;
+ download_retrans_rate -> 6;
+ initial_window_size -> 7;
+ client_certificate_vector_size -> 8
+ end,
+ << 0:6, IsWasPersistedFlag:1, IsPersistFlag:1, ID:24, Value:32 >>
+ end || {Key, Value, WasPersistedFlag, PersistFlag} <- Settings],
+ Length = 4 + iolist_size(Entries),
+ [<< 1:1, 3:15, 4:16, 0:7, IsClearSettingsFlag:1, Length:24,
+ NbEntries:32 >>, Entries].
+
+-ifdef(TEST).
+settings_frame_test() ->
+ ClearSettingsFlag = false,
+ Settings = [{max_concurrent_streams,1000,false,false},
+ {initial_window_size,10485760,false,false}],
+ Bin = list_to_binary(cow_spdy:settings(ClearSettingsFlag, Settings)),
+ P = cow_spdy:parse(Bin, undefined),
+ P = {settings, ClearSettingsFlag, Settings},
+ ok.
+-endif.
+
+ping(PingID) ->
+ << 1:1, 3:15, 6:16, 0:8, 4:24, PingID:32 >>.
+
+goaway(LastGoodStreamID, Status) ->
+ StatusCode = case Status of
+ ok -> 0;
+ protocol_error -> 1;
+ internal_error -> 2
+ end,
+ << 1:1, 3:15, 7:16, 0:8, 8:24,
+ 0:1, LastGoodStreamID:31, StatusCode:32 >>.
+
+%% @todo headers
+%% @todo window_update
+
+build_headers(Zdef, Headers) ->
+ Headers1 = merge_headers(lists:sort(Headers), []),
+ NbHeaders = length(Headers1),
+ Headers2 = [begin
+ L1 = iolist_size(Key),
+ L2 = iolist_size(Value),
+ [<< L1:32 >>, Key, << L2:32 >>, Value]
+ end || {Key, Value} <- Headers1],
+ zlib:deflate(Zdef, [<< NbHeaders:32 >>, Headers2], full).
+
+merge_headers([], Acc) ->
+ lists:reverse(Acc);
+merge_headers([{Name, Value1}, {Name, Value2}|Tail], Acc) ->
+ merge_headers([{Name, [Value1, 0, Value2]}|Tail], Acc);
+merge_headers([Head|Tail], Acc) ->
+ merge_headers(Tail, [Head|Acc]).
+
+-ifdef(TEST).
+merge_headers_test_() ->
+ Tests = [
+ {[{<<"set-cookie">>, <<"session=123">>}, {<<"set-cookie">>, <<"other=456">>}, {<<"content-type">>, <<"text/html">>}],
+ [{<<"set-cookie">>, [<<"session=123">>, 0, <<"other=456">>]}, {<<"content-type">>, <<"text/html">>}]}
+ ],
+ [fun() -> D = merge_headers(R, []) end || {R, D} <- Tests].
+-endif.
+
+to_flag(false) -> 0;
+to_flag(true) -> 1.
diff --git a/server/_build/default/lib/cowlib/src/cow_spdy.hrl b/server/_build/default/lib/cowlib/src/cow_spdy.hrl
new file mode 100644
index 0000000..9637b1c
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_spdy.hrl
@@ -0,0 +1,181 @@
+%% Zlib dictionary.
+
+-define(ZDICT, <<
+ 16#00, 16#00, 16#00, 16#07, 16#6f, 16#70, 16#74, 16#69,
+ 16#6f, 16#6e, 16#73, 16#00, 16#00, 16#00, 16#04, 16#68,
+ 16#65, 16#61, 16#64, 16#00, 16#00, 16#00, 16#04, 16#70,
+ 16#6f, 16#73, 16#74, 16#00, 16#00, 16#00, 16#03, 16#70,
+ 16#75, 16#74, 16#00, 16#00, 16#00, 16#06, 16#64, 16#65,
+ 16#6c, 16#65, 16#74, 16#65, 16#00, 16#00, 16#00, 16#05,
+ 16#74, 16#72, 16#61, 16#63, 16#65, 16#00, 16#00, 16#00,
+ 16#06, 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#00,
+ 16#00, 16#00, 16#0e, 16#61, 16#63, 16#63, 16#65, 16#70,
+ 16#74, 16#2d, 16#63, 16#68, 16#61, 16#72, 16#73, 16#65,
+ 16#74, 16#00, 16#00, 16#00, 16#0f, 16#61, 16#63, 16#63,
+ 16#65, 16#70, 16#74, 16#2d, 16#65, 16#6e, 16#63, 16#6f,
+ 16#64, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#0f,
+ 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#2d, 16#6c,
+ 16#61, 16#6e, 16#67, 16#75, 16#61, 16#67, 16#65, 16#00,
+ 16#00, 16#00, 16#0d, 16#61, 16#63, 16#63, 16#65, 16#70,
+ 16#74, 16#2d, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#73,
+ 16#00, 16#00, 16#00, 16#03, 16#61, 16#67, 16#65, 16#00,
+ 16#00, 16#00, 16#05, 16#61, 16#6c, 16#6c, 16#6f, 16#77,
+ 16#00, 16#00, 16#00, 16#0d, 16#61, 16#75, 16#74, 16#68,
+ 16#6f, 16#72, 16#69, 16#7a, 16#61, 16#74, 16#69, 16#6f,
+ 16#6e, 16#00, 16#00, 16#00, 16#0d, 16#63, 16#61, 16#63,
+ 16#68, 16#65, 16#2d, 16#63, 16#6f, 16#6e, 16#74, 16#72,
+ 16#6f, 16#6c, 16#00, 16#00, 16#00, 16#0a, 16#63, 16#6f,
+ 16#6e, 16#6e, 16#65, 16#63, 16#74, 16#69, 16#6f, 16#6e,
+ 16#00, 16#00, 16#00, 16#0c, 16#63, 16#6f, 16#6e, 16#74,
+ 16#65, 16#6e, 16#74, 16#2d, 16#62, 16#61, 16#73, 16#65,
+ 16#00, 16#00, 16#00, 16#10, 16#63, 16#6f, 16#6e, 16#74,
+ 16#65, 16#6e, 16#74, 16#2d, 16#65, 16#6e, 16#63, 16#6f,
+ 16#64, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#10,
+ 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d,
+ 16#6c, 16#61, 16#6e, 16#67, 16#75, 16#61, 16#67, 16#65,
+ 16#00, 16#00, 16#00, 16#0e, 16#63, 16#6f, 16#6e, 16#74,
+ 16#65, 16#6e, 16#74, 16#2d, 16#6c, 16#65, 16#6e, 16#67,
+ 16#74, 16#68, 16#00, 16#00, 16#00, 16#10, 16#63, 16#6f,
+ 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#6c, 16#6f,
+ 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00,
+ 16#00, 16#0b, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e,
+ 16#74, 16#2d, 16#6d, 16#64, 16#35, 16#00, 16#00, 16#00,
+ 16#0d, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74,
+ 16#2d, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#00, 16#00,
+ 16#00, 16#0c, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e,
+ 16#74, 16#2d, 16#74, 16#79, 16#70, 16#65, 16#00, 16#00,
+ 16#00, 16#04, 16#64, 16#61, 16#74, 16#65, 16#00, 16#00,
+ 16#00, 16#04, 16#65, 16#74, 16#61, 16#67, 16#00, 16#00,
+ 16#00, 16#06, 16#65, 16#78, 16#70, 16#65, 16#63, 16#74,
+ 16#00, 16#00, 16#00, 16#07, 16#65, 16#78, 16#70, 16#69,
+ 16#72, 16#65, 16#73, 16#00, 16#00, 16#00, 16#04, 16#66,
+ 16#72, 16#6f, 16#6d, 16#00, 16#00, 16#00, 16#04, 16#68,
+ 16#6f, 16#73, 16#74, 16#00, 16#00, 16#00, 16#08, 16#69,
+ 16#66, 16#2d, 16#6d, 16#61, 16#74, 16#63, 16#68, 16#00,
+ 16#00, 16#00, 16#11, 16#69, 16#66, 16#2d, 16#6d, 16#6f,
+ 16#64, 16#69, 16#66, 16#69, 16#65, 16#64, 16#2d, 16#73,
+ 16#69, 16#6e, 16#63, 16#65, 16#00, 16#00, 16#00, 16#0d,
+ 16#69, 16#66, 16#2d, 16#6e, 16#6f, 16#6e, 16#65, 16#2d,
+ 16#6d, 16#61, 16#74, 16#63, 16#68, 16#00, 16#00, 16#00,
+ 16#08, 16#69, 16#66, 16#2d, 16#72, 16#61, 16#6e, 16#67,
+ 16#65, 16#00, 16#00, 16#00, 16#13, 16#69, 16#66, 16#2d,
+ 16#75, 16#6e, 16#6d, 16#6f, 16#64, 16#69, 16#66, 16#69,
+ 16#65, 16#64, 16#2d, 16#73, 16#69, 16#6e, 16#63, 16#65,
+ 16#00, 16#00, 16#00, 16#0d, 16#6c, 16#61, 16#73, 16#74,
+ 16#2d, 16#6d, 16#6f, 16#64, 16#69, 16#66, 16#69, 16#65,
+ 16#64, 16#00, 16#00, 16#00, 16#08, 16#6c, 16#6f, 16#63,
+ 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00,
+ 16#0c, 16#6d, 16#61, 16#78, 16#2d, 16#66, 16#6f, 16#72,
+ 16#77, 16#61, 16#72, 16#64, 16#73, 16#00, 16#00, 16#00,
+ 16#06, 16#70, 16#72, 16#61, 16#67, 16#6d, 16#61, 16#00,
+ 16#00, 16#00, 16#12, 16#70, 16#72, 16#6f, 16#78, 16#79,
+ 16#2d, 16#61, 16#75, 16#74, 16#68, 16#65, 16#6e, 16#74,
+ 16#69, 16#63, 16#61, 16#74, 16#65, 16#00, 16#00, 16#00,
+ 16#13, 16#70, 16#72, 16#6f, 16#78, 16#79, 16#2d, 16#61,
+ 16#75, 16#74, 16#68, 16#6f, 16#72, 16#69, 16#7a, 16#61,
+ 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#05,
+ 16#72, 16#61, 16#6e, 16#67, 16#65, 16#00, 16#00, 16#00,
+ 16#07, 16#72, 16#65, 16#66, 16#65, 16#72, 16#65, 16#72,
+ 16#00, 16#00, 16#00, 16#0b, 16#72, 16#65, 16#74, 16#72,
+ 16#79, 16#2d, 16#61, 16#66, 16#74, 16#65, 16#72, 16#00,
+ 16#00, 16#00, 16#06, 16#73, 16#65, 16#72, 16#76, 16#65,
+ 16#72, 16#00, 16#00, 16#00, 16#02, 16#74, 16#65, 16#00,
+ 16#00, 16#00, 16#07, 16#74, 16#72, 16#61, 16#69, 16#6c,
+ 16#65, 16#72, 16#00, 16#00, 16#00, 16#11, 16#74, 16#72,
+ 16#61, 16#6e, 16#73, 16#66, 16#65, 16#72, 16#2d, 16#65,
+ 16#6e, 16#63, 16#6f, 16#64, 16#69, 16#6e, 16#67, 16#00,
+ 16#00, 16#00, 16#07, 16#75, 16#70, 16#67, 16#72, 16#61,
+ 16#64, 16#65, 16#00, 16#00, 16#00, 16#0a, 16#75, 16#73,
+ 16#65, 16#72, 16#2d, 16#61, 16#67, 16#65, 16#6e, 16#74,
+ 16#00, 16#00, 16#00, 16#04, 16#76, 16#61, 16#72, 16#79,
+ 16#00, 16#00, 16#00, 16#03, 16#76, 16#69, 16#61, 16#00,
+ 16#00, 16#00, 16#07, 16#77, 16#61, 16#72, 16#6e, 16#69,
+ 16#6e, 16#67, 16#00, 16#00, 16#00, 16#10, 16#77, 16#77,
+ 16#77, 16#2d, 16#61, 16#75, 16#74, 16#68, 16#65, 16#6e,
+ 16#74, 16#69, 16#63, 16#61, 16#74, 16#65, 16#00, 16#00,
+ 16#00, 16#06, 16#6d, 16#65, 16#74, 16#68, 16#6f, 16#64,
+ 16#00, 16#00, 16#00, 16#03, 16#67, 16#65, 16#74, 16#00,
+ 16#00, 16#00, 16#06, 16#73, 16#74, 16#61, 16#74, 16#75,
+ 16#73, 16#00, 16#00, 16#00, 16#06, 16#32, 16#30, 16#30,
+ 16#20, 16#4f, 16#4b, 16#00, 16#00, 16#00, 16#07, 16#76,
+ 16#65, 16#72, 16#73, 16#69, 16#6f, 16#6e, 16#00, 16#00,
+ 16#00, 16#08, 16#48, 16#54, 16#54, 16#50, 16#2f, 16#31,
+ 16#2e, 16#31, 16#00, 16#00, 16#00, 16#03, 16#75, 16#72,
+ 16#6c, 16#00, 16#00, 16#00, 16#06, 16#70, 16#75, 16#62,
+ 16#6c, 16#69, 16#63, 16#00, 16#00, 16#00, 16#0a, 16#73,
+ 16#65, 16#74, 16#2d, 16#63, 16#6f, 16#6f, 16#6b, 16#69,
+ 16#65, 16#00, 16#00, 16#00, 16#0a, 16#6b, 16#65, 16#65,
+ 16#70, 16#2d, 16#61, 16#6c, 16#69, 16#76, 16#65, 16#00,
+ 16#00, 16#00, 16#06, 16#6f, 16#72, 16#69, 16#67, 16#69,
+ 16#6e, 16#31, 16#30, 16#30, 16#31, 16#30, 16#31, 16#32,
+ 16#30, 16#31, 16#32, 16#30, 16#32, 16#32, 16#30, 16#35,
+ 16#32, 16#30, 16#36, 16#33, 16#30, 16#30, 16#33, 16#30,
+ 16#32, 16#33, 16#30, 16#33, 16#33, 16#30, 16#34, 16#33,
+ 16#30, 16#35, 16#33, 16#30, 16#36, 16#33, 16#30, 16#37,
+ 16#34, 16#30, 16#32, 16#34, 16#30, 16#35, 16#34, 16#30,
+ 16#36, 16#34, 16#30, 16#37, 16#34, 16#30, 16#38, 16#34,
+ 16#30, 16#39, 16#34, 16#31, 16#30, 16#34, 16#31, 16#31,
+ 16#34, 16#31, 16#32, 16#34, 16#31, 16#33, 16#34, 16#31,
+ 16#34, 16#34, 16#31, 16#35, 16#34, 16#31, 16#36, 16#34,
+ 16#31, 16#37, 16#35, 16#30, 16#32, 16#35, 16#30, 16#34,
+ 16#35, 16#30, 16#35, 16#32, 16#30, 16#33, 16#20, 16#4e,
+ 16#6f, 16#6e, 16#2d, 16#41, 16#75, 16#74, 16#68, 16#6f,
+ 16#72, 16#69, 16#74, 16#61, 16#74, 16#69, 16#76, 16#65,
+ 16#20, 16#49, 16#6e, 16#66, 16#6f, 16#72, 16#6d, 16#61,
+ 16#74, 16#69, 16#6f, 16#6e, 16#32, 16#30, 16#34, 16#20,
+ 16#4e, 16#6f, 16#20, 16#43, 16#6f, 16#6e, 16#74, 16#65,
+ 16#6e, 16#74, 16#33, 16#30, 16#31, 16#20, 16#4d, 16#6f,
+ 16#76, 16#65, 16#64, 16#20, 16#50, 16#65, 16#72, 16#6d,
+ 16#61, 16#6e, 16#65, 16#6e, 16#74, 16#6c, 16#79, 16#34,
+ 16#30, 16#30, 16#20, 16#42, 16#61, 16#64, 16#20, 16#52,
+ 16#65, 16#71, 16#75, 16#65, 16#73, 16#74, 16#34, 16#30,
+ 16#31, 16#20, 16#55, 16#6e, 16#61, 16#75, 16#74, 16#68,
+ 16#6f, 16#72, 16#69, 16#7a, 16#65, 16#64, 16#34, 16#30,
+ 16#33, 16#20, 16#46, 16#6f, 16#72, 16#62, 16#69, 16#64,
+ 16#64, 16#65, 16#6e, 16#34, 16#30, 16#34, 16#20, 16#4e,
+ 16#6f, 16#74, 16#20, 16#46, 16#6f, 16#75, 16#6e, 16#64,
+ 16#35, 16#30, 16#30, 16#20, 16#49, 16#6e, 16#74, 16#65,
+ 16#72, 16#6e, 16#61, 16#6c, 16#20, 16#53, 16#65, 16#72,
+ 16#76, 16#65, 16#72, 16#20, 16#45, 16#72, 16#72, 16#6f,
+ 16#72, 16#35, 16#30, 16#31, 16#20, 16#4e, 16#6f, 16#74,
+ 16#20, 16#49, 16#6d, 16#70, 16#6c, 16#65, 16#6d, 16#65,
+ 16#6e, 16#74, 16#65, 16#64, 16#35, 16#30, 16#33, 16#20,
+ 16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65, 16#20,
+ 16#55, 16#6e, 16#61, 16#76, 16#61, 16#69, 16#6c, 16#61,
+ 16#62, 16#6c, 16#65, 16#4a, 16#61, 16#6e, 16#20, 16#46,
+ 16#65, 16#62, 16#20, 16#4d, 16#61, 16#72, 16#20, 16#41,
+ 16#70, 16#72, 16#20, 16#4d, 16#61, 16#79, 16#20, 16#4a,
+ 16#75, 16#6e, 16#20, 16#4a, 16#75, 16#6c, 16#20, 16#41,
+ 16#75, 16#67, 16#20, 16#53, 16#65, 16#70, 16#74, 16#20,
+ 16#4f, 16#63, 16#74, 16#20, 16#4e, 16#6f, 16#76, 16#20,
+ 16#44, 16#65, 16#63, 16#20, 16#30, 16#30, 16#3a, 16#30,
+ 16#30, 16#3a, 16#30, 16#30, 16#20, 16#4d, 16#6f, 16#6e,
+ 16#2c, 16#20, 16#54, 16#75, 16#65, 16#2c, 16#20, 16#57,
+ 16#65, 16#64, 16#2c, 16#20, 16#54, 16#68, 16#75, 16#2c,
+ 16#20, 16#46, 16#72, 16#69, 16#2c, 16#20, 16#53, 16#61,
+ 16#74, 16#2c, 16#20, 16#53, 16#75, 16#6e, 16#2c, 16#20,
+ 16#47, 16#4d, 16#54, 16#63, 16#68, 16#75, 16#6e, 16#6b,
+ 16#65, 16#64, 16#2c, 16#74, 16#65, 16#78, 16#74, 16#2f,
+ 16#68, 16#74, 16#6d, 16#6c, 16#2c, 16#69, 16#6d, 16#61,
+ 16#67, 16#65, 16#2f, 16#70, 16#6e, 16#67, 16#2c, 16#69,
+ 16#6d, 16#61, 16#67, 16#65, 16#2f, 16#6a, 16#70, 16#67,
+ 16#2c, 16#69, 16#6d, 16#61, 16#67, 16#65, 16#2f, 16#67,
+ 16#69, 16#66, 16#2c, 16#61, 16#70, 16#70, 16#6c, 16#69,
+ 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#2f, 16#78,
+ 16#6d, 16#6c, 16#2c, 16#61, 16#70, 16#70, 16#6c, 16#69,
+ 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#2f, 16#78,
+ 16#68, 16#74, 16#6d, 16#6c, 16#2b, 16#78, 16#6d, 16#6c,
+ 16#2c, 16#74, 16#65, 16#78, 16#74, 16#2f, 16#70, 16#6c,
+ 16#61, 16#69, 16#6e, 16#2c, 16#74, 16#65, 16#78, 16#74,
+ 16#2f, 16#6a, 16#61, 16#76, 16#61, 16#73, 16#63, 16#72,
+ 16#69, 16#70, 16#74, 16#2c, 16#70, 16#75, 16#62, 16#6c,
+ 16#69, 16#63, 16#70, 16#72, 16#69, 16#76, 16#61, 16#74,
+ 16#65, 16#6d, 16#61, 16#78, 16#2d, 16#61, 16#67, 16#65,
+ 16#3d, 16#67, 16#7a, 16#69, 16#70, 16#2c, 16#64, 16#65,
+ 16#66, 16#6c, 16#61, 16#74, 16#65, 16#2c, 16#73, 16#64,
+ 16#63, 16#68, 16#63, 16#68, 16#61, 16#72, 16#73, 16#65,
+ 16#74, 16#3d, 16#75, 16#74, 16#66, 16#2d, 16#38, 16#63,
+ 16#68, 16#61, 16#72, 16#73, 16#65, 16#74, 16#3d, 16#69,
+ 16#73, 16#6f, 16#2d, 16#38, 16#38, 16#35, 16#39, 16#2d,
+ 16#31, 16#2c, 16#75, 16#74, 16#66, 16#2d, 16#2c, 16#2a,
+ 16#2c, 16#65, 16#6e, 16#71, 16#3d, 16#30, 16#2e >>).
diff --git a/server/_build/default/lib/cowlib/src/cow_sse.erl b/server/_build/default/lib/cowlib/src/cow_sse.erl
new file mode 100644
index 0000000..6e7081f
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_sse.erl
@@ -0,0 +1,349 @@
+%% Copyright (c) 2017-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_sse).
+
+-export([init/0]).
+-export([parse/2]).
+-export([events/1]).
+-export([event/1]).
+
+-record(state, {
+ state_name = bom :: bom | events,
+ buffer = <<>> :: binary(),
+ last_event_id = <<>> :: binary(),
+ last_event_id_set = false :: boolean(),
+ event_type = <<>> :: binary(),
+ data = [] :: iolist(),
+ retry = undefined :: undefined | non_neg_integer()
+}).
+-type state() :: #state{}.
+-export_type([state/0]).
+
+-type parsed_event() :: #{
+ last_event_id := binary(),
+ event_type := binary(),
+ data := iolist()
+}.
+
+-type event() :: #{
+ comment => iodata(),
+ data => iodata(),
+ event => iodata() | atom(),
+ id => iodata(),
+ retry => non_neg_integer()
+}.
+-export_type([event/0]).
+
+-spec init() -> state().
+init() ->
+ #state{}.
+
+%% @todo Add a function to retrieve the retry value from the state.
+
+-spec parse(binary(), State)
+ -> {event, parsed_event(), State} | {more, State}
+ when State::state().
+parse(Data0, State=#state{state_name=bom, buffer=Buffer}) ->
+ Data1 = case Buffer of
+ <<>> -> Data0;
+ _ -> << Buffer/binary, Data0/binary >>
+ end,
+ case Data1 of
+ %% Skip the BOM.
+ << 16#fe, 16#ff, Data/bits >> ->
+ parse_event(Data, State#state{state_name=events, buffer= <<>>});
+ %% Not enough data to know wether we have a BOM.
+ << 16#fe >> ->
+ {more, State#state{buffer=Data1}};
+ <<>> ->
+ {more, State};
+ %% No BOM.
+ _ ->
+ parse_event(Data1, State#state{state_name=events, buffer= <<>>})
+ end;
+%% Try to process data from the buffer if there is no new input.
+parse(<<>>, State=#state{buffer=Buffer}) ->
+ parse_event(Buffer, State#state{buffer= <<>>});
+%% Otherwise process the input data as-is.
+parse(Data0, State=#state{buffer=Buffer}) ->
+ Data = case Buffer of
+ <<>> -> Data0;
+ _ -> << Buffer/binary, Data0/binary >>
+ end,
+ parse_event(Data, State).
+
+parse_event(Data, State0) ->
+ case binary:split(Data, [<<"\r\n">>, <<"\r">>, <<"\n">>]) of
+ [Line, Rest] ->
+ case parse_line(Line, State0) of
+ {ok, State} ->
+ parse_event(Rest, State);
+ {event, Event, State} ->
+ {event, Event, State#state{buffer=Rest}}
+ end;
+ [_] ->
+ {more, State0#state{buffer=Data}}
+ end.
+
+%% Dispatch events on empty line.
+parse_line(<<>>, State) ->
+ dispatch_event(State);
+%% Ignore comments.
+parse_line(<< $:, _/bits >>, State) ->
+ {ok, State};
+%% Normal line.
+parse_line(Line, State) ->
+ case binary:split(Line, [<<":\s">>, <<":">>]) of
+ [Field, Value] ->
+ process_field(Field, Value, State);
+ [Field] ->
+ process_field(Field, <<>>, State)
+ end.
+
+process_field(<<"event">>, Value, State) ->
+ {ok, State#state{event_type=Value}};
+process_field(<<"data">>, Value, State=#state{data=Data}) ->
+ {ok, State#state{data=[<<$\n>>, Value|Data]}};
+process_field(<<"id">>, Value, State) ->
+ {ok, State#state{last_event_id=Value, last_event_id_set=true}};
+process_field(<<"retry">>, Value, State) ->
+ try
+ {ok, State#state{retry=binary_to_integer(Value)}}
+ catch _:_ ->
+ {ok, State}
+ end;
+process_field(_, _, State) ->
+ {ok, State}.
+
+%% Data is an empty string; abort.
+dispatch_event(State=#state{last_event_id_set=false, data=[]}) ->
+ {ok, State#state{event_type= <<>>}};
+%% Data is an empty string but we have a last_event_id:
+%% propagate it on its own so that the caller knows the
+%% most recent ID.
+dispatch_event(State=#state{last_event_id=LastEventID, data=[]}) ->
+ {event, #{
+ last_event_id => LastEventID
+ }, State#state{last_event_id_set=false, event_type= <<>>}};
+%% Dispatch the event.
+%%
+%% Always remove the last linebreak from the data.
+dispatch_event(State=#state{last_event_id=LastEventID,
+ event_type=EventType, data=[_|Data]}) ->
+ {event, #{
+ last_event_id => LastEventID,
+ event_type => case EventType of
+ <<>> -> <<"message">>;
+ _ -> EventType
+ end,
+ data => lists:reverse(Data)
+ }, State#state{last_event_id_set=false, event_type= <<>>, data=[]}}.
+
+-ifdef(TEST).
+parse_example1_test() ->
+ {event, #{
+ event_type := <<"message">>,
+ last_event_id := <<>>,
+ data := Data
+ }, State} = parse(<<
+ "data: YHOO\n"
+ "data: +2\n"
+ "data: 10\n"
+ "\n">>, init()),
+ <<"YHOO\n+2\n10">> = iolist_to_binary(Data),
+ {more, _} = parse(<<>>, State),
+ ok.
+
+parse_example2_test() ->
+ {event, #{
+ event_type := <<"message">>,
+ last_event_id := <<"1">>,
+ data := Data1
+ }, State0} = parse(<<
+ ": test stream\n"
+ "\n"
+ "data: first event\n"
+ "id: 1\n"
+ "\n"
+ "data:second event\n"
+ "id\n"
+ "\n"
+ "data: third event\n"
+ "\n">>, init()),
+ <<"first event">> = iolist_to_binary(Data1),
+ {event, #{
+ event_type := <<"message">>,
+ last_event_id := <<>>,
+ data := Data2
+ }, State1} = parse(<<>>, State0),
+ <<"second event">> = iolist_to_binary(Data2),
+ {event, #{
+ event_type := <<"message">>,
+ last_event_id := <<>>,
+ data := Data3
+ }, State} = parse(<<>>, State1),
+ <<" third event">> = iolist_to_binary(Data3),
+ {more, _} = parse(<<>>, State),
+ ok.
+
+parse_example3_test() ->
+ {event, #{
+ event_type := <<"message">>,
+ last_event_id := <<>>,
+ data := Data1
+ }, State0} = parse(<<
+ "data\n"
+ "\n"
+ "data\n"
+ "data\n"
+ "\n"
+ "data:\n">>, init()),
+ <<>> = iolist_to_binary(Data1),
+ {event, #{
+ event_type := <<"message">>,
+ last_event_id := <<>>,
+ data := Data2
+ }, State} = parse(<<>>, State0),
+ <<"\n">> = iolist_to_binary(Data2),
+ {more, _} = parse(<<>>, State),
+ ok.
+
+parse_example4_test() ->
+ {event, Event, State0} = parse(<<
+ "data:test\n"
+ "\n"
+ "data: test\n"
+ "\n">>, init()),
+ {event, Event, State} = parse(<<>>, State0),
+ {more, _} = parse(<<>>, State),
+ ok.
+
+parse_id_without_data_test() ->
+ {event, Event1, State0} = parse(<<
+ "id: 1\n"
+ "\n"
+ "data: data\n"
+ "\n"
+ "id: 2\n"
+ "\n">>, init()),
+ 1 = maps:size(Event1),
+ #{last_event_id := <<"1">>} = Event1,
+ {event, #{
+ event_type := <<"message">>,
+ last_event_id := <<"1">>,
+ data := Data
+ }, State1} = parse(<<>>, State0),
+ <<"data">> = iolist_to_binary(Data),
+ {event, Event2, State} = parse(<<>>, State1),
+ 1 = maps:size(Event2),
+ #{last_event_id := <<"2">>} = Event2,
+ {more, _} = parse(<<>>, State),
+ ok.
+
+parse_repeated_id_without_data_test() ->
+ {event, Event1, State0} = parse(<<
+ "id: 1\n"
+ "\n"
+ "event: message\n" %% This will be ignored since there's no data.
+ "\n"
+ "id: 1\n"
+ "\n"
+ "id: 2\n"
+ "\n">>, init()),
+ {event, Event1, State1} = parse(<<>>, State0),
+ 1 = maps:size(Event1),
+ #{last_event_id := <<"1">>} = Event1,
+ {event, Event2, State} = parse(<<>>, State1),
+ 1 = maps:size(Event2),
+ #{last_event_id := <<"2">>} = Event2,
+ {more, _} = parse(<<>>, State),
+ ok.
+
+parse_split_event_test() ->
+ {more, State} = parse(<<
+ "data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA">>, init()),
+ {event, _, _} = parse(<<"==\n\n">>, State),
+ ok.
+-endif.
+
+-spec events([event()]) -> iolist().
+events(Events) ->
+ [event(Event) || Event <- Events].
+
+-spec event(event()) -> iolist().
+event(Event) ->
+ [
+ event_comment(Event),
+ event_id(Event),
+ event_name(Event),
+ event_data(Event),
+ event_retry(Event),
+ $\n
+ ].
+
+event_comment(#{comment := Comment}) ->
+ prefix_lines(Comment, <<>>);
+event_comment(_) ->
+ [].
+
+event_id(#{id := ID}) ->
+ nomatch = binary:match(iolist_to_binary(ID), <<"\n">>),
+ [<<"id: ">>, ID, $\n];
+event_id(_) ->
+ [].
+
+event_name(#{event := Name0}) ->
+ Name = if
+ is_atom(Name0) -> atom_to_binary(Name0, utf8);
+ true -> iolist_to_binary(Name0)
+ end,
+ nomatch = binary:match(Name, <<"\n">>),
+ [<<"event: ">>, Name, $\n];
+event_name(_) ->
+ [].
+
+event_data(#{data := Data}) ->
+ prefix_lines(Data, <<"data">>);
+event_data(_) ->
+ [].
+
+event_retry(#{retry := Retry}) ->
+ [<<"retry: ">>, integer_to_binary(Retry), $\n];
+event_retry(_) ->
+ [].
+
+prefix_lines(IoData, Prefix) ->
+ Lines = binary:split(iolist_to_binary(IoData), <<"\n">>, [global]),
+ [[Prefix, <<": ">>, Line, $\n] || Line <- Lines].
+
+-ifdef(TEST).
+event_test() ->
+ _ = event(#{}),
+ _ = event(#{comment => "test"}),
+ _ = event(#{data => "test"}),
+ _ = event(#{data => "test\ntest\ntest"}),
+ _ = event(#{data => "test\ntest\ntest\n"}),
+ _ = event(#{data => <<"test\ntest\ntest">>}),
+ _ = event(#{data => [<<"test">>, $\n, <<"test">>, [$\n, "test"]]}),
+ _ = event(#{event => test}),
+ _ = event(#{event => "test"}),
+ _ = event(#{id => "test"}),
+ _ = event(#{retry => 5000}),
+ _ = event(#{event => "test", data => "test"}),
+ _ = event(#{id => "test", event => "test", data => "test"}),
+ ok.
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_uri.erl b/server/_build/default/lib/cowlib/src/cow_uri.erl
new file mode 100644
index 0000000..4480d6b
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_uri.erl
@@ -0,0 +1,339 @@
+%% Copyright (c) 2016-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_uri).
+
+-export([urldecode/1]).
+-export([urlencode/1]).
+
+%% @doc Decode a percent encoded string. (RFC3986 2.1)
+
+-spec urldecode(B) -> B when B::binary().
+urldecode(B) ->
+ urldecode(B, <<>>).
+
+urldecode(<< $%, H, L, Rest/bits >>, Acc) ->
+ C = (unhex(H) bsl 4 bor unhex(L)),
+ urldecode(Rest, << Acc/bits, C >>);
+urldecode(<< $!, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $! >>);
+urldecode(<< $$, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $$ >>);
+urldecode(<< $&, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $& >>);
+urldecode(<< $', Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $' >>);
+urldecode(<< $(, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $( >>);
+urldecode(<< $), Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $) >>);
+urldecode(<< $*, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $* >>);
+urldecode(<< $+, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $+ >>);
+urldecode(<< $,, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $, >>);
+urldecode(<< $-, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $- >>);
+urldecode(<< $., Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $. >>);
+urldecode(<< $0, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $0 >>);
+urldecode(<< $1, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $1 >>);
+urldecode(<< $2, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $2 >>);
+urldecode(<< $3, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $3 >>);
+urldecode(<< $4, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $4 >>);
+urldecode(<< $5, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $5 >>);
+urldecode(<< $6, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $6 >>);
+urldecode(<< $7, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $7 >>);
+urldecode(<< $8, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $8 >>);
+urldecode(<< $9, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $9 >>);
+urldecode(<< $:, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $: >>);
+urldecode(<< $;, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $; >>);
+urldecode(<< $=, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $= >>);
+urldecode(<< $@, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $@ >>);
+urldecode(<< $A, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $A >>);
+urldecode(<< $B, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $B >>);
+urldecode(<< $C, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $C >>);
+urldecode(<< $D, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $D >>);
+urldecode(<< $E, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $E >>);
+urldecode(<< $F, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $F >>);
+urldecode(<< $G, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $G >>);
+urldecode(<< $H, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $H >>);
+urldecode(<< $I, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $I >>);
+urldecode(<< $J, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $J >>);
+urldecode(<< $K, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $K >>);
+urldecode(<< $L, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $L >>);
+urldecode(<< $M, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $M >>);
+urldecode(<< $N, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $N >>);
+urldecode(<< $O, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $O >>);
+urldecode(<< $P, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $P >>);
+urldecode(<< $Q, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $Q >>);
+urldecode(<< $R, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $R >>);
+urldecode(<< $S, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $S >>);
+urldecode(<< $T, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $T >>);
+urldecode(<< $U, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $U >>);
+urldecode(<< $V, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $V >>);
+urldecode(<< $W, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $W >>);
+urldecode(<< $X, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $X >>);
+urldecode(<< $Y, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $Y >>);
+urldecode(<< $Z, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $Z >>);
+urldecode(<< $_, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $_ >>);
+urldecode(<< $a, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $a >>);
+urldecode(<< $b, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $b >>);
+urldecode(<< $c, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $c >>);
+urldecode(<< $d, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $d >>);
+urldecode(<< $e, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $e >>);
+urldecode(<< $f, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $f >>);
+urldecode(<< $g, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $g >>);
+urldecode(<< $h, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $h >>);
+urldecode(<< $i, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $i >>);
+urldecode(<< $j, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $j >>);
+urldecode(<< $k, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $k >>);
+urldecode(<< $l, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $l >>);
+urldecode(<< $m, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $m >>);
+urldecode(<< $n, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $n >>);
+urldecode(<< $o, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $o >>);
+urldecode(<< $p, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $p >>);
+urldecode(<< $q, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $q >>);
+urldecode(<< $r, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $r >>);
+urldecode(<< $s, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $s >>);
+urldecode(<< $t, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $t >>);
+urldecode(<< $u, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $u >>);
+urldecode(<< $v, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $v >>);
+urldecode(<< $w, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $w >>);
+urldecode(<< $x, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $x >>);
+urldecode(<< $y, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $y >>);
+urldecode(<< $z, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $z >>);
+urldecode(<< $~, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $~ >>);
+urldecode(<<>>, Acc) -> Acc.
+
+unhex($0) -> 0;
+unhex($1) -> 1;
+unhex($2) -> 2;
+unhex($3) -> 3;
+unhex($4) -> 4;
+unhex($5) -> 5;
+unhex($6) -> 6;
+unhex($7) -> 7;
+unhex($8) -> 8;
+unhex($9) -> 9;
+unhex($A) -> 10;
+unhex($B) -> 11;
+unhex($C) -> 12;
+unhex($D) -> 13;
+unhex($E) -> 14;
+unhex($F) -> 15;
+unhex($a) -> 10;
+unhex($b) -> 11;
+unhex($c) -> 12;
+unhex($d) -> 13;
+unhex($e) -> 14;
+unhex($f) -> 15.
+
+-ifdef(TEST).
+urldecode_test_() ->
+ Tests = [
+ {<<"%20">>, <<" ">>},
+ {<<"+">>, <<"+">>},
+ {<<"%00">>, <<0>>},
+ {<<"%fF">>, <<255>>},
+ {<<"123">>, <<"123">>},
+ {<<"%i5">>, error},
+ {<<"%5">>, error}
+ ],
+ [{Qs, fun() ->
+ E = try urldecode(Qs) of
+ R -> R
+ catch _:_ ->
+ error
+ end
+ end} || {Qs, E} <- Tests].
+
+urldecode_identity_test_() ->
+ Tests = [
+ <<"%20">>,
+ <<"+">>,
+ <<"nothingnothingnothingnothing">>,
+ <<"Small+fast+modular+HTTP+server">>,
+ <<"Small%20fast%20modular%20HTTP%20server">>,
+ <<"Small%2F+fast%2F+modular+HTTP+server.">>,
+ <<"%E3%83%84%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%A6%E3%83"
+ "%AB%E3%80%9C%E8%BC%AA%E5%BB%BB%E3%81%99%E3%82%8B%E6%97%8B%E5"
+ "%BE%8B%E3%80%9C">>
+ ],
+ [{V, fun() -> V = urlencode(urldecode(V)) end} || V <- Tests].
+
+horse_urldecode() ->
+ horse:repeat(100000,
+ urldecode(<<"nothingnothingnothingnothing">>)
+ ).
+
+horse_urldecode_hex() ->
+ horse:repeat(100000,
+ urldecode(<<"Small%2C%20fast%2C%20modular%20HTTP%20server.">>)
+ ).
+
+horse_urldecode_jp_hex() ->
+ horse:repeat(100000,
+ urldecode(<<"%E3%83%84%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%A6%E3%83"
+ "%AB%E3%80%9C%E8%BC%AA%E5%BB%BB%E3%81%99%E3%82%8B%E6%97%8B%E5"
+ "%BE%8B%E3%80%9C">>)
+ ).
+-endif.
+
+%% @doc Percent encode a string. (RFC3986 2.1)
+%%
+%% This function is meant to be used for path components.
+
+-spec urlencode(B) -> B when B::binary().
+urlencode(B) ->
+ urlencode(B, <<>>).
+
+urlencode(<< $!, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $! >>);
+urlencode(<< $$, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $$ >>);
+urlencode(<< $&, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $& >>);
+urlencode(<< $', Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $' >>);
+urlencode(<< $(, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $( >>);
+urlencode(<< $), Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $) >>);
+urlencode(<< $*, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $* >>);
+urlencode(<< $+, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $+ >>);
+urlencode(<< $,, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $, >>);
+urlencode(<< $-, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $- >>);
+urlencode(<< $., Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $. >>);
+urlencode(<< $0, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $0 >>);
+urlencode(<< $1, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $1 >>);
+urlencode(<< $2, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $2 >>);
+urlencode(<< $3, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $3 >>);
+urlencode(<< $4, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $4 >>);
+urlencode(<< $5, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $5 >>);
+urlencode(<< $6, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $6 >>);
+urlencode(<< $7, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $7 >>);
+urlencode(<< $8, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $8 >>);
+urlencode(<< $9, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $9 >>);
+urlencode(<< $:, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $: >>);
+urlencode(<< $;, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $; >>);
+urlencode(<< $=, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $= >>);
+urlencode(<< $@, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $@ >>);
+urlencode(<< $A, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $A >>);
+urlencode(<< $B, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $B >>);
+urlencode(<< $C, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $C >>);
+urlencode(<< $D, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $D >>);
+urlencode(<< $E, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $E >>);
+urlencode(<< $F, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $F >>);
+urlencode(<< $G, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $G >>);
+urlencode(<< $H, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $H >>);
+urlencode(<< $I, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $I >>);
+urlencode(<< $J, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $J >>);
+urlencode(<< $K, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $K >>);
+urlencode(<< $L, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $L >>);
+urlencode(<< $M, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $M >>);
+urlencode(<< $N, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $N >>);
+urlencode(<< $O, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $O >>);
+urlencode(<< $P, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $P >>);
+urlencode(<< $Q, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Q >>);
+urlencode(<< $R, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $R >>);
+urlencode(<< $S, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $S >>);
+urlencode(<< $T, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $T >>);
+urlencode(<< $U, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $U >>);
+urlencode(<< $V, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $V >>);
+urlencode(<< $W, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $W >>);
+urlencode(<< $X, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $X >>);
+urlencode(<< $Y, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Y >>);
+urlencode(<< $Z, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Z >>);
+urlencode(<< $_, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $_ >>);
+urlencode(<< $a, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $a >>);
+urlencode(<< $b, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $b >>);
+urlencode(<< $c, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $c >>);
+urlencode(<< $d, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $d >>);
+urlencode(<< $e, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $e >>);
+urlencode(<< $f, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $f >>);
+urlencode(<< $g, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $g >>);
+urlencode(<< $h, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $h >>);
+urlencode(<< $i, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $i >>);
+urlencode(<< $j, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $j >>);
+urlencode(<< $k, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $k >>);
+urlencode(<< $l, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $l >>);
+urlencode(<< $m, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $m >>);
+urlencode(<< $n, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $n >>);
+urlencode(<< $o, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $o >>);
+urlencode(<< $p, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $p >>);
+urlencode(<< $q, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $q >>);
+urlencode(<< $r, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $r >>);
+urlencode(<< $s, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $s >>);
+urlencode(<< $t, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $t >>);
+urlencode(<< $u, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $u >>);
+urlencode(<< $v, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $v >>);
+urlencode(<< $w, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $w >>);
+urlencode(<< $x, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $x >>);
+urlencode(<< $y, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $y >>);
+urlencode(<< $z, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $z >>);
+urlencode(<< $~, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $~ >>);
+urlencode(<< C, Rest/bits >>, Acc) ->
+ H = hex(C bsr 4),
+ L = hex(C band 16#0f),
+ urlencode(Rest, << Acc/bits, $%, H, L >>);
+urlencode(<<>>, Acc) ->
+ Acc.
+
+hex( 0) -> $0;
+hex( 1) -> $1;
+hex( 2) -> $2;
+hex( 3) -> $3;
+hex( 4) -> $4;
+hex( 5) -> $5;
+hex( 6) -> $6;
+hex( 7) -> $7;
+hex( 8) -> $8;
+hex( 9) -> $9;
+hex(10) -> $A;
+hex(11) -> $B;
+hex(12) -> $C;
+hex(13) -> $D;
+hex(14) -> $E;
+hex(15) -> $F.
+
+-ifdef(TEST).
+urlencode_test_() ->
+ Tests = [
+ {<<255, 0>>, <<"%FF%00">>},
+ {<<255, " ">>, <<"%FF%20">>},
+ {<<"+">>, <<"+">>},
+ {<<"aBc123">>, <<"aBc123">>},
+ {<<"!$&'()*+,:;=@-._~">>, <<"!$&'()*+,:;=@-._~">>}
+ ],
+ [{V, fun() -> E = urlencode(V) end} || {V, E} <- Tests].
+
+urlencode_identity_test_() ->
+ Tests = [
+ <<"+">>,
+ <<"nothingnothingnothingnothing">>,
+ <<"Small fast modular HTTP server">>,
+ <<"Small, fast, modular HTTP server.">>,
+ <<227,131,132,227,130,164,227,131,179,227,130,189,227,
+ 130,166,227,131,171,227,128,156,232,188,170,229,187,187,227,
+ 129,153,227,130,139,230,151,139,229,190,139,227,128,156>>
+ ],
+ [{V, fun() -> V = urldecode(urlencode(V)) end} || V <- Tests].
+
+horse_urlencode() ->
+ horse:repeat(100000,
+ urlencode(<<"nothingnothingnothingnothing">>)
+ ).
+
+horse_urlencode_spaces() ->
+ horse:repeat(100000,
+ urlencode(<<"Small fast modular HTTP server">>)
+ ).
+
+horse_urlencode_jp() ->
+ horse:repeat(100000,
+ urlencode(<<227,131,132,227,130,164,227,131,179,227,130,189,227,
+ 130,166,227,131,171,227,128,156,232,188,170,229,187,187,227,
+ 129,153,227,130,139,230,151,139,229,190,139,227,128,156>>)
+ ).
+
+horse_urlencode_mix() ->
+ horse:repeat(100000,
+ urlencode(<<"Small, fast, modular HTTP server.">>)
+ ).
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_uri_template.erl b/server/_build/default/lib/cowlib/src/cow_uri_template.erl
new file mode 100644
index 0000000..ccc355d
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_uri_template.erl
@@ -0,0 +1,360 @@
+%% Copyright (c) 2019-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% This is a full level 4 implementation of URI Templates
+%% as defined by RFC6570.
+
+-module(cow_uri_template).
+
+-export([parse/1]).
+-export([expand/2]).
+
+-type op() :: simple_string_expansion
+ | reserved_expansion
+ | fragment_expansion
+ | label_expansion_with_dot_prefix
+ | path_segment_expansion
+ | path_style_parameter_expansion
+ | form_style_query_expansion
+ | form_style_query_continuation.
+
+-type var_list() :: [
+ {no_modifier, binary()}
+ | {{prefix_modifier, pos_integer()}, binary()}
+ | {explode_modifier, binary()}
+].
+
+-type uri_template() :: [
+ binary() | {expr, op(), var_list()}
+].
+-export_type([uri_template/0]).
+
+-type variables() :: #{
+ binary() => binary()
+ | integer()
+ | float()
+ | [binary()]
+ | #{binary() => binary()}
+}.
+
+-include("cow_inline.hrl").
+-include("cow_parse.hrl").
+
+%% Parse a URI template.
+
+-spec parse(binary()) -> uri_template().
+parse(URITemplate) ->
+ parse(URITemplate, <<>>).
+
+parse(<<>>, <<>>) ->
+ [];
+parse(<<>>, Acc) ->
+ [Acc];
+parse(<<${,R/bits>>, <<>>) ->
+ parse_expr(R);
+parse(<<${,R/bits>>, Acc) ->
+ [Acc|parse_expr(R)];
+%% @todo Probably should reject unallowed characters so that
+%% we don't produce invalid URIs.
+parse(<<C,R/bits>>, Acc) when C =/= $} ->
+ parse(R, <<Acc/binary, C>>).
+
+parse_expr(<<$+,R/bits>>) ->
+ parse_var_list(R, reserved_expansion, []);
+parse_expr(<<$#,R/bits>>) ->
+ parse_var_list(R, fragment_expansion, []);
+parse_expr(<<$.,R/bits>>) ->
+ parse_var_list(R, label_expansion_with_dot_prefix, []);
+parse_expr(<<$/,R/bits>>) ->
+ parse_var_list(R, path_segment_expansion, []);
+parse_expr(<<$;,R/bits>>) ->
+ parse_var_list(R, path_style_parameter_expansion, []);
+parse_expr(<<$?,R/bits>>) ->
+ parse_var_list(R, form_style_query_expansion, []);
+parse_expr(<<$&,R/bits>>) ->
+ parse_var_list(R, form_style_query_continuation, []);
+parse_expr(R) ->
+ parse_var_list(R, simple_string_expansion, []).
+
+parse_var_list(<<C,R/bits>>, Op, List)
+ when ?IS_ALPHANUM(C) or (C =:= $_) ->
+ parse_varname(R, Op, List, <<C>>).
+
+parse_varname(<<C,R/bits>>, Op, List, Name)
+ when ?IS_ALPHANUM(C) or (C =:= $_) or (C =:= $.) or (C =:= $%) ->
+ parse_varname(R, Op, List, <<Name/binary,C>>);
+parse_varname(<<$:,C,R/bits>>, Op, List, Name)
+ when (C =:= $1) or (C =:= $2) or (C =:= $3) or (C =:= $4) or (C =:= $5)
+ or (C =:= $6) or (C =:= $7) or (C =:= $8) or (C =:= $9) ->
+ parse_prefix_modifier(R, Op, List, Name, <<C>>);
+parse_varname(<<$*,$,,R/bits>>, Op, List, Name) ->
+ parse_var_list(R, Op, [{explode_modifier, Name}|List]);
+parse_varname(<<$*,$},R/bits>>, Op, List, Name) ->
+ [{expr, Op, lists:reverse([{explode_modifier, Name}|List])}|parse(R, <<>>)];
+parse_varname(<<$,,R/bits>>, Op, List, Name) ->
+ parse_var_list(R, Op, [{no_modifier, Name}|List]);
+parse_varname(<<$},R/bits>>, Op, List, Name) ->
+ [{expr, Op, lists:reverse([{no_modifier, Name}|List])}|parse(R, <<>>)].
+
+parse_prefix_modifier(<<C,R/bits>>, Op, List, Name, Acc)
+ when ?IS_DIGIT(C), byte_size(Acc) < 4 ->
+ parse_prefix_modifier(R, Op, List, Name, <<Acc/binary,C>>);
+parse_prefix_modifier(<<$,,R/bits>>, Op, List, Name, Acc) ->
+ parse_var_list(R, Op, [{{prefix_modifier, binary_to_integer(Acc)}, Name}|List]);
+parse_prefix_modifier(<<$},R/bits>>, Op, List, Name, Acc) ->
+ [{expr, Op, lists:reverse([{{prefix_modifier, binary_to_integer(Acc)}, Name}|List])}|parse(R, <<>>)].
+
+%% Expand a URI template (after parsing it if necessary).
+
+-spec expand(binary() | uri_template(), variables()) -> iodata().
+expand(URITemplate, Vars) when is_binary(URITemplate) ->
+ expand(parse(URITemplate), Vars);
+expand(URITemplate, Vars) ->
+ expand1(URITemplate, Vars).
+
+expand1([], _) ->
+ [];
+expand1([Literal|Tail], Vars) when is_binary(Literal) ->
+ [Literal|expand1(Tail, Vars)];
+expand1([{expr, simple_string_expansion, VarList}|Tail], Vars) ->
+ [simple_string_expansion(VarList, Vars)|expand1(Tail, Vars)];
+expand1([{expr, reserved_expansion, VarList}|Tail], Vars) ->
+ [reserved_expansion(VarList, Vars)|expand1(Tail, Vars)];
+expand1([{expr, fragment_expansion, VarList}|Tail], Vars) ->
+ [fragment_expansion(VarList, Vars)|expand1(Tail, Vars)];
+expand1([{expr, label_expansion_with_dot_prefix, VarList}|Tail], Vars) ->
+ [label_expansion_with_dot_prefix(VarList, Vars)|expand1(Tail, Vars)];
+expand1([{expr, path_segment_expansion, VarList}|Tail], Vars) ->
+ [path_segment_expansion(VarList, Vars)|expand1(Tail, Vars)];
+expand1([{expr, path_style_parameter_expansion, VarList}|Tail], Vars) ->
+ [path_style_parameter_expansion(VarList, Vars)|expand1(Tail, Vars)];
+expand1([{expr, form_style_query_expansion, VarList}|Tail], Vars) ->
+ [form_style_query_expansion(VarList, Vars)|expand1(Tail, Vars)];
+expand1([{expr, form_style_query_continuation, VarList}|Tail], Vars) ->
+ [form_style_query_continuation(VarList, Vars)|expand1(Tail, Vars)].
+
+simple_string_expansion(VarList, Vars) ->
+ lists:join($,, [
+ apply_modifier(Modifier, unreserved, $,, Value)
+ || {Modifier, _Name, Value} <- lookup_variables(VarList, Vars)]).
+
+reserved_expansion(VarList, Vars) ->
+ lists:join($,, [
+ apply_modifier(Modifier, reserved, $,, Value)
+ || {Modifier, _Name, Value} <- lookup_variables(VarList, Vars)]).
+
+fragment_expansion(VarList, Vars) ->
+ case reserved_expansion(VarList, Vars) of
+ [] -> [];
+ Expanded -> [$#, Expanded]
+ end.
+
+label_expansion_with_dot_prefix(VarList, Vars) ->
+ segment_expansion(VarList, Vars, $.).
+
+path_segment_expansion(VarList, Vars) ->
+ segment_expansion(VarList, Vars, $/).
+
+segment_expansion(VarList, Vars, Sep) ->
+ Expanded = lists:join(Sep, [
+ apply_modifier(Modifier, unreserved, Sep, Value)
+ || {Modifier, _Name, Value} <- lookup_variables(VarList, Vars)]),
+ case Expanded of
+ [] -> [];
+ [[]] -> [];
+ _ -> [Sep, Expanded]
+ end.
+
+path_style_parameter_expansion(VarList, Vars) ->
+ parameter_expansion(VarList, Vars, $;, $;, trim).
+
+form_style_query_expansion(VarList, Vars) ->
+ parameter_expansion(VarList, Vars, $?, $&, no_trim).
+
+form_style_query_continuation(VarList, Vars) ->
+ parameter_expansion(VarList, Vars, $&, $&, no_trim).
+
+parameter_expansion(VarList, Vars, LeadingSep, Sep, Trim) ->
+ Expanded = lists:join(Sep, [
+ apply_parameter_modifier(Modifier, unreserved, Sep, Trim, Name, Value)
+ || {Modifier, Name, Value} <- lookup_variables(VarList, Vars)]),
+ case Expanded of
+ [] -> [];
+ [[]] -> [];
+ _ -> [LeadingSep, Expanded]
+ end.
+
+lookup_variables([], _) ->
+ [];
+lookup_variables([{Modifier, Name}|Tail], Vars) ->
+ case Vars of
+ #{Name := Value} -> [{Modifier, Name, Value}|lookup_variables(Tail, Vars)];
+ _ -> lookup_variables(Tail, Vars)
+ end.
+
+apply_modifier(no_modifier, AllowedChars, _, List) when is_list(List) ->
+ lists:join($,, [urlencode(Value, AllowedChars) || Value <- List]);
+apply_modifier(explode_modifier, AllowedChars, ExplodeSep, List) when is_list(List) ->
+ lists:join(ExplodeSep, [urlencode(Value, AllowedChars) || Value <- List]);
+apply_modifier(Modifier, AllowedChars, ExplodeSep, Map) when is_map(Map) ->
+ {JoinSep, KVSep} = case Modifier of
+ no_modifier -> {$,, $,};
+ explode_modifier -> {ExplodeSep, $=}
+ end,
+ lists:reverse(lists:join(JoinSep,
+ maps:fold(fun(Key, Value, Acc) ->
+ [[
+ urlencode(Key, AllowedChars),
+ KVSep,
+ urlencode(Value, AllowedChars)
+ ]|Acc]
+ end, [], Map)
+ ));
+apply_modifier({prefix_modifier, MaxLen}, AllowedChars, _, Value) ->
+ urlencode(string:slice(binarize(Value), 0, MaxLen), AllowedChars);
+apply_modifier(_, AllowedChars, _, Value) ->
+ urlencode(binarize(Value), AllowedChars).
+
+apply_parameter_modifier(_, _, _, _, _, []) ->
+ [];
+apply_parameter_modifier(_, _, _, _, _, Map) when Map =:= #{} ->
+ [];
+apply_parameter_modifier(no_modifier, AllowedChars, _, _, Name, List) when is_list(List) ->
+ [
+ Name,
+ $=,
+ lists:join($,, [urlencode(Value, AllowedChars) || Value <- List])
+ ];
+apply_parameter_modifier(explode_modifier, AllowedChars, ExplodeSep, _, Name, List) when is_list(List) ->
+ lists:join(ExplodeSep, [[
+ Name,
+ $=,
+ urlencode(Value, AllowedChars)
+ ] || Value <- List]);
+apply_parameter_modifier(Modifier, AllowedChars, ExplodeSep, _, Name, Map) when is_map(Map) ->
+ {JoinSep, KVSep} = case Modifier of
+ no_modifier -> {$,, $,};
+ explode_modifier -> {ExplodeSep, $=}
+ end,
+ [
+ case Modifier of
+ no_modifier ->
+ [
+ Name,
+ $=
+ ];
+ explode_modifier ->
+ []
+ end,
+ lists:reverse(lists:join(JoinSep,
+ maps:fold(fun(Key, Value, Acc) ->
+ [[
+ urlencode(Key, AllowedChars),
+ KVSep,
+ urlencode(Value, AllowedChars)
+ ]|Acc]
+ end, [], Map)
+ ))
+ ];
+apply_parameter_modifier(Modifier, AllowedChars, _, Trim, Name, Value0) ->
+ Value1 = binarize(Value0),
+ Value = case Modifier of
+ {prefix_modifier, MaxLen} ->
+ string:slice(Value1, 0, MaxLen);
+ no_modifier ->
+ Value1
+ end,
+ [
+ Name,
+ case Value of
+ <<>> when Trim =:= trim ->
+ [];
+ <<>> when Trim =:= no_trim ->
+ $=;
+ _ ->
+ [
+ $=,
+ urlencode(Value, AllowedChars)
+ ]
+ end
+ ].
+
+binarize(Value) when is_integer(Value) ->
+ integer_to_binary(Value);
+binarize(Value) when is_float(Value) ->
+ float_to_binary(Value, [{decimals, 10}, compact]);
+binarize(Value) ->
+ Value.
+
+urlencode(Value, unreserved) ->
+ urlencode_unreserved(Value, <<>>);
+urlencode(Value, reserved) ->
+ urlencode_reserved(Value, <<>>).
+
+urlencode_unreserved(<<C,R/bits>>, Acc)
+ when ?IS_URI_UNRESERVED(C) ->
+ urlencode_unreserved(R, <<Acc/binary,C>>);
+urlencode_unreserved(<<C,R/bits>>, Acc) ->
+ urlencode_unreserved(R, <<Acc/binary,$%,?HEX(C)>>);
+urlencode_unreserved(<<>>, Acc) ->
+ Acc.
+
+urlencode_reserved(<<$%,H,L,R/bits>>, Acc)
+ when ?IS_HEX(H), ?IS_HEX(L) ->
+ urlencode_reserved(R, <<Acc/binary,$%,H,L>>);
+urlencode_reserved(<<C,R/bits>>, Acc)
+ when ?IS_URI_UNRESERVED(C) or ?IS_URI_GEN_DELIMS(C) or ?IS_URI_SUB_DELIMS(C) ->
+ urlencode_reserved(R, <<Acc/binary,C>>);
+urlencode_reserved(<<C,R/bits>>, Acc) ->
+ urlencode_reserved(R, <<Acc/binary,$%,?HEX(C)>>);
+urlencode_reserved(<<>>, Acc) ->
+ Acc.
+
+-ifdef(TEST).
+expand_uritemplate_test_() ->
+ Files = filelib:wildcard("deps/uritemplate-tests/*.json"),
+ lists:flatten([begin
+ {ok, JSON} = file:read_file(File),
+ Tests = jsx:decode(JSON, [return_maps]),
+ [begin
+ %% Erlang doesn't have a NULL value.
+ Vars = maps:remove(<<"undef">>, Vars0),
+ [
+ {iolist_to_binary(io_lib:format("~s - ~s: ~s => ~s",
+ [filename:basename(File), Section, URITemplate,
+ if
+ is_list(Expected) -> lists:join(<<" OR ">>, Expected);
+ true -> Expected
+ end
+ ])),
+ fun() ->
+ io:format("expected: ~0p", [Expected]),
+ case Expected of
+ false ->
+ {'EXIT', _} = (catch expand(URITemplate, Vars));
+ [_|_] ->
+ Result = iolist_to_binary(expand(URITemplate, Vars)),
+ io:format("~p", [Result]),
+ true = lists:member(Result, Expected);
+ _ ->
+ Expected = iolist_to_binary(expand(URITemplate, Vars))
+ end
+ end}
+ || [URITemplate, Expected] <- Cases]
+ end || {Section, #{
+ <<"variables">> := Vars0,
+ <<"testcases">> := Cases
+ }} <- maps:to_list(Tests)]
+ end || File <- Files]).
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_ws.erl b/server/_build/default/lib/cowlib/src/cow_ws.erl
new file mode 100644
index 0000000..27c7c87
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_ws.erl
@@ -0,0 +1,741 @@
+%% Copyright (c) 2015-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-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_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 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]).
+
+-type frame() :: close | ping | pong
+ | {text | binary | close | ping | pong, iodata()}
+ | {close, close_code(), iodata()}
+ | {fragment, fin | nofin, text | binary | continuation, iodata()}.
+-export_type([frame/0]).
+
+-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>>.
+-export_type([rsv/0]).
+
+-type utf8_state() :: 0..8 | undefined.
+-export_type([utf8_state/0]).
+
+%% @doc Generate a key for the Websocket handshake request.
+
+-spec key() -> binary().
+key() ->
+ base64:encode(crypto:strong_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.
+
+-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;
+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 ->
+ negotiate_permessage_deflate1(Params2, Extensions, Opts)
+ end.
+
+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 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;
+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 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;
+%% Ignore if unknown parameter; ignore if parameter with invalid or missing value.
+negotiate_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(),
+ %% zlib 1.2.11+ now rejects -8. It used to transform it to -9.
+ %% We need to use 9 when 8 is requested for interoperability.
+ 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)),
+ %% Set the owner pid of the zlib contexts if requested.
+ case Opts of
+ #{owner := Pid} -> set_owner(Pid, Inflate, Deflate);
+ _ -> ok
+ end,
+ {Inflate, Deflate}.
+
+-ifdef(OTP_RELEASE).
+%% Using is_port/1 on a zlib context results in a Dialyzer warning in OTP 21.
+%% This function helps silence that warning while staying compatible
+%% with all supported versions.
+
+set_owner(Pid, Inflate, Deflate) ->
+ zlib:set_controlling_process(Inflate, Pid),
+ zlib:set_controlling_process(Deflate, Pid).
+-else.
+%% The zlib port became a reference in OTP 20.1+. There
+%% was however no way to change the controlling process
+%% until the OTP 20.1.3 patch version. Since we can't
+%% enable compression for 20.1, 20.1.1 and 20.1.2 we
+%% explicitly crash. The caller should ignore this extension.
+
+set_owner(Pid, Inflate, Deflate) when is_port(Inflate) ->
+ true = erlang:port_connect(Inflate, Pid),
+ true = unlink(Inflate),
+ true = erlang:port_connect(Deflate, Pid),
+ true = unlink(Deflate),
+ ok;
+set_owner(Pid, Inflate, Deflate) ->
+ case erlang:function_exported(zlib, set_controlling_process, 2) of
+ true ->
+ zlib:set_controlling_process(Inflate, Pid),
+ zlib:set_controlling_process(Deflate, Pid);
+ false ->
+ exit({error, incompatible_zlib_version,
+ 'OTP 20.1, 20.1.1 and 20.1.2 are missing required functionality.'})
+ end.
+-endif.
+
+%% @doc Negotiate the x-webkit-deflate-frame extension.
+%%
+%% 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) ->
+ % 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 ->
+ 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.
+%%
+%% This function also updates the fragmentation state according to
+%% information found in the frame's header.
+
+-spec parse_header(binary(), extensions(), frag_state())
+ -> error | more | {frame_type(), frag_state(), rsv(), non_neg_integer(), mask_key(), binary()}.
+%% RSV bits MUST be 0 unless an extension is negotiated
+%% 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 := _}, _) -> 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;
+parse_header(<< _:4, 5:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 6:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 7:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 11:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 12:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 13:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 14:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 15:4, _/bits >>, _, _) -> error;
+%% Control frames MUST NOT be fragmented.
+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;
+%% 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.
+parse_header(Data = << _:4, 8:4, 0:1, Len:7, _/bits >>, _, _) when Len > 1, byte_size(Data) < 4 -> more;
+parse_header(Data = << _:4, 8:4, 1:1, Len:7, _/bits >>, _, _) when Len > 1, byte_size(Data) < 8 -> more;
+%% 7 bits payload length.
+parse_header(<< Fin:1, Rsv:3/bits, Opcode:4, 0:1, Len:7, Rest/bits >>, _, FragState) when Len < 126 ->
+ parse_header(Opcode, Fin, FragState, Rsv, Len, undefined, Rest);
+parse_header(<< Fin:1, Rsv:3/bits, Opcode:4, 1:1, Len:7, MaskKey:32, Rest/bits >>, _, FragState) when Len < 126 ->
+ parse_header(Opcode, Fin, FragState, Rsv, Len, MaskKey, Rest);
+%% 16 bits payload length.
+parse_header(<< Fin:1, Rsv:3/bits, Opcode:4, 0:1, 126:7, Len:16, Rest/bits >>, _, FragState) when Len > 125, Opcode < 8 ->
+ parse_header(Opcode, Fin, FragState, Rsv, Len, undefined, Rest);
+parse_header(<< Fin:1, Rsv:3/bits, Opcode:4, 1:1, 126:7, Len:16, MaskKey:32, Rest/bits >>, _, FragState) when Len > 125, Opcode < 8 ->
+ parse_header(Opcode, Fin, FragState, Rsv, Len, MaskKey, Rest);
+%% 63 bits payload length.
+parse_header(<< Fin:1, Rsv:3/bits, Opcode:4, 0:1, 127:7, 0:1, Len:63, Rest/bits >>, _, FragState) when Len > 16#ffff, Opcode < 8 ->
+ parse_header(Opcode, Fin, FragState, Rsv, Len, undefined, Rest);
+parse_header(<< Fin:1, Rsv:3/bits, Opcode:4, 1:1, 127:7, 0:1, Len:63, MaskKey:32, Rest/bits >>, _, FragState) when Len > 16#ffff, Opcode < 8 ->
+ parse_header(Opcode, Fin, FragState, Rsv, Len, MaskKey, Rest);
+%% When payload length is over 63 bits, the most significant bit MUST be 0.
+parse_header(<< _:9, 127:7, 1:1, _/bits >>, _, _) -> error;
+%% For the next two clauses, it can be one of the following:
+%%
+%% * The minimal number of bytes MUST be used to encode the length
+%% * All control frames MUST have a payload length of 125 bytes or less
+parse_header(<< _:8, 0:1, 126:7, _:16, _/bits >>, _, _) -> error;
+parse_header(<< _:8, 1:1, 126:7, _:48, _/bits >>, _, _) -> error;
+parse_header(<< _:8, 0:1, 127:7, _:64, _/bits >>, _, _) -> error;
+parse_header(<< _:8, 1:1, 127:7, _:96, _/bits >>, _, _) -> error;
+%% Need more data.
+parse_header(_, _, _) -> more.
+
+parse_header(Opcode, Fin, FragState, Rsv, Len, MaskKey, Rest) ->
+ Type = opcode_to_frame_type(Opcode),
+ Type2 = case Fin of
+ 0 -> fragment;
+ 1 -> Type
+ end,
+ {Type2, frag_state(Type, Fin, Rsv, FragState), Rsv, Len, MaskKey, Rest}.
+
+opcode_to_frame_type(0) -> fragment;
+opcode_to_frame_type(1) -> text;
+opcode_to_frame_type(2) -> binary;
+opcode_to_frame_type(8) -> close;
+opcode_to_frame_type(9) -> ping;
+opcode_to_frame_type(10) -> pong.
+
+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.
+%%
+%% Validation is only required for text and close frames which feature
+%% a UTF-8 payload.
+
+-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()}
+ | {ok, close_code(), binary(), utf8_state(), binary()}
+ | {more, binary(), utf8_state()}
+ | {more, close_code(), binary(), utf8_state()}
+ | {error, badframe | badencoding}.
+%% 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, 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, _, _, _)
+ when Utf8State =:= 0; Utf8State =:= undefined ->
+ {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),
+ validate_payload(Payload, Rest, Utf8State, ParsedLen, Type, FragState, Eof).
+
+split_payload(Data, Len) ->
+ case byte_size(Data) of
+ Len ->
+ {Data, <<>>, true};
+ DataLen when DataLen < Len ->
+ {Data, <<>>, false};
+ _ ->
+ << Data2:Len/binary, Rest/bits >> = Data,
+ {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) ->
+ 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)),
+ mask(Data, MaskKey2, <<>>).
+
+mask(<<>>, _, Unmasked) ->
+ Unmasked;
+mask(<< O:32, Rest/bits >>, MaskKey, Acc) ->
+ T = O bxor MaskKey,
+ 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 >>;
+mask(<< O:16 >>, MaskKey, Acc) ->
+ << MaskKey2:16, _:16 >> = << MaskKey:32 >>,
+ T = O bxor MaskKey2,
+ << Acc/binary, T:16 >>;
+mask(<< O:8 >>, MaskKey, Acc) ->
+ << MaskKey2:8, _:24 >> = << MaskKey:32 >>,
+ T = O bxor MaskKey2,
+ << Acc/binary, T:8 >>.
+
+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)).
+
+%% The Utf8State variable can be set to 'undefined' to disable the validation.
+validate_payload(Payload, _, undefined, _, _, _, false) ->
+ {more, Payload, undefined};
+validate_payload(Payload, Rest, undefined, _, _, _, true) ->
+ {ok, Payload, undefined, Rest};
+%% 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(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(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, badencoding}
+ end;
+validate_payload(Payload, _, Utf8State, _, _, _, false) ->
+ {more, Payload, Utf8State};
+validate_payload(Payload, Rest, Utf8State, _, _, _, true) ->
+ {ok, Payload, Utf8State, Rest}.
+
+%% Based on the Flexible and Economical UTF-8 Decoder algorithm by
+%% Bjoern Hoehrmann <bjoern@hoehrmann.de> (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.
+
+-spec frame(frame(), extensions()) -> iodata().
+%% Control frames. Control packets must not be > 125 in length.
+frame(close, _) ->
+ << 1:1, 0:3, 8:4, 0:8 >>;
+frame(ping, _) ->
+ << 1:1, 0:3, 9:4, 0:8 >>;
+frame(pong, _) ->
+ << 1:1, 0:3, 10:4, 0:8 >>;
+frame({close, Payload}, Extensions) ->
+ frame({close, 1000, Payload}, Extensions);
+frame({close, StatusCode, Payload}, _) ->
+ Len = 2 + iolist_size(Payload),
+ true = Len =< 125,
+ [<< 1:1, 0:3, 8:4, 0:1, Len:7, StatusCode:16 >>, Payload];
+frame({ping, Payload}, _) ->
+ Len = iolist_size(Payload),
+ true = Len =< 125,
+ [<< 1:1, 0:3, 9:4, 0:1, Len:7 >>, Payload];
+frame({pong, Payload}, _) ->
+ Len = iolist_size(Payload),
+ true = Len =< 125,
+ [<< 1:1, 0:3, 10:4, 0:1, Len:7 >>, Payload];
+%% Data frames, deflate-frame extension.
+frame({text, Payload}, #{deflate := Deflate, deflate_takeover := TakeOver})
+ when Deflate =/= false ->
+ 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 := Deflate, deflate_takeover := TakeOver})
+ when Deflate =/= false ->
+ 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.
+frame({text, Payload}, _) ->
+ Len = payload_length(Payload),
+ [<< 1:1, 0:3, 1:4, 0:1, Len/bits >>, Payload];
+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:strong_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:strong_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:strong_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})
+ when Deflate =/= false ->
+ MaskKeyBin = << MaskKey:32 >> = crypto:strong_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})
+ when Deflate =/= false ->
+ MaskKeyBin = << MaskKey:32 >> = crypto:strong_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:strong_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:strong_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 iolist_size(Payload) of
+ N when N =< 125 -> << N:7 >>;
+ N when N =< 16#ffff -> << 126:7, N:16 >>;
+ N when N =< 16#7fffffffffffffff -> << 127:7, N:64 >>
+ end.
+
+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;
+ _ -> Deflated
+ end.