commit 8ebac63dacd5e702414ad3c3c879d46a28d248b6 Author: absc Date: Tue Jul 23 22:04:25 2024 +0200 Import cowlib. Cowlib is the base library used by ranch and cowboy, respectively the listener and the webserver used in the dudeswave stack. URL: https://github.com/ninenines/cowlib diff --git a/cowlib/LICENSE b/cowlib/LICENSE new file mode 100644 index 0000000..fac238f --- /dev/null +++ b/cowlib/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2013-2024, Loïc Hoguin + +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. diff --git a/cowlib/Makefile b/cowlib/Makefile new file mode 100644 index 0000000..160022d --- /dev/null +++ b/cowlib/Makefile @@ -0,0 +1,7 @@ +.PHONY: all clean + +all: + ${MAKE} -C src + +clean: + ${MAKE} -C src clean diff --git a/cowlib/README.asciidoc b/cowlib/README.asciidoc new file mode 100644 index 0000000..949e22d --- /dev/null +++ b/cowlib/README.asciidoc @@ -0,0 +1,18 @@ += Cowlib + +Cowlib is a support library for manipulating Web protocols. + +== Goals + +Cowlib provides libraries for parsing and building messages +for various Web protocols, including HTTP/1.1, HTTP/2 and +Websocket. + +It is optimized for completeness rather than speed. No value +is ignored, they are all returned. + +== Support + +* Official IRC Channel: #ninenines on irc.freenode.net +* https://ninenines.eu/services[Commercial Support] +* https://github.com/sponsors/essen[Sponsor me!] diff --git a/cowlib/doc/src/manual/cow_cookie.asciidoc b/cowlib/doc/src/manual/cow_cookie.asciidoc new file mode 100644 index 0000000..035342d --- /dev/null +++ b/cowlib/doc/src/manual/cow_cookie.asciidoc @@ -0,0 +1,114 @@ += cow_cookie(3) + +== Name + +cow_cookie - Cookies + +== Description + +The module `cow_cookie` provides functions for parsing +and manipulating cookie headers. + +== Exports + +* link:man:cow_cookie:parse_cookie(3)[cow_cookie:parse_cookie(3)] - Parse a cookie header +* link:man:cow_cookie:parse_set_cookie(3)[cow_cookie:parse_set_cookie(3)] - Parse a set-cookie header +* link:man:cow_cookie:cookie(3)[cow_cookie:cookie(3)] - Generate a cookie header +* link:man:cow_cookie:setcookie(3)[cow_cookie:setcookie(3)] - Generate a set-cookie header + +== Types + +=== cookie_attrs() + +[source,erlang] +---- +cookie_attrs() :: #{ + expires => calendar:datetime(), + max_age => calendar:datetime(), + domain => binary(), + path => binary(), + secure => true, + http_only => true, + same_site => default | none | strict | lax +} +---- + +Cookie attributes parsed from the set-cookie header. +The attributes must be passed as-is to a cookie store +engine for processing, along with the cookie name and value. +More information about the attributes can be found in +https://tools.ietf.org/html/rfc6265[RFC 6265]. + +=== cookie_opts() + +[source,erlang] +---- +cookie_opts() :: #{ + domain => binary(), + http_only => boolean(), + max_age => non_neg_integer(), + path => binary(), + same_site => default | none | strict | lax, + secure => boolean() +} +---- + +Options for the set-cookie header. They are added to the +header as attributes. More information about the options +can be found in https://tools.ietf.org/html/rfc6265[RFC 6265]. + +The following options are defined: + +domain:: + +Hosts to which the cookie will be sent. By default it will +only be sent to the origin server. + +http_only:: + +Whether the cookie should be restricted to HTTP requests, or +it should also be exposed to other APIs, for example Javascript. +By default there are no restrictions. + +max_age:: + +Maximum lifetime of the cookie, in seconds. By default the +cookie is kept for the duration of the session. + +path:: + +Path to which the cookie will be sent. By default it will +be sent to the current "directory" of the effective request URI. + +same_site:: + +Whether the cookie should be sent along with cross-site +requests. This attribute is currently non-standard but is in +the process of being standardized. Please refer to the +https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7[RFC 6265 (bis) draft] +for details. ++ +The default value for this attribute may vary depending on +user agent and configuration. Browsers are known to be more +strict over TCP compared to TLS. + +secure:: + +Whether the cookie should be sent only on secure channels +(for example TLS). Note that this does not guarantee the +integrity of the cookie, only its confidentiality during +transfer. By default there are no restrictions. + +== Changelog + +* *2.12*: The `same_site` attribute and option may now be + set to `default`. +* *2.10*: The `same_site` attribute and option may now be + set to `none`. +* *2.9*: The `cookie_attrs` type was added. +* *1.0*: Module introduced. + +== See also + +link:man:cowlib(7)[cowlib(7)], +https://tools.ietf.org/html/rfc6265[RFC 6265] diff --git a/cowlib/doc/src/manual/cow_cookie.cookie.asciidoc b/cowlib/doc/src/manual/cow_cookie.cookie.asciidoc new file mode 100644 index 0000000..c40d66b --- /dev/null +++ b/cowlib/doc/src/manual/cow_cookie.cookie.asciidoc @@ -0,0 +1,45 @@ += cow_cookie:cookie(3) + +== Name + +cow_cookie:cookie - Generate a cookie header + +== Description + +[source,erlang] +---- +cookie(Cookies) -> iolist() + +Cookies :: [{Name :: iodata(), Value :: iodata()}] +---- + +Generate a cookie header. + +== Arguments + +Cookies:: + +A list of pairs of cookie name and value. + +== Return value + +An iolist with the generated cookie header value. + +== Changelog + +* *2.9*: Function introduced. + +== Examples + +.Generate a cookie header +[source,erlang] +---- +Cookie = cow_cookie:cookie([{<<"sessionid">>, ID}]). +---- + +== See also + +link:man:cow_cookie(3)[cow_cookie(3)], +link:man:cow_cookie:parse_cookie(3)[cow_cookie:parse_cookie(3)], +link:man:cow_cookie:parse_set_cookie(3)[cow_cookie:parse_set_cookie(3)], +link:man:cow_cookie:setcookie(3)[cow_cookie:setcookie(3)] diff --git a/cowlib/doc/src/manual/cow_cookie.parse_cookie.asciidoc b/cowlib/doc/src/manual/cow_cookie.parse_cookie.asciidoc new file mode 100644 index 0000000..2f27d7d --- /dev/null +++ b/cowlib/doc/src/manual/cow_cookie.parse_cookie.asciidoc @@ -0,0 +1,50 @@ += cow_cookie:parse_cookie(3) + +== Name + +cow_cookie:parse_cookie - Parse a cookie header + +== Description + +[source,erlang] +---- +parse_cookie(Cookie :: binary()) + -> [{binary(), binary()}] +---- + +Parse a cookie header. + +== Arguments + +Cookie:: + +The cookie header value. + +== Return value + +A list of cookie name/value pairs is returned on success. + +An exception is thrown in the event of a parse error. + +== Changelog + +* *2.9*: Fixes to the parser may lead to potential incompatibilities. + A cookie name starting with `$` is no longer ignored. + A cookie without a `=` will be parsed as the value of + the cookie named `<<>>` (empty name). +* *1.0*: Function introduced. + +== Examples + +.Parse a cookie header +[source,erlang] +---- +Cookies = cow_cookie:parse_cookie(CookieHd). +---- + +== See also + +link:man:cow_cookie(3)[cow_cookie(3)], +link:man:cow_cookie:parse_set_cookie(3)[cow_cookie:parse_set_cookie(3)], +link:man:cow_cookie:cookie(3)[cow_cookie:cookie(3)], +link:man:cow_cookie:setcookie(3)[cow_cookie:setcookie(3)] diff --git a/cowlib/doc/src/manual/cow_cookie.parse_set_cookie.asciidoc b/cowlib/doc/src/manual/cow_cookie.parse_set_cookie.asciidoc new file mode 100644 index 0000000..6a49dde --- /dev/null +++ b/cowlib/doc/src/manual/cow_cookie.parse_set_cookie.asciidoc @@ -0,0 +1,57 @@ += cow_cookie:parse_set_cookie(3) + +== Name + +cow_cookie:parse_set_cookie - Parse a set-cookie header + +== Description + +[source,erlang] +---- +parse_set_cookie(SetCookie :: binary()) + -> {ok, Name, Value, Attrs} | ignore + +Name :: binary() +Value :: binary() +Attrs :: cow_cookie:cookie_attrs() +---- + +Parse a set-cookie header. + +== Arguments + +SetCookie:: + +The set-cookie header value. + +== Return value + +An `ok` tuple with the cookie name, value and attributes +is returned on success. + +An atom `ignore` is returned when the cookie has both +an empty name and an empty value, and must be ignored. + +== Changelog + +* *2.9*: Function introduced. + +== Examples + +.Parse a cookie header +[source,erlang] +---- +case cow_cookie:parse_set_cookie(SetCookieHd) of + {ok, Name, Value, Attrs} -> + cookie_engine_set_cookie(Name, Value, Attrs); + ignore -> + do_nothing() +end. +---- + +== See also + +link:man:cow_cookie(3)[cow_cookie(3)], +link:man:cow_cookie:parse_cookie(3)[cow_cookie:parse_cookie(3)], +link:man:cow_cookie:cookie(3)[cow_cookie:cookie(3)], +link:man:cow_cookie:setcookie(3)[cow_cookie:setcookie(3)] diff --git a/cowlib/doc/src/manual/cow_cookie.setcookie.asciidoc b/cowlib/doc/src/manual/cow_cookie.setcookie.asciidoc new file mode 100644 index 0000000..77f98b1 --- /dev/null +++ b/cowlib/doc/src/manual/cow_cookie.setcookie.asciidoc @@ -0,0 +1,58 @@ += cow_cookie:setcookie(3) + +== Name + +cow_cookie:setcookie - Generate a set-cookie header + +== Description + +[source,erlang] +---- +setcookie(Name :: iodata(), + Value :: iodata(), + Opts :: cow_cookie:cookie_opts()) + -> iolist() +---- + +Generate a set-cookie header. + +== Arguments + +Name:: + +Cookie name. + +Value:: + +Cookie value. + +Opts:: + +Options added to the set-cookie header as attributes. + +== Return value + +An iolist with the generated set-cookie header value. + +== Changelog + +* *2.12*: The `Version` attribute is no longer generated. +* *1.0*: Function introduced. + +== Examples + +.Generate a set-cookie header +[source,erlang] +---- +SetCookie = cow_cookie:setcookie(<<"sessionid">>, ID, #{ + http_only => true, + secure => true +}). +---- + +== See also + +link:man:cow_cookie(3)[cow_cookie(3)], +link:man:cow_cookie:parse_cookie(3)[cow_cookie:parse_cookie(3)], +link:man:cow_cookie:parse_set_cookie(3)[cow_cookie:parse_set_cookie(3)], +link:man:cow_cookie:cookie(3)[cow_cookie:cookie(3)] diff --git a/cowlib/doc/src/manual/cowlib_app.asciidoc b/cowlib/doc/src/manual/cowlib_app.asciidoc new file mode 100644 index 0000000..5e659d2 --- /dev/null +++ b/cowlib/doc/src/manual/cowlib_app.asciidoc @@ -0,0 +1,40 @@ += cowlib(7) + +== Name + +cowlib - Support library for manipulating Web protocols + +== Description + +Cowlib provides libraries for parsing and building messages +for various Web protocols, including HTTP/1.1, HTTP/2 and +Websocket. + +It is optimized for completeness rather than speed. No value +is ignored, they are all returned. + +== Modules + +* link:man:cow_cookie(3)[cow_cookie(3)] - Cookies + +== Dependencies + +* crypto - Crypto functions + +All these applications must be started before the `cowlib` +application. To start Cowlib and all dependencies at once: + +[source,erlang] +---- +{ok, _} = application:ensure_all_started(cowlib). +---- + +== Environment + +The `cowlib` application does not define any application +environment configuration parameters. + +== See also + +link:man:cowboy(7)[cowboy(7)], +link:man:gun(7)[gun(7)] diff --git a/cowlib/ebin/cowlib.app b/cowlib/ebin/cowlib.app new file mode 100644 index 0000000..ade4ae5 --- /dev/null +++ b/cowlib/ebin/cowlib.app @@ -0,0 +1,8 @@ +{application, 'cowlib', [ + {description, "Support library for manipulating Web protocols."}, + {vsn, "2.13.0"}, + {modules, ['cow_base64url','cow_cookie','cow_date','cow_hpack','cow_http','cow_http2','cow_http2_machine','cow_http_hd','cow_http_struct_hd','cow_http_te','cow_iolists','cow_link','cow_mimetypes','cow_multipart','cow_qs','cow_spdy','cow_sse','cow_uri','cow_uri_template','cow_ws']}, + {registered, []}, + {applications, [kernel,stdlib,crypto]}, + {env, []} +]}. \ No newline at end of file diff --git a/cowlib/include/cow_inline.hrl b/cowlib/include/cow_inline.hrl new file mode 100644 index 0000000..1ad417e --- /dev/null +++ b/cowlib/include/cow_inline.hrl @@ -0,0 +1,447 @@ +%% Copyright (c) 2014-2023, Loïc Hoguin +%% +%% 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. + +-ifndef(COW_INLINE_HRL). +-define(COW_INLINE_HRL, 1). + +%% LC(Character) + +-define(LC(C), case C of + $A -> $a; + $B -> $b; + $C -> $c; + $D -> $d; + $E -> $e; + $F -> $f; + $G -> $g; + $H -> $h; + $I -> $i; + $J -> $j; + $K -> $k; + $L -> $l; + $M -> $m; + $N -> $n; + $O -> $o; + $P -> $p; + $Q -> $q; + $R -> $r; + $S -> $s; + $T -> $t; + $U -> $u; + $V -> $v; + $W -> $w; + $X -> $x; + $Y -> $y; + $Z -> $z; + _ -> C +end). + +%% LOWER(Bin) +%% +%% Lowercase the entire binary string in a binary comprehension. + +-define(LOWER(Bin), << << ?LC(C) >> || << C >> <= Bin >>). + +%% LOWERCASE(Function, Rest, Acc, ...) +%% +%% To be included at the end of a case block. +%% Defined for up to 10 extra arguments. + +-define(LOWER(Function, Rest, Acc), case C of + $A -> Function(Rest, << Acc/binary, $a >>); + $B -> Function(Rest, << Acc/binary, $b >>); + $C -> Function(Rest, << Acc/binary, $c >>); + $D -> Function(Rest, << Acc/binary, $d >>); + $E -> Function(Rest, << Acc/binary, $e >>); + $F -> Function(Rest, << Acc/binary, $f >>); + $G -> Function(Rest, << Acc/binary, $g >>); + $H -> Function(Rest, << Acc/binary, $h >>); + $I -> Function(Rest, << Acc/binary, $i >>); + $J -> Function(Rest, << Acc/binary, $j >>); + $K -> Function(Rest, << Acc/binary, $k >>); + $L -> Function(Rest, << Acc/binary, $l >>); + $M -> Function(Rest, << Acc/binary, $m >>); + $N -> Function(Rest, << Acc/binary, $n >>); + $O -> Function(Rest, << Acc/binary, $o >>); + $P -> Function(Rest, << Acc/binary, $p >>); + $Q -> Function(Rest, << Acc/binary, $q >>); + $R -> Function(Rest, << Acc/binary, $r >>); + $S -> Function(Rest, << Acc/binary, $s >>); + $T -> Function(Rest, << Acc/binary, $t >>); + $U -> Function(Rest, << Acc/binary, $u >>); + $V -> Function(Rest, << Acc/binary, $v >>); + $W -> Function(Rest, << Acc/binary, $w >>); + $X -> Function(Rest, << Acc/binary, $x >>); + $Y -> Function(Rest, << Acc/binary, $y >>); + $Z -> Function(Rest, << Acc/binary, $z >>); + C -> Function(Rest, << Acc/binary, C >>) +end). + +-define(LOWER(Function, Rest, A0, Acc), case C of + $A -> Function(Rest, A0, << Acc/binary, $a >>); + $B -> Function(Rest, A0, << Acc/binary, $b >>); + $C -> Function(Rest, A0, << Acc/binary, $c >>); + $D -> Function(Rest, A0, << Acc/binary, $d >>); + $E -> Function(Rest, A0, << Acc/binary, $e >>); + $F -> Function(Rest, A0, << Acc/binary, $f >>); + $G -> Function(Rest, A0, << Acc/binary, $g >>); + $H -> Function(Rest, A0, << Acc/binary, $h >>); + $I -> Function(Rest, A0, << Acc/binary, $i >>); + $J -> Function(Rest, A0, << Acc/binary, $j >>); + $K -> Function(Rest, A0, << Acc/binary, $k >>); + $L -> Function(Rest, A0, << Acc/binary, $l >>); + $M -> Function(Rest, A0, << Acc/binary, $m >>); + $N -> Function(Rest, A0, << Acc/binary, $n >>); + $O -> Function(Rest, A0, << Acc/binary, $o >>); + $P -> Function(Rest, A0, << Acc/binary, $p >>); + $Q -> Function(Rest, A0, << Acc/binary, $q >>); + $R -> Function(Rest, A0, << Acc/binary, $r >>); + $S -> Function(Rest, A0, << Acc/binary, $s >>); + $T -> Function(Rest, A0, << Acc/binary, $t >>); + $U -> Function(Rest, A0, << Acc/binary, $u >>); + $V -> Function(Rest, A0, << Acc/binary, $v >>); + $W -> Function(Rest, A0, << Acc/binary, $w >>); + $X -> Function(Rest, A0, << Acc/binary, $x >>); + $Y -> Function(Rest, A0, << Acc/binary, $y >>); + $Z -> Function(Rest, A0, << Acc/binary, $z >>); + C -> Function(Rest, A0, << Acc/binary, C >>) +end). + +-define(LOWER(Function, Rest, A0, A1, Acc), case C of + $A -> Function(Rest, A0, A1, << Acc/binary, $a >>); + $B -> Function(Rest, A0, A1, << Acc/binary, $b >>); + $C -> Function(Rest, A0, A1, << Acc/binary, $c >>); + $D -> Function(Rest, A0, A1, << Acc/binary, $d >>); + $E -> Function(Rest, A0, A1, << Acc/binary, $e >>); + $F -> Function(Rest, A0, A1, << Acc/binary, $f >>); + $G -> Function(Rest, A0, A1, << Acc/binary, $g >>); + $H -> Function(Rest, A0, A1, << Acc/binary, $h >>); + $I -> Function(Rest, A0, A1, << Acc/binary, $i >>); + $J -> Function(Rest, A0, A1, << Acc/binary, $j >>); + $K -> Function(Rest, A0, A1, << Acc/binary, $k >>); + $L -> Function(Rest, A0, A1, << Acc/binary, $l >>); + $M -> Function(Rest, A0, A1, << Acc/binary, $m >>); + $N -> Function(Rest, A0, A1, << Acc/binary, $n >>); + $O -> Function(Rest, A0, A1, << Acc/binary, $o >>); + $P -> Function(Rest, A0, A1, << Acc/binary, $p >>); + $Q -> Function(Rest, A0, A1, << Acc/binary, $q >>); + $R -> Function(Rest, A0, A1, << Acc/binary, $r >>); + $S -> Function(Rest, A0, A1, << Acc/binary, $s >>); + $T -> Function(Rest, A0, A1, << Acc/binary, $t >>); + $U -> Function(Rest, A0, A1, << Acc/binary, $u >>); + $V -> Function(Rest, A0, A1, << Acc/binary, $v >>); + $W -> Function(Rest, A0, A1, << Acc/binary, $w >>); + $X -> Function(Rest, A0, A1, << Acc/binary, $x >>); + $Y -> Function(Rest, A0, A1, << Acc/binary, $y >>); + $Z -> Function(Rest, A0, A1, << Acc/binary, $z >>); + C -> Function(Rest, A0, A1, << Acc/binary, C >>) +end). + +-define(LOWER(Function, Rest, A0, A1, A2, Acc), case C of + $A -> Function(Rest, A0, A1, A2, << Acc/binary, $a >>); + $B -> Function(Rest, A0, A1, A2, << Acc/binary, $b >>); + $C -> Function(Rest, A0, A1, A2, << Acc/binary, $c >>); + $D -> Function(Rest, A0, A1, A2, << Acc/binary, $d >>); + $E -> Function(Rest, A0, A1, A2, << Acc/binary, $e >>); + $F -> Function(Rest, A0, A1, A2, << Acc/binary, $f >>); + $G -> Function(Rest, A0, A1, A2, << Acc/binary, $g >>); + $H -> Function(Rest, A0, A1, A2, << Acc/binary, $h >>); + $I -> Function(Rest, A0, A1, A2, << Acc/binary, $i >>); + $J -> Function(Rest, A0, A1, A2, << Acc/binary, $j >>); + $K -> Function(Rest, A0, A1, A2, << Acc/binary, $k >>); + $L -> Function(Rest, A0, A1, A2, << Acc/binary, $l >>); + $M -> Function(Rest, A0, A1, A2, << Acc/binary, $m >>); + $N -> Function(Rest, A0, A1, A2, << Acc/binary, $n >>); + $O -> Function(Rest, A0, A1, A2, << Acc/binary, $o >>); + $P -> Function(Rest, A0, A1, A2, << Acc/binary, $p >>); + $Q -> Function(Rest, A0, A1, A2, << Acc/binary, $q >>); + $R -> Function(Rest, A0, A1, A2, << Acc/binary, $r >>); + $S -> Function(Rest, A0, A1, A2, << Acc/binary, $s >>); + $T -> Function(Rest, A0, A1, A2, << Acc/binary, $t >>); + $U -> Function(Rest, A0, A1, A2, << Acc/binary, $u >>); + $V -> Function(Rest, A0, A1, A2, << Acc/binary, $v >>); + $W -> Function(Rest, A0, A1, A2, << Acc/binary, $w >>); + $X -> Function(Rest, A0, A1, A2, << Acc/binary, $x >>); + $Y -> Function(Rest, A0, A1, A2, << Acc/binary, $y >>); + $Z -> Function(Rest, A0, A1, A2, << Acc/binary, $z >>); + C -> Function(Rest, A0, A1, A2, << Acc/binary, C >>) +end). + +-define(LOWER(Function, Rest, A0, A1, A2, A3, Acc), case C of + $A -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $a >>); + $B -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $b >>); + $C -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $c >>); + $D -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $d >>); + $E -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $e >>); + $F -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $f >>); + $G -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $g >>); + $H -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $h >>); + $I -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $i >>); + $J -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $j >>); + $K -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $k >>); + $L -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $l >>); + $M -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $m >>); + $N -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $n >>); + $O -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $o >>); + $P -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $p >>); + $Q -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $q >>); + $R -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $r >>); + $S -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $s >>); + $T -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $t >>); + $U -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $u >>); + $V -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $v >>); + $W -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $w >>); + $X -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $x >>); + $Y -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $y >>); + $Z -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $z >>); + C -> Function(Rest, A0, A1, A2, A3, << Acc/binary, C >>) +end). + +-define(LOWER(Function, Rest, A0, A1, A2, A3, A4, Acc), case C of + $A -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $a >>); + $B -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $b >>); + $C -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $c >>); + $D -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $d >>); + $E -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $e >>); + $F -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $f >>); + $G -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $g >>); + $H -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $h >>); + $I -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $i >>); + $J -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $j >>); + $K -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $k >>); + $L -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $l >>); + $M -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $m >>); + $N -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $n >>); + $O -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $o >>); + $P -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $p >>); + $Q -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $q >>); + $R -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $r >>); + $S -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $s >>); + $T -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $t >>); + $U -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $u >>); + $V -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $v >>); + $W -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $w >>); + $X -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $x >>); + $Y -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $y >>); + $Z -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $z >>); + C -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, C >>) +end). + +-define(LOWER(Function, Rest, A0, A1, A2, A3, A4, A5, Acc), case C of + $A -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $a >>); + $B -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $b >>); + $C -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $c >>); + $D -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $d >>); + $E -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $e >>); + $F -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $f >>); + $G -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $g >>); + $H -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $h >>); + $I -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $i >>); + $J -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $j >>); + $K -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $k >>); + $L -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $l >>); + $M -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $m >>); + $N -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $n >>); + $O -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $o >>); + $P -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $p >>); + $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $q >>); + $R -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $r >>); + $S -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $s >>); + $T -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $t >>); + $U -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $u >>); + $V -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $v >>); + $W -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $w >>); + $X -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $x >>); + $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $y >>); + $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $z >>); + C -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, C >>) +end). + +-define(LOWER(Function, Rest, A0, A1, A2, A3, A4, A5, A6, Acc), case C of + $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $a >>); + $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $b >>); + $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $c >>); + $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $d >>); + $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $e >>); + $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $f >>); + $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $g >>); + $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $h >>); + $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $i >>); + $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $j >>); + $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $k >>); + $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $l >>); + $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $m >>); + $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $n >>); + $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $o >>); + $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $p >>); + $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $q >>); + $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $r >>); + $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $s >>); + $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $t >>); + $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $u >>); + $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $v >>); + $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $w >>); + $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $x >>); + $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $y >>); + $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $z >>); + C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, C >>) +end). + +-define(LOWER(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, Acc), case C of + $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $a >>); + $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $b >>); + $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $c >>); + $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $d >>); + $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $e >>); + $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $f >>); + $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $g >>); + $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $h >>); + $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $i >>); + $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $j >>); + $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $k >>); + $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $l >>); + $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $m >>); + $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $n >>); + $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $o >>); + $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $p >>); + $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $q >>); + $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $r >>); + $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $s >>); + $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $t >>); + $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $u >>); + $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $v >>); + $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $w >>); + $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $x >>); + $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $y >>); + $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $z >>); + C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, C >>) +end). + +-define(LOWER(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, Acc), case C of + $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $a >>); + $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $b >>); + $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $c >>); + $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $d >>); + $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $e >>); + $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $f >>); + $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $g >>); + $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $h >>); + $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $i >>); + $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $j >>); + $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $k >>); + $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $l >>); + $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $m >>); + $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $n >>); + $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $o >>); + $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $p >>); + $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $q >>); + $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $r >>); + $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $s >>); + $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $t >>); + $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $u >>); + $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $v >>); + $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $w >>); + $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $x >>); + $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $y >>); + $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $z >>); + C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, C >>) +end). + +-define(LOWER(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, Acc), case C of + $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $a >>); + $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $b >>); + $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $c >>); + $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $d >>); + $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $e >>); + $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $f >>); + $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $g >>); + $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $h >>); + $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $i >>); + $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $j >>); + $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $k >>); + $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $l >>); + $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $m >>); + $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $n >>); + $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $o >>); + $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $p >>); + $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $q >>); + $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $r >>); + $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $s >>); + $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $t >>); + $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $u >>); + $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $v >>); + $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $w >>); + $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $x >>); + $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $y >>); + $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $z >>); + C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, C >>) +end). + +%% HEX(C) + +-define(HEX(C), (?HEXHL(C bsr 4)), (?HEXHL(C band 16#0f))). + +-define(HEXHL(HL), + case HL of + 0 -> $0; + 1 -> $1; + 2 -> $2; + 3 -> $3; + 4 -> $4; + 5 -> $5; + 6 -> $6; + 7 -> $7; + 8 -> $8; + 9 -> $9; + 10 -> $A; + 11 -> $B; + 12 -> $C; + 13 -> $D; + 14 -> $E; + 15 -> $F + end +). + +%% UNHEX(H, L) + +-define(UNHEX(H, L), (?UNHEX(H) bsl 4 bor ?UNHEX(L))). + +-define(UNHEX(C), + case C of + $0 -> 0; + $1 -> 1; + $2 -> 2; + $3 -> 3; + $4 -> 4; + $5 -> 5; + $6 -> 6; + $7 -> 7; + $8 -> 8; + $9 -> 9; + $A -> 10; + $B -> 11; + $C -> 12; + $D -> 13; + $E -> 14; + $F -> 15; + $a -> 10; + $b -> 11; + $c -> 12; + $d -> 13; + $e -> 14; + $f -> 15 + end +). + +-endif. diff --git a/cowlib/include/cow_parse.hrl b/cowlib/include/cow_parse.hrl new file mode 100644 index 0000000..72eaff6 --- /dev/null +++ b/cowlib/include/cow_parse.hrl @@ -0,0 +1,83 @@ +%% Copyright (c) 2015-2023, Loïc Hoguin +%% +%% 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. + +-ifndef(COW_PARSE_HRL). +-define(COW_PARSE_HRL, 1). + +-define(IS_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) or + (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) +). + +-define(IS_ALPHANUM(C), ?IS_ALPHA(C) or ?IS_DIGIT(C)). +-define(IS_CHAR(C), C > 0, C < 128). + +-define(IS_DIGIT(C), + (C =:= $0) or (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)). + +-define(IS_ETAGC(C), C =:= 16#21; C >= 16#23, C =/= 16#7f). + +-define(IS_HEX(C), + ?IS_DIGIT(C) or + (C =:= $a) or (C =:= $b) or (C =:= $c) or + (C =:= $d) or (C =:= $e) or (C =:= $f) or + (C =:= $A) or (C =:= $B) or (C =:= $C) or + (C =:= $D) or (C =:= $E) or (C =:= $F)). + +-define(IS_LHEX(C), + ?IS_DIGIT(C) or + (C =:= $a) or (C =:= $b) or (C =:= $c) or + (C =:= $d) or (C =:= $e) or (C =:= $f)). + +-define(IS_TOKEN(C), + ?IS_ALPHA(C) or ?IS_DIGIT(C) or + (C =:= $!) or (C =:= $#) or (C =:= $$) or (C =:= $%) or (C =:= $&) or + (C =:= $') or (C =:= $*) or (C =:= $+) or (C =:= $-) or (C =:= $.) or + (C =:= $^) or (C =:= $_) or (C =:= $`) or (C =:= $|) or (C =:= $~)). + +-define(IS_TOKEN68(C), + ?IS_ALPHA(C) or ?IS_DIGIT(C) or + (C =:= $-) or (C =:= $.) or (C =:= $_) or + (C =:= $~) or (C =:= $+) or (C =:= $/)). + +-define(IS_URI_UNRESERVED(C), + ?IS_ALPHA(C) or ?IS_DIGIT(C) or + (C =:= $-) or (C =:= $.) or (C =:= $_) or (C =:= $~)). + +-define(IS_URI_GEN_DELIMS(C), + (C =:= $:) or (C =:= $/) or (C =:= $?) or (C =:= $#) or + (C =:= $[) or (C =:= $]) or (C =:= $@)). + +-define(IS_URI_SUB_DELIMS(C), + (C =:= $!) or (C =:= $$) or (C =:= $&) or (C =:= $') or + (C =:= $() or (C =:= $)) or (C =:= $*) or (C =:= $+) or + (C =:= $,) or (C =:= $;) or (C =:= $=)). + +-define(IS_VCHAR(C), C =:= $\t; C > 31, C < 127). +-define(IS_VCHAR_OBS(C), C =:= $\t; C > 31, C =/= 127). +-define(IS_WS(C), (C =:= $\s) or (C =:= $\t)). +-define(IS_WS_COMMA(C), ?IS_WS(C) or (C =:= $,)). + +-endif. diff --git a/cowlib/src/Makefile b/cowlib/src/Makefile new file mode 100644 index 0000000..d79a736 --- /dev/null +++ b/cowlib/src/Makefile @@ -0,0 +1,22 @@ +.PHONY: all +.SUFFIXES: .erl .beam + +ERLC?= erlc -server +ERLFLAGS+= -I ../include + +OBJS+= cow_base64url.beam cow_cookie.beam cow_date.beam +OBJS+= cow_hpack.beam cow_http.beam cow_http2.beam +OBJS+= cow_http2_machine.beam cow_http_hd.beam +OBJS+= cow_http_struct_hd.beam cow_http_te.beam cow_iolists.beam +OBJS+= cow_link.beam cow_mimetypes.beam cow_multipart.beam +OBJS+= cow_qs.beam cow_spdy.beam cow_sse.beam cow_uri.beam +OBJS+= cow_uri_template.beam cow_ws.beam + +all: ${OBJS} + +.erl.beam: + ${ERLC} ${ERLOPTS} ${ERLFLAGS} $< + +clean: + rm -f *.beam + diff --git a/cowlib/src/cow_base64url.beam b/cowlib/src/cow_base64url.beam new file mode 100644 index 0000000..7430dc8 Binary files /dev/null and b/cowlib/src/cow_base64url.beam differ diff --git a/cowlib/src/cow_base64url.erl b/cowlib/src/cow_base64url.erl new file mode 100644 index 0000000..e591fcf --- /dev/null +++ b/cowlib/src/cow_base64url.erl @@ -0,0 +1,81 @@ +%% Copyright (c) 2017-2023, Loïc Hoguin +%% +%% 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, <>); +encode(<<$/, R/bits>>, Opts, Acc) -> encode(R, Opts, <>); +encode(<<$=, _/bits>>, #{padding := false}, Acc) -> Acc; +encode(<>, Opts, Acc) -> encode(R, Opts, <>); +encode(<<>>, _, Acc) -> Acc. + +-ifdef(TEST). + +rfc7515_test() -> + Dec = <<3,236,255,224,193>>, + Enc = <<"A-z_4ME">>, + Pad = <<"A-z_4ME=">>, + Dec = decode(<>), + 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/cowlib/src/cow_cookie.beam b/cowlib/src/cow_cookie.beam new file mode 100644 index 0000000..59e2e2c Binary files /dev/null and b/cowlib/src/cow_cookie.beam differ diff --git a/cowlib/src/cow_cookie.erl b/cowlib/src/cow_cookie.erl new file mode 100644 index 0000000..11cf339 --- /dev/null +++ b/cowlib/src/cow_cookie.erl @@ -0,0 +1,456 @@ +%% Copyright (c) 2013-2023, Loïc Hoguin +%% +%% 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(<>) -> + 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(<>, Acc) -> take_until_semicolon(R, <>); +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 +%% +%% @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/cowlib/src/cow_date.beam b/cowlib/src/cow_date.beam new file mode 100644 index 0000000..fadd9db Binary files /dev/null and b/cowlib/src/cow_date.beam differ diff --git a/cowlib/src/cow_date.erl b/cowlib/src/cow_date.erl new file mode 100644 index 0000000..00bc8af --- /dev/null +++ b/cowlib/src/cow_date.erl @@ -0,0 +1,434 @@ +%% Copyright (c) 2013-2023, Loïc Hoguin +%% +%% 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/cowlib/src/cow_hpack.beam b/cowlib/src/cow_hpack.beam new file mode 100644 index 0000000..6f585ae Binary files /dev/null and b/cowlib/src/cow_hpack.beam differ diff --git a/cowlib/src/cow_hpack.erl b/cowlib/src/cow_hpack.erl new file mode 100644 index 0000000..d7ae475 --- /dev/null +++ b/cowlib/src/cow_hpack.erl @@ -0,0 +1,1449 @@ +%% Copyright (c) 2015-2023, Loïc Hoguin +%% +%% 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(<>, 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(<>, 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(<>, 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), + <> = Rest1, + {Str, Rest}; +dec_str(<<0:1, Length:7, Rest0/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(<>, 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, <>); + {undefined, CharB} -> dec_huffman(R, Len - 1, Huff, <>); + {CharA, CharB} -> dec_huffman(R, Len - 1, Huff, <>) + end; +dec_huffman(<>, 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} -> + {<>, Rest}; + {undefined, CharB} -> + {<>, Rest}; + _ -> + {<>, 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 -> + <>; +enc_big_int(Int, Acc) -> + enc_big_int(Int bsr 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/cowlib/src/cow_hpack_dec_huffman_lookup.hrl b/cowlib/src/cow_hpack_dec_huffman_lookup.hrl new file mode 100644 index 0000000..6e5da31 --- /dev/null +++ b/cowlib/src/cow_hpack_dec_huffman_lookup.hrl @@ -0,0 +1,4132 @@ +%% Copyright (c) 2019-2023, Loïc Hoguin +%% +%% 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/cowlib/src/cow_http.erl b/cowlib/src/cow_http.erl new file mode 100644 index 0000000..93e9193 --- /dev/null +++ b/cowlib/src/cow_http.erl @@ -0,0 +1,426 @@ +%% Copyright (c) 2013-2023, Loïc Hoguin +%% +%% 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">>), + <> = 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(<>, 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 + <> -> + status_to_integer(H, T, U); + <> -> + 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/cowlib/src/cow_http2.erl b/cowlib/src/cow_http2.erl new file mode 100644 index 0000000..2925e37 --- /dev/null +++ b/cowlib/src/cow_http2.erl @@ -0,0 +1,482 @@ +%% Copyright (c) 2015-2023, Loïc Hoguin +%% +%% 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), + <> = <<"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/cowlib/src/cow_http2_machine.erl b/cowlib/src/cow_http2_machine.erl new file mode 100644 index 0000000..69df267 --- /dev/null +++ b/cowlib/src/cow_http2_machine.erl @@ -0,0 +1,1677 @@ +%% Copyright (c) 2018-2024, Loïc Hoguin +%% +%% 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_fragmented_header_block_size => 16384..16#7fffffff, + 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}}}) -> + case continuation_frame_append(HeaderFragment0, HeaderFragment1, State) of + {ok, HeaderData} -> + headers_decode(Frame#headers{head=head_fin, data=HeaderData}, + State#http2_machine{state=normal}, Type, stream_get(StreamID, State)); + Error -> + Error + end; +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}}}) -> + case continuation_frame_append(HeaderFragment0, HeaderFragment1, State) of + {ok, HeaderData} -> + headers_decode(#headers{id=PromisedStreamID, fin=fin, + head=head_fin, data=HeaderData}, + State#http2_machine{state=normal}, Type, undefined); + Error -> + Error + end; +continuation_frame(#continuation{id=StreamID, data=HeaderFragment1}, + State=#http2_machine{state={continuation, Type, ContinuedFrame}}) + when element(2, ContinuedFrame) =:= StreamID -> + case ContinuedFrame of + #headers{data=HeaderFragment0} -> + case continuation_frame_append(HeaderFragment0, HeaderFragment1, State) of + {ok, HeaderData} -> + {ok, State#http2_machine{state={continuation, Type, + ContinuedFrame#headers{data=HeaderData}}}}; + Error -> + Error + end; + #push_promise{data=HeaderFragment0} -> + case continuation_frame_append(HeaderFragment0, HeaderFragment1, State) of + {ok, HeaderData} -> + {ok, State#http2_machine{state={continuation, Type, + ContinuedFrame#push_promise{data=HeaderData}}}}; + Error -> + Error + end + end; +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}. + +continuation_frame_append(Fragment0, Fragment1, State=#http2_machine{opts=Opts}) -> + MaxSize = maps:get(max_fragmented_header_block_size, Opts, 32768), + case byte_size(Fragment0) + byte_size(Fragment1) =< MaxSize of + true -> + {ok, <>}; + false -> + {error, {connection_error, enhance_your_calm, + 'Larger fragmented header block size than we are willing to accept.'}, + State} + end. + +%% 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/cowlib/src/cow_http_hd.erl b/cowlib/src/cow_http_hd.erl new file mode 100644 index 0000000..f0e4fba --- /dev/null +++ b/cowlib/src/cow_http_hd.erl @@ -0,0 +1,3642 @@ +%% Copyright (c) 2014-2023, Loïc Hoguin +%% +%% 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(<>) + 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(<>) + 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(<>) + 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(<>, Acc) when ?IS_WS_COMMA(C) -> nodeid_list(R, Acc); +nodeid_list(<>, Acc) when ?IS_NODEID_TOKEN(C) -> nodeid(R, Acc, <>). + +nodeid(<>, Acc, T) when ?IS_NODEID_TOKEN(C) -> nodeid(R, Acc, <>); +nodeid(R, Acc, T) -> nodeid_list_sep(R, [T|Acc]). + +nodeid_list_sep(<<>>, Acc) -> lists:reverse(Acc); +nodeid_list_sep(<>, 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/cowlib/src/cow_http_struct_hd.erl b/cowlib/src/cow_http_struct_hd.erl new file mode 100644 index 0000000..a79c691 --- /dev/null +++ b/cowlib/src/cow_http_struct_hd.erl @@ -0,0 +1,522 @@ +%% Copyright (c) 2019-2023, Loïc Hoguin +%% +%% 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(<>) when ?IS_LC_ALPHA(C) or (C =:= $*) -> + parse_dict_key(R, [], <>). + +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(<>, Acc, K) + when ?IS_LC_ALPHA(C) or ?IS_DIGIT(C) + or (C =:= $_) or (C =:= $-) or (C =:= $.) or (C =:= $*) -> + parse_dict_key(R, Acc, <>); +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(<>, 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(<>, Acc) when ?IS_LC_ALPHA(C) or (C =:= $*) -> + parse_dict_key(R, Acc, <>). + +-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 = <>} = 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(<>, Acc) when ?IS_LC_ALPHA(C) or (C =:= $*) -> + parse_param(R, Acc, <>). + +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(<>, Acc, K) + when ?IS_LC_ALPHA(C) or ?IS_DIGIT(C) + or (C =:= $_) or (C =:= $-) or (C =:= $.) or (C =:= $*) -> + parse_param(R, Acc, <>); +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(<>) when ?IS_DIGIT(C) -> parse_number(R, 1, <>); +%% String. +parse_bare_item(<<$",R/bits>>) -> parse_string(R, <<>>); +%% Token. +parse_bare_item(<>) when ?IS_ALPHA(C) or (C =:= $*) -> parse_token(R, <>); +%% 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(<>, L, Acc) when ?IS_DIGIT(C) -> + parse_number(R, L+1, <>); +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(<>, L1, L2, IntAcc, FracAcc) when ?IS_DIGIT(C) -> + parse_decimal(R, L1, L2+1, IntAcc, <>); +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>> -> <<>>; + <> -> <>; + <> -> <>; + <> -> <>; + _ -> 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, <>); +parse_string(<<$\\,$\\,R/bits>>, Acc) -> + parse_string(R, <>); +parse_string(<<$",R/bits>>, Acc) -> + {{string, Acc}, R}; +parse_string(<>, Acc) when + C >= 16#20, C =< 16#21; + C >= 16#23, C =< 16#5b; + C >= 16#5d, C =< 16#7e -> + parse_string(R, <>). + +parse_token(<>, Acc) when ?IS_TOKEN(C) or (C =:= $:) or (C =:= $/) -> + parse_token(R, <>); +parse_token(R, Acc) -> + {{token, Acc}, R}. + +parse_binary(<<$:,R/bits>>, Acc) -> + {{binary, base64:decode(Acc)}, R}; +parse_binary(<>, Acc) when ?IS_ALPHANUM(C) or (C =:= $+) or (C =:= $/) or (C =:= $=) -> + parse_binary(R, <>). + +-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, <>); +escape_string(<<$",R/bits>>, Acc) -> escape_string(R, <>); +escape_string(<>, Acc) -> escape_string(R, <>). + +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/cowlib/src/cow_http_te.erl b/cowlib/src/cow_http_te.erl new file mode 100644 index 0000000..e3473cf --- /dev/null +++ b/cowlib/src/cow_http_te.erl @@ -0,0 +1,373 @@ +%% Copyright (c) 2014-2023, Loïc Hoguin +%% +%% 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/cowlib/src/cow_iolists.erl b/cowlib/src/cow_iolists.erl new file mode 100644 index 0000000..a5e75df --- /dev/null +++ b/cowlib/src/cow_iolists.erl @@ -0,0 +1,95 @@ +%% Copyright (c) 2017-2023, Loïc Hoguin +%% +%% 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 -> + <> = iolist_to_binary(Input), + (ExpectBefore =:= iolist_to_binary(Before)) + andalso (ExpectAfter =:= iolist_to_binary(After)) + end + end). + +-endif. diff --git a/cowlib/src/cow_link.erl b/cowlib/src/cow_link.erl new file mode 100644 index 0000000..b649786 --- /dev/null +++ b/cowlib/src/cow_link.erl @@ -0,0 +1,445 @@ +%% Copyright (c) 2019-2023, Loïc Hoguin +%% +%% 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(<>, Acc) when ?IS_WS(C) -> before_target(R, Acc). + +target(<<$>,R/bits>>, Acc, T) -> param_sep(R, Acc, T, []); +target(<>, Acc, T) -> target(R, Acc, <>). + +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(<>, Acc, T, P) when ?IS_WS(C) -> param_sep(R, Acc, T, P). + +before_param(<>, Acc, T, P) when ?IS_WS(C) -> before_param(R, Acc, T, P); +before_param(<>, 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, <>); +param(<>, 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, <>); +quoted(<>, Acc, T, P, K, V) when ?IS_VCHAR_OBS(C) -> quoted(R, Acc, T, P, K, <>). + +value(<>, Acc, T, P, K, V) when ?IS_TOKEN(C) -> value(R, Acc, T, P, K, <>); +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. + {<<"; 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 => [] + } + ]}, + {<<"; rel=\"copyright\"; anchor=\"#foo\"">>, [ + #{ + target => <<"/terms">>, + rel => <<"copyright">>, + attributes => [ + {<<"anchor">>, <<"#foo">>} + ] + } + ]}, +% {<<"; rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, " +% "; rel=\"next\"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel">>, [ +% %% @todo +% ]} + {<<"; rel=\"start http://example.net/relation/other\"">>, [ + #{ + target => <<"http://example.org/">>, + rel => <<"start http://example.net/relation/other">>, + attributes => [] + } + ]}, + {<<"; rel=\"start\", " + "; 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. + {<<"; 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, _] -> <>; + [_] -> <<$/, 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, <>); +escape(<<$\",R/bits>>, Acc) -> escape(R, <>); +escape(<>, Acc) -> escape(R, <>). + +-ifdef(TEST). +link_test_() -> + Tests = [ + {<<>>, []}, + %% Examples from the RFC. + {<<"; 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 => [] + } + ]}, + {<<"; rel=\"copyright\"; anchor=\"#foo\"">>, [ + #{ + target => <<"/terms">>, + rel => <<"copyright">>, + attributes => [ + {<<"anchor">>, <<"#foo">>} + ] + } + ]}, +% {<<"; rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, " +% "; rel=\"next\"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel">>, [ +% %% @todo +% ]} + {<<"; rel=\"start http://example.net/relation/other\"">>, [ + #{ + target => <<"http://example.org/">>, + rel => <<"start http://example.net/relation/other">>, + attributes => [] + } + ]}, + {<<"; rel=\"start\", " + "; 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/cowlib/src/cow_mimetypes.erl b/cowlib/src/cow_mimetypes.erl new file mode 100644 index 0000000..756e609 --- /dev/null +++ b/cowlib/src/cow_mimetypes.erl @@ -0,0 +1,1045 @@ +%% Copyright (c) 2013-2023, Loïc Hoguin +%% +%% 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/cowlib/src/cow_mimetypes.erl.src b/cowlib/src/cow_mimetypes.erl.src new file mode 100644 index 0000000..7cccdd3 --- /dev/null +++ b/cowlib/src/cow_mimetypes.erl.src @@ -0,0 +1,61 @@ +%% Copyright (c) 2013-2023, Loïc Hoguin +%% +%% 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/cowlib/src/cow_multipart.erl b/cowlib/src/cow_multipart.erl new file mode 100644 index 0000000..4d6d574 --- /dev/null +++ b/cowlib/src/cow_multipart.erl @@ -0,0 +1,775 @@ +%% Copyright (c) 2014-2023, Loïc Hoguin +%% +%% 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/cowlib/src/cow_qs.erl b/cowlib/src/cow_qs.erl new file mode 100644 index 0000000..442ecc8 --- /dev/null +++ b/cowlib/src/cow_qs.erl @@ -0,0 +1,563 @@ +%% Copyright (c) 2013-2023, Loïc Hoguin +%% +%% 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/cowlib/src/cow_spdy.erl b/cowlib/src/cow_spdy.erl new file mode 100644 index 0000000..e7b4043 --- /dev/null +++ b/cowlib/src/cow_spdy.erl @@ -0,0 +1,313 @@ +%% Copyright (c) 2013-2023, Loïc Hoguin +%% +%% 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/cowlib/src/cow_spdy.hrl b/cowlib/src/cow_spdy.hrl new file mode 100644 index 0000000..9637b1c --- /dev/null +++ b/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/cowlib/src/cow_sse.erl b/cowlib/src/cow_sse.erl new file mode 100644 index 0000000..6e7081f --- /dev/null +++ b/cowlib/src/cow_sse.erl @@ -0,0 +1,349 @@ +%% Copyright (c) 2017-2023, Loïc Hoguin +%% +%% 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/cowlib/src/cow_uri.erl b/cowlib/src/cow_uri.erl new file mode 100644 index 0000000..4480d6b --- /dev/null +++ b/cowlib/src/cow_uri.erl @@ -0,0 +1,339 @@ +%% Copyright (c) 2016-2023, Loïc Hoguin +%% +%% 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/cowlib/src/cow_uri_template.erl b/cowlib/src/cow_uri_template.erl new file mode 100644 index 0000000..ccc355d --- /dev/null +++ b/cowlib/src/cow_uri_template.erl @@ -0,0 +1,360 @@ +%% Copyright (c) 2019-2023, Loïc Hoguin +%% +%% 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(<>, Acc) when C =/= $} -> + parse(R, <>). + +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(<>, Op, List) + when ?IS_ALPHANUM(C) or (C =:= $_) -> + parse_varname(R, Op, List, <>). + +parse_varname(<>, Op, List, Name) + when ?IS_ALPHANUM(C) or (C =:= $_) or (C =:= $.) or (C =:= $%) -> + parse_varname(R, Op, List, <>); +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, <>); +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(<>, Op, List, Name, Acc) + when ?IS_DIGIT(C), byte_size(Acc) < 4 -> + parse_prefix_modifier(R, Op, List, Name, <>); +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(<>, Acc) + when ?IS_URI_UNRESERVED(C) -> + urlencode_unreserved(R, <>); +urlencode_unreserved(<>, Acc) -> + urlencode_unreserved(R, <>); +urlencode_unreserved(<<>>, Acc) -> + Acc. + +urlencode_reserved(<<$%,H,L,R/bits>>, Acc) + when ?IS_HEX(H), ?IS_HEX(L) -> + urlencode_reserved(R, <>); +urlencode_reserved(<>, Acc) + when ?IS_URI_UNRESERVED(C) or ?IS_URI_GEN_DELIMS(C) or ?IS_URI_SUB_DELIMS(C) -> + urlencode_reserved(R, <>); +urlencode_reserved(<>, Acc) -> + urlencode_reserved(R, <>); +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/cowlib/src/cow_ws.erl b/cowlib/src/cow_ws.erl new file mode 100644 index 0000000..27c7c87 --- /dev/null +++ b/cowlib/src/cow_ws.erl @@ -0,0 +1,741 @@ +%% Copyright (c) 2015-2023, Loïc Hoguin +%% +%% 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 (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.